diff options
-rw-r--r-- | BuildTools/SCons/SConscript.boot | 1 | ||||
-rw-r--r-- | BuildTools/SCons/SConstruct | 16 | ||||
-rw-r--r-- | SwifTools/HunspellChecker.cpp | 55 | ||||
-rw-r--r-- | SwifTools/HunspellChecker.h | 27 | ||||
-rw-r--r-- | SwifTools/SConscript | 12 | ||||
-rw-r--r-- | SwifTools/SpellChecker.h | 30 | ||||
-rw-r--r-- | SwifTools/SpellCheckerFactory.cpp | 34 | ||||
-rw-r--r-- | SwifTools/SpellCheckerFactory.h | 20 | ||||
-rw-r--r-- | SwifTools/SpellParser.cpp | 69 | ||||
-rw-r--r-- | SwifTools/SpellParser.h | 31 | ||||
-rw-r--r-- | SwifTools/UnitTest/SConscript | 5 | ||||
-rw-r--r-- | SwifTools/UnitTest/SpellParserTest.cpp | 51 | ||||
-rw-r--r-- | Swift/Controllers/SettingConstants.cpp | 4 | ||||
-rw-r--r-- | Swift/Controllers/SettingConstants.h | 4 | ||||
-rw-r--r-- | Swift/QtUI/QtChatWindow.cpp | 2 | ||||
-rw-r--r-- | Swift/QtUI/QtJoinMUCWindow.ui | 6 | ||||
-rw-r--r-- | Swift/QtUI/QtSpellCheckerWindow.cpp | 104 | ||||
-rw-r--r-- | Swift/QtUI/QtSpellCheckerWindow.h | 33 | ||||
-rw-r--r-- | Swift/QtUI/QtSpellCheckerWindow.ui | 87 | ||||
-rw-r--r-- | Swift/QtUI/QtTextEdit.cpp | 169 | ||||
-rw-r--r-- | Swift/QtUI/QtTextEdit.h | 28 | ||||
-rw-r--r-- | Swift/QtUI/SConscript | 4 |
22 files changed, 784 insertions, 8 deletions
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot index a8b7446..3e9e48c 100644 --- a/BuildTools/SCons/SConscript.boot +++ b/BuildTools/SCons/SConscript.boot @@ -38,6 +38,7 @@ if os.name == "nt" : vars.Add(PackageVariable("bonjour", "Bonjour SDK location", "yes")) vars.Add(PackageVariable("openssl", "OpenSSL location", "yes")) vars.Add(BoolVariable("openssl_force_bundled", "Force use of the bundled OpenSSL", "no")) +vars.Add(PackageVariable("hunspell", "Hunspell location", False)) vars.Add(PathVariable("boost_includedir", "Boost headers location", None, PathVariable.PathAccept)) vars.Add(PathVariable("boost_libdir", "Boost library location", None, PathVariable.PathAccept)) vars.Add(PathVariable("expat_includedir", "Expat headers location", None, PathVariable.PathAccept)) diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct index b4c3740..698217f 100644 --- a/BuildTools/SCons/SConstruct +++ b/BuildTools/SCons/SConstruct @@ -445,6 +445,22 @@ else : openssl_conf.Finish() +#Hunspell +hunspell_env = conf_env.Clone() +hunspell_prefix = env["hunspell"] if isinstance(env.get("hunspell", False), str) else "" +hunspell_flags = {} +if hunspell_prefix : + hunspell_flags = {"CPPPATH":[os.path.join(hunspell_prefix, "include")], "LIBPATH":[os.path.join(hunspell_prefix, "lib")]} +hunspell_env.MergeFlags(hunspell_flags) + +env["HAVE_HUNSPELL"] = 0; +hunspell_conf = Configure(hunspell_env) +if hunspell_conf.CheckCXXHeader("hunspell/hunspell.hxx") and hunspell_conf.CheckLib("hunspell") : + env["HAVE_HUNSPELL"] = 1 + hunspell_flags["LIBS"] = ["hunspell"] + env["HUNSPELL_FLAGS"] = hunspell_flags +hunspell_conf.Finish() + # Bonjour if env["PLATFORM"] == "darwin" : env["HAVE_BONJOUR"] = 1 diff --git a/SwifTools/HunspellChecker.cpp b/SwifTools/HunspellChecker.cpp new file mode 100644 index 0000000..ecd352e --- /dev/null +++ b/SwifTools/HunspellChecker.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <SwifTools/HunspellChecker.h> + +#include <algorithm> +#include <hunspell/hunspell.hxx> +#include <boost/algorithm/string.hpp> + + +namespace Swift { + +HunspellChecker::HunspellChecker(const char* affix_path, const char* dictionary_path) { + speller_ = new Hunspell(affix_path, dictionary_path); +} + +HunspellChecker::~HunspellChecker() { + delete speller_; +} + +bool HunspellChecker::isCorrect(const std::string& word) { + return speller_->spell(word.c_str()); +} + +void HunspellChecker::getSuggestions(const std::string& word, std::vector<std::string>& list) { + char **suggestList; + int words_returned; + if (!word.empty()) { + words_returned = speller_->suggest(&suggestList, word.c_str()); + } + for (int i = 0; i < words_returned; ++i) { + list.push_back(suggestList[i]); + free(suggestList[i]); + } + free(suggestList); +} + +void HunspellChecker::checkFragment(const std::string& fragment, PositionPairList& misspelledPositions) { + if (!fragment.empty()) { + parser_->check(fragment, misspelledPositions); + for (PositionPairList::iterator it = misspelledPositions.begin(); it != misspelledPositions.end();) { + if (isCorrect(fragment.substr(boost::get<0>(*it), boost::get<1>(*it) - boost::get<0>(*it)))) { + it = misspelledPositions.erase(it); + } + else { + ++it; + } + } + } +} + +} diff --git a/SwifTools/HunspellChecker.h b/SwifTools/HunspellChecker.h new file mode 100644 index 0000000..12c0485 --- /dev/null +++ b/SwifTools/HunspellChecker.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <vector> +#include <boost/algorithm/string.hpp> +#include <boost/tuple/tuple.hpp> +#include <SwifTools/SpellChecker.h> + +#pragma once + +class Hunspell; + +namespace Swift { + class HunspellChecker : public SpellChecker { + public: + HunspellChecker(const char* affix_path, const char* dict_path); + virtual ~HunspellChecker(); + virtual bool isCorrect(const std::string& word); + virtual void getSuggestions(const std::string& word, std::vector<std::string>& list); + virtual void checkFragment(const std::string& fragment, PositionPairList& misspelledPositions); + private: + Hunspell* speller_; + }; +} diff --git a/SwifTools/SConscript b/SwifTools/SConscript index fa2686a..eaf5787 100644 --- a/SwifTools/SConscript +++ b/SwifTools/SConscript @@ -9,6 +9,8 @@ if env["SCONS_STAGE"] == "flags" : "LIBPATH": [Dir(".")], "LIBS": ["SwifTools"] } + if env["HAVE_HUNSPELL"] : + env.MergeFlags(env["HUNSPELL_FLAGS"]) ################################################################################ # Build @@ -30,6 +32,16 @@ if env["SCONS_STAGE"] == "build" : "TabComplete.cpp", "LastLineTracker.cpp", ] + + if swiftools_env["HAVE_HUNSPELL"] : + swiftools_env.MergeFlags(swiftools_env["HUNSPELL_FLAGS"]) + swiftools_env.Append(CPPDEFINES = ["HAVE_HUNSPELL"]) + sources += [ + "SpellCheckerFactory.cpp", + "HunspellChecker.cpp", + "SpellParser.cpp", + ] + if swiftools_env.get("HAVE_SPARKLE", 0) : swiftools_env.MergeFlags(swiftools_env["SPARKLE_FLAGS"]) diff --git a/SwifTools/SpellChecker.h b/SwifTools/SpellChecker.h new file mode 100644 index 0000000..746fcaf --- /dev/null +++ b/SwifTools/SpellChecker.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <SwifTools/SpellParser.h> + +#include <boost/algorithm/string.hpp> +#include <boost/tuple/tuple.hpp> +#include <vector> + +#pragma once + +namespace Swift { + class SpellChecker { + public: + SpellChecker() { + parser_ = new SpellParser(); + } + virtual ~SpellChecker() { + delete parser_; + }; + virtual bool isCorrect(const std::string& word) = 0; + virtual void getSuggestions(const std::string& word, std::vector<std::string>& list) = 0; + virtual void checkFragment(const std::string& fragment, PositionPairList& misspelledPositions) = 0; + protected: + SpellParser *parser_; + }; +} diff --git a/SwifTools/SpellCheckerFactory.cpp b/SwifTools/SpellCheckerFactory.cpp new file mode 100644 index 0000000..6061d78 --- /dev/null +++ b/SwifTools/SpellCheckerFactory.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/filesystem/operations.hpp> + +#include <SwifTools/SpellChecker.h> +#include <SwifTools/HunspellChecker.h> +#include <SwifTools/SpellCheckerFactory.h> + +#ifdef HAVE_HUNSPELL +#include <hunspell/hunspell.hxx> +#endif + +namespace Swift { + +SpellCheckerFactory::SpellCheckerFactory() { +} + +SpellChecker* SpellCheckerFactory::createSpellChecker(const std::string& dictFile) { +#ifdef HAVE_HUNSPELL + std::string affixFile(dictFile); + boost::replace_all(affixFile, ".dic", ".aff"); + if ((boost::filesystem::exists(dictFile)) && (boost::filesystem::exists(affixFile))) { + return new HunspellChecker(affixFile.c_str(), dictFile.c_str()); + } + // If dictionaries don't exist disable the checker +#endif + return NULL; +} + +} diff --git a/SwifTools/SpellCheckerFactory.h b/SwifTools/SpellCheckerFactory.h new file mode 100644 index 0000000..086ea66 --- /dev/null +++ b/SwifTools/SpellCheckerFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#ifdef HAVE_HUNSPELL +#define HAVE_SPELLCHECKER +#endif + +namespace Swift { + class SpellChecker; + class SpellCheckerFactory { + public: + SpellCheckerFactory(); + SpellChecker* createSpellChecker(const std::string& dictFile); + }; +} diff --git a/SwifTools/SpellParser.cpp b/SwifTools/SpellParser.cpp new file mode 100644 index 0000000..7208cdb --- /dev/null +++ b/SwifTools/SpellParser.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <SwifTools/SpellParser.h> + +#include <boost/spirit/include/lex_lexertl.hpp> +#include <boost/bind.hpp> +#include <boost/ref.hpp> + +#include <string> + +namespace lex = boost::spirit::lex; + +namespace Swift { + +template <typename Lexer> +struct word_count_tokens : lex::lexer<Lexer> +{ + word_count_tokens() + { + // define tokens (regular expresions) to match strings + // order is important + this->self.add + ("w{3}.[^ ]+", ID_WWW) + ("http:\\/\\/[^ ]+", ID_HTTP) + ("\\w{1,}['?|\\-?]?\\w{1,}", ID_WORD) + (".", ID_CHAR); + } +}; + +struct counter +{ + typedef bool result_type; + // the function operator gets called for each of the matched tokens + template <typename Token> + bool operator()(Token const& t, PositionPairList& wordPositions, std::size_t& position) const + { + switch (t.id()) { + case ID_WWW: + position += t.value().size(); + break; + case ID_HTTP: + position += t.value().size(); + break; + case ID_WORD: // matched a word + wordPositions.push_back(boost::tuples::make_tuple(position, position + t.value().size())); + position += t.value().size(); + break; + case ID_CHAR: // match a simple char + ++position; + break; + } + return true; // always continue to tokenize + } +}; + +void SpellParser::check(const std::string& fragment, PositionPairList& wordPositions) { + std::size_t position = 0; + // create the token definition instance needed to invoke the lexical analyzer + word_count_tokens<lex::lexertl::lexer<> > word_count_functor; + char const* first = fragment.c_str(); + char const* last = &first[fragment.size()]; + lex::tokenize(first, last, word_count_functor, boost::bind(counter(), _1, boost::ref(wordPositions), boost::ref(position))); +} + +} diff --git a/SwifTools/SpellParser.h b/SwifTools/SpellParser.h new file mode 100644 index 0000000..a6eafb5 --- /dev/null +++ b/SwifTools/SpellParser.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/algorithm/string.hpp> +#include <boost/tuple/tuple.hpp> +#include <boost/algorithm/string.hpp> + +#include <vector> + +namespace Swift { + enum token_ids + { + ID_WWW = 1, + ID_HTTP = 2, + ID_WORD = 3, + ID_CHAR = 4, + }; + + typedef boost::tuple<int, int> PositionPair; + typedef std::vector<PositionPair > PositionPairList; + + class SpellParser{ + public: + void check(const std::string& fragment, PositionPairList& wordPositions); + }; +} diff --git a/SwifTools/UnitTest/SConscript b/SwifTools/UnitTest/SConscript index e469deb..dbd1ce5 100644 --- a/SwifTools/UnitTest/SConscript +++ b/SwifTools/UnitTest/SConscript @@ -5,3 +5,8 @@ env.Append(UNITTEST_SOURCES = [ File("TabCompleteTest.cpp"), File("LastLineTrackerTest.cpp"), ]) + +if env["HAVE_HUNSPELL"] : + env.Append(UNITTEST_SOURCES = [ + File("SpellParserTest.cpp"), + ]) diff --git a/SwifTools/UnitTest/SpellParserTest.cpp b/SwifTools/UnitTest/SpellParserTest.cpp new file mode 100644 index 0000000..09e686c --- /dev/null +++ b/SwifTools/UnitTest/SpellParserTest.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2012 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <boost/algorithm/string.hpp> + +#include <SwifTools/SpellParser.h> + +using namespace Swift; + +class SpellParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(SpellParserTest); + CPPUNIT_TEST(testSimpleCheckFragment); + CPPUNIT_TEST(testWWWCheckFragment); + CPPUNIT_TEST_SUITE_END(); + public: + SpellParserTest() { + parser_ = new SpellParser(); + }; + void tearDown() { + position_.clear(); + } + void testSimpleCheckFragment() { + parser_->check("fragment test", position_); + int size = position_.size(); + CPPUNIT_ASSERT_EQUAL(2, size); + CPPUNIT_ASSERT_EQUAL(0, boost::get<0>(position_.front())); + CPPUNIT_ASSERT_EQUAL(8, boost::get<1>(position_.front())); + CPPUNIT_ASSERT_EQUAL(9, boost::get<0>(position_.back())); + CPPUNIT_ASSERT_EQUAL(13, boost::get<1>(position_.back())); + } + void testWWWCheckFragment() { + parser_->check("www.link.com fragment test", position_); + int size = position_.size(); + CPPUNIT_ASSERT_EQUAL(2, size); + CPPUNIT_ASSERT_EQUAL(13, boost::get<0>(position_.front())); + CPPUNIT_ASSERT_EQUAL(21, boost::get<1>(position_.front())); + CPPUNIT_ASSERT_EQUAL(22, boost::get<0>(position_.back())); + CPPUNIT_ASSERT_EQUAL(26, boost::get<1>(position_.back())); + } + private: + SpellParser *parser_; + PositionPairList position_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(SpellParserTest); diff --git a/Swift/Controllers/SettingConstants.cpp b/Swift/Controllers/SettingConstants.cpp index e430c77..0717fd5 100644 --- a/Swift/Controllers/SettingConstants.cpp +++ b/Swift/Controllers/SettingConstants.cpp @@ -20,4 +20,8 @@ const SettingsProvider::Setting<bool> SettingConstants::SHOW_OFFLINE("showOfflin const SettingsProvider::Setting<std::string> SettingConstants::EXPANDED_ROSTER_GROUPS("GroupExpandiness", ""); const SettingsProvider::Setting<bool> SettingConstants::PLAY_SOUNDS("playSounds", true); const SettingsProvider::Setting<std::string> SettingConstants::HIGHLIGHT_RULES("highlightRules", "@"); +const SettingsProvider::Setting<bool> SettingConstants::SPELL_CHECKER("spellChecker", false); +const SettingsProvider::Setting<std::string> SettingConstants::DICT_PATH("dictPath", "/usr/share/myspell/dicts/"); +const SettingsProvider::Setting<std::string> SettingConstants::PERSONAL_DICT_PATH("personaldictPath", "/home/"); +const SettingsProvider::Setting<std::string> SettingConstants::DICT_FILE("dictFile", "en_US.dic"); } diff --git a/Swift/Controllers/SettingConstants.h b/Swift/Controllers/SettingConstants.h index cc3af47..a3a1650 100644 --- a/Swift/Controllers/SettingConstants.h +++ b/Swift/Controllers/SettingConstants.h @@ -23,5 +23,9 @@ namespace Swift { static const SettingsProvider::Setting<std::string> EXPANDED_ROSTER_GROUPS; static const SettingsProvider::Setting<bool> PLAY_SOUNDS; static const SettingsProvider::Setting<std::string> HIGHLIGHT_RULES; + static const SettingsProvider::Setting<bool> SPELL_CHECKER; + static const SettingsProvider::Setting<std::string> DICT_PATH; + static const SettingsProvider::Setting<std::string> PERSONAL_DICT_PATH; + static const SettingsProvider::Setting<std::string> DICT_FILE; }; } diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 7f27cb6..11e64ab 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -152,7 +152,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt QHBoxLayout* inputBarLayout = new QHBoxLayout(); inputBarLayout->setContentsMargins(0,0,0,0); inputBarLayout->setSpacing(2); - input_ = new QtTextEdit(this); + input_ = new QtTextEdit(settings_, this); input_->setAcceptRichText(false); inputBarLayout->addWidget(midBar_); inputBarLayout->addWidget(input_); diff --git a/Swift/QtUI/QtJoinMUCWindow.ui b/Swift/QtUI/QtJoinMUCWindow.ui index 5a69292..9225f6f 100644 --- a/Swift/QtUI/QtJoinMUCWindow.ui +++ b/Swift/QtUI/QtJoinMUCWindow.ui @@ -43,9 +43,6 @@ </property> </widget> </item> - <item row="1" column="1" colspan="2"> - <widget class="QLineEdit" name="nickName"/> - </item> <item row="0" column="1"> <widget class="QLineEdit" name="room"> <property name="text"> @@ -63,6 +60,9 @@ <item row="2" column="1"> <widget class="QLineEdit" name="password"/> </item> + <item row="1" column="1" colspan="2"> + <widget class="QLineEdit" name="nickName"/> + </item> </layout> </item> <item> diff --git a/Swift/QtUI/QtSpellCheckerWindow.cpp b/Swift/QtUI/QtSpellCheckerWindow.cpp new file mode 100644 index 0000000..e2c5b0d --- /dev/null +++ b/Swift/QtUI/QtSpellCheckerWindow.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swift/QtUI/QtSpellCheckerWindow.h" + +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> +#include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/QtSwiftUtil.h> + +#include <QCoreApplication> +#include <QFileDialog> +#include <QDir> +#include <QStringList> + +namespace Swift { + +QtSpellCheckerWindow::QtSpellCheckerWindow(SettingsProvider* settings, QWidget* parent) : QDialog(parent) { + settings_ = settings; + ui_.setupUi(this); + connect(ui_.spellChecker, SIGNAL(toggled(bool)), this, SLOT(handleChecker(bool))); + connect(ui_.cancel, SIGNAL(clicked()), this, SLOT(handleCancel())); + connect(ui_.apply, SIGNAL(clicked()), this, SLOT(handleApply())); + connect(ui_.pathButton, SIGNAL(clicked()), this, SLOT(handlePathButton())); + setFromSettings(); +} + +void QtSpellCheckerWindow::setFromSettings() { + ui_.spellChecker->setChecked(settings_->getSetting(SettingConstants::SPELL_CHECKER)); + ui_.pathContent->setText(P2QSTRING(settings_->getSetting(SettingConstants::DICT_PATH))); + ui_.currentLanguageValue->setText(P2QSTRING(settings_->getSetting(SettingConstants::DICT_FILE))); + std::string currentPath = settings_->getSetting(SettingConstants::DICT_PATH); + QString filename = "*.dic"; + QDir dictDirectory = QDir(P2QSTRING(currentPath)); + QStringList files = dictDirectory.entryList(QStringList(filename), QDir::Files); + showFiles(files); + setEnabled(settings_->getSetting(SettingConstants::SPELL_CHECKER)); +} + +void QtSpellCheckerWindow::handleChecker(bool state) { + setEnabled(state); +} + +void QtSpellCheckerWindow::setEnabled(bool state) { + ui_.pathContent->setEnabled(state); + ui_.languageView->setEnabled(state); + ui_.pathButton->setEnabled(state); + ui_.pathLabel->setEnabled(state); + ui_.currentLanguage->setEnabled(state); + ui_.currentLanguageValue->setEnabled(state); + ui_.language->setEnabled(state); +} + +void QtSpellCheckerWindow::handleApply() { + settings_->storeSetting(SettingConstants::SPELL_CHECKER, ui_.spellChecker->isChecked()); + QList<QListWidgetItem* > selectedLanguage = ui_.languageView->selectedItems(); + if (!selectedLanguage.empty()) { + settings_->storeSetting(SettingConstants::DICT_FILE, Q2PSTRING((selectedLanguage.first())->text())); + } + this->done(0); +} + +void QtSpellCheckerWindow::handleCancel() { + this->done(0); +} + +void QtSpellCheckerWindow::handlePathButton() { + std::string currentPath = settings_->getSetting(SettingConstants::DICT_PATH); + QString dirpath = QFileDialog::getExistingDirectory(this, tr("Dictionary Path"), P2QSTRING(currentPath)); + if (dirpath != P2QSTRING(currentPath)) { + ui_.languageView->clear(); + settings_->storeSetting(SettingConstants::DICT_FILE, ""); + ui_.currentLanguageValue->setText(" "); + } + if (!dirpath.isEmpty()) { + if (!dirpath.endsWith("/")) { + dirpath.append("/"); + } + settings_->storeSetting(SettingConstants::DICT_PATH, Q2PSTRING(dirpath)); + QDir dictDirectory = QDir(dirpath); + ui_.pathContent->setText(dirpath); + QString filename = "*.dic"; + QStringList files = dictDirectory.entryList(QStringList(filename), QDir::Files); + showFiles(files); + } +} + +void QtSpellCheckerWindow::handlePersonalPathButton() { + std::string currentPath = settings_->getSetting(SettingConstants::PERSONAL_DICT_PATH); + QString filename = QFileDialog::getOpenFileName(this, tr("Select Personal Dictionary"), P2QSTRING(currentPath), tr("(*.dic")); + settings_->storeSetting(SettingConstants::PERSONAL_DICT_PATH, Q2PSTRING(filename)); +} + +void QtSpellCheckerWindow::showFiles(const QStringList& files) { + ui_.languageView->clear(); + for (int i = 0; i < files.size(); ++i) { + ui_.languageView->insertItem(i, files[i]); + } +} + +} diff --git a/Swift/QtUI/QtSpellCheckerWindow.h b/Swift/QtUI/QtSpellCheckerWindow.h new file mode 100644 index 0000000..ad94907 --- /dev/null +++ b/Swift/QtUI/QtSpellCheckerWindow.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2011 Vlad Voicu + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "ui_QtSpellCheckerWindow.h" + +#include <QDialog> + +namespace Swift { + class SettingsProvider; + class QtSpellCheckerWindow : public QDialog, protected Ui::QtSpellCheckerWindow { + Q_OBJECT + public: + QtSpellCheckerWindow(SettingsProvider* settings, QWidget* parent = NULL); + public slots: + void handleChecker(bool state); + void handleCancel(); + void handlePathButton(); + void handlePersonalPathButton(); + void handleApply(); + + private: + void setEnabled(bool state); + void setFromSettings(); + void showFiles(const QStringList& files); + SettingsProvider* settings_; + Ui::QtSpellCheckerWindow ui_; + }; +} diff --git a/Swift/QtUI/QtSpellCheckerWindow.ui b/Swift/QtUI/QtSpellCheckerWindow.ui new file mode 100644 index 0000000..b98bb6d --- /dev/null +++ b/Swift/QtUI/QtSpellCheckerWindow.ui @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QtSpellCheckerWindow</class> + <widget class="QDialog" name="QtSpellCheckerWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>353</width> + <height>207</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="4" column="3" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="cancel"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="apply"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0" colspan="3"> + <widget class="QCheckBox" name="spellChecker"> + <property name="text"> + <string>Spell Checker Enabled</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QLabel" name="pathLabel"> + <property name="text"> + <string>Dictionary Path:</string> + </property> + </widget> + </item> + <item row="1" column="2" colspan="2"> + <widget class="QLineEdit" name="pathContent"/> + </item> + <item row="1" column="4"> + <widget class="QPushButton" name="pathButton"> + <property name="text"> + <string>Change</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QLabel" name="currentLanguage"> + <property name="text"> + <string>Current Language:</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="currentLanguageValue"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="3" column="1" colspan="4"> + <widget class="QListWidget" name="languageView"/> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="language"> + <property name="text"> + <string>Language:</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp index 0497d01..d1a75dd 100644 --- a/Swift/QtUI/QtTextEdit.cpp +++ b/Swift/QtUI/QtTextEdit.cpp @@ -4,18 +4,42 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ +#include <boost/tuple/tuple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> + +#include <Swiften/Base/foreach.h> + +#include <SwifTools/SpellCheckerFactory.h> +#include <SwifTools/SpellChecker.h> + #include <Swift/QtUI/QtTextEdit.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtSpellCheckerWindow.h> +#include <Swift/Controllers/SettingConstants.h> +#include <QApplication> #include <QFontMetrics> #include <QKeyEvent> +#include <QDebug> +#include <QMenu> namespace Swift { -QtTextEdit::QtTextEdit(QWidget* parent) : QTextEdit(parent){ +QtTextEdit::QtTextEdit(SettingsProvider* settings, QWidget* parent) : QTextEdit(parent) { connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged())); + checker_ = NULL; + settings_ = settings; +#ifdef HAVE_SPELLCHECKER + setUpSpellChecker(); +#endif handleTextChanged(); } +QtTextEdit::~QtTextEdit() { + delete checker_; +} + void QtTextEdit::keyPressEvent(QKeyEvent* event) { int key = event->key(); Qt::KeyboardModifiers modifiers = event->modifiers(); @@ -35,13 +59,41 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) { emit unhandledKeyPressEvent(event); } else if ((key == Qt::Key_Up) - || (key == Qt::Key_Down) - ){ + || (key == Qt::Key_Down)) { emit unhandledKeyPressEvent(event); QTextEdit::keyPressEvent(event); } else { QTextEdit::keyPressEvent(event); +#ifdef HAVE_SPELLCHECKER + if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { + underlineMisspells(); + } +#endif + } +} + +void QtTextEdit::underlineMisspells() { + QTextCursor cursor = textCursor(); + misspelledPositions_.clear(); + QTextCharFormat normalFormat; + cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor, 1); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor, 1); + cursor.setCharFormat(normalFormat); + if (checker_ == NULL) { + return; + } + QTextCharFormat spellingErrorFormat; + spellingErrorFormat.setUnderlineColor(QColor(Qt::red)); + spellingErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); + std::string fragment = Q2PSTRING(cursor.selectedText()); + checker_->checkFragment(fragment, misspelledPositions_); + foreach (PositionPair position, misspelledPositions_) { + cursor.setPosition(boost::get<0>(position), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(position), QTextCursor::KeepAnchor); + cursor.setCharFormat(spellingErrorFormat); + cursor.clearSelection(); + cursor.setCharFormat(normalFormat); } } @@ -53,6 +105,24 @@ void QtTextEdit::handleTextChanged() { } } +void QtTextEdit::replaceMisspelledWord(const QString& word, int cursorPosition) { + QTextCursor cursor = textCursor(); + PositionPair wordPosition = getWordFromCursor(cursorPosition); + cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor); + QTextCharFormat normalFormat; + cursor.insertText(word, normalFormat); +} + +PositionPair QtTextEdit::getWordFromCursor(int cursorPosition) { + for (PositionPairList::iterator it = misspelledPositions_.begin(); it != misspelledPositions_.end(); ++it) { + if (cursorPosition >= boost::get<0>(*it) && cursorPosition <= boost::get<1>(*it)) { + return *it; + } + } + return boost::make_tuple(-1,-1); +} + QSize QtTextEdit::sizeHint() const { QFontMetrics inputMetrics(currentFont()); QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999); @@ -66,7 +136,100 @@ QSize QtTextEdit::sizeHint() const { //return QSize(QTextEdit::sizeHint().width(), lineHeight * numberOfLines); } +void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) { + QMenu* menu = createStandardContextMenu(); + QTextCursor cursor = cursorForPosition(event->pos()); +#ifdef HAVE_SPELLCHECKER + QAction* insertPoint = menu->actions().first(); + QAction* settingsAction = new QAction(QApplication::translate("QtTextEdit", "Spell Checker Options", 0, QApplication::UnicodeUTF8), menu); + menu->insertAction(insertPoint, settingsAction); + menu->insertAction(insertPoint, menu->addSeparator()); + addSuggestions(menu, event); + QAction* result = menu->exec(event->globalPos()); + if (result == settingsAction) { + spellCheckerSettingsWindow(); + } + for (std::vector<QAction*>::iterator it = replaceWordActions_.begin(); it != replaceWordActions_.end(); ++it) { + if (*it == result) { + replaceMisspelledWord((*it)->text(), cursor.position()); + } + } +#else + menu->exec(event->globalPos()); +#endif + delete menu; } +void QtTextEdit::addSuggestions(QMenu* menu, QContextMenuEvent* event) +{ + replaceWordActions_.clear(); + QAction* insertPoint = menu->actions().first(); + QTextCursor cursor = cursorForPosition(event->pos()); + PositionPair wordPosition = getWordFromCursor(cursor.position()); + if (boost::get<0>(wordPosition) < 0) { + // The click was executed outside a spellable word so no + // suggestions are necessary + return; + } + cursor.setPosition(boost::get<0>(wordPosition), QTextCursor::MoveAnchor); + cursor.setPosition(boost::get<1>(wordPosition), QTextCursor::KeepAnchor); + std::vector<std::string> wordList; + checker_->getSuggestions(Q2PSTRING(cursor.selectedText()), wordList); + if (wordList.size() == 0) { + QAction* noSuggestions = new QAction(QApplication::translate("QtTextEdit", "No Suggestions", 0, QApplication::UnicodeUTF8), menu); + noSuggestions->setDisabled(true); + menu->insertAction(insertPoint, noSuggestions); + } + else { + for (std::vector<std::string>::iterator it = wordList.begin(); it != wordList.end(); ++it) { + QAction* wordAction = new QAction(it->c_str(), menu); + menu->insertAction(insertPoint, wordAction); + replaceWordActions_.push_back(wordAction); + } + } + menu->insertAction(insertPoint, menu->addSeparator()); +} +#ifdef HAVE_SPELLCHECKER +void QtTextEdit::setUpSpellChecker() +{ + SpellCheckerFactory* checkerFactory = new SpellCheckerFactory(); + delete checker_; + if (settings_->getSetting(SettingConstants::SPELL_CHECKER)) { + std::string dictPath = settings_->getSetting(SettingConstants::DICT_PATH); + std::string dictFile = settings_->getSetting(SettingConstants::DICT_FILE); + checker_ = checkerFactory->createSpellChecker(dictPath + dictFile); + delete checkerFactory; + } + else { + checker_ = NULL; + } +} +#endif + +void QtTextEdit::spellCheckerSettingsWindow() { + if (!spellCheckerWindow_) { + spellCheckerWindow_ = new QtSpellCheckerWindow(settings_); + settings_->onSettingChanged.connect(boost::bind(&QtTextEdit::handleSettingChanged, this, _1)); + spellCheckerWindow_->show(); + } + else { + spellCheckerWindow_->show(); + spellCheckerWindow_->raise(); + spellCheckerWindow_->activateWindow(); + } +} + +void QtTextEdit::handleSettingChanged(const std::string& settings) { + if (settings == SettingConstants::SPELL_CHECKER.getKey() + || settings == SettingConstants::DICT_PATH.getKey() + || settings == SettingConstants::DICT_FILE.getKey()) { +#ifdef HAVE_SPELLCHECKER + setUpSpellChecker(); + underlineMisspells(); +#endif + } +} + +} diff --git a/Swift/QtUI/QtTextEdit.h b/Swift/QtUI/QtTextEdit.h index 075728b..a8df4d3 100644 --- a/Swift/QtUI/QtTextEdit.h +++ b/Swift/QtUI/QtTextEdit.h @@ -5,20 +5,46 @@ */ #pragma once + +#include <SwifTools/SpellParser.h> + +#include <Swift/Controllers/Settings/SettingsProvider.h> +#include <Swift/Controllers/SettingConstants.h> + #include <QTextEdit> +#include <QPointer> namespace Swift { + class SpellChecker; + class QtSpellCheckerWindow; class QtTextEdit : public QTextEdit { Q_OBJECT public: - QtTextEdit(QWidget* parent = 0); + QtTextEdit(SettingsProvider* settings, QWidget* parent = 0); + virtual ~QtTextEdit(); virtual QSize sizeHint() const; signals: + void wordCorrected(QString& word); void returnPressed(); void unhandledKeyPressEvent(QKeyEvent* event); + public slots: + void handleSettingChanged(const std::string& settings); protected: virtual void keyPressEvent(QKeyEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* event); private slots: void handleTextChanged(); + private: + SpellChecker *checker_; + std::vector<QAction*> replaceWordActions_; + PositionPairList misspelledPositions_; + SettingsProvider *settings_; + QPointer<QtSpellCheckerWindow> spellCheckerWindow_; + void addSuggestions(QMenu* menu, QContextMenuEvent* event); + void replaceMisspelledWord(const QString& word, int cursorPosition); + void setUpSpellChecker(); + void underlineMisspells(); + void spellCheckerSettingsWindow(); + PositionPair getWordFromCursor(int cursorPosition); }; } diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 13d36fa..bfa35a5 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -45,6 +45,8 @@ if myenv["swift_mobile"] : if myenv.get("HAVE_SNARL", False) : myenv.UseFlags(myenv["SNARL_FLAGS"]) myenv.Append(CPPDEFINES = ["HAVE_SNARL"]) +if myenv.get("HAVE_HUNSPELL", True): + myenv.Append(CPPDEFINES = ["HAVE_HUNSPELL"]) if env["PLATFORM"] == "win32" : myenv.Append(LIBS = ["cryptui"]) myenv.UseFlags(myenv["PLATFORM_FLAGS"]) @@ -83,6 +85,7 @@ myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("# sources = [ "main.cpp", "QtAboutWidget.cpp", + "QtSpellCheckerWindow.cpp", "QtAvatarWidget.cpp", "QtUIFactory.cpp", "QtChatWindowFactory.cpp", @@ -270,6 +273,7 @@ myenv.Uic4("QtHistoryWindow.ui") myenv.Uic4("QtConnectionSettings.ui") myenv.Uic4("QtHighlightRuleWidget.ui") myenv.Uic4("QtHighlightEditorWidget.ui") +myenv.Uic4("QtSpellCheckerWindow.ui") myenv.Qrc("DefaultTheme.qrc") myenv.Qrc("Swift.qrc") |