diff --git a/src/core/backgroundchecker.cpp b/src/core/backgroundchecker.cpp index 8df4eda..cb20e75 100644 --- a/src/core/backgroundchecker.cpp +++ b/src/core/backgroundchecker.cpp @@ -1,213 +1,213 @@ -/** +/* * backgroundchecker.cpp * * SPDX-FileCopyrightText: 2004 Zack Rusin * SPDX-FileCopyrightText: 2009 Jakub Stachowski * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "backgroundchecker.h" #include "backgroundchecker_p.h" #include "core_debug.h" using namespace Sonnet; void BackgroundCheckerPrivate::start() { sentenceOffset = -1; continueChecking(); } void BackgroundCheckerPrivate::continueChecking() { metaObject()->invokeMethod(this, "checkNext", Qt::QueuedConnection); } void BackgroundCheckerPrivate::checkNext() { do { // go over current sentence while (sentenceOffset != -1 && words.hasNext()) { QStringRef word = words.next(); if (!words.isSpellcheckable()) { continue; } // ok, this is valid word, do something if (currentDict.isMisspelled(word.toString())) { lastMisspelled = word; emit misspelling(word.toString(), word.position()+sentenceOffset); return; } } // current sentence done, grab next suitable sentenceOffset = -1; const bool autodetectLanguage = currentDict.testAttribute(Speller::AutoDetectLanguage); const bool ignoreUpperCase = !currentDict.testAttribute(Speller::CheckUppercase); while (mainTokenizer.hasNext()) { QStringRef sentence = mainTokenizer.next(); if (autodetectLanguage) { if (!mainTokenizer.isSpellcheckable()) { continue; } // FIXME: find best from family en -> en_US, en_GB, ... ? currentDict.setLanguage(mainTokenizer.language()); } sentenceOffset = sentence.position(); words.setBuffer(sentence.toString()); words.setIgnoreUppercase(ignoreUpperCase); break; } } while (sentenceOffset != -1); emit done(); } BackgroundChecker::BackgroundChecker(QObject *parent) : QObject(parent) , d(new BackgroundCheckerPrivate) { connect(d, SIGNAL(misspelling(QString,int)), SIGNAL(misspelling(QString,int))); connect(d, SIGNAL(done()), SLOT(slotEngineDone())); } BackgroundChecker::BackgroundChecker(const Speller &speller, QObject *parent) : QObject(parent) , d(new BackgroundCheckerPrivate) { d->currentDict = speller; connect(d, &BackgroundCheckerPrivate::misspelling, this, &BackgroundChecker::misspelling); connect(d, &BackgroundCheckerPrivate::done, this, &BackgroundChecker::slotEngineDone); } BackgroundChecker::~BackgroundChecker() { delete d; } void BackgroundChecker::setText(const QString &text) { d->mainTokenizer.setBuffer(text); d->start(); } void BackgroundChecker::start() { // ## what if d->currentText.isEmpty()? //TODO: carry state from last buffer d->mainTokenizer.setBuffer(fetchMoreText()); d->start(); } void BackgroundChecker::stop() { // d->stop(); } QString BackgroundChecker::fetchMoreText() { return QString(); } void BackgroundChecker::finishedCurrentFeed() { } void BackgroundChecker::setSpeller(const Speller &speller) { d->currentDict = speller; } Speller BackgroundChecker::speller() const { return d->currentDict; } bool BackgroundChecker::checkWord(const QString &word) { return d->currentDict.isCorrect(word); } bool BackgroundChecker::addWordToPersonal(const QString &word) { return d->currentDict.addToPersonal(word); } bool BackgroundChecker::addWordToSession(const QString &word) { return d->currentDict.addToSession(word); } QStringList BackgroundChecker::suggest(const QString &word) const { return d->currentDict.suggest(word); } void BackgroundChecker::changeLanguage(const QString &lang) { // this sets language only for current sentence d->currentDict.setLanguage(lang); } void BackgroundChecker::continueChecking() { d->continueChecking(); } void BackgroundChecker::slotEngineDone() { finishedCurrentFeed(); const QString currentText = fetchMoreText(); if (currentText.isNull()) { emit done(); } else { d->mainTokenizer.setBuffer(currentText); d->start(); } } QString BackgroundChecker::text() const { return d->mainTokenizer.buffer(); } QString BackgroundChecker::currentContext() const { int len = 60; //we don't want the expression underneath casted to an unsigned int //which would cause it to always evaluate to false int currentPosition = d->lastMisspelled.position()+d->sentenceOffset; bool begin = ((currentPosition - len/2) <= 0) ? true : false; QString buffer = d->mainTokenizer.buffer(); buffer.replace(currentPosition, d->lastMisspelled.length(), QStringLiteral("%1").arg(d->lastMisspelled.toString())); QString context; if (begin) { context = QStringLiteral("%1...") .arg(buffer.mid(0, len)); } else { context = QStringLiteral("...%1...") .arg(buffer.mid(currentPosition - 20, len)); } context.replace(QLatin1Char('\n'), QLatin1Char(' ')); return context; } void Sonnet::BackgroundChecker::replace(int start, const QString &oldText, const QString &newText) { //FIXME: here we assume that replacement is in current fragment. So 'words' has //to be adjusted and sentenceOffset does not d->words.replace(start-(d->sentenceOffset), oldText.length(), newText); d->mainTokenizer.replace(start, oldText.length(), newText); } diff --git a/src/core/backgroundchecker_p.h b/src/core/backgroundchecker_p.h index 9b73866..08ed955 100644 --- a/src/core/backgroundchecker_p.h +++ b/src/core/backgroundchecker_p.h @@ -1,44 +1,44 @@ -/** +/* * backgroundchecker_p.h * * SPDX-FileCopyrightText: 2009 Jakub Stachowski * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef SONNET_BACKGROUNDCHECKER_P_H #define SONNET_BACKGROUNDCHECKER_P_H #include #include "backgroundchecker.h" #include "languagefilter_p.h" #include "tokenizer_p.h" #include "speller.h" using namespace Sonnet; class BackgroundCheckerPrivate : public QObject { Q_OBJECT public: BackgroundCheckerPrivate() : mainTokenizer(new SentenceTokenizer) , sentenceOffset(-1) { } void start(); void continueChecking(); LanguageFilter mainTokenizer; WordTokenizer words; QStringRef lastMisspelled; Speller currentDict; int sentenceOffset; private Q_SLOTS: void checkNext(); Q_SIGNALS: void misspelling(const QString &, int); void done(); }; #endif diff --git a/src/plugins/aspell/aspellclient.cpp b/src/plugins/aspell/aspellclient.cpp index 1dd5f48..e05a229 100644 --- a/src/plugins/aspell/aspellclient.cpp +++ b/src/plugins/aspell/aspellclient.cpp @@ -1,54 +1,54 @@ -/** +/* * kspell_aspellclient.cpp * * SPDX-FileCopyrightText: 2003 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "aspellclient.h" #include "aspelldict.h" #include "aspell_debug.h" #ifdef Q_OS_WIN #include #endif using namespace Sonnet; ASpellClient::ASpellClient(QObject *parent) : Client(parent) { m_config = new_aspell_config(); #ifdef Q_OS_WIN aspell_config_replace(m_config, "data-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData()); aspell_config_replace(m_config, "dict-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData()); #endif } ASpellClient::~ASpellClient() { delete_aspell_config(m_config); } SpellerPlugin *ASpellClient::createSpeller(const QString &language) { ASpellDict *ad = new ASpellDict(language); return ad; } QStringList ASpellClient::languages() const { AspellDictInfoList *l = get_aspell_dict_info_list(m_config); AspellDictInfoEnumeration *el = aspell_dict_info_list_elements(l); QStringList langs; const AspellDictInfo *di = nullptr; while ((di = aspell_dict_info_enumeration_next(el))) { langs.append(QString::fromLatin1(di->name)); } delete_aspell_dict_info_enumeration(el); return langs; } diff --git a/src/plugins/aspell/aspellclient.h b/src/plugins/aspell/aspellclient.h index a71e99f..8f4ae5d 100644 --- a/src/plugins/aspell/aspellclient.h +++ b/src/plugins/aspell/aspellclient.h @@ -1,48 +1,48 @@ -/** +/* * kspell_aspellclient.h * * SPDX-FileCopyrightText: 2003 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_ASPELLCLIENT_H #define KSPELL_ASPELLCLIENT_H #include "client_p.h" #include "aspell.h" namespace Sonnet { class SpellerPlugin; } using Sonnet::SpellerPlugin; class ASpellClient : public Sonnet::Client { Q_OBJECT Q_INTERFACES(Sonnet::Client) Q_PLUGIN_METADATA(IID "org.kde.Sonnet.ASpellClient") public: explicit ASpellClient(QObject *parent = nullptr); ~ASpellClient() override; int reliability() const override { return 20; } SpellerPlugin *createSpeller(const QString &language) override; QStringList languages() const override; QString name() const override { return QStringLiteral("ASpell"); } private: AspellConfig *m_config; }; #endif diff --git a/src/plugins/aspell/aspelldict.cpp b/src/plugins/aspell/aspelldict.cpp index 71a2b1b..978a11e 100644 --- a/src/plugins/aspell/aspelldict.cpp +++ b/src/plugins/aspell/aspelldict.cpp @@ -1,126 +1,126 @@ -/** +/* * kspell_aspelldict.cpp * * SPDX-FileCopyrightText: 2003 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "aspelldict.h" #include "aspell_debug.h" #include #ifdef Q_OS_WIN #include #endif using namespace Sonnet; ASpellDict::ASpellDict(const QString &lang) : SpellerPlugin(lang) { m_config = new_aspell_config(); aspell_config_replace(m_config, "lang", lang.toLatin1().constData()); /* All communication with Aspell is done in UTF-8 */ /* For reference, please look at BR#87250 */ aspell_config_replace(m_config, "encoding", "utf-8"); #ifdef Q_OS_WIN aspell_config_replace(m_config, "data-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData()); aspell_config_replace(m_config, "dict-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData()); #endif AspellCanHaveError *possible_err = new_aspell_speller(m_config); if (aspell_error_number(possible_err) != 0) { qCWarning(SONNET_LOG_ASPELL) << "aspell error: " << aspell_error_message(possible_err); } else { m_speller = to_aspell_speller(possible_err); } } ASpellDict::~ASpellDict() { delete_aspell_speller(m_speller); delete_aspell_config(m_config); } bool ASpellDict::isCorrect(const QString &word) const { /* ASpell is expecting length of a string in char representation */ /* word.length() != word.toUtf8().length() for nonlatin strings */ if (!m_speller) { return false; } int correct = aspell_speller_check(m_speller, word.toUtf8().constData(), word.toUtf8().length()); return correct; } QStringList ASpellDict::suggest(const QString &word) const { if (!m_speller) { return QStringList(); } /* Needed for Unicode conversion */ QTextCodec *codec = QTextCodec::codecForName("utf8"); /* ASpell is expecting length of a string in char representation */ /* word.length() != word.toUtf8().length() for nonlatin strings */ const AspellWordList *suggestions = aspell_speller_suggest(m_speller, word.toUtf8().constData(), word.toUtf8().length()); AspellStringEnumeration *elements = aspell_word_list_elements(suggestions); QStringList qsug; const char *cword; while ((cword = aspell_string_enumeration_next(elements))) { /* Since while creating the class ASpellDict the encoding is set */ /* to utf-8, one has to convert output from Aspell to Unicode */ qsug.append(codec->toUnicode(cword)); } delete_aspell_string_enumeration(elements); return qsug; } bool ASpellDict::storeReplacement(const QString &bad, const QString &good) { if (!m_speller) { return false; } /* ASpell is expecting length of a string in char representation */ /* word.length() != word.toUtf8().length() for nonlatin strings */ return aspell_speller_store_replacement(m_speller, bad.toUtf8().constData(), bad.toUtf8().length(), good.toUtf8().constData(), good.toUtf8().length()); } bool ASpellDict::addToPersonal(const QString &word) { if (!m_speller) { return false; } qCDebug(SONNET_LOG_ASPELL) << "Adding" << word << "to aspell personal dictionary"; /* ASpell is expecting length of a string in char representation */ /* word.length() != word.toUtf8().length() for nonlatin strings */ aspell_speller_add_to_personal(m_speller, word.toUtf8().constData(), word.toUtf8().length()); /* Add is not enough, one has to save it. This is not documented */ /* in ASpell's API manual. I found it in */ /* aspell-0.60.2/example/example-c.c */ return aspell_speller_save_all_word_lists(m_speller); } bool ASpellDict::addToSession(const QString &word) { if (!m_speller) { return false; } /* ASpell is expecting length of a string in char representation */ /* word.length() != word.toUtf8().length() for nonlatin strings */ return aspell_speller_add_to_session(m_speller, word.toUtf8().constData(), word.toUtf8().length()); } diff --git a/src/plugins/aspell/aspelldict.h b/src/plugins/aspell/aspelldict.h index 1969c09..92aa33f 100644 --- a/src/plugins/aspell/aspelldict.h +++ b/src/plugins/aspell/aspelldict.h @@ -1,33 +1,33 @@ -/** +/* * kspell_aspelldict.h * * SPDX-FileCopyrightText: 2003 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_ASPELLDICT_H #define KSPELL_ASPELLDICT_H #include "spellerplugin_p.h" #include "aspell.h" class ASpellDict : public Sonnet::SpellerPlugin { public: explicit ASpellDict(const QString &lang); ~ASpellDict() override; bool isCorrect(const QString &word) const override; QStringList suggest(const QString &word) const override; bool storeReplacement(const QString &bad, const QString &good) override; bool addToPersonal(const QString &word) override; bool addToSession(const QString &word) override; private: AspellConfig *m_config = nullptr; AspellSpeller *m_speller = nullptr; }; #endif diff --git a/src/plugins/enchant/enchantclient.cpp b/src/plugins/enchant/enchantclient.cpp index 106fa9e..2910d1a 100644 --- a/src/plugins/enchant/enchantclient.cpp +++ b/src/plugins/enchant/enchantclient.cpp @@ -1,80 +1,80 @@ -/** +/* * SPDX-FileCopyrightText: 2006 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "enchantclient.h" #include "enchantdict.h" #include using namespace Sonnet; static void enchantDictDescribeFn(const char *const lang_tag, const char *const provider_name, const char *const provider_desc, const char *const provider_file, void *user_data) { QSpellEnchantClient *client = reinterpret_cast(user_data); //qDebug()<addLanguage(QString::fromLatin1(lang_tag)); } QSpellEnchantClient::QSpellEnchantClient(QObject *parent) : Client(parent) { m_broker = enchant_broker_init(); enchant_broker_list_dicts(m_broker, enchantDictDescribeFn, this); } QSpellEnchantClient::~QSpellEnchantClient() { enchant_broker_free(m_broker); } SpellerPlugin *QSpellEnchantClient::createSpeller( const QString &language) { EnchantDict *dict = enchant_broker_request_dict(m_broker, language.toUtf8().constData()); if (!dict) { #ifndef NDEBUG const char *err = enchant_broker_get_error(m_broker); qDebug() << "Couldn't create speller for" << language << ": " << err; #endif return 0; } else { //Enchant caches dictionaries, so it will always return the same one. int refs = m_dictRefs[dict]; ++refs; m_dictRefs[dict] = refs; return new QSpellEnchantDict(this, m_broker, dict, language); } } QStringList QSpellEnchantClient::languages() const { return m_languages.toList(); } void QSpellEnchantClient::addLanguage(const QString &lang) { m_languages.insert(lang); } void QSpellEnchantClient::removeDictRef(EnchantDict *dict) { int refs = m_dictRefs[dict]; --refs; m_dictRefs[dict] = refs; if (refs <= 0) { m_dictRefs.remove(dict); enchant_broker_free_dict(m_broker, dict); } } diff --git a/src/plugins/enchant/enchantclient.h b/src/plugins/enchant/enchantclient.h index fd5d84b..931ecae 100644 --- a/src/plugins/enchant/enchantclient.h +++ b/src/plugins/enchant/enchantclient.h @@ -1,53 +1,53 @@ -/** +/* * SPDX-FileCopyrightText: 2006 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef QSPELL_ENCHANTCLIENT_H #define QSPELL_ENCHANTCLIENT_H #include "spellerplugin_p.h" #include "client_p.h" #include #include namespace Sonnet { class SpellerPlugin; } using Sonnet::SpellerPlugin; class QSpellEnchantClient : public Sonnet::Client { Q_OBJECT Q_INTERFACES(Sonnet::Client) Q_PLUGIN_METADATA(IID "org.kde.Sonnet.EnchantClient") public: explicit QSpellEnchantClient(QObject *parent = nullptr); ~QSpellEnchantClient(); virtual int reliability() const { return 30; } virtual SpellerPlugin *createSpeller(const QString &language); virtual QStringList languages() const; virtual QString name() const { return QString::fromLatin1("Enchant"); } void addLanguage(const QString &lang); void removeDictRef(EnchantDict *dict); private: EnchantBroker *m_broker; QSet m_languages; QHash m_dictRefs; }; #endif diff --git a/src/plugins/enchant/enchantdict.cpp b/src/plugins/enchant/enchantdict.cpp index a35533b..50a3bd6 100644 --- a/src/plugins/enchant/enchantdict.cpp +++ b/src/plugins/enchant/enchantdict.cpp @@ -1,82 +1,82 @@ -/** +/* * SPDX-FileCopyrightText: 2006 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "enchantdict.h" #include "enchantclient.h" #include #include using namespace Sonnet; QSpellEnchantDict::QSpellEnchantDict(QSpellEnchantClient *client, EnchantBroker *broker, EnchantDict *dict, const QString &language) : SpellerPlugin(language) , m_broker(broker) , m_dict(dict) , m_client(client) { qDebug() << "Enchant dict for" << language << dict; } QSpellEnchantDict::~QSpellEnchantDict() { //Enchant caches dictionaries, so it will always return the same one. // therefore we do not want to delete the EnchantDict here but in the // client when it knows that nothing is using it anymore m_client->removeDictRef(m_dict); } bool QSpellEnchantDict::isCorrect(const QString &word) const { int wrong = enchant_dict_check(m_dict, word.toUtf8().constData(), word.toUtf8().length()); return !wrong; } QStringList QSpellEnchantDict::suggest(const QString &word) const { /* Needed for Unicode conversion */ QTextCodec *codec = QTextCodec::codecForName("utf8"); size_t number = 0; char **suggestions = enchant_dict_suggest(m_dict, word.toUtf8().constData(), word.toUtf8().length(), &number); QStringList qsug; for (size_t i = 0; i < number; ++i) { qsug.append(codec->toUnicode(suggestions[i])); } if (suggestions && number) { enchant_dict_free_string_list(m_dict, suggestions); } return qsug; } bool QSpellEnchantDict::storeReplacement(const QString &bad, const QString &good) { enchant_dict_store_replacement(m_dict, bad.toUtf8().constData(), bad.toUtf8().length(), good.toUtf8().constData(), good.toUtf8().length()); return true; } bool QSpellEnchantDict::addToPersonal(const QString &word) { qDebug() << "QSpellEnchantDict::addToPersonal: word = " << word; enchant_dict_add_to_pwl(m_dict, word.toUtf8().constData(), word.toUtf8().length()); return true; } bool QSpellEnchantDict::addToSession(const QString &word) { enchant_dict_add_to_session(m_dict, word.toUtf8().constData(), word.toUtf8().length()); return true; } diff --git a/src/plugins/enchant/enchantdict.h b/src/plugins/enchant/enchantdict.h index c8cd1ff..6d1b9b0 100644 --- a/src/plugins/enchant/enchantdict.h +++ b/src/plugins/enchant/enchantdict.h @@ -1,38 +1,38 @@ -/** +/* * SPDX-FileCopyrightText: 2006 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef QSPELL_ENCHANTDICT_H #define QSPELL_ENCHANTDICT_H #include "spellerplugin_p.h" #include class QSpellEnchantClient; class QSpellEnchantDict : public Sonnet::SpellerPlugin { public: ~QSpellEnchantDict(); virtual bool isCorrect(const QString &word) const; virtual QStringList suggest(const QString &word) const; virtual bool storeReplacement(const QString &bad, const QString &good); virtual bool addToPersonal(const QString &word); virtual bool addToSession(const QString &word); protected: friend class QSpellEnchantClient; QSpellEnchantDict(QSpellEnchantClient *client, EnchantBroker *broker, EnchantDict *dict, const QString &language); private: EnchantBroker *m_broker; EnchantDict *m_dict; QSpellEnchantClient *m_client; }; #endif diff --git a/src/plugins/hspell/hspellclient.cpp b/src/plugins/hspell/hspellclient.cpp index a0e123b..8e5c7e7 100644 --- a/src/plugins/hspell/hspellclient.cpp +++ b/src/plugins/hspell/hspellclient.cpp @@ -1,38 +1,38 @@ -/** +/* * kspell_hspellclient.cpp * * SPDX-FileCopyrightText: 2003 Zack Rusin * SPDX-FileCopyrightText: 2005 Mashrab Kuvatov * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "hspellclient.h" #include "hspelldict.h" using namespace Sonnet; HSpellClient::HSpellClient(QObject *parent) : Client(parent) { } HSpellClient::~HSpellClient() { } SpellerPlugin *HSpellClient::createSpeller(const QString &language) { HSpellDict *ad = new HSpellDict(language); return ad; } QStringList HSpellClient::languages() const { QStringList langs; HSpellDict ad(QStringLiteral("he")); if (ad.isInitialized()) { langs.append(QStringLiteral("he")); } return langs; } diff --git a/src/plugins/hspell/hspellclient.h b/src/plugins/hspell/hspellclient.h index 2aa0a17..916f418 100644 --- a/src/plugins/hspell/hspellclient.h +++ b/src/plugins/hspell/hspellclient.h @@ -1,50 +1,50 @@ -/** +/* * kspell_hspellclient.h * * SPDX-FileCopyrightText: 2003 Zack Rusin * SPDX-FileCopyrightText: 2005 Mashrab Kuvatov * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_HSPELLCLIENT_H #define KSPELL_HSPELLCLIENT_H #include "client_p.h" /* libhspell is a C library and it does not have #ifdef __cplusplus */ extern "C" { #include "hspell.h" } namespace Sonnet { class SpellerPlugin; } using Sonnet::SpellerPlugin; class HSpellClient : public Sonnet::Client { Q_OBJECT Q_INTERFACES(Sonnet::Client) Q_PLUGIN_METADATA(IID "org.kde.Sonnet.HSpellClient") public: explicit HSpellClient(QObject *parent = nullptr); ~HSpellClient(); int reliability() const override { return 20; } SpellerPlugin *createSpeller(const QString &language) override; QStringList languages() const override; QString name() const override { return QString::fromLatin1("HSpell"); } private: }; #endif diff --git a/src/plugins/hspell/hspelldict.cpp b/src/plugins/hspell/hspelldict.cpp index 1d441a8..51622e8 100644 --- a/src/plugins/hspell/hspelldict.cpp +++ b/src/plugins/hspell/hspelldict.cpp @@ -1,132 +1,132 @@ -/** +/* * kspell_hspelldict.cpp * * SPDX-FileCopyrightText: 2003 Zack Rusin * SPDX-FileCopyrightText: 2005 Mashrab Kuvatov * SPDX-FileCopyrightText: 2013 Martin Sandsmark * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "hspelldict.h" #include "hspell_debug.h" #include #include using namespace Sonnet; HSpellDict::HSpellDict(const QString &lang) : SpellerPlugin(lang) { int int_error = hspell_init(&m_speller, HSPELL_OPT_DEFAULT); if (int_error == -1) { qCWarning(SONNET_LOG_HSPELL) << "HSpellDict::HSpellDict: Init failed"; initialized = false; } else { /* hspell understans only iso8859-8-i */ codec = QTextCodec::codecForName("iso8859-8-i"); initialized = true; } QSettings settings(QStringLiteral("KDE"), QStringLiteral("SonnetHSpellPlugin")); m_personalWords = QSet::fromList(settings.value(QStringLiteral("PersonalWords"), QStringList()).toStringList()); QVariantHash replacementMap = settings.value(QStringLiteral("Replacements"), QVariant()).toHash(); for (const QString &key : replacementMap.keys()) { m_replacements[key] = replacementMap[key].toString(); } } HSpellDict::~HSpellDict() { /* It exists in =< hspell-0.8 */ if (initialized) { hspell_uninit(m_speller); } } bool HSpellDict::isCorrect(const QString &word) const { if (m_sessionWords.contains(word)) { return true; } if (m_personalWords.contains(word)) { return true; } if (!initialized) { // Not much we can do, so just return true (less annoying for the user) return true; } int preflen; QByteArray wordISO = codec->fromUnicode(word); // returns 1 if the word is correct, 0 otherwise int correct = hspell_check_word(m_speller, wordISO.constData(), &preflen); //this argument might be removed, it isn't useful // gimatria is a representation of numbers with hebrew letters, we accept these if (correct != 1) { if (hspell_is_canonic_gimatria(wordISO.constData()) != 0) { correct = 1; } } return correct == 1; } QStringList HSpellDict::suggest(const QString &word) const { QStringList suggestions; if (m_replacements.contains(word)) { suggestions.append(m_replacements[word]); } struct corlist correctionList; int suggestionCount; corlist_init(&correctionList); hspell_trycorrect(m_speller, codec->fromUnicode(word).constData(), &correctionList); for (suggestionCount = 0; suggestionCount < corlist_n(&correctionList); suggestionCount++) { suggestions.append(codec->toUnicode(corlist_str(&correctionList, suggestionCount))); } corlist_free(&correctionList); return suggestions; } bool HSpellDict::storeReplacement(const QString &bad, const QString &good) { m_replacements[bad] = good; storePersonalWords(); return true; } bool HSpellDict::addToPersonal(const QString &word) { m_personalWords.insert(word); storePersonalWords(); return true; } bool HSpellDict::addToSession(const QString &word) { m_sessionWords.insert(word); return true; } void HSpellDict::storePersonalWords() { QSettings settings(QStringLiteral("KDE"), QStringLiteral("SonnetHSpellPlugin")); settings.setValue(QStringLiteral("PersonalWords"), QVariant(m_personalWords.toList())); QVariantHash variantHash; for (const QString &key : m_replacements.keys()) { variantHash[key] = QVariant(m_replacements[key]); } settings.setValue(QStringLiteral("Replacements"), variantHash); } diff --git a/src/plugins/hspell/hspelldict.h b/src/plugins/hspell/hspelldict.h index 738bd59..e6edf4f 100644 --- a/src/plugins/hspell/hspelldict.h +++ b/src/plugins/hspell/hspelldict.h @@ -1,49 +1,49 @@ -/** +/* * kspell_hspelldict.h * * SPDX-FileCopyrightText: 2003 Zack Rusin * SPDX-FileCopyrightText: 2005 Mashrab Kuvatov * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_HSPELLDICT_H #define KSPELL_HSPELLDICT_H #include #include "spellerplugin_p.h" /* libhspell is a C library and it does not have #ifdef __cplusplus */ extern "C" { #include "hspell.h" } class HSpellDict : public Sonnet::SpellerPlugin { public: explicit HSpellDict(const QString &lang); ~HSpellDict(); bool isCorrect(const QString &word) const override; QStringList suggest(const QString &word) const override; bool storeReplacement(const QString &bad, const QString &good) override; bool addToPersonal(const QString &word) override; bool addToSession(const QString &word) override; inline bool isInitialized() const { return initialized; } private: void storePersonalWords(); struct dict_radix *m_speller; QTextCodec *codec; bool initialized; QSet m_sessionWords; QSet m_personalWords; QHash m_replacements; }; #endif diff --git a/src/plugins/hunspell/hunspellclient.cpp b/src/plugins/hunspell/hunspellclient.cpp index 0b5ede5..3c7508d 100644 --- a/src/plugins/hunspell/hunspellclient.cpp +++ b/src/plugins/hunspell/hunspellclient.cpp @@ -1,74 +1,74 @@ -/** +/* * kspell_hunspellclient.cpp * * SPDX-FileCopyrightText: 2009 Montel Laurent * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "hunspellclient.h" #include "hunspelldict.h" #include "hunspelldebug.h" #include #include #include using namespace Sonnet; HunspellClient::HunspellClient(QObject *parent) : Client(parent) { qCDebug(SONNET_HUNSPELL) << " HunspellClient::HunspellClient"; QStringList dirList; // search QStandardPaths dirList.append(QStandardPaths::locateAll( QStandardPaths::GenericDataLocation, QStringLiteral("hunspell"), QStandardPaths::LocateDirectory)); auto maybeAddPath = [&dirList](const QString &path) { if (QFileInfo::exists(path)) { dirList.append(path); QDir dir(path); for (const QString &subDir : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { dirList.append(dir.absoluteFilePath(subDir)); } } }; #ifdef Q_OS_WIN maybeAddPath(QStringLiteral(SONNET_INSTALL_PREFIX "/bin/data/hunspell/")); #else maybeAddPath(QStringLiteral("/System/Library/Spelling")); maybeAddPath(QStringLiteral("/usr/share/hunspell/")); maybeAddPath(QStringLiteral("/usr/share/myspell/")); #endif for (const QString &dirString : dirList) { QDir dir(dirString); const auto dicts = dir.entryInfoList({QStringLiteral("*.aff")}, QDir::Files); for (const QFileInfo &dict : dicts) { m_languagePaths.insert(dict.baseName(), dict.canonicalPath()); } } } HunspellClient::~HunspellClient() { } SpellerPlugin *HunspellClient::createSpeller(const QString &language) { qCDebug(SONNET_HUNSPELL) << " SpellerPlugin *HunspellClient::createSpeller(const QString &language) ;" << language; HunspellDict *ad = new HunspellDict(language, m_languagePaths.value(language)); return ad; } QStringList HunspellClient::languages() const { return m_languagePaths.keys(); } diff --git a/src/plugins/hunspell/hunspellclient.h b/src/plugins/hunspell/hunspellclient.h index bc31416..14d91c5 100644 --- a/src/plugins/hunspell/hunspellclient.h +++ b/src/plugins/hunspell/hunspellclient.h @@ -1,46 +1,46 @@ -/** +/* * kspell_hunspellclient.h * * SPDX-FileCopyrightText: 2009 Montel Laurent * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_HUNSPELLCLIENT_H #define KSPELL_HUNSPELLCLIENT_H #include "client_p.h" #include namespace Sonnet { class SpellerPlugin; } using Sonnet::SpellerPlugin; class HunspellClient : public Sonnet::Client { Q_OBJECT Q_INTERFACES(Sonnet::Client) Q_PLUGIN_METADATA(IID "org.kde.Sonnet.HunspellClient") public: explicit HunspellClient(QObject *parent = nullptr); ~HunspellClient() override; int reliability() const override { return 40; } SpellerPlugin *createSpeller(const QString &language) override; QStringList languages() const override; QString name() const override { return QStringLiteral("Hunspell"); } private: QMap m_languagePaths; }; #endif diff --git a/src/plugins/hunspell/hunspelldict.cpp b/src/plugins/hunspell/hunspelldict.cpp index a1bfb6e..e932047 100644 --- a/src/plugins/hunspell/hunspelldict.cpp +++ b/src/plugins/hunspell/hunspelldict.cpp @@ -1,164 +1,164 @@ -/** +/* * kspell_hunspelldict.cpp * * SPDX-FileCopyrightText: 2009 Montel Laurent * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "hunspelldict.h" #include "config-hunspell.h" #include "hunspelldebug.h" #include #include #include #include #include #include using namespace Sonnet; HunspellDict::HunspellDict(const QString &lang, QString path) : SpellerPlugin(lang) { qCDebug(SONNET_HUNSPELL) << "Loading dictionary for" << lang << "from" << path; if (!path.endsWith(QLatin1Char('/'))) { path += QLatin1Char('/'); } path += lang; QString dictionary = path + QStringLiteral(".dic"); QString aff = path + QStringLiteral(".aff"); if (QFileInfo::exists(dictionary) && QFileInfo::exists(aff)) { m_speller = new Hunspell(aff.toLocal8Bit().constData(), dictionary.toLocal8Bit().constData()); m_codec = QTextCodec::codecForName(m_speller->get_dic_encoding()); if (!m_codec) { qCWarning(SONNET_HUNSPELL) << "Failed to find a text codec for name" << m_speller->get_dic_encoding() << "defaulting to locale text codec"; m_codec = QTextCodec::codecForLocale(); Q_ASSERT(m_codec); } } else { qCWarning(SONNET_HUNSPELL) << "Unable to find dictionary for" << lang << "in path" << path; } QString userDic = QDir::home().filePath(QLatin1String(".hunspell_") % lang); QFile userDicFile(userDic); if (userDicFile.open(QIODevice::ReadOnly | QIODevice::Text)) { qCDebug(SONNET_HUNSPELL) << "Load a user dictionary" << userDic; QTextStream userDicIn(&userDicFile); while (!userDicIn.atEnd()) { QString word = userDicIn.readLine(); if (word.contains(QLatin1Char('/'))) { QStringList wordParts = word.split(QLatin1Char('/')); m_speller->add_with_affix(toDictEncoding(wordParts.at( 0)).constData(), toDictEncoding(wordParts.at(1)).constData()); } if (word.at(0) == QLatin1Char('*')) { m_speller->remove(toDictEncoding(word.mid(1)).constData()); } else { m_speller->add(toDictEncoding(word).constData()); } } userDicFile.close(); } qCDebug(SONNET_HUNSPELL) << "Created " << m_speller; } HunspellDict::~HunspellDict() { delete m_speller; } QByteArray HunspellDict::toDictEncoding(const QString &word) const { if (m_codec) { return m_codec->fromUnicode(word); } return {}; } bool HunspellDict::isCorrect(const QString &word) const { qCDebug(SONNET_HUNSPELL) << " isCorrect :" << word; if (!m_speller) { return false; } #if USE_OLD_HUNSPELL_API int result = m_speller->spell(toDictEncoding(word).constData()); qCDebug(SONNET_HUNSPELL) << " result :" << result; return result != 0; #else bool result = m_speller->spell(toDictEncoding(word).toStdString()); qCDebug(SONNET_HUNSPELL) << " result :" << result; return result; #endif } QStringList HunspellDict::suggest(const QString &word) const { if (!m_speller) { return QStringList(); } QStringList lst; #if USE_OLD_HUNSPELL_API char **selection; int nbWord = m_speller->suggest(&selection, toDictEncoding(word).constData()); for (int i = 0; i < nbWord; ++i) { lst << m_codec->toUnicode(selection[i]); } m_speller->free_list(&selection, nbWord); #else const auto suggestions = m_speller->suggest(toDictEncoding(word).toStdString()); for_each (suggestions.begin(), suggestions.end(), [this, &lst](const std::string &suggestion) { lst << m_codec->toUnicode(suggestion.c_str()); }); #endif return lst; } bool HunspellDict::storeReplacement(const QString &bad, const QString &good) { Q_UNUSED(bad); Q_UNUSED(good); if (!m_speller) { return false; } qCDebug(SONNET_HUNSPELL) << "HunspellDict::storeReplacement not implemented"; return false; } bool HunspellDict::addToPersonal(const QString &word) { if (!m_speller) { return false; } m_speller->add(toDictEncoding(word).constData()); QString userDic = QDir::home().filePath(QLatin1String(".hunspell_") % language()); QFile userDicFile(userDic); if (userDicFile.open(QIODevice::Append | QIODevice::Text)) { QTextStream out(&userDicFile); out << word << '\n'; userDicFile.close(); return true; } return false; } bool HunspellDict::addToSession(const QString &word) { if (!m_speller) { return false; } int r = m_speller->add(toDictEncoding(word).constData()); return r == 0; } diff --git a/src/plugins/hunspell/hunspelldict.h b/src/plugins/hunspell/hunspelldict.h index 9c93921..a831ba8 100644 --- a/src/plugins/hunspell/hunspelldict.h +++ b/src/plugins/hunspell/hunspelldict.h @@ -1,35 +1,35 @@ -/** +/* * kspell_aspelldict.h * * SPDX-FileCopyrightText: 2009 Montel Laurent * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_HUNSPELLDICT_H #define KSPELL_HUNSPELLDICT_H #include "spellerplugin_p.h" #include "hunspell.hxx" class HunspellDict : public Sonnet::SpellerPlugin { public: explicit HunspellDict(const QString &lang, QString path); ~HunspellDict() override; bool isCorrect(const QString &word) const override; QStringList suggest(const QString &word) const override; bool storeReplacement(const QString &bad, const QString &good) override; bool addToPersonal(const QString &word) override; bool addToSession(const QString &word) override; private: QByteArray toDictEncoding(const QString &word) const; Hunspell *m_speller = nullptr; QTextCodec *m_codec = nullptr; }; #endif diff --git a/src/plugins/nsspellchecker/nsspellcheckerclient.h b/src/plugins/nsspellchecker/nsspellcheckerclient.h index 8331e29..80cc5b7 100644 --- a/src/plugins/nsspellchecker/nsspellcheckerclient.h +++ b/src/plugins/nsspellchecker/nsspellcheckerclient.h @@ -1,37 +1,37 @@ -/** +/* * nsspellcheckerclient.h * * SPDX-FileCopyrightText: 2015 Nick Shaforostoff * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_NSSPELLCLIENT_H #define KSPELL_NSSPELLCLIENT_H #include "client_p.h" namespace Sonnet { class SpellerPlugin; } using Sonnet::SpellerPlugin; class NSSpellCheckerClient : public Sonnet::Client { Q_OBJECT Q_INTERFACES(Sonnet::Client) Q_PLUGIN_METADATA(IID "org.kde.Sonnet.NSSpellClient") public: explicit NSSpellCheckerClient(QObject *parent = nullptr); ~NSSpellCheckerClient(); int reliability() const; SpellerPlugin *createSpeller(const QString &language); QStringList languages() const; QString name() const { return QStringLiteral("NSSpellChecker"); } }; #endif diff --git a/src/plugins/nsspellchecker/nsspellcheckerclient.mm b/src/plugins/nsspellchecker/nsspellcheckerclient.mm index c5eda71..450215b 100644 --- a/src/plugins/nsspellchecker/nsspellcheckerclient.mm +++ b/src/plugins/nsspellchecker/nsspellcheckerclient.mm @@ -1,44 +1,44 @@ -/** +/* * nsspellcheckerclient.mm * * SPDX-FileCopyrightText: 2015 Nick Shaforostoff * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "nsspellcheckerclient.h" #include "nsspellcheckerdict.h" #import using namespace Sonnet; NSSpellCheckerClient::NSSpellCheckerClient(QObject *parent) : Client(parent) { } NSSpellCheckerClient::~NSSpellCheckerClient() { } int NSSpellCheckerClient::reliability() const { return qEnvironmentVariableIsSet("SONNET_PREFER_NSSPELLCHECKER") ? 9999 : 30; } SpellerPlugin *NSSpellCheckerClient::createSpeller(const QString &language) { return new NSSpellCheckerDict(language); } QStringList NSSpellCheckerClient::languages() const { QStringList lst; NSArray* availableLanguages = [[NSSpellChecker sharedSpellChecker] availableLanguages]; for (NSString* lang_code in availableLanguages) { lst.append(QString::fromNSString(lang_code)); } return lst; } diff --git a/src/plugins/nsspellchecker/nsspellcheckerdict.h b/src/plugins/nsspellchecker/nsspellcheckerdict.h index 97956cc..d1441e1 100644 --- a/src/plugins/nsspellchecker/nsspellcheckerdict.h +++ b/src/plugins/nsspellchecker/nsspellcheckerdict.h @@ -1,34 +1,34 @@ -/** +/* * nsspellcheckerdict.h * * SPDX-FileCopyrightText: 2015 Nick Shaforostoff * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef KSPELL_NSSPELLDICT_H #define KSPELL_NSSPELLDICT_H #include "spellerplugin_p.h" class NSSpellCheckerDict : public Sonnet::SpellerPlugin { public: explicit NSSpellCheckerDict(const QString &lang); ~NSSpellCheckerDict(); virtual bool isCorrect(const QString &word) const; virtual QStringList suggest(const QString &word) const; virtual bool storeReplacement(const QString &bad, const QString &good); virtual bool addToPersonal(const QString &word); virtual bool addToSession(const QString &word); private: #ifdef __OBJC__ NSString *m_langCode; #else void *m_langCode; #endif }; #endif diff --git a/src/plugins/nsspellchecker/nsspellcheckerdict.mm b/src/plugins/nsspellchecker/nsspellcheckerdict.mm index 0101b17..9ae779d 100644 --- a/src/plugins/nsspellchecker/nsspellcheckerdict.mm +++ b/src/plugins/nsspellchecker/nsspellcheckerdict.mm @@ -1,100 +1,100 @@ -/** +/* * nsspellcheckerdict.mm * * SPDX-FileCopyrightText: 2015 Nick Shaforostoff * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "nsspellcheckerdict.h" #include "nsspellcheckerdebug.h" #import using namespace Sonnet; NSSpellCheckerDict::NSSpellCheckerDict(const QString &lang) : SpellerPlugin(lang) , m_langCode([lang.toNSString() retain]) { NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; if ([checker setLanguage:m_langCode]) { qCDebug(SONNET_NSSPELLCHECKER) << "Loading dictionary for" << lang; [checker updatePanels]; } else { qCWarning(SONNET_NSSPELLCHECKER) << "Loading dictionary for unsupported language" << lang; } } NSSpellCheckerDict::~NSSpellCheckerDict() { [m_langCode release]; } bool NSSpellCheckerDict::isCorrect(const QString &word) const { NSString *nsWord = word.toNSString(); NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; NSRange range = [checker checkSpellingOfString:nsWord startingAt:0 language:m_langCode wrap:NO inSpellDocumentWithTag:0 wordCount:nullptr]; if (range.length == 0) { // Check if the user configured a replacement text for this string. Sadly // we can only signal an error if that's the case, Sonnet has no other way // to take such substitutions into account. if (NSDictionary *replacements = [checker userReplacementsDictionary]) { return [replacements objectForKey:nsWord] == nil; } else { return true; } } return false; } QStringList NSSpellCheckerDict::suggest(const QString &word) const { NSString *nsWord = word.toNSString(); NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; NSArray *suggestions = [checker guessesForWordRange:NSMakeRange(0, word.length()) inString:nsWord language:m_langCode inSpellDocumentWithTag:0]; QStringList lst; NSDictionary *replacements = [checker userReplacementsDictionary]; QString replacement; if ([replacements objectForKey:nsWord]) { // return the replacement text from the userReplacementsDictionary first. replacement = QString::fromNSString([replacements valueForKey:nsWord]); lst << replacement; } for (NSString *suggestion in suggestions) { // the replacement text from the userReplacementsDictionary will be in // the suggestions list; don't add it again. QString str = QString::fromNSString(suggestion); if (str != replacement) { lst << str; } } return lst; } bool NSSpellCheckerDict::storeReplacement(const QString &bad, const QString &good) { qCDebug(SONNET_NSSPELLCHECKER) << "Not storing replacement" << good << "for" << bad; return false; } bool NSSpellCheckerDict::addToPersonal(const QString &word) { NSString *nsWord = word.toNSString(); NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; if (![checker hasLearnedWord:nsWord]) { [checker learnWord:nsWord]; [checker updatePanels]; } return true; } bool NSSpellCheckerDict::addToSession(const QString &word) { qCDebug(SONNET_NSSPELLCHECKER) << "Not storing" << word << "in the session dictionary"; return false; } diff --git a/src/plugins/voikko/voikkoclient.cpp b/src/plugins/voikko/voikkoclient.cpp index 871073d..dc4b5c8 100644 --- a/src/plugins/voikko/voikkoclient.cpp +++ b/src/plugins/voikko/voikkoclient.cpp @@ -1,60 +1,60 @@ -/** +/* * voikkoclient.cpp * * SPDX-FileCopyrightText: 2015 Jesse Jaara * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "voikkoclient.h" #include "voikkodict.h" #include "voikkodebug.h" VoikkoClient::VoikkoClient(QObject *parent) : Sonnet::Client(parent) { qCDebug(SONNET_VOIKKO) << "Initializing Voikko spell checker plugin."; char **dictionaries = voikkoListSupportedSpellingLanguages(nullptr); if (!dictionaries) { return; } for (int i = 0; dictionaries[i] != nullptr; ++i) { QString language = QString::fromUtf8(dictionaries[i]); m_supportedLanguages.append(language); qCDebug(SONNET_VOIKKO) << "Found dictionary for language:" << language; } voikkoFreeCstrArray(dictionaries); } VoikkoClient::~VoikkoClient() { } int VoikkoClient::reliability() const { return 50; } Sonnet::SpellerPlugin *VoikkoClient::createSpeller(const QString &language) { VoikkoDict *speller = new VoikkoDict(language); if (speller->initFailed()) { delete speller; return nullptr; } return speller; } QStringList VoikkoClient::languages() const { return m_supportedLanguages; } QString VoikkoClient::name() const { return QStringLiteral("Voikko"); } diff --git a/src/plugins/voikko/voikkoclient.h b/src/plugins/voikko/voikkoclient.h index 64e3097..9f1ef61 100644 --- a/src/plugins/voikko/voikkoclient.h +++ b/src/plugins/voikko/voikkoclient.h @@ -1,36 +1,36 @@ -/** +/* * voikkoclient.h * * SPDX-FileCopyrightText: 2015 Jesse Jaara * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef SONNET_VOIKKOCLIENT_H #define SONNET_VOIKKOCLIENT_H #include "client_p.h" class VoikkoClient : public Sonnet::Client { Q_OBJECT Q_INTERFACES(Sonnet::Client) Q_PLUGIN_METADATA(IID "org.kde.Sonnet.VoikkoClient") public: explicit VoikkoClient(QObject *parent = nullptr); ~VoikkoClient(); int reliability() const override; Sonnet::SpellerPlugin *createSpeller(const QString &language) override; QStringList languages() const override; QString name() const override; private: QStringList m_supportedLanguages; }; #endif //SONNET_VOIKKOCLIENT_H diff --git a/src/plugins/voikko/voikkodict.cpp b/src/plugins/voikko/voikkodict.cpp index 98a7739..e8a84f6 100644 --- a/src/plugins/voikko/voikkodict.cpp +++ b/src/plugins/voikko/voikkodict.cpp @@ -1,322 +1,322 @@ -/** +/* * voikkodict.cpp * * SPDX-FileCopyrightText: 2015 Jesse Jaara * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "voikkodict.h" #include "voikkodebug.h" #include #include #include #ifdef Q_IS_WIN #include #endif #include #include #include namespace { // QString literals used in loading and storing user dictionary inline const QString replacement_bad_str() Q_DECL_NOEXCEPT { return QStringLiteral("bad"); } inline const QString replacement_good_str() Q_DECL_NOEXCEPT { return QStringLiteral("good"); } inline const QString personal_words_str() Q_DECL_NOEXCEPT { return QStringLiteral("PersonalWords"); } inline const QString replacements_str() Q_DECL_NOEXCEPT { return QStringLiteral("Replacements"); } // Set path to: QStandardPaths::GenericDataLocation/Sonnet/Voikko-user-dictionary.json QString getUserDictionaryPath() Q_DECL_NOEXCEPT { QString directory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); #ifdef Q_OS_WIN // Resolve the windows' Roaming directory manually directory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); if (QSysInfo::windowsVersion() == QSysInfo::WV_XP || QSysInfo::windowsVersion() == QSysInfo::WV_2003) { // In Xp Roaming is "/Application Data" // DataLocation: "/Local Settings/Application Data" directory += QStringLiteral("/../../Application Data"); } else { directory += QStringLiteral("/../Roaming"); } #endif directory += QStringLiteral("/Sonnet"); QDir path(directory); path.mkpath(path.absolutePath()); return path.absoluteFilePath(QStringLiteral("Voikko-user-dictionary.json")); } void addReplacementToNode(QJsonObject &languageNode, const QString &bad, const QString &good) Q_DECL_NOEXCEPT { QJsonObject pair; pair[replacement_bad_str()] = good; pair[replacement_good_str()] = bad; auto replaceList = languageNode[replacements_str()].toArray(); replaceList.append(pair); languageNode[replacements_str()] = replaceList; } void addPersonalWordToNode(QJsonObject &languageNode, const QString &word) Q_DECL_NOEXCEPT { auto arr = languageNode[personal_words_str()].toArray(); arr.append(word); languageNode[personal_words_str()] = arr; } /** * Read and return the root json object from fileName. * * Returns an empty node in case of an IO error or the file is empty. */ QJsonObject readJsonRootObject(const QString &fileName) Q_DECL_NOEXCEPT { QFile userDictFile(fileName); if (!userDictFile.exists()) { return QJsonObject(); // Nothing has been saved so far. } if (!userDictFile.open(QIODevice::ReadOnly)) { qCWarning(SONNET_VOIKKO) << "Could not open personal dictionary. Failed to open file" << fileName; qCWarning(SONNET_VOIKKO) << "Reason:" << userDictFile.errorString(); return QJsonObject(); } QJsonDocument dictDoc = QJsonDocument::fromJson(userDictFile.readAll()); userDictFile.close(); return dictDoc.object(); } } class VoikkoDictPrivate { public: VoikkoHandle *m_handle; const VoikkoDict *q; QSet m_sessionWords; QSet m_personalWords; QHash m_replacements; QString m_userDictionaryFilepath; // Used when converting Qstring to wchar_t strings QVector m_conversionBuffer; VoikkoDictPrivate(const QString &language, const VoikkoDict *publicPart) Q_DECL_NOEXCEPT : q(publicPart) , m_userDictionaryFilepath(getUserDictionaryPath()) , m_conversionBuffer(256) { const char *error; m_handle = voikkoInit(&error, language.toUtf8().data(), nullptr); if (error != nullptr) { qCWarning(SONNET_VOIKKO) << "Failed to initialize Voikko spelling backend. Reason:" << error; } else { // Continue to load user's own words loadUserDictionary(); } } /** * Store a new ignored/personal word or replacement pair in the user's * dictionary m_userDictionaryFilepath. * * returns true on success else false */ bool storePersonal(const QString &personalWord, const QString &bad = QString(), const QString &good = QString()) const Q_DECL_NOEXCEPT { QFile userDictFile(m_userDictionaryFilepath); if (!userDictFile.open(QIODevice::ReadWrite)) { qCWarning(SONNET_VOIKKO) << "Could not save personal dictionary. Failed to open file:" << m_userDictionaryFilepath; qCWarning(SONNET_VOIKKO) << "Reason:" << userDictFile.errorString(); return false; } QJsonDocument dictDoc = QJsonDocument::fromJson(userDictFile.readAll()); auto root = readJsonRootObject(m_userDictionaryFilepath); auto languageNode = root[q->language()].toObject(); // Empty value means we are storing a bad:good pair if (personalWord.isEmpty()) { addReplacementToNode(languageNode, bad, good); } else { addPersonalWordToNode(languageNode, personalWord); } root[q->language()] = languageNode; dictDoc.setObject(root); userDictFile.reset(); userDictFile.write(dictDoc.toJson()); userDictFile.close(); qCDebug(SONNET_VOIKKO) << "Changes to user dictionary saved to file: " << m_userDictionaryFilepath; return true; } /** * Load user's own personal words and replacement pairs from * m_userDictionaryFilepath. */ void loadUserDictionary() Q_DECL_NOEXCEPT { // If root is empty we will fail later on when checking if // languageNode is empty. auto root = readJsonRootObject(m_userDictionaryFilepath); auto languageNode = root[q->language()].toObject(); if (languageNode.isEmpty()) { return; // Nothing to load } loadUserWords(languageNode); loadUserReplacements(languageNode); } /** * Convert the given QString to a \0 terminated wchar_t string. * Uses QVector as a buffer and return it's internal data pointer. */ inline const wchar_t *QStringToWchar(const QString &str) Q_DECL_NOEXCEPT { m_conversionBuffer.resize(str.length() + 1); int size = str.toWCharArray(m_conversionBuffer.data()); m_conversionBuffer[size] = '\0'; return m_conversionBuffer.constData(); } private: /** * Extract and append user defined words from the languageNode. */ inline void loadUserWords(const QJsonObject &languageNode) Q_DECL_NOEXCEPT { const auto words = languageNode[personal_words_str()].toArray(); for (auto word : words) { m_personalWords.insert(word.toString()); } qCDebug(SONNET_VOIKKO) << QStringLiteral("Loaded %1 words from the user dictionary.").arg(words.size()); } /** * Extract and append user defined replacement pairs from the languageNode. */ inline void loadUserReplacements(const QJsonObject &languageNode) Q_DECL_NOEXCEPT { const auto words = languageNode[replacements_str()].toArray(); for (auto pair : words) { m_replacements[pair.toObject()[replacement_bad_str()].toString()] = pair.toObject()[replacement_good_str()].toString(); } qCDebug(SONNET_VOIKKO) << QStringLiteral("Loaded %1 replacements from the user dictionary.").arg(words.size()); } }; VoikkoDict::VoikkoDict(const QString &language) Q_DECL_NOEXCEPT : SpellerPlugin(language) , d(new VoikkoDictPrivate(language, this)) { qCDebug(SONNET_VOIKKO) << "Loading dictionary for language:" << language; } VoikkoDict::~VoikkoDict() { } bool VoikkoDict::isCorrect(const QString &word) const { // Check the session word list and personal word list first if (d->m_sessionWords.contains(word) || d->m_personalWords.contains(word)) { return true; } return voikkoSpellUcs4(d->m_handle, d->QStringToWchar(word)) == VOIKKO_SPELL_OK; } QStringList VoikkoDict::suggest(const QString &word) const { QStringList suggestions; auto userDictPos = d->m_replacements.constFind(word); if (userDictPos != d->m_replacements.constEnd()) { suggestions.append(*userDictPos); } auto voikkoSuggestions = voikkoSuggestUcs4(d->m_handle, d->QStringToWchar(word)); if (!voikkoSuggestions) { return suggestions; } for (int i = 0; voikkoSuggestions[i] != nullptr; ++i) { QString suggestion = QString::fromWCharArray(voikkoSuggestions[i]); suggestions.append(suggestion); } qCDebug(SONNET_VOIKKO) << "Misspelled:" << word << "|Suggestons:" << suggestions.join(QLatin1String(", ")); voikko_free_suggest_ucs4(voikkoSuggestions); return suggestions; } bool VoikkoDict::storeReplacement(const QString &bad, const QString &good) { qCDebug(SONNET_VOIKKO) << "Adding new replacement pair to user dictionary:" << bad << "->" << good; d->m_replacements[bad] = good; return d->storePersonal(QString(), bad, good); } bool VoikkoDict::addToPersonal(const QString &word) { qCDebug(SONNET_VOIKKO()) << "Adding new word to user dictionary" << word; d->m_personalWords.insert(word); return d->storePersonal(word); } bool VoikkoDict::addToSession(const QString &word) { qCDebug(SONNET_VOIKKO()) << "Adding new word to session dictionary" << word; d->m_sessionWords.insert(word); return true; } bool VoikkoDict::initFailed() const Q_DECL_NOEXCEPT { return !d->m_handle; } diff --git a/src/plugins/voikko/voikkodict.h b/src/plugins/voikko/voikkodict.h index 27b0d85..4c80b57 100644 --- a/src/plugins/voikko/voikkodict.h +++ b/src/plugins/voikko/voikkodict.h @@ -1,54 +1,54 @@ -/** +/* * voikkodict.h * * SPDX-FileCopyrightText: 2015 Jesse Jaara * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef SONNET_VOIKKODICT_H #define SONNET_VOIKKODICT_H #include "spellerplugin_p.h" #include #include #include class VoikkoClient; class VoikkoDictPrivate; class VoikkoDict : public Sonnet::SpellerPlugin { public: /** * Declare VoikkoClient as friend so we can use the protected constructor. */ friend class VoikkoClient; ~VoikkoDict(); bool isCorrect(const QString &word) const override; QStringList suggest(const QString &word) const override; bool storeReplacement(const QString &bad, const QString &good) override; bool addToPersonal(const QString &word) override; bool addToSession(const QString &word) override; /** * @returns true if initializing Voikko backend failed. */ bool initFailed() const Q_DECL_NOEXCEPT; protected: /** * Constructor is protected so that only spellers created * and validated through VoikkoClient can be used. */ explicit VoikkoDict(const QString &language) Q_DECL_NOEXCEPT; private: QScopedPointer d; }; #endif //SONNET_VOIKKODICT_H diff --git a/src/ui/configdialog.cpp b/src/ui/configdialog.cpp index 3567a81..fd54a22 100644 --- a/src/ui/configdialog.cpp +++ b/src/ui/configdialog.cpp @@ -1,90 +1,90 @@ -/** +/* * configdialog.cpp * * SPDX-FileCopyrightText: 2004 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "configdialog.h" #include "configwidget.h" #include #include using namespace Sonnet; class ConfigDialogPrivate { public: ConfigDialogPrivate(ConfigDialog *parent) : q(parent) { } ConfigWidget *ui = nullptr; ConfigDialog *q; void slotConfigChanged(); }; void ConfigDialogPrivate::slotConfigChanged() { emit q->languageChanged(ui->language()); } ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent) , d(new ConfigDialogPrivate(this)) { setObjectName(QStringLiteral("SonnetConfigDialog")); setModal(true); setWindowTitle(tr("Spell Checking Configuration")); QVBoxLayout *layout = new QVBoxLayout(this); d->ui = new ConfigWidget(this); layout->addWidget(d->ui); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, this, &ConfigDialog::slotOk); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(d->ui, SIGNAL(configChanged()), this, SLOT(slotConfigChanged())); connect(d->ui, SIGNAL(configChanged()), this, SIGNAL(configChanged())); } ConfigDialog::~ConfigDialog() { delete d; } void ConfigDialog::slotOk() { d->ui->save(); accept(); } void ConfigDialog::slotApply() { d->ui->save(); } void ConfigDialog::setLanguage(const QString &language) { d->ui->setLanguage(language); } QString ConfigDialog::language() const { return d->ui->language(); } #include "moc_configdialog.cpp" diff --git a/src/ui/configwidget.cpp b/src/ui/configwidget.cpp index a18be2e..34c145e 100644 --- a/src/ui/configwidget.cpp +++ b/src/ui/configwidget.cpp @@ -1,204 +1,204 @@ -/** +/* * configwidget.cpp * * SPDX-FileCopyrightText: 2004 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "configwidget.h" #include "ui_configui.h" #include "loader_p.h" #include "settings_p.h" #include #include #include #include #include "ui_debug.h" using namespace Sonnet; class ConfigWidgetPrivate { public: Ui_SonnetConfigUI ui; Loader *loader = nullptr; QWidget *wdg = nullptr; }; ConfigWidget::ConfigWidget(QWidget *parent) : QWidget(parent) , d(new ConfigWidgetPrivate) { d->loader = Loader::openLoader(); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setObjectName(QStringLiteral("SonnetConfigUILayout")); d->wdg = new QWidget(this); d->ui.setupUi(d->wdg); d->ui.m_langCombo->setCurrentByDictionary(d->loader->settings()->defaultLanguage()); QStringList preferredLanguages = d->loader->settings()->preferredLanguages(); for (int i = 0; i < d->ui.m_langCombo->count(); i++) { const QString tag = d->ui.m_langCombo->itemData(i).toString(); if (tag.isEmpty()) { // skip separator continue; } QListWidgetItem *item = new QListWidgetItem(d->ui.m_langCombo->itemText(i), d->ui.languageList); item->setData(Qt::UserRole, tag); if (preferredLanguages.contains(tag)) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } } d->ui.m_skipUpperCB->setChecked(!d->loader->settings()->checkUppercase()); d->ui.m_skipRunTogetherCB->setChecked(d->loader->settings()->skipRunTogether()); d->ui.m_checkerEnabledByDefaultCB->setChecked(d->loader->settings()->checkerEnabledByDefault()); d->ui.m_autodetectCB->setChecked(d->loader->settings()->autodetectLanguage()); QStringList ignoreList = d->loader->settings()->currentIgnoreList(); ignoreList.sort(); d->ui.ignoreListWidget->addItems(ignoreList); d->ui.m_bgSpellCB->setChecked(d->loader->settings()->backgroundCheckerEnabled()); d->ui.m_bgSpellCB->hide();//hidden by default connect(d->ui.addButton, &QAbstractButton::clicked, this, &ConfigWidget::slotIgnoreWordAdded); connect(d->ui.removeButton, &QAbstractButton::clicked, this, &ConfigWidget::slotIgnoreWordRemoved); layout->addWidget(d->wdg); connect(d->ui.m_langCombo, &DictionaryComboBox::dictionaryChanged, this, &ConfigWidget::configChanged); connect(d->ui.languageList, &QListWidget::itemChanged, this, &ConfigWidget::configChanged); connect(d->ui.m_bgSpellCB, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); connect(d->ui.m_skipUpperCB, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); connect(d->ui.m_skipRunTogetherCB, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); connect(d->ui.m_checkerEnabledByDefaultCB, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); connect(d->ui.m_autodetectCB, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); connect(d->ui.newIgnoreEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotUpdateButton); connect(d->ui.ignoreListWidget, &QListWidget::itemSelectionChanged, this, &ConfigWidget::slotSelectionChanged); d->ui.nobackendfound->setVisible(d->loader->clients().isEmpty()); d->ui.addButton->setEnabled(false); d->ui.removeButton->setEnabled(false); } ConfigWidget::~ConfigWidget() { delete d; } void ConfigWidget::slotUpdateButton(const QString &text) { d->ui.addButton->setEnabled(!text.isEmpty()); } void ConfigWidget::slotSelectionChanged() { d->ui.removeButton->setEnabled(!d->ui.ignoreListWidget->selectedItems().isEmpty()); } void ConfigWidget::save() { setFromGui(); } void ConfigWidget::setFromGui() { Settings *settings = d->loader->settings(); if (d->ui.m_langCombo->count()) { settings->setDefaultLanguage(d->ui.m_langCombo->currentDictionary()); } QStringList preferredLanguages; for (int i = 0; i < d->ui.languageList->count(); i++) { if (d->ui.languageList->item(i)->checkState() == Qt::Unchecked) { continue; } preferredLanguages << d->ui.languageList->item(i)->data(Qt::UserRole).toString(); } settings->setPreferredLanguages(preferredLanguages); settings->setCheckUppercase(!d->ui.m_skipUpperCB->isChecked()); settings->setSkipRunTogether(d->ui.m_skipRunTogetherCB->isChecked()); settings->setBackgroundCheckerEnabled(d->ui.m_bgSpellCB->isChecked()); settings->setCheckerEnabledByDefault(d->ui.m_checkerEnabledByDefaultCB->isChecked()); settings->setAutodetectLanguage(d->ui.m_autodetectCB->isChecked()); if (settings->modified()) { settings->save(); } } void ConfigWidget::slotIgnoreWordAdded() { QStringList ignoreList = d->loader->settings()->currentIgnoreList(); QString newWord = d->ui.newIgnoreEdit->text(); d->ui.newIgnoreEdit->clear(); if (newWord.isEmpty() || ignoreList.contains(newWord)) { return; } ignoreList.append(newWord); d->loader->settings()->setCurrentIgnoreList(ignoreList); d->ui.ignoreListWidget->clear(); d->ui.ignoreListWidget->addItems(ignoreList); emit configChanged(); } void ConfigWidget::slotIgnoreWordRemoved() { QStringList ignoreList = d->loader->settings()->currentIgnoreList(); const QList selectedItems = d->ui.ignoreListWidget->selectedItems(); for (const QListWidgetItem *item : selectedItems) { ignoreList.removeAll(item->text()); } d->loader->settings()->setCurrentIgnoreList(ignoreList); d->ui.ignoreListWidget->clear(); d->ui.ignoreListWidget->addItems(ignoreList); emit configChanged(); } void ConfigWidget::setBackgroundCheckingButtonShown(bool b) { d->ui.m_bgSpellCB->setVisible(b); } bool ConfigWidget::backgroundCheckingButtonShown() const { return !d->ui.m_bgSpellCB->isHidden(); } void ConfigWidget::slotDefault() { d->ui.m_autodetectCB->setChecked(true); d->ui.m_skipUpperCB->setChecked(false); d->ui.m_skipRunTogetherCB->setChecked(false); d->ui.m_checkerEnabledByDefaultCB->setChecked(false); d->ui.m_bgSpellCB->setChecked(true); d->ui.ignoreListWidget->clear(); d->ui.m_langCombo->setCurrentByDictionary(d->loader->settings()->defaultLanguage()); } void ConfigWidget::setLanguage(const QString &language) { d->ui.m_langCombo->setCurrentByDictionary(language); } QString ConfigWidget::language() const { if (d->ui.m_langCombo->count()) { return d->ui.m_langCombo->currentDictionary(); } else { return QString(); } } diff --git a/src/ui/dialog.cpp b/src/ui/dialog.cpp index 2845d8d..e881fda 100644 --- a/src/ui/dialog.cpp +++ b/src/ui/dialog.cpp @@ -1,425 +1,425 @@ -/** +/* * dialog.cpp * * SPDX-FileCopyrightText: 2003 Zack Rusin * SPDX-FileCopyrightText: 2009-2010 Michel Ludwig * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "dialog.h" #include "ui_sonnetui.h" #include "backgroundchecker.h" #include "speller.h" #include "settings_p.h" #include #include #include #include #include namespace Sonnet { //to initially disable sorting in the suggestions listview #define NONSORTINGCOLUMN 2 class ReadOnlyStringListModel : public QStringListModel { public: ReadOnlyStringListModel(QObject *parent) : QStringListModel(parent) { } Qt::ItemFlags flags(const QModelIndex &index) const override { Q_UNUSED(index); return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } }; class DialogPrivate { public: Ui_SonnetUi ui; ReadOnlyStringListModel *suggestionsModel = nullptr; QWidget *wdg = nullptr; QDialogButtonBox *buttonBox = nullptr; QProgressDialog *progressDialog = nullptr; QString originalBuffer; BackgroundChecker *checker = nullptr; QString currentWord; int currentPosition; QMap replaceAllMap; bool restart;//used when text is distributed across several qtextedits, eg in KAider QMap dictsMap; int progressDialogTimeout; bool showCompletionMessageBox; bool spellCheckContinuedAfterReplacement; bool canceled; void deleteProgressDialog(bool directly) { if (progressDialog) { progressDialog->hide(); if (directly) { delete progressDialog; } else { progressDialog->deleteLater(); } progressDialog = nullptr; } } }; Dialog::Dialog(BackgroundChecker *checker, QWidget *parent) : QDialog(parent) , d(new DialogPrivate) { setModal(true); setWindowTitle(tr("Check Spelling", "@title:window")); d->checker = checker; d->canceled = false; d->showCompletionMessageBox = false; d->spellCheckContinuedAfterReplacement = true; d->progressDialogTimeout = -1; d->progressDialog = nullptr; initGui(); initConnections(); } Dialog::~Dialog() { delete d; } void Dialog::initConnections() { connect(d->ui.m_addBtn, &QAbstractButton::clicked, this, &Dialog::slotAddWord); connect(d->ui.m_replaceBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceWord); connect(d->ui.m_replaceAllBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceAll); connect(d->ui.m_skipBtn, &QAbstractButton::clicked, this, &Dialog::slotSkip); connect(d->ui.m_skipAllBtn, &QAbstractButton::clicked, this, &Dialog::slotSkipAll); connect(d->ui.m_suggestBtn, &QAbstractButton::clicked, this, &Dialog::slotSuggest); connect(d->ui.m_language, SIGNAL(activated(QString)), SLOT(slotChangeLanguage(QString))); connect(d->ui.m_suggestions, SIGNAL(clicked(QModelIndex)), SLOT(slotSelectionChanged(QModelIndex))); connect(d->checker, SIGNAL(misspelling(QString,int)), SLOT(slotMisspelling(QString,int))); connect(d->checker, SIGNAL(done()), SLOT(slotDone())); connect(d->ui.m_suggestions, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotReplaceWord())); connect(d->buttonBox, &QDialogButtonBox::accepted, this, &Dialog::slotFinished); connect(d->buttonBox, &QDialogButtonBox::rejected, this, &Dialog::slotCancel); connect(d->ui.m_replacement, SIGNAL(returnPressed()), this, SLOT(slotReplaceWord())); connect(d->ui.m_autoCorrect, SIGNAL(clicked()), SLOT(slotAutocorrect())); // button use by kword/kpresenter // hide by default d->ui.m_autoCorrect->hide(); } void Dialog::initGui() { QVBoxLayout *layout = new QVBoxLayout(this); d->wdg = new QWidget(this); d->ui.setupUi(d->wdg); layout->addWidget(d->wdg); setGuiEnabled(false); d->buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); layout->addWidget(d->wdg); layout->addWidget(d->buttonBox); //d->ui.m_suggestions->setSorting( NONSORTINGCOLUMN ); fillDictionaryComboBox(); d->restart = false; d->suggestionsModel = new ReadOnlyStringListModel(this); d->ui.m_suggestions->setModel(d->suggestionsModel); } void Dialog::activeAutoCorrect(bool _active) { if (_active) { d->ui.m_autoCorrect->show(); } else { d->ui.m_autoCorrect->hide(); } } void Dialog::showProgressDialog(int timeout) { d->progressDialogTimeout = timeout; } void Dialog::showSpellCheckCompletionMessage(bool b) { d->showCompletionMessageBox = b; } void Dialog::setSpellCheckContinuedAfterReplacement(bool b) { d->spellCheckContinuedAfterReplacement = b; } void Dialog::slotAutocorrect() { setGuiEnabled(false); setProgressDialogVisible(true); emit autoCorrect(d->currentWord, d->ui.m_replacement->text()); slotReplaceWord(); } void Dialog::setGuiEnabled(bool b) { d->wdg->setEnabled(b); } void Dialog::setProgressDialogVisible(bool b) { if (!b) { d->deleteProgressDialog(true); } else if (d->progressDialogTimeout >= 0) { if (d->progressDialog) { return; } d->progressDialog = new QProgressDialog(this); d->progressDialog->setLabelText(tr("Spell checking in progress...", "progress label")); d->progressDialog->setWindowTitle(tr("Check Spelling", "@title:window")); d->progressDialog->setModal(true); d->progressDialog->setAutoClose(false); d->progressDialog->setAutoReset(false); // create an 'indefinite' progress box as we currently cannot get progress feedback from // the speller d->progressDialog->reset(); d->progressDialog->setRange(0, 0); d->progressDialog->setValue(0); connect(d->progressDialog, &QProgressDialog::canceled, this, &Dialog::slotCancel); d->progressDialog->setMinimumDuration(d->progressDialogTimeout); } } void Dialog::slotFinished() { setProgressDialogVisible(false); emit stop(); //FIXME: should we emit done here? #if SONNETUI_BUILD_DEPRECATED_SINCE(5, 65) emit done(d->checker->text()); #endif emit spellCheckDone(d->checker->text()); emit spellCheckStatus(tr("Spell check stopped.")); accept(); } void Dialog::slotCancel() { d->canceled = true; d->deleteProgressDialog(false); // this method can be called in response to // pressing 'Cancel' on the dialog emit cancel(); emit spellCheckStatus(tr("Spell check canceled.")); reject(); } QString Dialog::originalBuffer() const { return d->originalBuffer; } QString Dialog::buffer() const { return d->checker->text(); } void Dialog::setBuffer(const QString &buf) { d->originalBuffer = buf; //it is possible to change buffer inside slot connected to done() signal d->restart = true; } void Dialog::fillDictionaryComboBox() { // Since m_language is changed to DictionaryComboBox most code here is gone, // So fillDictionaryComboBox() could be removed and code moved to initGui() // because the call in show() looks obsolete Speller speller = d->checker->speller(); d->dictsMap = speller.availableDictionaries(); updateDictionaryComboBox(); } void Dialog::updateDictionaryComboBox() { const Speller &speller = d->checker->speller(); d->ui.m_language->setCurrentByDictionary(speller.language()); } void Dialog::updateDialog(const QString &word) { d->ui.m_unknownWord->setText(word); d->ui.m_contextLabel->setText(d->checker->currentContext()); const QStringList suggs = d->checker->suggest(word); if (suggs.isEmpty()) { d->ui.m_replacement->clear(); } else { d->ui.m_replacement->setText(suggs.first()); } fillSuggestions(suggs); } void Dialog::show() { d->canceled = false; fillDictionaryComboBox(); if (d->originalBuffer.isEmpty()) { d->checker->start(); } else { d->checker->setText(d->originalBuffer); } setProgressDialogVisible(true); } void Dialog::slotAddWord() { setGuiEnabled(false); setProgressDialogVisible(true); d->checker->addWordToPersonal(d->currentWord); d->checker->continueChecking(); } void Dialog::slotReplaceWord() { setGuiEnabled(false); setProgressDialogVisible(true); QString replacementText = d->ui.m_replacement->text(); emit replace(d->currentWord, d->currentPosition, replacementText); if (d->spellCheckContinuedAfterReplacement) { d->checker->replace(d->currentPosition, d->currentWord, replacementText); d->checker->continueChecking(); } else { d->checker->stop(); } } void Dialog::slotReplaceAll() { setGuiEnabled(false); setProgressDialogVisible(true); d->replaceAllMap.insert(d->currentWord, d->ui.m_replacement->text()); slotReplaceWord(); } void Dialog::slotSkip() { setGuiEnabled(false); setProgressDialogVisible(true); d->checker->continueChecking(); } void Dialog::slotSkipAll() { setGuiEnabled(false); setProgressDialogVisible(true); //### do we want that or should we have a d->ignoreAll list? Speller speller = d->checker->speller(); speller.addToPersonal(d->currentWord); d->checker->setSpeller(speller); d->checker->continueChecking(); } void Dialog::slotSuggest() { const QStringList suggs = d->checker->suggest(d->ui.m_replacement->text()); fillSuggestions(suggs); } void Dialog::slotChangeLanguage(const QString &lang) { const QString languageCode = d->dictsMap[lang]; if (!languageCode.isEmpty()) { d->checker->changeLanguage(languageCode); slotSuggest(); emit languageChanged(languageCode); } } void Dialog::slotSelectionChanged(const QModelIndex &item) { d->ui.m_replacement->setText(item.data().toString()); } void Dialog::fillSuggestions(const QStringList &suggs) { d->suggestionsModel->setStringList(suggs); } void Dialog::slotMisspelling(const QString &word, int start) { setGuiEnabled(true); setProgressDialogVisible(false); emit misspelling(word, start); //NOTE this is HACK I had to introduce because BackgroundChecker lacks 'virtual' marks on methods //this dramatically reduces spellchecking time in Lokalize //as this doesn't fetch suggestions for words that are present in msgid if (!updatesEnabled()) { return; } d->currentWord = word; d->currentPosition = start; if (d->replaceAllMap.contains(word)) { d->ui.m_replacement->setText(d->replaceAllMap[ word ]); slotReplaceWord(); } else { updateDialog(word); } QDialog::show(); } void Dialog::slotDone() { d->restart = false; #if SONNETUI_BUILD_DEPRECATED_SINCE(5, 65) emit done(d->checker->text()); #endif emit spellCheckDone(d->checker->text()); if (d->restart) { updateDictionaryComboBox(); d->checker->setText(d->originalBuffer); d->restart = false; } else { setProgressDialogVisible(false); emit spellCheckStatus(tr("Spell check complete.")); accept(); if (!d->canceled && d->showCompletionMessageBox) { QMessageBox::information(this, tr("Spell check complete."), tr("Check Spelling", "@title:window")); } } } } diff --git a/src/ui/highlighter.cpp b/src/ui/highlighter.cpp index 054a11e..70bc602 100644 --- a/src/ui/highlighter.cpp +++ b/src/ui/highlighter.cpp @@ -1,519 +1,519 @@ -/** +/* * highlighter.cpp * * SPDX-FileCopyrightText: 2004 Zack Rusin * SPDX-FileCopyrightText: 2006 Laurent Montel * SPDX-FileCopyrightText: 2013 Martin Sandsmark * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "highlighter.h" #include "speller.h" #include "loader_p.h" #include "tokenizer_p.h" #include "settings_p.h" #include "languagefilter_p.h" #include "ui_debug.h" #include #include #include #include #include #include #include #include #include #include namespace Sonnet { // Cache of previously-determined languages (when using AutoDetectLanguage) // There is one such cache per block (paragraph) class LanguageCache : public QTextBlockUserData { public: // Key: QPair // Value: language name QMap, QString> languages; // Remove all cached language information after @p pos void invalidate(int pos) { QMutableMapIterator, QString> it(languages); it.toBack(); while (it.hasPrevious()) { it.previous(); if (it.key().first+it.key().second >= pos) { it.remove(); } else { break; } } } QString languageAtPos(int pos) const { // The data structure isn't really great for such lookups... QMapIterator, QString> it(languages); while (it.hasNext()) { it.next(); if (it.key().first <= pos && it.key().first + it.key().second >= pos) { return it.value(); } } return QString(); } }; class HighlighterPrivate { public: HighlighterPrivate(Highlighter *qq, const QColor &col) : textEdit(nullptr) , plainTextEdit(nullptr) , spellColor(col) , q(qq) { tokenizer = new WordTokenizer(); active = true; automatic = false; connected = false; wordCount = 0; errorCount = 0; intraWordEditing = false; completeRehighlightRequired = false; spellColor = spellColor.isValid() ? spellColor : Qt::red; languageFilter = new LanguageFilter(new SentenceTokenizer()); loader = Loader::openLoader(); loader->settings()->restore(); spellchecker = new Sonnet::Speller(); spellCheckerFound = spellchecker->isValid(); rehighlightRequest = new QTimer(q); q->connect(rehighlightRequest, SIGNAL(timeout()), q, SLOT(slotRehighlight())); if (!spellCheckerFound) { return; } disablePercentage = loader->settings()->disablePercentageWordError(); disableWordCount = loader->settings()->disableWordErrorCount(); completeRehighlightRequired = true; rehighlightRequest->setInterval(0); rehighlightRequest->setSingleShot(true); rehighlightRequest->start(); } ~HighlighterPrivate(); WordTokenizer *tokenizer = nullptr; LanguageFilter *languageFilter = nullptr; Loader *loader = nullptr; Speller *spellchecker = nullptr; QTextEdit *textEdit = nullptr; QPlainTextEdit *plainTextEdit = nullptr; bool active; bool automatic; bool completeRehighlightRequired; bool intraWordEditing; bool spellCheckerFound; //cached d->dict->isValid() value bool connected; int disablePercentage = 0; int disableWordCount = 0; int wordCount, errorCount; QTimer *rehighlightRequest = nullptr; QColor spellColor; Highlighter *q; }; HighlighterPrivate::~HighlighterPrivate() { delete spellchecker; delete languageFilter; delete tokenizer; } Highlighter::Highlighter(QTextEdit *edit, const QColor &_col) : QSyntaxHighlighter(edit) , d(new HighlighterPrivate(this, _col)) { d->textEdit = edit; d->textEdit->installEventFilter(this); d->textEdit->viewport()->installEventFilter(this); } Highlighter::Highlighter(QPlainTextEdit *edit, const QColor &col) : QSyntaxHighlighter(edit) , d(new HighlighterPrivate(this, col)) { d->plainTextEdit = edit; setDocument(d->plainTextEdit->document()); d->plainTextEdit->installEventFilter(this); d->plainTextEdit->viewport()->installEventFilter(this); } Highlighter::~Highlighter() { delete d; } bool Highlighter::spellCheckerFound() const { return d->spellCheckerFound; } void Highlighter::slotRehighlight() { if (d->completeRehighlightRequired) { d->wordCount = 0; d->errorCount = 0; rehighlight(); } else { //rehighlight the current para only (undo/redo safe) QTextCursor cursor; if (d->textEdit) { cursor = d->textEdit->textCursor(); } else { cursor = d->plainTextEdit->textCursor(); } if (cursor.hasSelection()) { cursor.clearSelection(); } cursor.insertText(QString()); } //if (d->checksDone == d->checksRequested) //d->completeRehighlightRequired = false; QTimer::singleShot(0, this, SLOT(slotAutoDetection())); } bool Highlighter::automatic() const { return d->automatic; } bool Highlighter::intraWordEditing() const { return d->intraWordEditing; } void Highlighter::setIntraWordEditing(bool editing) { d->intraWordEditing = editing; } void Highlighter::setAutomatic(bool automatic) { if (automatic == d->automatic) { return; } d->automatic = automatic; if (d->automatic) { slotAutoDetection(); } } void Highlighter::slotAutoDetection() { bool savedActive = d->active; //don't disable just because 1 of 4 is misspelled. if (d->automatic && d->wordCount >= 10) { // tme = Too many errors bool tme = (d->errorCount >= d->disableWordCount) && ( d->errorCount * 100 >= d->disablePercentage * d->wordCount); if (d->active && tme) { d->active = false; } else if (!d->active && !tme) { d->active = true; } } if (d->active != savedActive) { if (d->active) { emit activeChanged(tr("As-you-type spell checking enabled.")); } else { qCDebug(SONNET_LOG_UI) << "Sonnet: Disabling spell checking, too many errors"; emit activeChanged(tr("Too many misspelled words. " "As-you-type spell checking disabled.")); } d->completeRehighlightRequired = true; d->rehighlightRequest->setInterval(100); d->rehighlightRequest->setSingleShot(true); } } void Highlighter::setActive(bool active) { if (active == d->active) { return; } d->active = active; rehighlight(); if (d->active) { emit activeChanged(tr("As-you-type spell checking enabled.")); } else { emit activeChanged(tr("As-you-type spell checking disabled.")); } } bool Highlighter::isActive() const { return d->active; } void Highlighter::contentsChange(int pos, int add, int rem) { // Invalidate the cache where the text has changed const QTextBlock &lastBlock = document()->findBlock(pos + add - rem); QTextBlock block = document()->findBlock(pos); do { LanguageCache *cache = dynamic_cast(block.userData()); if (cache) { cache->invalidate(pos-block.position()); } block = block.next(); } while (block.isValid() && block < lastBlock); } static bool hasNotEmptyText(const QString &text) { for (int i = 0; i < text.length(); ++i) { if (!text.at(i).isSpace()) { return true; } } return false; } void Highlighter::highlightBlock(const QString &text) { if (!hasNotEmptyText(text) || !d->active || !d->spellCheckerFound) { return; } if (!d->connected) { connect(document(), SIGNAL(contentsChange(int,int,int)), SLOT(contentsChange(int,int,int))); d->connected = true; } QTextCursor cursor; if (d->textEdit) { cursor = d->textEdit->textCursor(); } else { cursor = d->plainTextEdit->textCursor(); } int index = cursor.position(); const int lengthPosition = text.length() - 1; if (index != lengthPosition || (lengthPosition > 0 && !text[lengthPosition-1].isLetter())) { d->languageFilter->setBuffer(text); LanguageCache *cache = dynamic_cast(currentBlockUserData()); if (!cache) { cache = new LanguageCache; setCurrentBlockUserData(cache); } const bool autodetectLanguage = d->spellchecker->testAttribute(Speller::AutoDetectLanguage); while (d->languageFilter->hasNext()) { QStringRef sentence = d->languageFilter->next(); if (autodetectLanguage) { QString lang; QPair spos = QPair(sentence.position(), sentence.length()); // try cache first if (cache->languages.contains(spos)) { lang = cache->languages.value(spos); } else { lang = d->languageFilter->language(); if (!d->languageFilter->isSpellcheckable()) { lang.clear(); } cache->languages[spos] = lang; } if (lang.isEmpty()) { continue; } d->spellchecker->setLanguage(lang); } d->tokenizer->setBuffer(sentence.toString()); int offset = sentence.position(); while (d->tokenizer->hasNext()) { QStringRef word = d->tokenizer->next(); if (!d->tokenizer->isSpellcheckable()) { continue; } ++d->wordCount; if (d->spellchecker->isMisspelled(word.toString())) { ++d->errorCount; setMisspelled(word.position()+offset, word.length()); } else { unsetMisspelled(word.position()+offset, word.length()); } } } } //QTimer::singleShot( 0, this, SLOT(checkWords()) ); setCurrentBlockState(0); } QString Highlighter::currentLanguage() const { return d->spellchecker->language(); } void Highlighter::setCurrentLanguage(const QString &lang) { QString prevLang = d->spellchecker->language(); d->spellchecker->setLanguage(lang); d->spellCheckerFound = d->spellchecker->isValid(); if (!d->spellCheckerFound) { qCDebug(SONNET_LOG_UI) << "No dictionary for \"" << lang << "\" staying with the current language."; d->spellchecker->setLanguage(prevLang); return; } d->wordCount = 0; d->errorCount = 0; if (d->automatic || d->active) { d->rehighlightRequest->start(0); } } void Highlighter::setMisspelled(int start, int count) { QTextCharFormat format; format.setFontUnderline(true); format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); format.setUnderlineColor(d->spellColor); setFormat(start, count, format); } void Highlighter::unsetMisspelled(int start, int count) { setFormat(start, count, QTextCharFormat()); } bool Highlighter::eventFilter(QObject *o, QEvent *e) { if (!d->spellCheckerFound) { return false; } if ((o == d->textEdit || o == d->plainTextEdit) && (e->type() == QEvent::KeyPress)) { QKeyEvent *k = static_cast(e); //d->autoReady = true; if (d->rehighlightRequest->isActive()) { // try to stay out of the users way d->rehighlightRequest->start(500); } if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return || k->key() == Qt::Key_Up || k->key() == Qt::Key_Down || k->key() == Qt::Key_Left || k->key() == Qt::Key_Right || k->key() == Qt::Key_PageUp || k->key() == Qt::Key_PageDown || k->key() == Qt::Key_Home || k->key() == Qt::Key_End || ((k->modifiers() == Qt::ControlModifier) && ((k->key() == Qt::Key_A) || (k->key() == Qt::Key_B) || (k->key() == Qt::Key_E) || (k->key() == Qt::Key_N) || (k->key() == Qt::Key_P)))) { if (intraWordEditing()) { setIntraWordEditing(false); d->completeRehighlightRequired = true; d->rehighlightRequest->setInterval(500); d->rehighlightRequest->setSingleShot(true); d->rehighlightRequest->start(); } } else { setIntraWordEditing(true); } if (k->key() == Qt::Key_Space || k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return) { QTimer::singleShot(0, this, SLOT(slotAutoDetection())); } } else if (((d->textEdit && (o == d->textEdit->viewport())) || (d->plainTextEdit && (o == d->plainTextEdit->viewport()))) && (e->type() == QEvent::MouseButtonPress)) { //d->autoReady = true; if (intraWordEditing()) { setIntraWordEditing(false); d->completeRehighlightRequired = true; d->rehighlightRequest->setInterval(0); d->rehighlightRequest->setSingleShot(true); d->rehighlightRequest->start(); } } return false; } void Highlighter::addWordToDictionary(const QString &word) { d->spellchecker->addToPersonal(word); } void Highlighter::ignoreWord(const QString &word) { d->spellchecker->addToSession(word); } QStringList Highlighter::suggestionsForWord(const QString &word, int max) { QStringList suggestions = d->spellchecker->suggest(word); if (max >= 0 && suggestions.count() > max) { suggestions = suggestions.mid(0, max); } return suggestions; } QStringList Highlighter::suggestionsForWord(const QString &word, const QTextCursor &cursor, int max) { LanguageCache *cache = dynamic_cast(cursor.block().userData()); if (cache) { const QString cachedLanguage = cache->languageAtPos(cursor.positionInBlock()); if (!cachedLanguage.isEmpty()) { d->spellchecker->setLanguage(cachedLanguage); } } QStringList suggestions = d->spellchecker->suggest(word); if (max >= 0 && suggestions.count() > max) { suggestions = suggestions.mid(0, max); } return suggestions; } bool Highlighter::isWordMisspelled(const QString &word) { return d->spellchecker->isMisspelled(word); } void Highlighter::setMisspelledColor(const QColor &color) { d->spellColor = color; } bool Highlighter::checkerEnabledByDefault() const { return d->loader->settings()->checkerEnabledByDefault(); } void Highlighter::setDocument(QTextDocument *document) { d->connected = false; QSyntaxHighlighter::setDocument(document); } }