diff options
author | Vlad Voicu <vladvoic@gmail.com> | 2011-11-28 16:37:32 (GMT) |
---|---|---|
committer | Vlad Voicu <vladvoic@gmail.com> | 2013-03-15 09:21:52 (GMT) |
commit | 2061b06eccca67595c50edd81c44c5b961bf108b (patch) | |
tree | 7fdc9e4cc80a9d8ddbe5364a531ef3449f72ab2b /Swift/QtUI | |
parent | a069a0df0f51a948a86e34d99f952a33eecd97ba (diff) | |
download | swift-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
Diffstat (limited to 'Swift/QtUI')
-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 |
8 files changed, 425 insertions, 8 deletions
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") |