From b8f10af29258705b4610e99ba69f5ecab42ef6ae Mon Sep 17 00:00:00 2001 From: Vlad Voicu Date: Mon, 28 Nov 2011 18:37:32 +0200 Subject: Initial, buggy version of a minimal spell-checker diff --git a/SwifTools/SConscript b/SwifTools/SConscript index e5085cc..3e1a105 100644 --- a/SwifTools/SConscript +++ b/SwifTools/SConscript @@ -7,7 +7,7 @@ Import("env") if env["SCONS_STAGE"] == "flags" : env["SWIFTOOLS_FLAGS"] = { "LIBPATH": [Dir(".")], - "LIBS": ["SwifTools"] + "LIBS": ["SwifTools", "libaspell"] } ################################################################################ @@ -28,6 +28,7 @@ if env["SCONS_STAGE"] == "build" : "Linkify.cpp", "TabComplete.cpp", "LastLineTracker.cpp", + "SpellChecker.cpp", ] if swiftools_env.get("HAVE_SPARKLE", 0) : diff --git a/SwifTools/SpellChecker.cpp b/SwifTools/SpellChecker.cpp new file mode 100644 index 0000000..8c9aeda --- /dev/null +++ b/SwifTools/SpellChecker.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011 Voicu Vlad + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include + +#include +#include +#include + + +namespace Swift { + +SpellChecker::SpellChecker() { + AspellCanHaveError* ret; + config_ = new_aspell_config(); + ret = new_aspell_speller(config_); + if (aspell_error(ret) != 0) { + //TODO(vladv): Proper log the error + //printf("Error: %s\n",aspell_error_message(ret)); + delete_aspell_can_have_error(ret); + } + speller_ = to_aspell_speller(ret); +} + +bool SpellChecker::isCorrect(const std::string& word) { + if (!word.empty()) { + int aspell_error = aspell_speller_check(speller_, word.c_str(), -1); + if (aspell_error == 1) { + return true; + } else { + //TODO(vladv): Proper log the error + } + } + return false; +} + +void SpellChecker::getSuggestions(const std::string& word, std::vector& list) { + const AspellWordList *alist; + if (!word.empty()) { + alist = aspell_speller_suggest(speller_, word.c_str(), -1); + AspellStringEnumeration *els = aspell_word_list_elements(alist); + const char* aword; + while (((aword = aspell_string_enumeration_next(els)) != 0) && list.size() <= 5) { + list.push_back(std::string(aword)); + } + } +} + +} diff --git a/SwifTools/SpellChecker.h b/SwifTools/SpellChecker.h new file mode 100644 index 0000000..3a9ff38 --- /dev/null +++ b/SwifTools/SpellChecker.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Voicu Vlad + * Licensed under the Simplified BSD license. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include +#include + +#pragma once + +class AspellSpeller; +class AspellConfig; + +namespace Swift { + class SpellChecker { + public: + SpellChecker(); + bool isCorrect(const std::string& word); + void getSuggestions(const std::string& word, std::vector& list); + private: + AspellSpeller* speller_; + AspellConfig* config_; + }; +} diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp index 3a62325..e1fdd26 100644 --- a/Swift/QtUI/QtTextEdit.cpp +++ b/Swift/QtUI/QtTextEdit.cpp @@ -4,18 +4,28 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ +#include + #include +#include #include #include +#include +#include namespace Swift { QtTextEdit::QtTextEdit(QWidget* parent) : QTextEdit(parent){ connect(this, SIGNAL(textChanged()), this, SLOT(handleTextChanged())); handleTextChanged(); + checker_ = new SpellChecker(); }; +QtTextEdit::~QtTextEdit() { + delete checker_; +} + void QtTextEdit::keyPressEvent(QKeyEvent* event) { int key = event->key(); Qt::KeyboardModifiers modifiers = event->modifiers(); @@ -35,13 +45,37 @@ 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); + underlineMisspells(); + } +} + +void QtTextEdit::underlineMisspells() { + QTextCharFormat spellingErrorFormat; + QTextCharFormat normalFormat; + spellingErrorFormat.setUnderlineColor(QColor(Qt::red)); + spellingErrorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); + QTextCursor cursor = textCursor(); + QString word; + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1); + word = cursor.selectedText(); + // Qt handles punctuation as words. Workaround that. + while (!word.isEmpty() && !word.at(0).isLetter()) { + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 2); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1); + word = cursor.selectedText(); + } + if ((!word.isEmpty()) && !checker_->isCorrect(word.toStdString())) { + cursor.mergeCharFormat(spellingErrorFormat); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::MoveAnchor, 1); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor, 1); + cursor.setCharFormat(normalFormat); } } @@ -53,6 +87,14 @@ void QtTextEdit::handleTextChanged() { } } +void QtTextEdit::handleReplaceMisspellWord(const QString& word, const QPoint& position) { + QTextCursor cursor = cursorForPosition(position); + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1); + QTextCharFormat normalFormat; + cursor.insertText(word, normalFormat); +} + QSize QtTextEdit::sizeHint() const { QFontMetrics inputMetrics(currentFont()); QRect horizontalBounds = contentsRect().adjusted(0,0,0,9999); @@ -66,7 +108,26 @@ QSize QtTextEdit::sizeHint() const { //return QSize(QTextEdit::sizeHint().width(), lineHeight * numberOfLines); } -} +void QtTextEdit::contextMenuEvent(QContextMenuEvent* event) { + QTextCursor cursor = cursorForPosition(event->pos()); + cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::MoveAnchor, 1); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1); + std::vector wordList; + checker_->getSuggestions(Q2PSTRING(cursor.selectedText()), wordList); + std::vector replaceWordActions; + QMenu *menu = createStandardContextMenu(); + for (std::vector::iterator it = wordList.begin(); it != wordList.end(); ++it) { + replaceWordActions.push_back(menu->addAction(tr(it->c_str()))); + } + QAction* result = menu->exec(event->globalPos()); + for (std::vector::iterator it = replaceWordActions.begin(); it != replaceWordActions.end(); ++it) { + if (*it == result) { + handleReplaceMisspellWord((*it)->text(), event->pos()); + } + } + delete menu; +} +} diff --git a/Swift/QtUI/QtTextEdit.h b/Swift/QtUI/QtTextEdit.h index 075728b..a940879 100644 --- a/Swift/QtUI/QtTextEdit.h +++ b/Swift/QtUI/QtTextEdit.h @@ -5,20 +5,30 @@ */ #pragma once + #include namespace Swift { + class SpellChecker; class QtTextEdit : public QTextEdit { Q_OBJECT public: QtTextEdit(QWidget* parent = 0); + virtual ~QtTextEdit(); virtual QSize sizeHint() const; signals: + void wordCorrected(QString& word); void returnPressed(); void unhandledKeyPressEvent(QKeyEvent* event); + public slots: + void handleReplaceMisspellWord(const QString& word, const QPoint& position); protected: virtual void keyPressEvent(QKeyEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* event); private slots: void handleTextChanged(); + private: + SpellChecker *checker_; + void underlineMisspells(); }; } -- cgit v0.10.2-6-g49f6