File indexing completed on 2024-04-21 04:00:54

0001 /*
0002  * SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
0003  * SPDX-FileCopyrightText: 2012 Martin Sandsmark <martin.sandsmark@kde.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-or-later
0006  */
0007 #include "client_p.h"
0008 #include "loader_p.h"
0009 #include "settingsimpl_p.h"
0010 #include "spellerplugin_p.h"
0011 
0012 #include "core_debug.h"
0013 
0014 #include <QCoreApplication>
0015 #include <QDir>
0016 #include <QHash>
0017 #include <QList>
0018 #include <QLocale>
0019 #include <QMap>
0020 #include <QPluginLoader>
0021 
0022 #include <algorithm>
0023 
0024 #ifdef SONNET_STATIC
0025 #include "../plugins/hunspell/hunspellclient.h"
0026 #ifdef Q_OS_MACOS
0027 #include "../plugins/nsspellchecker/nsspellcheckerclient.h"
0028 #endif
0029 #endif
0030 
0031 namespace Sonnet
0032 {
0033 class LoaderPrivate
0034 {
0035 public:
0036     SettingsImpl *settings;
0037 
0038     // <language, Clients with that language >
0039     QMap<QString, QList<Client *>> languageClients;
0040     QStringList clients;
0041 
0042     QSet<QString> loadedPlugins;
0043 
0044     QStringList languagesNameCache;
0045     QHash<QString, QSharedPointer<SpellerPlugin>> spellerCache;
0046 };
0047 
0048 Q_GLOBAL_STATIC(Loader, s_loader)
0049 
0050 Loader *Loader::openLoader()
0051 {
0052     if (s_loader.isDestroyed()) {
0053         return nullptr;
0054     }
0055 
0056     return s_loader();
0057 }
0058 
0059 Loader::Loader()
0060     : d(new LoaderPrivate)
0061 {
0062     d->settings = new SettingsImpl(this);
0063     d->settings->restore();
0064     loadPlugins();
0065 }
0066 
0067 Loader::~Loader()
0068 {
0069     qCDebug(SONNET_LOG_CORE) << "Removing loader: " << this;
0070     delete d->settings;
0071     d->settings = nullptr;
0072 }
0073 
0074 SpellerPlugin *Loader::createSpeller(const QString &language, const QString &clientName) const
0075 {
0076     QString backend = clientName;
0077     QString plang = language;
0078 
0079     if (plang.isEmpty()) {
0080         plang = d->settings->defaultLanguage();
0081     }
0082 
0083     auto clientsItr = d->languageClients.constFind(plang);
0084     if (clientsItr == d->languageClients.constEnd()) {
0085         if (language.isEmpty() || language == QStringLiteral("C")) {
0086             qCDebug(SONNET_LOG_CORE) << "No language dictionaries for the language:" << plang << "trying to load en_US as default";
0087             return createSpeller(QStringLiteral("en_US"), clientName);
0088         }
0089         qCWarning(SONNET_LOG_CORE) << "No language dictionaries for the language:" << plang;
0090         Q_EMIT loadingDictionaryFailed(plang);
0091         return nullptr;
0092     }
0093 
0094     const QList<Client *> lClients = *clientsItr;
0095 
0096     if (backend.isEmpty()) {
0097         backend = d->settings->defaultClient();
0098         if (!backend.isEmpty()) {
0099             // check if the default client supports the requested language;
0100             // if it does it will be an element of lClients.
0101             bool unknown = !std::any_of(lClients.constBegin(), lClients.constEnd(), [backend](const Client *client) {
0102                 return client->name() == backend;
0103             });
0104             if (unknown) {
0105                 qCWarning(SONNET_LOG_CORE) << "Default client" << backend << "doesn't support language:" << plang;
0106                 backend = QString();
0107             }
0108         }
0109     }
0110 
0111     QListIterator<Client *> itr(lClients);
0112     while (itr.hasNext()) {
0113         Client *item = itr.next();
0114         if (!backend.isEmpty()) {
0115             if (backend == item->name()) {
0116                 SpellerPlugin *dict = item->createSpeller(plang);
0117                 qCDebug(SONNET_LOG_CORE) << "Using the" << item->name() << "plugin for language" << plang;
0118                 return dict;
0119             }
0120         } else {
0121             // the first one is the one with the highest
0122             // reliability
0123             SpellerPlugin *dict = item->createSpeller(plang);
0124             qCDebug(SONNET_LOG_CORE) << "Using the" << item->name() << "plugin for language" << plang;
0125             return dict;
0126         }
0127     }
0128 
0129     qCWarning(SONNET_LOG_CORE) << "The default client" << backend << "has no language dictionaries for the language:" << plang;
0130     return nullptr;
0131 }
0132 
0133 QSharedPointer<SpellerPlugin> Loader::cachedSpeller(const QString &language)
0134 {
0135     auto &speller = d->spellerCache[language];
0136     if (!speller) {
0137         speller.reset(createSpeller(language));
0138     }
0139     return speller;
0140 }
0141 
0142 void Loader::clearSpellerCache()
0143 {
0144     d->spellerCache.clear();
0145 }
0146 
0147 QStringList Loader::clients() const
0148 {
0149     return d->clients;
0150 }
0151 
0152 QStringList Loader::languages() const
0153 {
0154     return d->languageClients.keys();
0155 }
0156 
0157 QString Loader::languageNameForCode(const QString &langCode) const
0158 {
0159     QString currentDictionary = langCode; // e.g. en_GB-ize-wo_accents
0160     QString isoCode; // locale ISO name
0161     QString variantName; // dictionary variant name e.g. w_accents
0162     QString localizedLang; // localized language
0163     QString localizedCountry; // localized country
0164     QString localizedVariant;
0165     QByteArray variantEnglish; // dictionary variant in English
0166 
0167     int minusPos; // position of "-" char
0168     int variantCount = 0; // used to iterate over variantList
0169 
0170     struct variantListType {
0171         const char *variantShortName;
0172         const char *variantEnglishName;
0173     };
0174 
0175     /*
0176      * This redefines the QT_TRANSLATE_NOOP3 macro provided by Qt to indicate that
0177      * statically initialised text should be translated so that it expands to just
0178      * the string that should be translated, making it possible to use it in the
0179      * single string construct below.
0180      */
0181 #undef QT_TRANSLATE_NOOP3
0182 #define QT_TRANSLATE_NOOP3(a, b, c) b
0183 
0184     const variantListType variantList[] = {{"40", QT_TRANSLATE_NOOP3("Sonnet::Loader", "40", "dictionary variant")}, // what does 40 mean?
0185                                            {"60", QT_TRANSLATE_NOOP3("Sonnet::Loader", "60", "dictionary variant")}, // what does 60 mean?
0186                                            {"80", QT_TRANSLATE_NOOP3("Sonnet::Loader", "80", "dictionary variant")}, // what does 80 mean?
0187                                            {"ise", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes", "dictionary variant")},
0188                                            {"ize", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes", "dictionary variant")},
0189                                            {"ise-w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes and with accents", "dictionary variant")},
0190                                            {"ise-wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes and without accents", "dictionary variant")},
0191                                            {"ize-w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes and with accents", "dictionary variant")},
0192                                            {"ize-wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes and without accents", "dictionary variant")},
0193                                            {"lrg", QT_TRANSLATE_NOOP3("Sonnet::Loader", "large", "dictionary variant")},
0194                                            {"med", QT_TRANSLATE_NOOP3("Sonnet::Loader", "medium", "dictionary variant")},
0195                                            {"sml", QT_TRANSLATE_NOOP3("Sonnet::Loader", "small", "dictionary variant")},
0196                                            {"variant_0", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 0", "dictionary variant")},
0197                                            {"variant_1", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 1", "dictionary variant")},
0198                                            {"variant_2", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 2", "dictionary variant")},
0199                                            {"wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "without accents", "dictionary variant")},
0200                                            {"w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with accents", "dictionary variant")},
0201                                            {"ye", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with ye, modern russian", "dictionary variant")},
0202                                            {"yeyo", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with yeyo, modern and old russian", "dictionary variant")},
0203                                            {"yo", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with yo, old russian", "dictionary variant")},
0204                                            {"extended", QT_TRANSLATE_NOOP3("Sonnet::Loader", "extended", "dictionary variant")},
0205                                            {nullptr, nullptr}};
0206 
0207     minusPos = currentDictionary.indexOf(QLatin1Char('-'));
0208     if (minusPos != -1) {
0209         variantName = currentDictionary.right(currentDictionary.length() - minusPos - 1);
0210         while (variantList[variantCount].variantShortName != nullptr) {
0211             if (QLatin1String(variantList[variantCount].variantShortName) == variantName) {
0212                 break;
0213             } else {
0214                 variantCount++;
0215             }
0216         }
0217         if (variantList[variantCount].variantShortName != nullptr) {
0218             variantEnglish = variantList[variantCount].variantEnglishName;
0219         } else {
0220             variantEnglish = variantName.toLatin1();
0221         }
0222 
0223         localizedVariant = tr(variantEnglish.constData(), "dictionary variant");
0224         isoCode = currentDictionary.left(minusPos);
0225     } else {
0226         isoCode = currentDictionary;
0227     }
0228 
0229     QLocale locale(isoCode);
0230     localizedCountry = locale.nativeTerritoryName();
0231     localizedLang = locale.nativeLanguageName();
0232 
0233     if (localizedLang.isEmpty() && localizedCountry.isEmpty()) {
0234         return isoCode; // We have nothing
0235     }
0236 
0237     if (!localizedCountry.isEmpty() && !localizedVariant.isEmpty()) { // We have both a country name and a variant
0238         return tr("%1 (%2) [%3]", "dictionary name; %1 = language name, %2 = country name and %3 = language variant name")
0239             .arg(localizedLang, localizedCountry, localizedVariant);
0240     } else if (!localizedCountry.isEmpty()) { // We have a country name
0241         return tr("%1 (%2)", "dictionary name; %1 = language name, %2 = country name").arg(localizedLang, localizedCountry);
0242     } else { // We only have a language name
0243         return localizedLang;
0244     }
0245 }
0246 
0247 QStringList Loader::languageNames() const
0248 {
0249     /* For whatever reason languages() might change. So,
0250      * to be in sync with it let's do the following check.
0251      */
0252     if (d->languagesNameCache.count() == languages().count()) {
0253         return d->languagesNameCache;
0254     }
0255 
0256     QStringList allLocalizedDictionaries;
0257     for (const QString &langCode : languages()) {
0258         allLocalizedDictionaries.append(languageNameForCode(langCode));
0259     }
0260     // cache the list
0261     d->languagesNameCache = allLocalizedDictionaries;
0262     return allLocalizedDictionaries;
0263 }
0264 
0265 SettingsImpl *Loader::settings() const
0266 {
0267     return d->settings;
0268 }
0269 
0270 void Loader::loadPlugins()
0271 {
0272 #ifndef SONNET_STATIC
0273     const QStringList libPaths = QCoreApplication::libraryPaths() << QStringLiteral(INSTALLATION_PLUGIN_PATH);
0274     const QString pathSuffix(QStringLiteral("/kf6/sonnet/"));
0275     for (const QString &libPath : libPaths) {
0276         QDir dir(libPath + pathSuffix);
0277         if (!dir.exists()) {
0278             continue;
0279         }
0280         for (const QString &fileName : dir.entryList(QDir::Files)) {
0281             loadPlugin(dir.absoluteFilePath(fileName));
0282         }
0283     }
0284 
0285     if (d->loadedPlugins.isEmpty()) {
0286         qCWarning(SONNET_LOG_CORE) << "Sonnet: No speller backends available!";
0287     }
0288 #else
0289 #ifdef Q_OS_MACOS
0290     loadPlugin(QString());
0291 #endif
0292     loadPlugin(QStringLiteral("Hunspell"));
0293 #endif
0294 }
0295 
0296 void Loader::loadPlugin(const QString &pluginPath)
0297 {
0298 #ifndef SONNET_STATIC
0299     QPluginLoader plugin(pluginPath);
0300     const QString pluginIID = plugin.metaData()[QStringLiteral("IID")].toString();
0301     if (!pluginIID.isEmpty()) {
0302         if (d->loadedPlugins.contains(pluginIID)) {
0303             qCDebug(SONNET_LOG_CORE) << "Skipping already loaded" << pluginPath;
0304             return;
0305         }
0306         d->loadedPlugins.insert(pluginIID);
0307     }
0308 
0309     if (!plugin.load()) { // We do this separately for better error handling
0310         qCDebug(SONNET_LOG_CORE) << "Sonnet: Unable to load plugin" << pluginPath << "Error:" << plugin.errorString();
0311         d->loadedPlugins.remove(pluginIID);
0312         return;
0313     }
0314 
0315     Client *client = qobject_cast<Client *>(plugin.instance());
0316     if (!client) {
0317         qCWarning(SONNET_LOG_CORE) << "Sonnet: Invalid plugin loaded" << pluginPath;
0318         plugin.unload(); // don't leave it in memory
0319         return;
0320     }
0321 #else
0322     Client *client = nullptr;
0323     if (pluginPath == QLatin1String("Hunspell")) {
0324         client = new HunspellClient(this);
0325     }
0326 #ifdef Q_OS_MACOS
0327     else {
0328         client = new NSSpellCheckerClient(this);
0329     }
0330 #endif
0331 #endif
0332 
0333     const QStringList languages = client->languages();
0334     d->clients.append(client->name());
0335 
0336     for (const QString &language : languages) {
0337         QList<Client *> &languageClients = d->languageClients[language];
0338 
0339         if (languageClients.isEmpty() //
0340             || client->reliability() < languageClients.first()->reliability()) {
0341             languageClients.append(client); // less reliable, to the end
0342         } else {
0343             languageClients.prepend(client); // more reliable, to the front
0344         }
0345     }
0346 }
0347 
0348 void Loader::changed()
0349 {
0350     Q_EMIT configurationChanged();
0351 }
0352 }
0353 
0354 #include "moc_loader_p.cpp"