From 2ee740b3e66a1ce1a2fc642a412cdee144587727 Mon Sep 17 00:00:00 2001 From: marcel Date: Fri, 16 May 2014 09:44:27 +0200 Subject: [PATCH 01/18] - added spellchecking, currently only works on linux and mac - use spellchecking in inputtextwidget and provide a suggestion list --- projectfiles/QtCreator/TOX-Qt-GUI.pro | 10 ++-- src/inputtextwidget.cpp | 35 +++++++++++- src/inputtextwidget.hpp | 2 + src/spellchecker.cpp | 77 +++++++++++++++++++++++++++ src/spellchecker.hpp | 45 ++++++++++++++++ 5 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 src/spellchecker.cpp create mode 100644 src/spellchecker.hpp diff --git a/projectfiles/QtCreator/TOX-Qt-GUI.pro b/projectfiles/QtCreator/TOX-Qt-GUI.pro index 717e331..2e7ab89 100644 --- a/projectfiles/QtCreator/TOX-Qt-GUI.pro +++ b/projectfiles/QtCreator/TOX-Qt-GUI.pro @@ -43,9 +43,9 @@ win32 { LIBS += -lWS2_32 ../../libs/sodium/lib/libsodium.a } else { macx { - LIBS += -L/usr/local/lib -lsodium + LIBS += -L/usr/local/lib -lsodium -lhunspell } else { - LIBS += -lsodium + LIBS += -lsodium -lhunspell } } @@ -92,7 +92,8 @@ SOURCES += \ ../../src/frienditemdelegate.cpp \ ../../src/editablelabelwidget.cpp \ ../../src/esclineedit.cpp \ - ../../src/copyableelidelabel.cpp + ../../src/copyableelidelabel.cpp \ + ../../src/spellchecker.cpp HEADERS += \ ../../src/mainwindow.hpp \ @@ -135,7 +136,8 @@ HEADERS += \ ../../src/frienditemdelegate.hpp \ ../../src/editablelabelwidget.hpp \ ../../src/esclineedit.hpp \ - ../../src/copyableelidelabel.hpp + ../../src/copyableelidelabel.hpp \ + ../../src/spellchecker.hpp ### ToxCore section. Please keep it alphabetical ### diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index 7daff22..756d8bc 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -24,12 +24,13 @@ #include #include #include +#include #include "smileypack.hpp" #include "Settings/settings.hpp" InputTextWidget::InputTextWidget(QWidget* parent) : - QTextEdit(parent) + QTextEdit(parent), spellchecker(document()) { setMinimumSize(10, 50); @@ -155,5 +156,35 @@ void InputTextWidget::showContextMenu(const QPoint &pos) actionPaste->setDisabled(QApplication::clipboard()->text().isEmpty()); - contextMenu.exec(globalPos); + // get current selected word and - if neccessary - the suggested + // words by the spellchecker + // create a QAction for each suggested word and handle them after + // the execution of the context menu + QList actions; + QTextCursor cursor = textCursor(); + cursor.select(QTextCursor::WordUnderCursor); + QString selectedWord = cursor.selectedText(); + if (!spellchecker.isCorrect(selectedWord)) { + QStringList suggestions; + spellchecker.suggest(selectedWord, suggestions); + + if (!suggestions.isEmpty()) { + contextMenu.addSeparator(); + QStringListIterator it(suggestions); + while (it.hasNext()) { + QString suggestion = it.next(); + QAction* action = new QAction(suggestion, this); + action->setData(suggestion); + contextMenu.addAction(action); + actions.append(action); + } + } + } + + + QAction* selected = contextMenu.exec(globalPos); + if (actions.contains(selected)) { + cursor.insertText(selected->data().toString()); + } + qDeleteAll(actions); } diff --git a/src/inputtextwidget.hpp b/src/inputtextwidget.hpp index d43c6e7..280f5e2 100644 --- a/src/inputtextwidget.hpp +++ b/src/inputtextwidget.hpp @@ -18,6 +18,7 @@ #ifndef INPUTTEXTWIDGET_HPP #define INPUTTEXTWIDGET_HPP +#include "spellchecker.hpp" #include class InputTextWidget : public QTextEdit @@ -42,6 +43,7 @@ private slots: private: QString desmile(QString htmlText); + Spellchecker spellchecker; QAction *actionUndo; QAction *actionRedo; diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp new file mode 100644 index 0000000..6022271 --- /dev/null +++ b/src/spellchecker.cpp @@ -0,0 +1,77 @@ +/* + Copyright (C) 2013 by Maxim Biro + + This file is part of Tox Qt GUI. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#include "spellchecker.hpp" + +#include +#include +#include +#include +#include +#include + +Spellchecker::Spellchecker(QTextDocument* document) + : QSyntaxHighlighter(document), regEx("\\W") /* any non-word character */ +{ + QFileInfo aff("/usr/share/hunspell/en_US.aff"); + QFileInfo dic("/usr/share/hunspell/en_US.dic"); + hunspell = new Hunspell( + aff.absoluteFilePath().toLocal8Bit().constData(), + dic.absoluteFilePath().toLocal8Bit().constData() + ); + + format.setUnderlineColor(QColor(255,0,0)); + format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); +} + +Spellchecker::~Spellchecker() +{ + delete hunspell; +} + +void Spellchecker::highlightBlock(const QString& text) +{ + const QStringList tokens = text.split(regEx); + QStringListIterator it(tokens); + QString token; + int start, length; + start = length = 0; + + while (it.hasNext()) { + token = it.next(); + length = token.length(); + + if (!isCorrect(token)) { + setFormat(start, length, format); + } + + start += length + 1; // skip the non-word character + } +} + +bool Spellchecker::isCorrect(const QString& word) +{ + return hunspell->spell(word.toLocal8Bit().constData()) != 0; +} + +void Spellchecker::suggest(const QString& word, QStringList& suggestions) +{ + char** slst; + int n = hunspell->suggest(&slst, word.toLocal8Bit().constData()); + while (n-- > 0) { + suggestions << slst[n]; + } +} diff --git a/src/spellchecker.hpp b/src/spellchecker.hpp new file mode 100644 index 0000000..67e33df --- /dev/null +++ b/src/spellchecker.hpp @@ -0,0 +1,45 @@ +/* + Copyright (C) 2013 by Maxim Biro + + This file is part of Tox Qt GUI. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#ifndef SPELLCHECKER_HPP +#define SPELLCHECKER_HPP + +#include +#include + +class QRegExp; +class QTextCharFormat; + +class Spellchecker : public QSyntaxHighlighter +{ + Q_OBJECT +public: + Spellchecker(QTextDocument*); + ~Spellchecker(); + + bool isCorrect(const QString&); + void suggest(const QString&, QStringList&); + +protected: + void highlightBlock(const QString& text); + +private: + Hunspell* hunspell; + QRegExp regEx; + QTextCharFormat format; +}; + +#endif // SPELLCHECKER_HPP From fc2f967484bf5cbb3162c4dae46711fa600c1fa4 Mon Sep 17 00:00:00 2001 From: marcel Date: Fri, 16 May 2014 09:50:25 +0200 Subject: [PATCH 02/18] added maximum number of suggestions --- src/inputtextwidget.cpp | 4 ++-- src/inputtextwidget.hpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index 756d8bc..864e2ce 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -30,7 +30,7 @@ #include "Settings/settings.hpp" InputTextWidget::InputTextWidget(QWidget* parent) : - QTextEdit(parent), spellchecker(document()) + QTextEdit(parent), spellchecker(document()), maxSuggestions(4) { setMinimumSize(10, 50); @@ -171,7 +171,7 @@ void InputTextWidget::showContextMenu(const QPoint &pos) if (!suggestions.isEmpty()) { contextMenu.addSeparator(); QStringListIterator it(suggestions); - while (it.hasNext()) { + for (int i = 0; i < maxSuggestions && it.hasNext(); i++) { QString suggestion = it.next(); QAction* action = new QAction(suggestion, this); action->setData(suggestion); diff --git a/src/inputtextwidget.hpp b/src/inputtextwidget.hpp index 280f5e2..94976cb 100644 --- a/src/inputtextwidget.hpp +++ b/src/inputtextwidget.hpp @@ -44,6 +44,7 @@ private slots: private: QString desmile(QString htmlText); Spellchecker spellchecker; + const int maxSuggestions; QAction *actionUndo; QAction *actionRedo; From 30158e0b9de6e14031e4cbbd33f57b3f4bac5721 Mon Sep 17 00:00:00 2001 From: retuxx Date: Mon, 19 May 2014 13:37:22 +0200 Subject: [PATCH 03/18] Fixed order of suggested words. --- src/spellchecker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index 6022271..cdd652a 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -70,8 +70,8 @@ bool Spellchecker::isCorrect(const QString& word) void Spellchecker::suggest(const QString& word, QStringList& suggestions) { char** slst; - int n = hunspell->suggest(&slst, word.toLocal8Bit().constData()); - while (n-- > 0) { - suggestions << slst[n]; + const int numberOfSuggestions = hunspell->suggest(&slst, word.toLocal8Bit().constData()); + for (int i = 0; i < numberOfSuggestions; i++) { + suggestions << slst[i]; } } From a16633e2ec6b24f0a43291a2e720ffb003760faa Mon Sep 17 00:00:00 2001 From: retuxx Date: Mon, 19 May 2014 14:09:56 +0200 Subject: [PATCH 04/18] select the word of the clicked position instead of the current cursor position --- src/inputtextwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index 864e2ce..c1a55d4 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -161,7 +161,7 @@ void InputTextWidget::showContextMenu(const QPoint &pos) // create a QAction for each suggested word and handle them after // the execution of the context menu QList actions; - QTextCursor cursor = textCursor(); + QTextCursor cursor = cursorForPosition(pos); cursor.select(QTextCursor::WordUnderCursor); QString selectedWord = cursor.selectedText(); if (!spellchecker.isCorrect(selectedWord)) { From 95946c81fac4ff49a0e6302052c835956039371d Mon Sep 17 00:00:00 2001 From: retuxx Date: Mon, 19 May 2014 14:37:44 +0200 Subject: [PATCH 05/18] install hunspell in Travis-CI to make project compile --- .travis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1e61192..1d3df5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,14 @@ before_install: - sudo make install - sudo ldconfig - cd .. + - wget http://dfn.dl.sourceforge.net/project/hunspell/Hunspell/1.3.2/hunspell-1.3.2.tar.gz > /dev/null + - tar xfz hunspell-1.3.2.tar.gz > /dev/null + - cd hunspell-1.3.2 + - ./configure > /dev/null + - make -j3 > /dev/null + - sudo make install > /dev/null + - cd .. + script: - ~/Qt5.2.0/5.2.0/gcc_64/bin/qmake -v @@ -35,4 +43,4 @@ notifications: - "chat.freenode.net#Tox-Qt-GUI" on_success: always on_failure: always - \ No newline at end of file + From ce4f0f5c575b6e9f1427319e6e5c5d84bc3dac50 Mon Sep 17 00:00:00 2001 From: retuxx Date: Mon, 19 May 2014 14:47:37 +0200 Subject: [PATCH 06/18] use ldconfig after installation of hunspell to ensure ld will find it --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1d3df5e..d73e331 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,6 @@ before_install: - ./autogen.sh - ./configure && make -j3 check - sudo make install - - sudo ldconfig - cd .. - wget http://dfn.dl.sourceforge.net/project/hunspell/Hunspell/1.3.2/hunspell-1.3.2.tar.gz > /dev/null - tar xfz hunspell-1.3.2.tar.gz > /dev/null @@ -29,6 +28,7 @@ before_install: - make -j3 > /dev/null - sudo make install > /dev/null - cd .. + - sudo ldconfig script: From 725f966517947a590e12cb8e68807eba9673c2b7 Mon Sep 17 00:00:00 2001 From: retuxx Date: Mon, 19 May 2014 15:25:39 +0200 Subject: [PATCH 07/18] try to install hunspell from apt instead compile it from source --- .travis.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d73e331..a1465b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ before_install: - if [ "$CXX" = "g++" ]; then sudo apt-get install g++-4.8; fi - if [ "$CXX" = "g++" ]; then sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 50; fi - if [ "$CXX" = "g++" ]; then sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50; fi + - sudo apt-get install hunspell - wget -O Qt5.2.0.tar.xz https://dl.dropboxusercontent.com/u/20447449/Qt5.2.0.tar.xz - mkdir ~/Qt5.2.0 - tar -xJf Qt5.2.0.tar.xz -C ~/Qt5.2.0 @@ -21,13 +22,6 @@ before_install: - ./configure && make -j3 check - sudo make install - cd .. - - wget http://dfn.dl.sourceforge.net/project/hunspell/Hunspell/1.3.2/hunspell-1.3.2.tar.gz > /dev/null - - tar xfz hunspell-1.3.2.tar.gz > /dev/null - - cd hunspell-1.3.2 - - ./configure > /dev/null - - make -j3 > /dev/null - - sudo make install > /dev/null - - cd .. - sudo ldconfig From 4211340f2de20941d17b68b8024906189e50902e Mon Sep 17 00:00:00 2001 From: retuxx Date: Mon, 19 May 2014 15:31:39 +0200 Subject: [PATCH 08/18] use the dev package of hunspell --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a1465b4..8fe5c14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ before_install: - if [ "$CXX" = "g++" ]; then sudo apt-get install g++-4.8; fi - if [ "$CXX" = "g++" ]; then sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 50; fi - if [ "$CXX" = "g++" ]; then sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50; fi - - sudo apt-get install hunspell + - sudo apt-get install libhunspell-dev - wget -O Qt5.2.0.tar.xz https://dl.dropboxusercontent.com/u/20447449/Qt5.2.0.tar.xz - mkdir ~/Qt5.2.0 - tar -xJf Qt5.2.0.tar.xz -C ~/Qt5.2.0 From a4b8177bfe1d810fa3ce19f77535f4cfb7bcb854 Mon Sep 17 00:00:00 2001 From: retuxx Date: Mon, 19 May 2014 15:56:49 +0200 Subject: [PATCH 09/18] show suggested words on top of the context menu --- src/inputtextwidget.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index c1a55d4..19abc31 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -144,17 +144,8 @@ void InputTextWidget::cutPlainText() void InputTextWidget::showContextMenu(const QPoint &pos) { - QPoint globalPos = mapToGlobal(pos); - + const QPoint globalPos = mapToGlobal(pos); QMenu contextMenu; - contextMenu.addAction(actionUndo); - contextMenu.addAction(actionRedo); - contextMenu.addSeparator(); - contextMenu.addAction(actionCut); - contextMenu.addAction(actionCopy); - contextMenu.addAction(actionPaste); - - actionPaste->setDisabled(QApplication::clipboard()->text().isEmpty()); // get current selected word and - if neccessary - the suggested // words by the spellchecker @@ -163,13 +154,12 @@ void InputTextWidget::showContextMenu(const QPoint &pos) QList actions; QTextCursor cursor = cursorForPosition(pos); cursor.select(QTextCursor::WordUnderCursor); - QString selectedWord = cursor.selectedText(); + const QString selectedWord = cursor.selectedText(); if (!spellchecker.isCorrect(selectedWord)) { QStringList suggestions; spellchecker.suggest(selectedWord, suggestions); if (!suggestions.isEmpty()) { - contextMenu.addSeparator(); QStringListIterator it(suggestions); for (int i = 0; i < maxSuggestions && it.hasNext(); i++) { QString suggestion = it.next(); @@ -178,9 +168,18 @@ void InputTextWidget::showContextMenu(const QPoint &pos) contextMenu.addAction(action); actions.append(action); } + contextMenu.addSeparator(); } } + contextMenu.addAction(actionUndo); + contextMenu.addAction(actionRedo); + contextMenu.addSeparator(); + contextMenu.addAction(actionCut); + contextMenu.addAction(actionCopy); + contextMenu.addAction(actionPaste); + + actionPaste->setDisabled(QApplication::clipboard()->text().isEmpty()); QAction* selected = contextMenu.exec(globalPos); if (actions.contains(selected)) { From 4d6d39470ede4e7cb1e10c75cc3cf55fb34edbb6 Mon Sep 17 00:00:00 2001 From: marcel Date: Mon, 19 May 2014 17:16:22 +0200 Subject: [PATCH 10/18] use QRegularExpression over QRegExp --- src/spellchecker.cpp | 1 - src/spellchecker.hpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index cdd652a..b374afe 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include diff --git a/src/spellchecker.hpp b/src/spellchecker.hpp index 67e33df..fd3bd01 100644 --- a/src/spellchecker.hpp +++ b/src/spellchecker.hpp @@ -18,9 +18,9 @@ #define SPELLCHECKER_HPP #include +#include #include -class QRegExp; class QTextCharFormat; class Spellchecker : public QSyntaxHighlighter @@ -38,7 +38,7 @@ class Spellchecker : public QSyntaxHighlighter private: Hunspell* hunspell; - QRegExp regEx; + QRegularExpression regEx; QTextCharFormat format; }; From fc64d8fb3cbb65364ee9ac9bee17885d495dd8d9 Mon Sep 17 00:00:00 2001 From: marcel Date: Mon, 19 May 2014 17:20:16 +0200 Subject: [PATCH 11/18] updated name and email --- src/spellchecker.cpp | 2 +- src/spellchecker.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index b374afe..bb41ba2 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 by Maxim Biro + Copyright (C) 2013 by retuxx This file is part of Tox Qt GUI. diff --git a/src/spellchecker.hpp b/src/spellchecker.hpp index fd3bd01..faee4a5 100644 --- a/src/spellchecker.hpp +++ b/src/spellchecker.hpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2013 by Maxim Biro + Copyright (C) 2013 by retuxx This file is part of Tox Qt GUI. From ce78af86365b7ea3cb9a1079d334db10e5953ac2 Mon Sep 17 00:00:00 2001 From: marcel Date: Mon, 19 May 2014 18:25:23 +0200 Subject: [PATCH 12/18] skip spell checking for currently written words (not fully working yet) --- src/inputtextwidget.cpp | 4 ++++ src/spellchecker.cpp | 23 ++++++++++++++++++----- src/spellchecker.hpp | 5 ++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index 19abc31..d752aff 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -73,6 +73,10 @@ InputTextWidget::InputTextWidget(QWidget* parent) : actionCut->setEnabled(enabled); actionCopy->setEnabled(enabled); }); + + connect(this, &InputTextWidget::textChanged, [this]() { + spellchecker.setSkippedPosition(textCursor().position()); + }); } /*! Handle keyboard events. */ diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index bb41ba2..1d6dc5f 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -23,7 +23,7 @@ #include Spellchecker::Spellchecker(QTextDocument* document) - : QSyntaxHighlighter(document), regEx("\\W") /* any non-word character */ + : QSyntaxHighlighter(document), regEx("\\W") /* any non-word character */, skippedPosition(-1) { QFileInfo aff("/usr/share/hunspell/en_US.aff"); QFileInfo dic("/usr/share/hunspell/en_US.dic"); @@ -46,14 +46,17 @@ void Spellchecker::highlightBlock(const QString& text) const QStringList tokens = text.split(regEx); QStringListIterator it(tokens); QString token; - int start, length; - start = length = 0; + const int offset = currentBlock().position(); + int start, length, end; + start = length = end = 0; while (it.hasNext()) { - token = it.next(); + token = it.next(); length = token.length(); + end = start + length; - if (!isCorrect(token)) { + if (!(skippedPosition >= offset + start && skippedPosition < offset + end) && // skip ignored position + !isCorrect(token)) { setFormat(start, length, format); } @@ -74,3 +77,13 @@ void Spellchecker::suggest(const QString& word, QStringList& suggestions) suggestions << slst[i]; } } + +int Spellchecker::getSkippedPosition() +{ + return skippedPosition; +} + +void Spellchecker::setSkippedPosition(int position) +{ + skippedPosition = position; +} diff --git a/src/spellchecker.hpp b/src/spellchecker.hpp index faee4a5..5c3777d 100644 --- a/src/spellchecker.hpp +++ b/src/spellchecker.hpp @@ -32,14 +32,17 @@ class Spellchecker : public QSyntaxHighlighter bool isCorrect(const QString&); void suggest(const QString&, QStringList&); + int getSkippedPosition(); + void setSkippedPosition(int); protected: void highlightBlock(const QString& text); private: Hunspell* hunspell; - QRegularExpression regEx; + const QRegularExpression regEx; QTextCharFormat format; + int skippedPosition; // absolute position in document }; #endif // SPELLCHECKER_HPP From fef994efe42224a4d75dccc447be2b6df47bf61a Mon Sep 17 00:00:00 2001 From: Maxim Biro Date: Mon, 19 May 2014 17:35:46 -0400 Subject: [PATCH 13/18] Made hunspell work on Windows --- projectfiles/QtCreator/TOX-Qt-GUI.pro | 4 ++-- src/spellchecker.cpp | 15 ++++++++++++--- src/spellchecker.hpp | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/projectfiles/QtCreator/TOX-Qt-GUI.pro b/projectfiles/QtCreator/TOX-Qt-GUI.pro index 2e7ab89..b7a6897 100644 --- a/projectfiles/QtCreator/TOX-Qt-GUI.pro +++ b/projectfiles/QtCreator/TOX-Qt-GUI.pro @@ -36,11 +36,11 @@ TEMPLATE = app CONFIG += c++11 INCLUDEPATH += ../../src/ ../../submodules/ProjectTox-Core/toxcore/ -win32:INCLUDEPATH += ../../libs/sodium/include/ +win32:INCLUDEPATH += ../../libs/sodium/include/ ../../libs/hunspell/include/ macx:INCLUDEPATH += /usr/local/include win32 { - LIBS += -lWS2_32 ../../libs/sodium/lib/libsodium.a + LIBS += -lWS2_32 ../../libs/sodium/lib/libsodium.a ../../libs/hunspell/lib/libhunspell.a } else { macx { LIBS += -L/usr/local/lib -lsodium -lhunspell diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index 1d6dc5f..77bff37 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -16,17 +16,26 @@ #include "spellchecker.hpp" +#include +#include #include #include #include #include -#include + +#include Spellchecker::Spellchecker(QTextDocument* document) : QSyntaxHighlighter(document), regEx("\\W") /* any non-word character */, skippedPosition(-1) { - QFileInfo aff("/usr/share/hunspell/en_US.aff"); - QFileInfo dic("/usr/share/hunspell/en_US.dic"); + QString basePath; +#ifdef Q_OS_WIN + basePath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + '/' + "hunspell" + '/'; +#else + basePath = "/usr/share/hunspell/"; +#endif + QFileInfo aff(basePath + "en_US.aff"); + QFileInfo dic(basePath + "en_US.dic"); hunspell = new Hunspell( aff.absoluteFilePath().toLocal8Bit().constData(), dic.absoluteFilePath().toLocal8Bit().constData() diff --git a/src/spellchecker.hpp b/src/spellchecker.hpp index 5c3777d..731769d 100644 --- a/src/spellchecker.hpp +++ b/src/spellchecker.hpp @@ -17,10 +17,10 @@ #ifndef SPELLCHECKER_HPP #define SPELLCHECKER_HPP -#include #include -#include +#include +class Hunspell; class QTextCharFormat; class Spellchecker : public QSyntaxHighlighter From 52337cf1c76a164b8d6e5468e629cb09ac3a9721 Mon Sep 17 00:00:00 2001 From: retuxx Date: Tue, 20 May 2014 02:37:06 +0200 Subject: [PATCH 14/18] ignore currently written word --- src/inputtextwidget.cpp | 20 +++++++++++++++++--- src/inputtextwidget.hpp | 2 ++ src/spellchecker.cpp | 9 +++++++-- src/spellchecker.hpp | 3 +++ 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index d752aff..f2501a0 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -30,7 +30,7 @@ #include "Settings/settings.hpp" InputTextWidget::InputTextWidget(QWidget* parent) : - QTextEdit(parent), spellchecker(document()), maxSuggestions(4) + QTextEdit(parent), spellchecker(document()), contentChanged(false), maxSuggestions(4) { setMinimumSize(10, 50); @@ -74,9 +74,22 @@ InputTextWidget::InputTextWidget(QWidget* parent) : actionCopy->setEnabled(enabled); }); - connect(this, &InputTextWidget::textChanged, [this]() { + + connect(this, &InputTextWidget::cursorPositionChanged, [this]() { + if (!contentChanged) { + spellchecker.setSkippedPosition(Spellchecker::NO_SKIPPING); + spellchecker.rehighlight(); + } else { + contentChanged = false; + } + }); + connect(document(), &QTextDocument::contentsChange, [this](int position, int charsRemoved, int charsAdded) { + contentChanged = true; spellchecker.setSkippedPosition(textCursor().position()); }); + // this is a simple hack to ensure that the connected slots above + // will be triggered before the highlighting will be applied. + spellchecker.setDocument(document()); } /*! Handle keyboard events. */ @@ -159,7 +172,8 @@ void InputTextWidget::showContextMenu(const QPoint &pos) QTextCursor cursor = cursorForPosition(pos); cursor.select(QTextCursor::WordUnderCursor); const QString selectedWord = cursor.selectedText(); - if (!spellchecker.isCorrect(selectedWord)) { + if (!spellchecker.skipRange(cursor.position(), selectedWord.length()) && + !spellchecker.isCorrect(selectedWord)) { QStringList suggestions; spellchecker.suggest(selectedWord, suggestions); diff --git a/src/inputtextwidget.hpp b/src/inputtextwidget.hpp index 94976cb..a339e5f 100644 --- a/src/inputtextwidget.hpp +++ b/src/inputtextwidget.hpp @@ -43,7 +43,9 @@ private slots: private: QString desmile(QString htmlText); + Spellchecker spellchecker; + bool contentChanged; const int maxSuggestions; QAction *actionUndo; diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index 77bff37..7ec82de 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -26,7 +26,7 @@ #include Spellchecker::Spellchecker(QTextDocument* document) - : QSyntaxHighlighter(document), regEx("\\W") /* any non-word character */, skippedPosition(-1) + : QSyntaxHighlighter(document), regEx("\\W") /* any non-word character */, skippedPosition(NO_SKIPPING) { QString basePath; #ifdef Q_OS_WIN @@ -64,7 +64,7 @@ void Spellchecker::highlightBlock(const QString& text) length = token.length(); end = start + length; - if (!(skippedPosition >= offset + start && skippedPosition < offset + end) && // skip ignored position + if (!skipRange(offset + start, offset + end) && !isCorrect(token)) { setFormat(start, length, format); } @@ -96,3 +96,8 @@ void Spellchecker::setSkippedPosition(int position) { skippedPosition = position; } + +bool Spellchecker::skipRange(int start, int end) +{ + return skippedPosition >= start && skippedPosition <= end; +} diff --git a/src/spellchecker.hpp b/src/spellchecker.hpp index 731769d..672b523 100644 --- a/src/spellchecker.hpp +++ b/src/spellchecker.hpp @@ -27,6 +27,8 @@ class Spellchecker : public QSyntaxHighlighter { Q_OBJECT public: + static const int NO_SKIPPING = -1; + Spellchecker(QTextDocument*); ~Spellchecker(); @@ -34,6 +36,7 @@ class Spellchecker : public QSyntaxHighlighter void suggest(const QString&, QStringList&); int getSkippedPosition(); void setSkippedPosition(int); + bool skipRange(int /*inclusive*/, int /*inclusive*/); protected: void highlightBlock(const QString& text); From 211cb6e22e0f5581fcaadcae169568609125952f Mon Sep 17 00:00:00 2001 From: retuxx Date: Tue, 20 May 2014 17:38:24 +0200 Subject: [PATCH 15/18] moved handler for skipping into the spellchecker to reduce the amount of internal knowledge --- src/inputtextwidget.cpp | 23 +++----------- src/spellchecker.cpp | 68 +++++++++++++++++++++++++++++++++++------ src/spellchecker.hpp | 30 +++++++++++++++--- 3 files changed, 88 insertions(+), 33 deletions(-) diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index f2501a0..41581ff 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -30,7 +30,7 @@ #include "Settings/settings.hpp" InputTextWidget::InputTextWidget(QWidget* parent) : - QTextEdit(parent), spellchecker(document()), contentChanged(false), maxSuggestions(4) + QTextEdit(parent), spellchecker(this), contentChanged(false), maxSuggestions(4) { setMinimumSize(10, 50); @@ -73,23 +73,6 @@ InputTextWidget::InputTextWidget(QWidget* parent) : actionCut->setEnabled(enabled); actionCopy->setEnabled(enabled); }); - - - connect(this, &InputTextWidget::cursorPositionChanged, [this]() { - if (!contentChanged) { - spellchecker.setSkippedPosition(Spellchecker::NO_SKIPPING); - spellchecker.rehighlight(); - } else { - contentChanged = false; - } - }); - connect(document(), &QTextDocument::contentsChange, [this](int position, int charsRemoved, int charsAdded) { - contentChanged = true; - spellchecker.setSkippedPosition(textCursor().position()); - }); - // this is a simple hack to ensure that the connected slots above - // will be triggered before the highlighting will be applied. - spellchecker.setDocument(document()); } /*! Handle keyboard events. */ @@ -172,7 +155,9 @@ void InputTextWidget::showContextMenu(const QPoint &pos) QTextCursor cursor = cursorForPosition(pos); cursor.select(QTextCursor::WordUnderCursor); const QString selectedWord = cursor.selectedText(); - if (!spellchecker.skipRange(cursor.position(), selectedWord.length()) && + // cursor.position() points to the end of the selected word + // substract selectedWord.length() to get the start position + if (!spellchecker.skipRange(cursor.position() - selectedWord.length(), cursor.position()) && !spellchecker.isCorrect(selectedWord)) { QStringList suggestions; spellchecker.suggest(selectedWord, suggestions); diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index 7ec82de..38f5cab 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -22,11 +22,17 @@ #include #include #include +#include #include -Spellchecker::Spellchecker(QTextDocument* document) - : QSyntaxHighlighter(document), regEx("\\W") /* any non-word character */, skippedPosition(NO_SKIPPING) +Spellchecker::Spellchecker(QTextEdit* parent) + : QSyntaxHighlighter(parent), + textEdit(parent), + regEx("\\W"), /* any non-word character. */ + format(), + skippedPosition(NO_SKIPPING), /* skipping should be disabled by default. */ + contentChanged(false) { QString basePath; #ifdef Q_OS_WIN @@ -43,6 +49,13 @@ Spellchecker::Spellchecker(QTextDocument* document) format.setUnderlineColor(QColor(255,0,0)); format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); + + connect(textEdit, &QTextEdit::cursorPositionChanged, this, &Spellchecker::cursorPositionChanged); + connect(textEdit->document(), &QTextDocument::contentsChange, this, &Spellchecker::contentsChanged); + // this is a simple hack to ensure that the connected slot above + // will be triggered before the highlighting will be applied. + setDocument(textEdit->document()); + } Spellchecker::~Spellchecker() @@ -54,13 +67,12 @@ void Spellchecker::highlightBlock(const QString& text) { const QStringList tokens = text.split(regEx); QStringListIterator it(tokens); - QString token; const int offset = currentBlock().position(); int start, length, end; start = length = end = 0; while (it.hasNext()) { - token = it.next(); + const QString& token = it.next(); length = token.length(); end = start + length; @@ -87,17 +99,53 @@ void Spellchecker::suggest(const QString& word, QStringList& suggestions) } } -int Spellchecker::getSkippedPosition() +bool Spellchecker::skipRange(int start, int end) { - return skippedPosition; + return skippedPosition >= start && skippedPosition <= end; } -void Spellchecker::setSkippedPosition(int position) +int Spellchecker::toPositionInBlock(int position) { - skippedPosition = position; + int counter = 0; + const QTextBlock block = textEdit->document()->findBlock(position); + const QString text = block.text(); + const int mappedPosition = position - block.position(); + const QStringList tokens = text.split(regEx); + + QStringListIterator it(tokens); + int start = 0; + + while (it.hasNext() && start < mappedPosition) { + start += it.next().length() + 1; // skip the non-word character + counter++; + } + + return start < mappedPosition ? -1 : counter; } -bool Spellchecker::skipRange(int start, int end) +void Spellchecker::contentsChanged(int position, int charsRemoved, int charsAdded) { - return skippedPosition >= start && skippedPosition <= end; + contentChanged = true; + skippedPosition = textEdit->textCursor().position(); +} + +void Spellchecker::cursorPositionChanged() +{ + // block signal if content was changed because + // skipping was already handled by slot Spellchecker::contentsChanged(); + if (!contentChanged && skippedPosition >= 0) { + const int pos = textEdit->textCursor().position(); + const int current = toPositionInBlock(skippedPosition); + const int moved = toPositionInBlock(pos); + + if (current >= 0 && moved >= 0 && current == moved) { + skippedPosition = pos; + } else { + skippedPosition = NO_SKIPPING; + } + + rehighlight(); + } + + contentChanged = false; } diff --git a/src/spellchecker.hpp b/src/spellchecker.hpp index 672b523..8b801c0 100644 --- a/src/spellchecker.hpp +++ b/src/spellchecker.hpp @@ -22,6 +22,7 @@ class Hunspell; class QTextCharFormat; +class QTextEdit; class Spellchecker : public QSyntaxHighlighter { @@ -29,23 +30,44 @@ class Spellchecker : public QSyntaxHighlighter public: static const int NO_SKIPPING = -1; - Spellchecker(QTextDocument*); + Spellchecker(QTextEdit*); ~Spellchecker(); bool isCorrect(const QString&); void suggest(const QString&, QStringList&); - int getSkippedPosition(); - void setSkippedPosition(int); bool skipRange(int /*inclusive*/, int /*inclusive*/); protected: void highlightBlock(const QString& text); private: + /* the view to highlight */ + QTextEdit* textEdit; + + /* the current used dictionary */ Hunspell* hunspell; + + /* the regular expression to use for tokenizing each line */ const QRegularExpression regEx; + + /* the format to apply to misspelled words */ QTextCharFormat format; - int skippedPosition; // absolute position in document + + /* the spell checker will ignore the word which + * has at least one character at this position (absolute) */ + int skippedPosition; + + /* indicates whether the content was changed or not + * do not use until you know what you are doing */ + bool contentChanged; + + /* maps the given absolute position of a character in the document + * to the relative position of its words inside its block */ + int toPositionInBlock(int); + +private slots: + void contentsChanged(int, int, int); + void cursorPositionChanged(); }; #endif // SPELLCHECKER_HPP From c713011f4a9a5f27ef10069ca6b75c7741d641b1 Mon Sep 17 00:00:00 2001 From: retuxx Date: Wed, 21 May 2014 00:37:22 +0200 Subject: [PATCH 16/18] fixed off by one --- src/spellchecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index 38f5cab..cf20bbd 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -120,7 +120,7 @@ int Spellchecker::toPositionInBlock(int position) counter++; } - return start < mappedPosition ? -1 : counter; + return start <= mappedPosition ? -1 : counter; } void Spellchecker::contentsChanged(int position, int charsRemoved, int charsAdded) From 341984643ca8d9e55e38bf5452be450ff586a781 Mon Sep 17 00:00:00 2001 From: retuxx Date: Wed, 21 May 2014 00:39:38 +0200 Subject: [PATCH 17/18] removed unused attribute --- src/inputtextwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inputtextwidget.cpp b/src/inputtextwidget.cpp index 41581ff..0c96adc 100644 --- a/src/inputtextwidget.cpp +++ b/src/inputtextwidget.cpp @@ -30,7 +30,7 @@ #include "Settings/settings.hpp" InputTextWidget::InputTextWidget(QWidget* parent) : - QTextEdit(parent), spellchecker(this), contentChanged(false), maxSuggestions(4) + QTextEdit(parent), spellchecker(this), maxSuggestions(4) { setMinimumSize(10, 50); From d227bafa1ba4a1cdea0452405566ee9907a7cb2a Mon Sep 17 00:00:00 2001 From: retuxx Date: Wed, 21 May 2014 10:18:54 +0200 Subject: [PATCH 18/18] somehow missed this file in c713011f4a9a5f27ef10069ca6b75c7741d641b1 --- src/inputtextwidget.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/inputtextwidget.hpp b/src/inputtextwidget.hpp index a339e5f..48b8f22 100644 --- a/src/inputtextwidget.hpp +++ b/src/inputtextwidget.hpp @@ -45,7 +45,6 @@ private slots: QString desmile(QString htmlText); Spellchecker spellchecker; - bool contentChanged; const int maxSuggestions; QAction *actionUndo;