diff --git a/autotests/test_settings.cpp b/autotests/test_settings.cpp index a1354f2..344040d 100644 --- a/autotests/test_settings.cpp +++ b/autotests/test_settings.cpp @@ -1,167 +1,167 @@ /** * test_settings.cpp * * SPDX-FileCopyrightText: 2015 Kåre Särs * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "test_settings.h" #include "speller.h" -#include "settings_p.h" +#include "settingsimpl_p.h" #include #include #include #include #include #include QTEST_GUILESS_MAIN(SonnetSettingsTest) using namespace Sonnet; void SonnetSettingsTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); } void SonnetSettingsTest::testRestoreDoesNotSave() { QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); QString fileName = settings.fileName(); QDateTime startTime = QFileInfo(fileName).lastModified(); // NOTE: We use new/delete to be able to test that the settings are not // needlessly saved on deletion of Speller Speller *speller = new Speller(); // NOTE: This test works on Unix, but should _not_ fail on Windows as // QFileInfo::lastModified() always returns invalid QDateTime QCOMPARE(QFileInfo(fileName).lastModified(), startTime); speller->language(); QCOMPARE(QFileInfo(fileName).lastModified(), startTime); QStringList langs = speller->availableLanguages(); for (int i = 0; i < langs.count(); ++i) { speller->setLanguage(langs[i]); } QCOMPARE(QFileInfo(fileName).lastModified(), startTime); speller->availableLanguages(); QCOMPARE(QFileInfo(fileName).lastModified(), startTime); speller->restore(); QCOMPARE(QFileInfo(fileName).lastModified(), startTime); // Test that the settings are not saved needlessly on delete delete speller; QCOMPARE(QFileInfo(fileName).lastModified(), startTime); } void SonnetSettingsTest::testSpellerAPIChangeSaves() { QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); Speller speller; // Default Language QString defLang = speller.defaultLanguage(); QString settingsLang = settings.value(QStringLiteral("defaultLanguage"), QLocale::system().name()).toString(); QCOMPARE(defLang, settingsLang); QStringList langs = speller.availableLanguages(); for (int i = 0; i < langs.count(); ++i) { if (langs[i] != defLang) { speller.setDefaultLanguage(langs[i]); settingsLang = settings.value(QStringLiteral("defaultLanguage"), QLocale::system().name()).toString(); QCOMPARE(settingsLang, langs[i]); QCOMPARE(speller.defaultLanguage(), langs[i]); break; } } // set the original value speller.setDefaultLanguage(defLang); settingsLang = settings.value(QStringLiteral("defaultLanguage"), QLocale::system().name()).toString(); QCOMPARE(settingsLang, defLang); QCOMPARE(speller.defaultLanguage(), defLang); // Default Client QString defClient = speller.defaultClient(); QString settingsClient = settings.value(QStringLiteral("defaultClient"), QString()).toString(); QCOMPARE(defClient, settingsClient); QStringList clients = speller.availableBackends(); qDebug() << clients; for (int i = 0; i < clients.count(); ++i) { if (clients[i] != defLang) { speller.setDefaultClient(clients[i]); settingsClient = settings.value(QStringLiteral("defaultClient"), QString()).toString(); QCOMPARE(settingsClient, clients[i]); QCOMPARE(speller.defaultClient(), clients[i]); break; } } // set the original value if (defClient.isEmpty()) { // setting default to "" does not work. settings.remove(QStringLiteral("defaultClient")); } else { speller.setDefaultClient(defClient); } settingsClient = settings.value(QStringLiteral("defaultClient"), QString()).toString(); QCOMPARE(settingsClient, defClient); if (!defClient.isEmpty()) { QCOMPARE(speller.defaultClient(), defClient); } // Check uppercase bool checkUppercase = speller.testAttribute(Speller::CheckUppercase); bool settingsUppercase = settings.value(QStringLiteral("checkUppercase"), true).toBool(); QCOMPARE(checkUppercase, settingsUppercase); // Change the atribute speller.setAttribute(Speller::CheckUppercase, !checkUppercase); settingsUppercase = settings.value(QStringLiteral("checkUppercase"), true).toBool(); QCOMPARE(!checkUppercase, settingsUppercase); QCOMPARE(!checkUppercase, speller.testAttribute(Speller::CheckUppercase)); //now set it back to what it was speller.setAttribute(Speller::CheckUppercase, checkUppercase); settingsUppercase = settings.value(QStringLiteral("checkUppercase"), true).toBool(); QCOMPARE(checkUppercase, settingsUppercase); QCOMPARE(checkUppercase, speller.testAttribute(Speller::CheckUppercase)); // Skip Run Together bool skipRunTogether = speller.testAttribute(Speller::SkipRunTogether); bool settingsSkipRunTogether = settings.value(QStringLiteral("skipRunTogether"), true).toBool(); QCOMPARE(skipRunTogether, settingsSkipRunTogether); // Change the atribute speller.setAttribute(Speller::SkipRunTogether, !skipRunTogether); settingsSkipRunTogether = settings.value(QStringLiteral("skipRunTogether"), true).toBool(); QCOMPARE(!skipRunTogether, settingsSkipRunTogether); QCOMPARE(!skipRunTogether, speller.testAttribute(Speller::SkipRunTogether)); //now set it back to what it was speller.setAttribute(Speller::SkipRunTogether, skipRunTogether); settingsSkipRunTogether = settings.value(QStringLiteral("skipRunTogether"), true).toBool(); QCOMPARE(skipRunTogether, settingsSkipRunTogether); QCOMPARE(skipRunTogether, speller.testAttribute(Speller::SkipRunTogether)); // Auto Detect Language bool autodetectLanguage = speller.testAttribute(Speller::AutoDetectLanguage); bool settingsAutoDetectLanguage = settings.value(QStringLiteral("autodetectLanguage"), true).toBool(); QCOMPARE(autodetectLanguage, settingsAutoDetectLanguage); // Change the atribute speller.setAttribute(Speller::AutoDetectLanguage, !autodetectLanguage); settingsAutoDetectLanguage = settings.value(QStringLiteral("autodetectLanguage"), true).toBool(); QCOMPARE(!autodetectLanguage, settingsAutoDetectLanguage); QCOMPARE(!autodetectLanguage, speller.testAttribute(Speller::AutoDetectLanguage)); //now set it back to what it was speller.setAttribute(Speller::AutoDetectLanguage, autodetectLanguage); settingsAutoDetectLanguage = settings.value(QStringLiteral("autodetectLanguage"), true).toBool(); QCOMPARE(autodetectLanguage, settingsAutoDetectLanguage); QCOMPARE(autodetectLanguage, speller.testAttribute(Speller::AutoDetectLanguage)); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d94a8cb..47a4616 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,104 +1,106 @@ project(sonnetcore) set(sonnetcore_SRCS loader.cpp client.cpp spellerplugin.cpp speller.cpp + settingsimpl.cpp settings.cpp backgroundchecker.cpp guesslanguage.cpp textbreaks.cpp tokenizer.cpp languagefilter.cpp ) # create trigrams file + add trigrams resource if (TARGET KF5::parsetrigrams) add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/data/trigrams.map" COMMAND KF5::parsetrigrams "${CMAKE_SOURCE_DIR}/data/trigrams" > "${CMAKE_BINARY_DIR}/data/trigrams.map") configure_file(${CMAKE_SOURCE_DIR}/data/trigrams.qrc.in ${CMAKE_BINARY_DIR}/data/trigrams.qrc @ONLY) qt5_add_resources(sonnetcore_SRCS "${CMAKE_BINARY_DIR}/data/trigrams.qrc") endif() ecm_qt_declare_logging_category(sonnetcore_SRCS HEADER core_debug.h IDENTIFIER SONNET_LOG_CORE CATEGORY_NAME sonnet.core DESCRIPTION "Sonnet Core" EXPORT SONNET ) # Dear packagers, this is just used as an extra search paths for plugins. Don't get your panties in a twist. add_definitions(-DINSTALLATION_PLUGIN_PATH="${CMAKE_INSTALL_PREFIX}/${KDE_INSTALL_PLUGINDIR}") add_library(KF5SonnetCore ${sonnetcore_SRCS}) ecm_generate_export_header(KF5SonnetCore BASE_NAME SonnetCore GROUP_BASE_NAME KF VERSION ${KF5_VERSION} DEPRECATED_BASE_VERSION 0 #DEPRECATION_VERSIONS 5.65 EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} ) add_library(KF5::SonnetCore ALIAS KF5SonnetCore) ecm_generate_headers(SonnetCore_CamelCase_HEADERS HEADER_NAMES BackgroundChecker Speller GuessLanguage + Settings PREFIX Sonnet REQUIRED_HEADERS SonnetCore_HEADERS ) target_link_libraries(KF5SonnetCore PUBLIC Qt5::Core) set_target_properties(KF5SonnetCore PROPERTIES VERSION ${SONNET_VERSION_STRING} SOVERSION ${SONNET_SOVERSION} EXPORT_NAME SonnetCore ) # CMAKE_CURRENT_BINARY_DIR: for camelcase headers and lowercase forwarders target_include_directories(KF5SonnetCore INTERFACE "$") target_include_directories(KF5SonnetCore PUBLIC "$") install(TARGETS KF5SonnetCore EXPORT KF5SonnetTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${SonnetCore_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/SonnetCore/Sonnet COMPONENT Devel) install(FILES ${SonnetCore_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/sonnetcore_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/SonnetCore/sonnet COMPONENT Devel) if (BUILD_QCH) ecm_add_qch( KF5SonnetCore_QCH NAME SonnetCore BASE_NAME KF5SonnetCore VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${SonnetCore_HEADERS} LINK_QCHS Qt5Core_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS SONNETCORE_EXPORT SONNETCORE_DEPRECATED_EXPORT SONNETCORE_DEPRECATED "SONNETCORE_DEPRECATED_VERSION(x, y, t)" TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME SonnetCore LIB_NAME KF5SonnetCore DEPS "core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/SonnetCore) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/core/languagefilter.cpp b/src/core/languagefilter.cpp index cd6dd5b..baab60e 100644 --- a/src/core/languagefilter.cpp +++ b/src/core/languagefilter.cpp @@ -1,144 +1,144 @@ /* This file is part of the KDE libraries SPDX-FileCopyrightText: 2009 Jakub Stachowski SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "languagefilter_p.h" #include "guesslanguage.h" #include "speller.h" #include "loader_p.h" -#include "settings_p.h" +#include "settingsimpl_p.h" namespace Sonnet { #define MIN_RELIABILITY 0.1 #define MAX_ITEMS 5 class LanguageFilterPrivate { public: LanguageFilterPrivate(AbstractTokenizer *s) : source(s) { gl.setLimits(MAX_ITEMS, MIN_RELIABILITY); } ~LanguageFilterPrivate() { delete source; } QString mainLanguage() const; AbstractTokenizer *source = nullptr; QStringRef lastToken; mutable QString lastLanguage; mutable QString cachedMainLanguage; QString prevLanguage; GuessLanguage gl; Speller sp; }; QString LanguageFilterPrivate::mainLanguage() const { if (cachedMainLanguage.isNull()) { cachedMainLanguage = gl.identify(source->buffer(), QStringList(Loader::openLoader()->settings()->defaultLanguage())); } return cachedMainLanguage; } /* -----------------------------------------------------------------*/ LanguageFilter::LanguageFilter(AbstractTokenizer *source) : d(new LanguageFilterPrivate(source)) { d->prevLanguage = Loader::openLoader()->settings()->defaultLanguage(); } LanguageFilter::LanguageFilter(const LanguageFilter &other) : d(new LanguageFilterPrivate(other.d-> source)) { d->lastToken = other.d->lastToken; d->lastLanguage = other.d->lastLanguage; d->cachedMainLanguage = other.d->cachedMainLanguage; d->prevLanguage = other.d->prevLanguage; } LanguageFilter::~LanguageFilter() { delete d; } bool LanguageFilter::hasNext() const { return d->source->hasNext(); } void LanguageFilter::setBuffer(const QString &buffer) { d->cachedMainLanguage = QString(); d->source->setBuffer(buffer); } QStringRef LanguageFilter::next() { d->lastToken = d->source->next(); d->prevLanguage = d->lastLanguage; d->lastLanguage = QString(); return d->lastToken; } QString LanguageFilter::language() const { if (d->lastLanguage.isNull()) { d->lastLanguage = d->gl.identify(d->lastToken.toString(), QStringList() << d->prevLanguage << Loader::openLoader()->settings()->defaultLanguage()); } const QStringList available = d->sp.availableLanguages(); //FIXME: do something a little more smart here if (!available.contains(d->lastLanguage)) { for (const QString &lang : available) { if (lang.startsWith(d->lastLanguage)) { d->lastLanguage = lang; break; } } } return d->lastLanguage; } bool LanguageFilter::isSpellcheckable() const { const QString &lastlang = language(); if (lastlang.isEmpty()) { return false; } if (d->sp.availableLanguages().contains(lastlang)) { return true; } return false; } QString LanguageFilter::buffer() const { return d->source->buffer(); } void LanguageFilter::replace(int position, int len, const QString &newWord) { d->source->replace(position, len, newWord); //FIXME: fix lastToken // uncache language for current token - it may have changed d->lastLanguage = QString(); } } diff --git a/src/core/loader.cpp b/src/core/loader.cpp index 1b8e363..a26c2bf 100644 --- a/src/core/loader.cpp +++ b/src/core/loader.cpp @@ -1,349 +1,349 @@ /* * SPDX-FileCopyrightText: 2003 Zack Rusin * SPDX-FileCopyrightText: 2012 Martin Sandsmark * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "loader_p.h" -#include "settings_p.h" +#include "settingsimpl_p.h" #include "client_p.h" #include "spellerplugin_p.h" #include #include #include #include #include #include "core_debug.h" #include #include #ifdef SONNET_STATIC #include "../plugins/hunspell/hunspellclient.h" #ifdef Q_OS_MACOS #include "../plugins/nsspellchecker/nsspellcheckerclient.h" #endif #endif namespace Sonnet { class LoaderPrivate { public: - Settings *settings; + SettingsImpl *settings; // QMap > languageClients; QStringList clients; QStringList languagesNameCache; QHash > spellerCache; }; Q_GLOBAL_STATIC(Loader, s_loader) Loader *Loader::openLoader() { if (s_loader.isDestroyed()) { return nullptr; } return s_loader(); } Loader::Loader() : d(new LoaderPrivate) { - d->settings = new Settings(this); + d->settings = new SettingsImpl(this); d->settings->restore(); loadPlugins(); } Loader::~Loader() { qCDebug(SONNET_LOG_CORE) << "Removing loader: " << this; delete d->settings; d->settings = nullptr; delete d; } SpellerPlugin *Loader::createSpeller(const QString &language, const QString &clientName) const { QString backend = clientName; QString plang = language; if (plang.isEmpty()) { plang = d->settings->defaultLanguage(); } auto clientsItr = d->languageClients.constFind(plang); if (clientsItr == d->languageClients.constEnd()) { qCWarning(SONNET_LOG_CORE) << "No language dictionaries for the language:" << plang; emit loadingDictionaryFailed(plang); return nullptr; } const QVector lClients = *clientsItr; if (backend.isEmpty()) { backend = d->settings->defaultClient(); if (!backend.isEmpty()) { // check if the default client supports the requested language; // if it does it will be an element of lClients. bool unknown = !std::any_of(lClients.constBegin(), lClients.constEnd(), [backend] (const Client *client) { return client->name() == backend; }); if (unknown) { qCWarning(SONNET_LOG_CORE) << "Default client" << backend << "doesn't support language:" << plang; backend = QString(); } } } QVectorIterator itr(lClients); while (itr.hasNext()) { Client *item = itr.next(); if (!backend.isEmpty()) { if (backend == item->name()) { SpellerPlugin *dict = item->createSpeller(plang); qCDebug(SONNET_LOG_CORE) << "Using the" << item->name() << "plugin for language" << plang; return dict; } } else { //the first one is the one with the highest //reliability SpellerPlugin *dict = item->createSpeller(plang); qCDebug(SONNET_LOG_CORE) << "Using the" << item->name() << "plugin for language" << plang; return dict; } } qCWarning(SONNET_LOG_CORE) << "The default client" << backend << "has no language dictionaries for the language:" << plang; return nullptr; } QSharedPointer Loader::cachedSpeller(const QString &language) { auto &speller = d->spellerCache[language]; if (!speller) { speller.reset(createSpeller(language)); } return speller; } void Loader::clearSpellerCache() { d->spellerCache.clear(); } QStringList Loader::clients() const { return d->clients; } QStringList Loader::languages() const { return d->languageClients.keys(); } QString Loader::languageNameForCode(const QString &langCode) const { QString currentDictionary = langCode, // e.g. en_GB-ize-wo_accents isoCode, // locale ISO name variantName, // dictionary variant name e.g. w_accents localizedLang, // localized language localizedCountry, // localized country localizedVariant; QByteArray variantEnglish; // dictionary variant in English int minusPos, // position of "-" char variantCount = 0; // used to iterate over variantList struct variantListType { const char *variantShortName; const char *variantEnglishName; }; /* * This redefines the QT_TRANSLATE_NOOP3 macro provided by Qt to indicate that * statically initialised text should be translated so that it expands to just * the string that should be translated, making it possible to use it in the * single string construct below. */ #undef QT_TRANSLATE_NOOP3 #define QT_TRANSLATE_NOOP3(a, b, c) b const variantListType variantList[] = { { "40", QT_TRANSLATE_NOOP3("Sonnet::Loader", "40", "dictionary variant") }, // what does 40 mean? { "60", QT_TRANSLATE_NOOP3("Sonnet::Loader", "60", "dictionary variant") }, // what does 60 mean? { "80", QT_TRANSLATE_NOOP3("Sonnet::Loader", "80", "dictionary variant") }, // what does 80 mean? { "ise", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes", "dictionary variant") }, { "ize", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes", "dictionary variant") }, { "ise-w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes and with accents", "dictionary variant") }, { "ise-wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes and without accents", "dictionary variant") }, { "ize-w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes and with accents", "dictionary variant") }, { "ize-wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes and without accents", "dictionary variant") }, { "lrg", QT_TRANSLATE_NOOP3("Sonnet::Loader", "large", "dictionary variant") }, { "med", QT_TRANSLATE_NOOP3("Sonnet::Loader", "medium", "dictionary variant") }, { "sml", QT_TRANSLATE_NOOP3("Sonnet::Loader", "small", "dictionary variant") }, { "variant_0", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 0", "dictionary variant") }, { "variant_1", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 1", "dictionary variant") }, { "variant_2", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 2", "dictionary variant") }, { "wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "without accents", "dictionary variant") }, { "w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with accents", "dictionary variant") }, { "ye", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with ye, modern russian", "dictionary variant") }, { "yeyo", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with yeyo, modern and old russian", "dictionary variant") }, { "yo", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with yo, old russian", "dictionary variant") }, { "extended", QT_TRANSLATE_NOOP3("Sonnet::Loader", "extended", "dictionary variant") }, { nullptr, nullptr } }; minusPos = currentDictionary.indexOf(QLatin1Char('-')); if (minusPos != -1) { variantName = currentDictionary.right(currentDictionary.length() - minusPos - 1); while (variantList[variantCount].variantShortName != nullptr) { if (QLatin1String(variantList[variantCount].variantShortName) == variantName) { break; } else { variantCount++; } } if (variantList[variantCount].variantShortName != nullptr) { variantEnglish = variantList[variantCount].variantEnglishName; } else { variantEnglish = variantName.toLatin1(); } localizedVariant = tr(variantEnglish.constData(), "dictionary variant"); isoCode = currentDictionary.left(minusPos); } else { isoCode = currentDictionary; } QLocale locale(isoCode); localizedCountry = locale.nativeCountryName(); localizedLang = locale.nativeLanguageName(); if (localizedLang.isEmpty() && localizedCountry.isEmpty()) { return isoCode; // We have nothing } if (!localizedCountry.isEmpty() && !localizedVariant.isEmpty()) { // We have both a country name and a variant return tr("%1 (%2) [%3]", "dictionary name; %1 = language name, %2 = country name and %3 = language variant name" ).arg(localizedLang, localizedCountry, localizedVariant); } else if (!localizedCountry.isEmpty()) { // We have a country name return tr("%1 (%2)", "dictionary name; %1 = language name, %2 = country name" ).arg(localizedLang, localizedCountry); } else { // We only have a language name return localizedLang; } } QStringList Loader::languageNames() const { /* For whatever reason languages() might change. So, * to be in sync with it let's do the following check. */ if (d->languagesNameCache.count() == languages().count()) { return d->languagesNameCache; } QStringList allLocalizedDictionaries; for (const QString &langCode : languages()) { allLocalizedDictionaries.append(languageNameForCode(langCode)); } // cache the list d->languagesNameCache = allLocalizedDictionaries; return allLocalizedDictionaries; } -Settings *Loader::settings() const +SettingsImpl *Loader::settings() const { return d->settings; } void Loader::loadPlugins() { #ifndef SONNET_STATIC const QStringList libPaths = QCoreApplication::libraryPaths() << QStringLiteral(INSTALLATION_PLUGIN_PATH); const QLatin1String pathSuffix("/kf5/sonnet/"); int plugins = 0; for (const QString &libPath : libPaths) { QDir dir(libPath + pathSuffix); if (!dir.exists()) { continue; } for (const QString &fileName : dir.entryList(QDir::Files)) { loadPlugin(dir.absoluteFilePath(fileName)); plugins++; } } if (plugins == 0) { qCWarning(SONNET_LOG_CORE) << "Sonnet: No speller backends available!"; } #else #ifdef Q_OS_MACOS loadPlugin(QString()); #endif loadPlugin(QStringLiteral("Hunspell")); #endif } void Loader::loadPlugin(const QString &pluginPath) { #ifndef SONNET_STATIC QPluginLoader plugin(pluginPath); if (!plugin.load()) { // We do this separately for better error handling qCWarning(SONNET_LOG_CORE) << "Sonnet: Unable to load plugin" << pluginPath << "Error:" << plugin.errorString(); return; } Client *client = qobject_cast(plugin.instance()); if (!client) { qCWarning(SONNET_LOG_CORE) << "Sonnet: Invalid plugin loaded" << pluginPath; plugin.unload(); // don't leave it in memory return; } #else Client *client = nullptr; if (pluginPath == QLatin1String("Hunspell")) { client = new HunspellClient(this); } #ifdef Q_OS_MACOS else { client = new NSSpellCheckerClient(this); } #endif #endif const QStringList languages = client->languages(); d->clients.append(client->name()); for (const QString &language : languages) { QVector &languageClients = d->languageClients[language]; if (languageClients.isEmpty() || client->reliability() < languageClients.first()->reliability()) { languageClients.append(client); // less reliable, to the end } else { languageClients.prepend(client); // more reliable, to the front } } } void Loader::changed() { emit configurationChanged(); } } diff --git a/src/core/loader_p.h b/src/core/loader_p.h index 64d2817..03de99d 100644 --- a/src/core/loader_p.h +++ b/src/core/loader_p.h @@ -1,136 +1,136 @@ /* * SPDX-FileCopyrightText: 2003 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef SONNET_LOADER_P_H #define SONNET_LOADER_P_H #include "sonnetcore_export.h" #include #include #include #include namespace Sonnet { -class Settings; +class SettingsImpl; class SpellerPlugin; class LoaderPrivate; /** * \internal * @short Class used to deal with dictionaries * * This class manages all dictionaries. It's the top level * Sonnet class, you can think of it as the kernel or manager * of the Sonnet architecture. */ class SONNETCORE_EXPORT Loader : public QObject { Q_OBJECT public: /** * Constructs the loader. * * It's very important that you leave the return value in a Loader::Ptr. * Loader is reference counted so if you don't want to have it deleted * under you simply have to hold it in a Loader::Ptr for as long as * you're using it. */ static Loader *openLoader(); public: Loader(); ~Loader(); /** * Returns dictionary for the given language and preferred client. * * @param language specifies the language of the dictionary. If an * empty string will be passed the default language will * be used. Has to be one of the values returned by * \ref languages() * @param client specifies the preferred client. If no client is * specified a client which supports the given * language is picked. If a few clients supports * the same language the one with the biggest * reliability value is returned. * */ SpellerPlugin *createSpeller( const QString &language = QString(), const QString &client = QString()) const; /** * Returns a shared, cached, dictionary for the given language. * * @param language specifies the language of the dictionary. If an * empty string will be passed the default language will * be used. Has to be one of the values returned by * \ref languages() */ QSharedPointer cachedSpeller(const QString &language); /** * Returns a shared, cached, dictionary for the given language. * * @param language specifies the language of the dictionary. If an * empty string will be passed the default language will * be used. Has to be one of the values returned by * \ref languages() */ void clearSpellerCache(); /** * Returns names of all supported clients (e.g. ISpell, ASpell) */ QStringList clients() const; /** * Returns a list of supported languages. */ QStringList languages() const; /** * Returns a localized list of names of supported languages. */ QStringList languageNames() const; /** * @param langCode the dictionary name/language code, e.g. en_gb * @return the localized language name, e.g. "British English" * @since 4.2 */ QString languageNameForCode(const QString &langCode) const; /** - * Returns the Settings object used by the loader. + * Returns the SettingsImpl object used by the loader. */ - Settings *settings() const; + SettingsImpl *settings() const; Q_SIGNALS: /** - * Signal is emitted whenever the Settings object + * Signal is emitted whenever the SettingsImpl object * associated with this Loader changes. */ void configurationChanged(); /** * Emitted when loading a dictionary fails, so that Ui parts can * display an appropriate error message informing the user about * the issue. * @param language the name of the dictionary that failed to be loaded * @since 5.56 */ void loadingDictionaryFailed(const QString &language) const; protected: - friend class Settings; + friend class SettingsImpl; void changed(); private: void loadPlugins(); void loadPlugin(const QString &pluginPath); private: LoaderPrivate *const d; }; } #endif // SONNET_LOADER_P_H diff --git a/src/core/settings.cpp b/src/core/settings.cpp index e9950b0..eb00669 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -1,308 +1,194 @@ /* - * SPDX-FileCopyrightText: 2003 Zack Rusin - * SPDX-FileCopyrightText: 2006 Laurent Montel - * SPDX-FileCopyrightText: 2013 Martin Sandsmark + * SPDX-FileCopyrightText: 2020 Benjamin Port * * SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "settings_p.h" +#include "settingsimpl_p.h" -#include "loader_p.h" - -#include #include -#include + +#include "loader_p.h" +#include "settings.h" namespace Sonnet { class SettingsPrivate { public: - Loader *loader = nullptr; //can't be a Ptr since we don't want to hold a ref on it - bool modified = false; - - QString defaultLanguage; - QStringList preferredLanguages; - QString defaultClient; - - bool checkUppercase = false; - bool skipRunTogether = false; - bool backgroundCheckerEnabled = false; - bool checkerEnabledByDefault = false; - bool autodetectLanguage = false; - - int disablePercentage; - int disableWordCount; - - QMap ignore; + Loader *loader; }; -Settings::Settings(Loader *loader) - : d(new SettingsPrivate) +Settings::Settings(QObject *parent) + : QObject(parent) + , d(new SettingsPrivate) { - d->loader = loader; - - d->modified = false; - d->checkerEnabledByDefault = false; - restore(); + d->loader = Loader::openLoader(); } Settings::~Settings() { delete d; } -bool Settings::setDefaultLanguage(const QString &lang) +void Settings::setDefaultLanguage(const QString &lang) { - const QStringList cs = d->loader->languages(); - if (cs.indexOf(lang) != -1 - && d->defaultLanguage != lang) { - d->defaultLanguage = lang; - d->modified = true; - d->loader->changed(); - return true; - } - return false; + d->loader->settings()->setDefaultLanguage(lang); } QString Settings::defaultLanguage() const { - return d->defaultLanguage; + return d->loader->settings()->defaultLanguage(); } -bool Settings::setPreferredLanguages(const QStringList &lang) +void Settings::setPreferredLanguages(const QStringList &lang) { - if (d->preferredLanguages != lang) { - d->modified = true; - d->preferredLanguages = lang; - return true; - } - - return false; + d->loader->settings()->setPreferredLanguages(lang); } QStringList Settings::preferredLanguages() const { - return d->preferredLanguages; + return d->loader->settings()->preferredLanguages(); } -bool Settings::setDefaultClient(const QString &client) +void Settings::setDefaultClient(const QString &client) { - //Different from setDefaultLanguage because - //the number of clients can't be even close - //as big as the number of languages - if (d->loader->clients().contains(client)) { - d->defaultClient = client; - d->modified = true; - d->loader->changed(); - return true; - } - return false; + d->loader->settings()->setDefaultClient(client); } QString Settings::defaultClient() const { - return d->defaultClient; + return d->loader->settings()->defaultClient(); } -bool Settings::setCheckUppercase(bool check) +void Settings::setSkipUppercase(bool skip) { - if (d->checkUppercase != check) { - d->modified = true; - d->checkUppercase = check; - return true; - } - return false; + d->loader->settings()->setCheckUppercase(!skip); } -bool Settings::checkUppercase() const +bool Settings::skipUppercase() const { - return d->checkUppercase; + return !d->loader->settings()->checkUppercase(); } -bool Settings::setAutodetectLanguage(bool detect) +void Settings::setAutodetectLanguage(bool detect) { - if (d->autodetectLanguage != detect) { - d->modified = true; - d->autodetectLanguage = detect; - return true; - } - return false; + d->loader->settings()->setAutodetectLanguage(detect); } bool Settings::autodetectLanguage() const { - return d->autodetectLanguage; + return d->loader->settings()->autodetectLanguage(); } -bool Settings::setSkipRunTogether(bool skip) +void Settings::setSkipRunTogether(bool skip) { - if (d->skipRunTogether != skip) { - d->modified = true; - d->skipRunTogether = skip; - return true; - } - return false; + d->loader->settings()->setSkipRunTogether(skip); } bool Settings::skipRunTogether() const { - return d->skipRunTogether; + return d->loader->settings()->skipRunTogether(); } -bool Settings::setCheckerEnabledByDefault(bool check) +void Settings::setCheckerEnabledByDefault(bool check) { - if (d->checkerEnabledByDefault != check) { - d->modified = true; - d->checkerEnabledByDefault = check; - return true; - } - return false; + d->loader->settings()->setCheckerEnabledByDefault(check); } bool Settings::checkerEnabledByDefault() const { - return d->checkerEnabledByDefault; + return d->loader->settings()->checkerEnabledByDefault(); } -bool Settings::setBackgroundCheckerEnabled(bool enable) +void Settings::setBackgroundCheckerEnabled(bool enable) { - if (d->backgroundCheckerEnabled != enable) { - d->modified = true; - d->backgroundCheckerEnabled = enable; - return true; - } - return false; + d->loader->settings()->setBackgroundCheckerEnabled(enable); } bool Settings::backgroundCheckerEnabled() const { - return d->backgroundCheckerEnabled; + return d->loader->settings()->backgroundCheckerEnabled(); } -bool Settings::setCurrentIgnoreList(const QStringList &ignores) +void Settings::setCurrentIgnoreList(const QStringList &ignores) { - bool changed = setQuietIgnoreList(ignores); - d->modified = true; - return changed; -} - -bool Settings::setQuietIgnoreList(const QStringList &ignores) -{ - bool changed = false; - d->ignore = QMap();//clear out - for (QStringList::const_iterator itr = ignores.begin(); - itr != ignores.end(); ++itr) { - d->ignore.insert(*itr, true); - changed = true; - } - return changed; + d->loader->settings()->setCurrentIgnoreList(ignores); } QStringList Settings::currentIgnoreList() const { - return d->ignore.keys(); -} - -bool Settings::addWordToIgnore(const QString &word) -{ - if (!d->ignore.contains(word)) { - d->modified = true; - d->ignore.insert(word, true); - return true; - } - return false; -} - -bool Settings::ignore(const QString &word) -{ - return d->ignore.contains(word); + return d->loader->settings()->currentIgnoreList(); } -int Settings::disablePercentageWordError() const +QStringList Settings::clients() const { - return d->disablePercentage; + return d->loader->clients(); } -int Settings::disableWordErrorCount() const +void Settings::save() { - return d->disableWordCount; + d->loader->settings()->save(); } -void Settings::save() +bool Settings::modified() const { - QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); - settings.setValue(QStringLiteral("defaultClient"), d->defaultClient); - settings.setValue(QStringLiteral("defaultLanguage"), d->defaultLanguage); - settings.setValue(QStringLiteral("preferredLanguages"), d->preferredLanguages); - settings.setValue(QStringLiteral("checkUppercase"), d->checkUppercase); - settings.setValue(QStringLiteral("skipRunTogether"), d->skipRunTogether); - settings.setValue(QStringLiteral("backgroundCheckerEnabled"), d->backgroundCheckerEnabled); - settings.setValue(QStringLiteral("checkerEnabledByDefault"), d->checkerEnabledByDefault); - settings.setValue(QStringLiteral("autodetectLanguage"), d->autodetectLanguage); - QString defaultLanguage = QStringLiteral("ignore_%1").arg(d->defaultLanguage); - if (settings.contains(defaultLanguage) && d->ignore.isEmpty()) { - settings.remove(defaultLanguage); - } else if (!d->ignore.isEmpty()) { - settings.setValue(defaultLanguage, QStringList(d->ignore.keys())); - } + return d->loader->settings()->modified(); } +// default values // A static list of KDE specific words that we want to recognize -static QStringList kdeWords() +QStringList Settings::defaultIgnoreList() { QStringList l; l.append(QStringLiteral("KMail")); l.append(QStringLiteral("KOrganizer")); l.append(QStringLiteral("KAddressBook")); l.append(QStringLiteral("KHTML")); l.append(QStringLiteral("KIO")); l.append(QStringLiteral("KJS")); l.append(QStringLiteral("Konqueror")); l.append(QStringLiteral("Sonnet")); l.append(QStringLiteral("Kontact")); l.append(QStringLiteral("Qt")); l.append(QStringLiteral("Okular")); l.append(QStringLiteral("KMix")); l.append(QStringLiteral("Amarok")); l.append(QStringLiteral("KDevelop")); l.append(QStringLiteral("Nepomuk")); return l; } -void Settings::restore() +bool Settings::defaultSkipUppercase() +{ + return false; +} + +bool Settings::defaultAutodetectLanguage() { - QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); - d->defaultClient = settings.value(QStringLiteral("defaultClient"), QString()).toString(); - d->defaultLanguage = settings.value(QStringLiteral("defaultLanguage"), - QLocale::system().name()).toString(); - d->preferredLanguages = settings.value(QStringLiteral("preferredLanguages")).toStringList(); + return true; +} - //same defaults are in the default filter (filter.cpp) - d->checkUppercase = settings.value(QStringLiteral("checkUppercase"), true).toBool(); - d->skipRunTogether = settings.value(QStringLiteral("skipRunTogether"), true).toBool(); - d->backgroundCheckerEnabled - = settings.value(QStringLiteral("backgroundCheckerEnabled"), true).toBool(); - d->checkerEnabledByDefault - = settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool(); - d->disablePercentage - = settings.value(QStringLiteral("Sonnet_AsYouTypeDisablePercentage"), 90).toInt(); - d->disableWordCount - = settings.value(QStringLiteral("Sonnet_AsYouTypeDisableWordCount"), 100).toInt(); - d->autodetectLanguage = settings.value(QStringLiteral("autodetectLanguage"), true).toBool(); +bool Settings::defaultBackgroundCheckerEnabled() +{ + return true; +} - const QString ignoreEntry = QStringLiteral("ignore_%1").arg(d->defaultLanguage); - const QStringList ignores = settings.value(ignoreEntry, kdeWords()).toStringList(); - setQuietIgnoreList(ignores); +bool Settings::defaultCheckerEnabledByDefault() +{ + return false; } -bool Settings::modified() const +bool Settings::defauktSkipRunTogether() { - return d->modified; + return true; } -void Settings::setModified(bool modified) +QString Settings::defaultDefaultLanguage() { - d->modified = modified; + return QLocale::system().name(); } -} // namespace Sonnet +QStringList Settings::defaultPreferredLanguages() +{ + return QStringList(); +} +} diff --git a/src/core/settings.h b/src/core/settings.h new file mode 100644 index 0000000..270bfc8 --- /dev/null +++ b/src/core/settings.h @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2020 Benjamin Port + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef SONNET_SETTINGS_H +#define SONNET_SETTINGS_H + +#include +#include +#include + +#include "sonnetcore_export.h" + +namespace Sonnet { +class Loader; +class SettingsImpl; +class SettingsPrivate; + +class SONNETCORE_EXPORT Settings : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool skipUppercase READ skipUppercase WRITE setSkipUppercase) + Q_PROPERTY(bool autodetectLanguage READ autodetectLanguage WRITE setAutodetectLanguage) + Q_PROPERTY(bool backgroundCheckerEnabled READ backgroundCheckerEnabled WRITE setBackgroundCheckerEnabled) + Q_PROPERTY(bool checkerEnabledByDefault READ checkerEnabledByDefault WRITE setCheckerEnabledByDefault) + Q_PROPERTY(bool skipRunTogether READ skipRunTogether WRITE setSkipRunTogether) + Q_PROPERTY(QStringList currentIgnoreList READ currentIgnoreList WRITE setCurrentIgnoreList) + Q_PROPERTY(QStringList preferredLanguages READ preferredLanguages WRITE setPreferredLanguages) + Q_PROPERTY(QString defaultLanguage READ defaultLanguage WRITE setDefaultLanguage) + +public: + explicit Settings(QObject *parent = nullptr); + ~Settings() override; + + void setDefaultLanguage(const QString &lang); + QString defaultLanguage() const; + + void setPreferredLanguages(const QStringList &lang); + QStringList preferredLanguages() const; + + void setDefaultClient(const QString &client); + QString defaultClient() const; + + void setSkipUppercase(bool); + bool skipUppercase() const; + + void setAutodetectLanguage(bool); + bool autodetectLanguage() const; + + void setSkipRunTogether(bool); + bool skipRunTogether() const; + + void setBackgroundCheckerEnabled(bool); + bool backgroundCheckerEnabled() const; + + void setCheckerEnabledByDefault(bool); + bool checkerEnabledByDefault() const; + + void setCurrentIgnoreList(const QStringList &ignores); + bool addWordToIgnore(const QString &word); + QStringList currentIgnoreList() const; + bool ignore(const QString &word); + + QStringList clients() const; + bool modified() const; + + void save(); + + static QStringList defaultIgnoreList(); + static bool defaultSkipUppercase(); + static bool defaultAutodetectLanguage(); + static bool defaultBackgroundCheckerEnabled(); + static bool defaultCheckerEnabledByDefault(); + static bool defauktSkipRunTogether(); + static QString defaultDefaultLanguage(); + static QStringList defaultPreferredLanguages(); + +private: + friend class Loader; + SettingsPrivate *const d; +}; +} + +#endif //SONNET_SETTINGS_H diff --git a/src/core/settings.cpp b/src/core/settingsimpl.cpp similarity index 69% copy from src/core/settings.cpp copy to src/core/settingsimpl.cpp index e9950b0..0d95d04 100644 --- a/src/core/settings.cpp +++ b/src/core/settingsimpl.cpp @@ -1,308 +1,286 @@ /* * SPDX-FileCopyrightText: 2003 Zack Rusin * SPDX-FileCopyrightText: 2006 Laurent Montel * SPDX-FileCopyrightText: 2013 Martin Sandsmark * * SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "settings_p.h" +#include "settingsimpl_p.h" #include "loader_p.h" #include -#include #include +#include "settings.h" + namespace Sonnet { -class SettingsPrivate +class SettingsImplPrivate { public: Loader *loader = nullptr; //can't be a Ptr since we don't want to hold a ref on it bool modified = false; QString defaultLanguage; QStringList preferredLanguages; QString defaultClient; bool checkUppercase = false; bool skipRunTogether = false; bool backgroundCheckerEnabled = false; bool checkerEnabledByDefault = false; bool autodetectLanguage = false; int disablePercentage; int disableWordCount; QMap ignore; }; -Settings::Settings(Loader *loader) - : d(new SettingsPrivate) +SettingsImpl::SettingsImpl(Loader *loader) + : d(new SettingsImplPrivate) { d->loader = loader; d->modified = false; d->checkerEnabledByDefault = false; restore(); } -Settings::~Settings() +SettingsImpl::~SettingsImpl() { delete d; } -bool Settings::setDefaultLanguage(const QString &lang) +bool SettingsImpl::setDefaultLanguage(const QString &lang) { const QStringList cs = d->loader->languages(); if (cs.indexOf(lang) != -1 && d->defaultLanguage != lang) { d->defaultLanguage = lang; d->modified = true; d->loader->changed(); return true; } return false; } -QString Settings::defaultLanguage() const +QString SettingsImpl::defaultLanguage() const { return d->defaultLanguage; } -bool Settings::setPreferredLanguages(const QStringList &lang) +bool SettingsImpl::setPreferredLanguages(const QStringList &lang) { if (d->preferredLanguages != lang) { d->modified = true; d->preferredLanguages = lang; return true; } return false; } -QStringList Settings::preferredLanguages() const +QStringList SettingsImpl::preferredLanguages() const { return d->preferredLanguages; } -bool Settings::setDefaultClient(const QString &client) +bool SettingsImpl::setDefaultClient(const QString &client) { //Different from setDefaultLanguage because //the number of clients can't be even close //as big as the number of languages if (d->loader->clients().contains(client)) { d->defaultClient = client; d->modified = true; d->loader->changed(); return true; } return false; } -QString Settings::defaultClient() const +QString SettingsImpl::defaultClient() const { return d->defaultClient; } -bool Settings::setCheckUppercase(bool check) +bool SettingsImpl::setCheckUppercase(bool check) { if (d->checkUppercase != check) { d->modified = true; d->checkUppercase = check; return true; } return false; } -bool Settings::checkUppercase() const +bool SettingsImpl::checkUppercase() const { return d->checkUppercase; } -bool Settings::setAutodetectLanguage(bool detect) +bool SettingsImpl::setAutodetectLanguage(bool detect) { if (d->autodetectLanguage != detect) { d->modified = true; d->autodetectLanguage = detect; return true; } return false; } -bool Settings::autodetectLanguage() const +bool SettingsImpl::autodetectLanguage() const { return d->autodetectLanguage; } -bool Settings::setSkipRunTogether(bool skip) +bool SettingsImpl::setSkipRunTogether(bool skip) { if (d->skipRunTogether != skip) { d->modified = true; d->skipRunTogether = skip; return true; } return false; } -bool Settings::skipRunTogether() const +bool SettingsImpl::skipRunTogether() const { return d->skipRunTogether; } -bool Settings::setCheckerEnabledByDefault(bool check) +bool SettingsImpl::setCheckerEnabledByDefault(bool check) { if (d->checkerEnabledByDefault != check) { d->modified = true; d->checkerEnabledByDefault = check; return true; } return false; } -bool Settings::checkerEnabledByDefault() const +bool SettingsImpl::checkerEnabledByDefault() const { return d->checkerEnabledByDefault; } -bool Settings::setBackgroundCheckerEnabled(bool enable) +bool SettingsImpl::setBackgroundCheckerEnabled(bool enable) { if (d->backgroundCheckerEnabled != enable) { d->modified = true; d->backgroundCheckerEnabled = enable; return true; } return false; } -bool Settings::backgroundCheckerEnabled() const +bool SettingsImpl::backgroundCheckerEnabled() const { return d->backgroundCheckerEnabled; } -bool Settings::setCurrentIgnoreList(const QStringList &ignores) +bool SettingsImpl::setCurrentIgnoreList(const QStringList &ignores) { bool changed = setQuietIgnoreList(ignores); d->modified = true; return changed; } -bool Settings::setQuietIgnoreList(const QStringList &ignores) +bool SettingsImpl::setQuietIgnoreList(const QStringList &ignores) { bool changed = false; d->ignore = QMap();//clear out for (QStringList::const_iterator itr = ignores.begin(); itr != ignores.end(); ++itr) { d->ignore.insert(*itr, true); changed = true; } return changed; } -QStringList Settings::currentIgnoreList() const +QStringList SettingsImpl::currentIgnoreList() const { return d->ignore.keys(); } -bool Settings::addWordToIgnore(const QString &word) +bool SettingsImpl::addWordToIgnore(const QString &word) { if (!d->ignore.contains(word)) { d->modified = true; d->ignore.insert(word, true); return true; } return false; } -bool Settings::ignore(const QString &word) +bool SettingsImpl::ignore(const QString &word) { return d->ignore.contains(word); } -int Settings::disablePercentageWordError() const +int SettingsImpl::disablePercentageWordError() const { return d->disablePercentage; } -int Settings::disableWordErrorCount() const +int SettingsImpl::disableWordErrorCount() const { return d->disableWordCount; } -void Settings::save() +void SettingsImpl::save() { QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); settings.setValue(QStringLiteral("defaultClient"), d->defaultClient); settings.setValue(QStringLiteral("defaultLanguage"), d->defaultLanguage); settings.setValue(QStringLiteral("preferredLanguages"), d->preferredLanguages); settings.setValue(QStringLiteral("checkUppercase"), d->checkUppercase); settings.setValue(QStringLiteral("skipRunTogether"), d->skipRunTogether); settings.setValue(QStringLiteral("backgroundCheckerEnabled"), d->backgroundCheckerEnabled); settings.setValue(QStringLiteral("checkerEnabledByDefault"), d->checkerEnabledByDefault); settings.setValue(QStringLiteral("autodetectLanguage"), d->autodetectLanguage); QString defaultLanguage = QStringLiteral("ignore_%1").arg(d->defaultLanguage); if (settings.contains(defaultLanguage) && d->ignore.isEmpty()) { settings.remove(defaultLanguage); } else if (!d->ignore.isEmpty()) { settings.setValue(defaultLanguage, QStringList(d->ignore.keys())); } } -// A static list of KDE specific words that we want to recognize -static QStringList kdeWords() -{ - QStringList l; - l.append(QStringLiteral("KMail")); - l.append(QStringLiteral("KOrganizer")); - l.append(QStringLiteral("KAddressBook")); - l.append(QStringLiteral("KHTML")); - l.append(QStringLiteral("KIO")); - l.append(QStringLiteral("KJS")); - l.append(QStringLiteral("Konqueror")); - l.append(QStringLiteral("Sonnet")); - l.append(QStringLiteral("Kontact")); - l.append(QStringLiteral("Qt")); - l.append(QStringLiteral("Okular")); - l.append(QStringLiteral("KMix")); - l.append(QStringLiteral("Amarok")); - l.append(QStringLiteral("KDevelop")); - l.append(QStringLiteral("Nepomuk")); - return l; -} - -void Settings::restore() +void SettingsImpl::restore() { QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); d->defaultClient = settings.value(QStringLiteral("defaultClient"), QString()).toString(); d->defaultLanguage = settings.value(QStringLiteral("defaultLanguage"), - QLocale::system().name()).toString(); - d->preferredLanguages = settings.value(QStringLiteral("preferredLanguages")).toStringList(); + Settings::defaultDefaultLanguage()).toString(); + d->preferredLanguages = settings.value(QStringLiteral("preferredLanguages"), Settings::defaultPreferredLanguages()).toStringList(); //same defaults are in the default filter (filter.cpp) - d->checkUppercase = settings.value(QStringLiteral("checkUppercase"), true).toBool(); - d->skipRunTogether = settings.value(QStringLiteral("skipRunTogether"), true).toBool(); + d->checkUppercase = settings.value(QStringLiteral("checkUppercase"), !Settings::defaultSkipUppercase()).toBool(); + d->skipRunTogether = settings.value(QStringLiteral("skipRunTogether"), Settings::defauktSkipRunTogether()).toBool(); d->backgroundCheckerEnabled - = settings.value(QStringLiteral("backgroundCheckerEnabled"), true).toBool(); + = settings.value(QStringLiteral("backgroundCheckerEnabled"), Settings::defaultBackgroundCheckerEnabled()).toBool(); d->checkerEnabledByDefault - = settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool(); + = settings.value(QStringLiteral("checkerEnabledByDefault"), Settings::defaultCheckerEnabledByDefault()).toBool(); d->disablePercentage = settings.value(QStringLiteral("Sonnet_AsYouTypeDisablePercentage"), 90).toInt(); d->disableWordCount = settings.value(QStringLiteral("Sonnet_AsYouTypeDisableWordCount"), 100).toInt(); - d->autodetectLanguage = settings.value(QStringLiteral("autodetectLanguage"), true).toBool(); + d->autodetectLanguage = settings.value(QStringLiteral("autodetectLanguage"), Settings::defaultAutodetectLanguage()).toBool(); const QString ignoreEntry = QStringLiteral("ignore_%1").arg(d->defaultLanguage); - const QStringList ignores = settings.value(ignoreEntry, kdeWords()).toStringList(); + const QStringList ignores = settings.value(ignoreEntry, Settings::defaultIgnoreList()).toStringList(); setQuietIgnoreList(ignores); } -bool Settings::modified() const +bool SettingsImpl::modified() const { return d->modified; } -void Settings::setModified(bool modified) +void SettingsImpl::setModified(bool modified) { d->modified = modified; } - } // namespace Sonnet diff --git a/src/core/settings_p.h b/src/core/settingsimpl_p.h similarity index 77% rename from src/core/settings_p.h rename to src/core/settingsimpl_p.h index 2daeb48..7990400 100644 --- a/src/core/settings_p.h +++ b/src/core/settingsimpl_p.h @@ -1,77 +1,77 @@ /* * SPDX-FileCopyrightText: 2003 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ -#ifndef SONNET_SETTINGS_P_H -#define SONNET_SETTINGS_P_H +#ifndef SONNET_SETTINGS_IMPL_P_H +#define SONNET_SETTINGS_IMPL_P_H + +#include "sonnetcore_export.h" #include #include -#include "sonnetcore_export.h" namespace Sonnet { class Loader; -class SettingsPrivate; +class SettingsImplPrivate; /** - * Settings class + * SettingsImpl class */ -class SONNETCORE_EXPORT Settings +class SONNETCORE_EXPORT SettingsImpl { public: - ~Settings(); + ~SettingsImpl(); - Settings(const Settings &) = delete; - Settings &operator=(const Settings &) = delete; + SettingsImpl(const SettingsImpl &) = delete; + SettingsImpl &operator=(const SettingsImpl &) = delete; bool modified() const; void setModified(bool modified); bool setDefaultLanguage(const QString &lang); QString defaultLanguage() const; bool setPreferredLanguages(const QStringList &lang); QStringList preferredLanguages() const; bool setDefaultClient(const QString &client); QString defaultClient() const; bool setCheckUppercase(bool); bool checkUppercase() const; bool setAutodetectLanguage(bool); bool autodetectLanguage() const; bool setSkipRunTogether(bool); bool skipRunTogether() const; bool setBackgroundCheckerEnabled(bool); bool backgroundCheckerEnabled() const; bool setCheckerEnabledByDefault(bool); bool checkerEnabledByDefault() const; bool setCurrentIgnoreList(const QStringList &ignores); bool addWordToIgnore(const QString &word); QStringList currentIgnoreList() const; bool ignore(const QString &word); void save(); void restore(); int disablePercentageWordError() const; int disableWordErrorCount() const; private: - void readIgnoreList(); bool setQuietIgnoreList(const QStringList &ignores); private: friend class Loader; - explicit Settings(Loader *loader); + explicit SettingsImpl(Loader *loader); private: - SettingsPrivate *const d; + SettingsImplPrivate *const d; }; } -#endif // SONNET_SETTINGS_P_H +#endif // SONNET_SETTINGS_IMPL_P_H diff --git a/src/core/speller.cpp b/src/core/speller.cpp index 381ecb3..577a6b3 100644 --- a/src/core/speller.cpp +++ b/src/core/speller.cpp @@ -1,277 +1,277 @@ /* * SPDX-FileCopyrightText: 2007 Zack Rusin * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "speller.h" #include "loader_p.h" -#include "settings_p.h" +#include "settingsimpl_p.h" #include "spellerplugin_p.h" #include #include "core_debug.h" namespace Sonnet { class SpellerPrivate { public: SpellerPrivate() { } ~SpellerPrivate() { } void init(const QString &lang) { Loader *loader = Loader::openLoader(); settings = loader->settings(); language = lang; if (language.isEmpty()) { language = settings->defaultLanguage(); } updateDict(); } void updateDict() { dict = Loader::openLoader()->cachedSpeller(language); } bool isValid() { if (settings->modified()) { recreateDict(); settings->setModified(false); } return dict; } void recreateDict() { Loader::openLoader()->clearSpellerCache(); updateDict(); } QSharedPointer dict; - Settings *settings = nullptr; + SettingsImpl *settings = nullptr; QString language; }; Speller::Speller(const QString &lang) : d(new SpellerPrivate) { d->init(lang); } Speller::~Speller() { qCDebug(SONNET_LOG_CORE) << "deleting" << this << "for" << d->language; delete d; } Speller::Speller(const Speller &speller) : d(new SpellerPrivate) { d->language = speller.language(); d->init(d->language); } Speller &Speller::operator=(const Speller &speller) { d->language = speller.language(); d->updateDict(); return *this; } bool Speller::isCorrect(const QString &word) const { if (!d->isValid()) { return true; } return d->dict->isCorrect(word); } bool Speller::isMisspelled(const QString &word) const { if (!d->isValid()) { return false; } return d->dict->isMisspelled(word); } QStringList Speller::suggest(const QString &word) const { if (!d->isValid()) { return QStringList(); } return d->dict->suggest(word); } bool Speller::checkAndSuggest(const QString &word, QStringList &suggestions) const { if (!d->isValid()) { return true; } return d->dict->checkAndSuggest(word, suggestions); } bool Speller::storeReplacement(const QString &bad, const QString &good) { if (!d->isValid()) { return false; } return d->dict->storeReplacement(bad, good); } bool Speller::addToPersonal(const QString &word) { if (!d->isValid()) { return false; } return d->dict->addToPersonal(word); } bool Speller::addToSession(const QString &word) { if (!d->isValid()) { return false; } return d->dict->addToSession(word); } QString Speller::language() const { if (!d->isValid()) { return QString(); } return d->dict->language(); } void Speller::save() { if (d->settings) { d->settings->save(); } } void Speller::restore() { if (d->settings) { d->settings->restore(); d->recreateDict(); } } QStringList Speller::availableBackends() const { Loader *l = Loader::openLoader(); return l->clients(); } QStringList Speller::availableLanguages() const { Loader *l = Loader::openLoader(); return l->languages(); } QStringList Speller::availableLanguageNames() const { Loader *l = Loader::openLoader(); return l->languageNames(); } void Speller::setDefaultLanguage(const QString &lang) { if (d->settings->setDefaultLanguage(lang)) { d->settings->save(); } } QString Speller::defaultLanguage() const { return d->settings->defaultLanguage(); } void Speller::setDefaultClient(const QString &client) { if (d->settings->setDefaultClient(client)) { d->settings->save(); } } QString Speller::defaultClient() const { return d->settings->defaultClient(); } void Speller::setAttribute(Attribute attr, bool b) { switch (attr) { case CheckUppercase: d->settings->setCheckUppercase(b); break; case SkipRunTogether: d->settings->setSkipRunTogether(b); break; case AutoDetectLanguage: d->settings->setAutodetectLanguage(b); break; } d->settings->save(); } bool Speller::testAttribute(Attribute attr) const { switch (attr) { case CheckUppercase: return d->settings->checkUppercase(); case SkipRunTogether: return d->settings->skipRunTogether(); case AutoDetectLanguage: return d->settings->autodetectLanguage(); } return false; } bool Speller::isValid() const { return d->dict; } void Speller::setLanguage(const QString &lang) { d->language = lang; d->updateDict(); } QMap Sonnet::Speller::availableDictionaries() const { Loader *l = Loader::openLoader(); const QStringList lst = l->languages(); QMap langs; for (const QString &tag : lst) { langs.insert(l->languageNameForCode(tag), tag); } return langs; } QMap Speller::preferredDictionaries() const { Loader *l = Loader::openLoader(); QMap langs; for (const QString &tag : l->settings()->preferredLanguages()) { langs.insert(l->languageNameForCode(tag), tag); } return langs; } } // namespace Sonnet diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 8ddcf34..56a47f1 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -1,110 +1,112 @@ project(sonnetui) ecm_create_qm_loader(sonnet_QM_LOADER sonnet5_qt) set(sonnetui_SRCS configdialog.cpp + configview.cpp configwidget.cpp dialog.cpp dictionarycombobox.cpp highlighter.cpp spellcheckdecorator.cpp ${sonnet_QM_LOADER} ) ecm_qt_declare_logging_category(sonnetui_SRCS HEADER ui_debug.h IDENTIFIER SONNET_LOG_UI CATEGORY_NAME sonnet.ui DESCRIPTION "Sonnet UI" EXPORT SONNET ) set(sonnetui_UI configui.ui sonnetui.ui ) ecm_generate_headers(SonnetUi_CamelCase_HEADERS HEADER_NAMES Dialog Highlighter ConfigDialog + ConfigView ConfigWidget DictionaryComboBox SpellCheckDecorator PREFIX Sonnet REQUIRED_HEADERS SonnetUi_HEADERS ) qt5_wrap_ui(sonnetui_SRCS ${sonnetui_UI}) add_library(KF5SonnetUi ${sonnetui_SRCS}) ecm_generate_export_header(KF5SonnetUi BASE_NAME SonnetUi GROUP_BASE_NAME KF VERSION ${KF5_VERSION} DEPRECATED_BASE_VERSION 0 DEPRECATION_VERSIONS 5.65 EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} ) add_library(KF5::SonnetUi ALIAS KF5SonnetUi) target_link_libraries(KF5SonnetUi PUBLIC Qt5::Widgets PRIVATE KF5::SonnetCore ) set_target_properties(KF5SonnetUi PROPERTIES VERSION ${SONNET_VERSION_STRING} SOVERSION ${SONNET_SOVERSION} EXPORT_NAME SonnetUi ) # CMAKE_CURRENT_BINARY_DIR: for camelcase headers and lowercase forwarders target_include_directories(KF5SonnetUi INTERFACE "$") target_include_directories(KF5SonnetUi PUBLIC "$") install(TARGETS KF5SonnetUi EXPORT KF5SonnetTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${SonnetUi_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/SonnetUi/Sonnet COMPONENT Devel) install(FILES ${SonnetUi_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/sonnetui_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/SonnetUi/sonnet COMPONENT Devel) if(BUILD_DESIGNERPLUGIN) add_subdirectory(designer) endif() if (BUILD_QCH) ecm_add_qch( KF5SonnetUi_QCH NAME SonnetUi BASE_NAME KF5SonnetUi VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${SonnetUi_HEADERS} LINK_QCHS Qt5Core_QCH Qt5Gui_QCH Qt5Widgets_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS SONNETUI_EXPORT SONNETUI_DEPRECATED_EXPORT SONNETUI_DEPRECATED "SONNETUI_DEPRECATED_VERSION(x, y, t)" TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME SonnetUi LIB_NAME KF5SonnetUi DEPS "widgets" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/SonnetUi) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/ui/configui.ui b/src/ui/configui.ui index 0ce7754..2fd503a 100644 --- a/src/ui/configui.ui +++ b/src/ui/configui.ui @@ -1,185 +1,185 @@ Zack Rusin <zack@kde.org> Licensed under GNU LGPL SonnetConfigUI 0 0 700 833 75 true true No backend found for spell checking Qt::AlignCenter Default language: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Qt::Vertical true Choose your preferred languages Options - + Enable autodetection of &language - + Enable &background spellchecking - + &Automatic spell checking enabled by default - + Skip all &uppercase words - + S&kip run-together words Ignored Words: &Add .. &Remove .. Qt::Vertical 20 40 Sonnet::DictionaryComboBox QComboBox
sonnet/dictionarycombobox.h
diff --git a/src/ui/configview.cpp b/src/ui/configview.cpp new file mode 100644 index 0000000..ef1ec0f --- /dev/null +++ b/src/ui/configview.cpp @@ -0,0 +1,198 @@ +/* + * configwidget.cpp + * + * SPDX-FileCopyrightText: 2004 Zack Rusin + * SPDX-FileCopyrightText: 2020 Benjamin Port + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "configview.h" +#include "ui_configui.h" + +#include +#include +#include +#include "ui_debug.h" + +using namespace Sonnet; + +class ConfigViewPrivate +{ +public: + explicit ConfigViewPrivate(ConfigView *v); + Ui_SonnetConfigUI ui; + QWidget *wdg = nullptr; + QStringList ignoreList; + ConfigView *q; + void slotUpdateButton(const QString &text); + void slotSelectionChanged(); + void slotIgnoreWordAdded(); + void slotIgnoreWordRemoved(); +}; + +ConfigViewPrivate::ConfigViewPrivate(ConfigView *v) +{ + q = v; +} + +void ConfigViewPrivate::slotUpdateButton(const QString &text) +{ + ui.addButton->setEnabled(!text.isEmpty()); +} + +void ConfigViewPrivate::slotSelectionChanged() +{ + ui.removeButton->setEnabled(!ui.ignoreListWidget->selectedItems().isEmpty()); +} + +void ConfigViewPrivate::slotIgnoreWordAdded() +{ + QString newWord = ui.newIgnoreEdit->text(); + ui.newIgnoreEdit->clear(); + if (newWord.isEmpty() || ignoreList.contains(newWord)) { + return; + } + ignoreList.append(newWord); + + ui.ignoreListWidget->clear(); + ui.ignoreListWidget->addItems(ignoreList); + + emit q->configChanged(); +} + +void ConfigViewPrivate::slotIgnoreWordRemoved() +{ + const QList selectedItems = ui.ignoreListWidget->selectedItems(); + for (const QListWidgetItem *item : selectedItems) { + ignoreList.removeAll(item->text()); + } + + ui.ignoreListWidget->clear(); + ui.ignoreListWidget->addItems(ignoreList); + + emit q->configChanged(); +} + +ConfigView::ConfigView(QWidget *parent) + : QWidget(parent) + , d(new ConfigViewPrivate(this)) +{ + auto *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setObjectName(QStringLiteral("SonnetConfigUILayout")); + d->wdg = new QWidget(this); + d->ui.setupUi(d->wdg); + + 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; + } + auto *item = new QListWidgetItem(d->ui.m_langCombo->itemText(i), d->ui.languageList); + item->setData(Qt::UserRole, tag); + } + + d->ui.kcfg_backgroundCheckerEnabled->hide();//hidden by default + + connect(d->ui.addButton, &QAbstractButton::clicked, this, [this]{ d->slotIgnoreWordAdded(); }); + connect(d->ui.removeButton, &QAbstractButton::clicked, this, [this]{ d->slotIgnoreWordRemoved(); }); + + layout->addWidget(d->wdg); + connect(d->ui.newIgnoreEdit, &QLineEdit::textChanged, this, [this](const QString &text){ d->slotUpdateButton(text); }); + connect(d->ui.ignoreListWidget, &QListWidget::itemSelectionChanged, this, [this]{ d->slotSelectionChanged(); }); + d->ui.addButton->setEnabled(false); + d->ui.removeButton->setEnabled(false); + + connect(d->ui.m_langCombo, &DictionaryComboBox::dictionaryChanged, this, + &ConfigView::configChanged); + connect(d->ui.languageList, &QListWidget::itemChanged, this, &ConfigView::configChanged); + + connect(d->ui.kcfg_backgroundCheckerEnabled, &QAbstractButton::clicked, this, &ConfigView::configChanged); + connect(d->ui.kcfg_skipUppercase, &QAbstractButton::clicked, this, &ConfigView::configChanged); + connect(d->ui.kcfg_skipRunTogether, &QAbstractButton::clicked, this, + &ConfigView::configChanged); + connect(d->ui.kcfg_checkerEnabledByDefault, &QAbstractButton::clicked, this, + &ConfigView::configChanged); + connect(d->ui.kcfg_autodetectLanguage, &QAbstractButton::clicked, this, &ConfigView::configChanged); +} + +ConfigView::~ConfigView() +{ + delete d; +} + +void ConfigView::setNoBackendFoundVisible(bool show) +{ + d->ui.nobackendfound->setVisible(show); +} + + +bool ConfigView::noBackendFoundVisible() const +{ + return d->ui.nobackendfound->isVisible(); +} + +void ConfigView::setBackgroundCheckingButtonShown(bool b) +{ + d->ui.kcfg_backgroundCheckerEnabled->setVisible(b); +} + +bool ConfigView::backgroundCheckingButtonShown() const +{ + return !d->ui.kcfg_backgroundCheckerEnabled->isHidden(); +} + +void ConfigView::setLanguage(const QString &language) +{ + d->ui.m_langCombo->setCurrentByDictionary(language); +} + +QString ConfigView::language() const +{ + if (d->ui.m_langCombo->count()) { + return d->ui.m_langCombo->currentDictionary(); + } else { + return QString(); + } +} + +void ConfigView::setPreferredLanguages(const QStringList& preferredLanguages) +{ + for(int i = 0; i < d->ui.languageList->count(); ++i) { + QListWidgetItem* item = d->ui.languageList->item(i); + QString tag = item->data(Qt::UserRole).toString(); + if (preferredLanguages.contains(tag)) { + item->setCheckState(Qt::Checked); + } else { + item->setCheckState(Qt::Unchecked); + } + } + emit configChanged(); +} + + +QStringList ConfigView::preferredLanguages() const +{ + 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(); + } + return preferredLanguages; +} + +void ConfigView::setIgnoreList(const QStringList& ignoreList) +{ + d->ignoreList = ignoreList; + d->ignoreList.sort(); + d->ui.ignoreListWidget->clear(); + d->ui.ignoreListWidget->addItems(d->ignoreList); + emit configChanged(); +} + +QStringList ConfigView::ignoreList() const +{ + return d->ignoreList; +} diff --git a/src/ui/configview.h b/src/ui/configview.h new file mode 100644 index 0000000..dd31631 --- /dev/null +++ b/src/ui/configview.h @@ -0,0 +1,52 @@ +/* + * + * SPDX-FileCopyrightText: 2004 Zack Rusin + * SPDX-FileCopyrightText: 2020 Benjamin Port + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef SONNET_CONFIGVIEW_H +#define SONNET_CONFIGVIEW_H + +#include + +#include "sonnetui_export.h" + +class ConfigViewPrivate; + +namespace Sonnet { + +class SONNETUI_EXPORT ConfigView : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString language READ language WRITE setLanguage) + Q_PROPERTY(QStringList ignoreList READ ignoreList WRITE setIgnoreList) + Q_PROPERTY(QStringList preferredLanguages READ preferredLanguages WRITE setPreferredLanguages) + Q_PROPERTY(bool backgroundCheckingButtonShown READ backgroundCheckingButtonShown WRITE setBackgroundCheckingButtonShown) + Q_PROPERTY(bool showNoBackendFound READ noBackendFoundVisible WRITE setNoBackendFoundVisible) +public: + explicit ConfigView(QWidget *parent = nullptr); + ~ConfigView() override; + + bool backgroundCheckingButtonShown() const; + bool noBackendFoundVisible() const; + QStringList preferredLanguages() const; + QString language() const; + QStringList ignoreList() const; + +public Q_SLOTS: + void setNoBackendFoundVisible(bool show); + void setBackgroundCheckingButtonShown(bool); + void setPreferredLanguages(const QStringList &ignoreList); + void setLanguage(const QString &language); + void setIgnoreList(const QStringList &ignoreList); + +Q_SIGNALS: + void configChanged(); + +private: + ConfigViewPrivate *const d; +}; +} + +#endif diff --git a/src/ui/configwidget.cpp b/src/ui/configwidget.cpp index 34c145e..8d7e1d9 100644 --- a/src/ui/configwidget.cpp +++ b/src/ui/configwidget.cpp @@ -1,204 +1,203 @@ /* * 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 "settingsimpl_p.h" +#include "settings.h" #include #include #include #include #include "ui_debug.h" using namespace Sonnet; class ConfigWidgetPrivate { public: Ui_SonnetConfigUI ui; - Loader *loader = nullptr; + Settings *settings = nullptr; QWidget *wdg = nullptr; }; ConfigWidget::ConfigWidget(QWidget *parent) : QWidget(parent) , d(new ConfigWidgetPrivate) { - d->loader = Loader::openLoader(); + d->settings = new Settings(this); 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()); + d->ui.m_langCombo->setCurrentByDictionary(d->settings->defaultLanguage()); - QStringList preferredLanguages = d->loader->settings()->preferredLanguages(); + QStringList preferredLanguages = d->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(); + d->ui.kcfg_skipUppercase->setChecked(d->settings->skipUppercase()); + d->ui.kcfg_skipRunTogether->setChecked(d->settings->skipRunTogether()); + d->ui.kcfg_checkerEnabledByDefault->setChecked(d->settings->checkerEnabledByDefault()); + d->ui.kcfg_autodetectLanguage->setChecked(d->settings->autodetectLanguage()); + QStringList ignoreList = d->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 + d->ui.kcfg_backgroundCheckerEnabled->setChecked(d->settings->backgroundCheckerEnabled()); + d->ui.kcfg_backgroundCheckerEnabled->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, + connect(d->ui.kcfg_backgroundCheckerEnabled, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); + connect(d->ui.kcfg_skipUppercase, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); + connect(d->ui.kcfg_skipRunTogether, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); - connect(d->ui.m_checkerEnabledByDefaultCB, &QAbstractButton::clicked, this, + connect(d->ui.kcfg_checkerEnabledByDefault, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); - connect(d->ui.m_autodetectCB, &QAbstractButton::clicked, this, &ConfigWidget::configChanged); + connect(d->ui.kcfg_autodetectLanguage, &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.nobackendfound->setVisible(d->settings->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()); + d->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); + d->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()); + d->settings->setSkipUppercase(d->ui.kcfg_skipUppercase->isChecked()); + d->settings->setSkipRunTogether(d->ui.kcfg_skipRunTogether->isChecked()); + d->settings->setBackgroundCheckerEnabled(d->ui.kcfg_backgroundCheckerEnabled->isChecked()); + d->settings->setCheckerEnabledByDefault(d->ui.kcfg_checkerEnabledByDefault->isChecked()); + d->settings->setAutodetectLanguage(d->ui.kcfg_autodetectLanguage->isChecked()); - if (settings->modified()) { - settings->save(); + if (d->settings->modified()) { + d->settings->save(); } } void ConfigWidget::slotIgnoreWordAdded() { - QStringList ignoreList = d->loader->settings()->currentIgnoreList(); + QStringList ignoreList = d->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->settings->setCurrentIgnoreList(ignoreList); d->ui.ignoreListWidget->clear(); d->ui.ignoreListWidget->addItems(ignoreList); emit configChanged(); } void ConfigWidget::slotIgnoreWordRemoved() { - QStringList ignoreList = d->loader->settings()->currentIgnoreList(); + QStringList ignoreList = d->settings->currentIgnoreList(); const QList selectedItems = d->ui.ignoreListWidget->selectedItems(); for (const QListWidgetItem *item : selectedItems) { ignoreList.removeAll(item->text()); } - d->loader->settings()->setCurrentIgnoreList(ignoreList); + d->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); + d->ui.kcfg_backgroundCheckerEnabled->setVisible(b); } bool ConfigWidget::backgroundCheckingButtonShown() const { - return !d->ui.m_bgSpellCB->isHidden(); + return !d->ui.kcfg_backgroundCheckerEnabled->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.kcfg_autodetectLanguage->setChecked(Settings::defaultAutodetectLanguage()); + d->ui.kcfg_skipUppercase->setChecked(Settings::defaultSkipUppercase()); + d->ui.kcfg_skipRunTogether->setChecked(Settings::defauktSkipRunTogether()); + d->ui.kcfg_checkerEnabledByDefault->setChecked(Settings::defaultCheckerEnabledByDefault()); + d->ui.kcfg_backgroundCheckerEnabled->setChecked(Settings::defaultBackgroundCheckerEnabled()); d->ui.ignoreListWidget->clear(); - d->ui.m_langCombo->setCurrentByDictionary(d->loader->settings()->defaultLanguage()); + d->ui.m_langCombo->setCurrentByDictionary(d->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 e881fda..5e0b116 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 "settingsimpl_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 70bc602..edbfdba 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 "settingsimpl_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); } }