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"