File indexing completed on 2024-04-28 15:34:18

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