summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVlad Voicu <vladvoic@gmail.com>2011-11-28 16:37:32 (GMT)
committerVlad Voicu <vladvoic@gmail.com>2013-03-15 09:21:52 (GMT)
commit2061b06eccca67595c50edd81c44c5b961bf108b (patch)
tree7fdc9e4cc80a9d8ddbe5364a531ef3449f72ab2b
parenta069a0df0f51a948a86e34d99f952a33eecd97ba (diff)
downloadswift-2061b06eccca67595c50edd81c44c5b961bf108b.zip
swift-2061b06eccca67595c50edd81c44c5b961bf108b.tar.bz2
Spell checker implementation using Hunspell
Change-Id: Ia15b6532edf6eef7c45bdfb273e77f65ce998f13 License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details
-rw-r--r--BuildTools/SCons/SConscript.boot1
-rw-r--r--BuildTools/SCons/SConstruct16
-rw-r--r--SwifTools/HunspellChecker.cpp55
-rw-r--r--SwifTools/HunspellChecker.h27
-rw-r--r--SwifTools/SConscript12
-rw-r--r--SwifTools/SpellChecker.h30
-rw-r--r--SwifTools/SpellCheckerFactory.cpp34
-rw-r--r--SwifTools/SpellCheckerFactory.h20
-rw-r--r--SwifTools/SpellParser.cpp69
-rw-r--r--SwifTools/SpellParser.h31
-rw-r--r--SwifTools/UnitTest/SConscript5
-rw-r--r--SwifTools/UnitTest/SpellParserTest.cpp51
-rw-r--r--Swift/Controllers/SettingConstants.cpp4
-rw-r--r--Swift/Controllers/SettingConstants.h4
-rw-r--r--Swift/QtUI/QtChatWindow.cpp2
-rw-r--r--Swift/QtUI/QtJoinMUCWindow.ui6
-rw-r--r--Swift/QtUI/QtSpellCheckerWindow.cpp104
-rw-r--r--Swift/QtUI/QtSpellCheckerWindow.h33
-rw-r--r--Swift/QtUI/QtSpellCheckerWindow.ui87
-rw-r--r--Swift/QtUI/QtTextEdit.cpp169
-rw-r--r--Swift/QtUI/QtTextEdit.h28
-rw-r--r--Swift/QtUI/SConscript4
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")