File indexing completed on 2024-05-05 08:03:50
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"