Sonnet

loader.cpp
1/*
2 * SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
3 * SPDX-FileCopyrightText: 2012 Martin Sandsmark <martin.sandsmark@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7#include "client_p.h"
8#include "loader_p.h"
9#include "settingsimpl_p.h"
10#include "spellerplugin_p.h"
11
12#include "core_debug.h"
13
14#include <QCoreApplication>
15#include <QDir>
16#include <QHash>
17#include <QList>
18#include <QLocale>
19#include <QMap>
20#include <QPluginLoader>
21
22#include <algorithm>
23
24namespace Sonnet
25{
26class LoaderPrivate
27{
28public:
29 SettingsImpl *settings;
30
31 // <language, Clients with that language >
32 QMap<QString, QList<Client *>> languageClients;
33 QStringList clients;
34
35 QSet<QString> loadedPlugins;
36
37 QStringList languagesNameCache;
38 QHash<QString, QSharedPointer<SpellerPlugin>> spellerCache;
39};
40
41Q_GLOBAL_STATIC(Loader, s_loader)
42
43Loader *Loader::openLoader()
44{
45 if (s_loader.isDestroyed()) {
46 return nullptr;
47 }
48
49 return s_loader();
50}
51
52Loader::Loader()
53 : d(new LoaderPrivate)
54{
55 d->settings = new SettingsImpl(this);
56 d->settings->restore();
57 loadPlugins();
58}
59
60Loader::~Loader()
61{
62 qCDebug(SONNET_LOG_CORE) << "Removing loader: " << this;
63 delete d->settings;
64 d->settings = nullptr;
65}
66
67SpellerPlugin *Loader::createSpeller(const QString &language, const QString &clientName) const
68{
69 QString backend = clientName;
70 QString plang = language;
71
72 if (plang.isEmpty()) {
73 plang = d->settings->defaultLanguage();
74 }
75
76 auto clientsItr = d->languageClients.constFind(plang);
77 if (clientsItr == d->languageClients.constEnd()) {
78 if (language.isEmpty() || language == QStringLiteral("C")) {
79 qCDebug(SONNET_LOG_CORE) << "No language dictionaries for the language:" << plang << "trying to load en_US as default";
80 return createSpeller(QStringLiteral("en_US"), clientName);
81 }
82 qCDebug(SONNET_LOG_CORE) << "No language dictionaries for the language:" << plang;
83 Q_EMIT loadingDictionaryFailed(plang);
84 return nullptr;
85 }
86
87 const QList<Client *> lClients = *clientsItr;
88
89 if (backend.isEmpty()) {
90 backend = d->settings->defaultClient();
91 if (!backend.isEmpty()) {
92 // check if the default client supports the requested language;
93 // if it does it will be an element of lClients.
94 bool unknown = !std::any_of(lClients.constBegin(), lClients.constEnd(), [backend](const Client *client) {
95 return client->name() == backend;
96 });
97 if (unknown) {
98 qCWarning(SONNET_LOG_CORE) << "Default client" << backend << "doesn't support language:" << plang;
99 backend = QString();
100 }
101 }
102 }
103
104 QListIterator<Client *> itr(lClients);
105 while (itr.hasNext()) {
106 Client *item = itr.next();
107 if (!backend.isEmpty()) {
108 if (backend == item->name()) {
109 SpellerPlugin *dict = item->createSpeller(plang);
110 qCDebug(SONNET_LOG_CORE) << "Using the" << item->name() << "plugin for language" << plang;
111 return dict;
112 }
113 } else {
114 // the first one is the one with the highest
115 // reliability
116 SpellerPlugin *dict = item->createSpeller(plang);
117 qCDebug(SONNET_LOG_CORE) << "Using the" << item->name() << "plugin for language" << plang;
118 return dict;
119 }
120 }
121
122 qCWarning(SONNET_LOG_CORE) << "The default client" << backend << "has no language dictionaries for the language:" << plang;
123 return nullptr;
124}
125
126QSharedPointer<SpellerPlugin> Loader::cachedSpeller(const QString &language)
127{
128 auto &speller = d->spellerCache[language];
129 if (!speller) {
130 speller.reset(createSpeller(language));
131 }
132 return speller;
133}
134
135void Loader::clearSpellerCache()
136{
137 d->spellerCache.clear();
138}
139
140QStringList Loader::clients() const
141{
142 return d->clients;
143}
144
145QStringList Loader::languages() const
146{
147 return d->languageClients.keys();
148}
149
150QString Loader::languageNameForCode(const QString &langCode) const
151{
152 QString currentDictionary = langCode; // e.g. en_GB-ize-wo_accents
153 QString isoCode; // locale ISO name
154 QString variantName; // dictionary variant name e.g. w_accents
155 QString localizedLang; // localized language
156 QString localizedCountry; // localized country
157 QString localizedVariant;
158 QByteArray variantEnglish; // dictionary variant in English
159
160 int minusPos; // position of "-" char
161 int variantCount = 0; // used to iterate over variantList
162
163 struct variantListType {
164 const char *variantShortName;
165 const char *variantEnglishName;
166 };
167
168 /*
169 * This redefines the QT_TRANSLATE_NOOP3 macro provided by Qt to indicate that
170 * statically initialised text should be translated so that it expands to just
171 * the string that should be translated, making it possible to use it in the
172 * single string construct below.
173 */
174#undef QT_TRANSLATE_NOOP3
175#define QT_TRANSLATE_NOOP3(a, b, c) b
176
177 const variantListType variantList[] = {{"40", QT_TRANSLATE_NOOP3("Sonnet::Loader", "40", "dictionary variant")}, // what does 40 mean?
178 {"60", QT_TRANSLATE_NOOP3("Sonnet::Loader", "60", "dictionary variant")}, // what does 60 mean?
179 {"80", QT_TRANSLATE_NOOP3("Sonnet::Loader", "80", "dictionary variant")}, // what does 80 mean?
180 {"ise", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes", "dictionary variant")},
181 {"ize", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes", "dictionary variant")},
182 {"ise-w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes and with accents", "dictionary variant")},
183 {"ise-wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ise suffixes and without accents", "dictionary variant")},
184 {"ize-w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes and with accents", "dictionary variant")},
185 {"ize-wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "-ize suffixes and without accents", "dictionary variant")},
186 {"lrg", QT_TRANSLATE_NOOP3("Sonnet::Loader", "large", "dictionary variant")},
187 {"med", QT_TRANSLATE_NOOP3("Sonnet::Loader", "medium", "dictionary variant")},
188 {"sml", QT_TRANSLATE_NOOP3("Sonnet::Loader", "small", "dictionary variant")},
189 {"variant_0", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 0", "dictionary variant")},
190 {"variant_1", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 1", "dictionary variant")},
191 {"variant_2", QT_TRANSLATE_NOOP3("Sonnet::Loader", "variant 2", "dictionary variant")},
192 {"wo_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "without accents", "dictionary variant")},
193 {"w_accents", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with accents", "dictionary variant")},
194 {"ye", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with ye, modern russian", "dictionary variant")},
195 {"yeyo", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with yeyo, modern and old russian", "dictionary variant")},
196 {"yo", QT_TRANSLATE_NOOP3("Sonnet::Loader", "with yo, old russian", "dictionary variant")},
197 {"extended", QT_TRANSLATE_NOOP3("Sonnet::Loader", "extended", "dictionary variant")},
198 {nullptr, nullptr}};
199
200 minusPos = currentDictionary.indexOf(QLatin1Char('-'));
201 if (minusPos != -1) {
202 variantName = currentDictionary.right(currentDictionary.length() - minusPos - 1);
203 while (variantList[variantCount].variantShortName != nullptr) {
204 if (QLatin1String(variantList[variantCount].variantShortName) == variantName) {
205 break;
206 } else {
207 variantCount++;
208 }
209 }
210 if (variantList[variantCount].variantShortName != nullptr) {
211 variantEnglish = variantList[variantCount].variantEnglishName;
212 } else {
213 variantEnglish = variantName.toLatin1();
214 }
215
216 localizedVariant = tr(variantEnglish.constData(), "dictionary variant");
217 isoCode = currentDictionary.left(minusPos);
218 } else {
219 isoCode = currentDictionary;
220 }
221
222 QLocale locale(isoCode);
223 localizedCountry = locale.nativeTerritoryName();
224 localizedLang = locale.nativeLanguageName();
225
226 if (localizedLang.isEmpty() && localizedCountry.isEmpty()) {
227 return isoCode; // We have nothing
228 }
229
230 if (!localizedCountry.isEmpty() && !localizedVariant.isEmpty()) { // We have both a country name and a variant
231 return tr("%1 (%2) [%3]", "dictionary name; %1 = language name, %2 = country name and %3 = language variant name")
232 .arg(localizedLang, localizedCountry, localizedVariant);
233 } else if (!localizedCountry.isEmpty()) { // We have a country name
234 return tr("%1 (%2)", "dictionary name; %1 = language name, %2 = country name").arg(localizedLang, localizedCountry);
235 } else { // We only have a language name
236 return localizedLang;
237 }
238}
239
240QStringList Loader::languageNames() const
241{
242 /* For whatever reason languages() might change. So,
243 * to be in sync with it let's do the following check.
244 */
245 if (d->languagesNameCache.count() == languages().count()) {
246 return d->languagesNameCache;
247 }
248
249 QStringList allLocalizedDictionaries;
250 for (const QString &langCode : languages()) {
251 allLocalizedDictionaries.append(languageNameForCode(langCode));
252 }
253 // cache the list
254 d->languagesNameCache = allLocalizedDictionaries;
255 return allLocalizedDictionaries;
256}
257
258SettingsImpl *Loader::settings() const
259{
260 return d->settings;
261}
262
263void Loader::loadPlugins()
264{
265#ifndef SONNET_STATIC
266 const QStringList libPaths = QCoreApplication::libraryPaths() << QStringLiteral(INSTALLATION_PLUGIN_PATH);
267 const QString pathSuffix(QStringLiteral("/kf6/sonnet/"));
268 for (const QString &libPath : libPaths) {
269 QDir dir(libPath + pathSuffix);
270 if (!dir.exists()) {
271 continue;
272 }
273 for (const QString &fileName : dir.entryList(QDir::Files)) {
274 loadPlugin(dir.absoluteFilePath(fileName));
275 }
276 }
277
278 if (d->loadedPlugins.isEmpty()) {
279 qCWarning(SONNET_LOG_CORE) << "Sonnet: No speller backends available!";
280 }
281#else
282 for (auto plugin : QPluginLoader::staticPlugins()) {
283 if (plugin.metaData()[QLatin1String("IID")].toString() == QLatin1String("org.kde.sonnet.Client")) {
284 loadPlugin(plugin);
285 }
286 }
287#endif
288}
289
290void Loader::loadPlugin(const QStaticPlugin &plugin)
291{
292 Client *client = qobject_cast<Client *>(plugin.instance());
293 if (!client) {
294 qCWarning(SONNET_LOG_CORE) << "Sonnet: Invalid static plugin loaded" << plugin.metaData();
295 return;
296 }
297
298 addClient(client);
299}
300
301void Loader::loadPlugin(const QString &pluginPath)
302{
303 QPluginLoader plugin(pluginPath);
304 const QString pluginId = QFileInfo(pluginPath).completeBaseName();
305 if (!pluginId.isEmpty()) {
306 if (d->loadedPlugins.contains(pluginId)) {
307 qCDebug(SONNET_LOG_CORE) << "Skipping already loaded" << pluginPath;
308 return;
309 }
310 }
311 d->loadedPlugins.insert(pluginId);
312
313 if (!plugin.load()) { // We do this separately for better error handling
314 qCDebug(SONNET_LOG_CORE) << "Sonnet: Unable to load plugin" << pluginPath << "Error:" << plugin.errorString();
315 d->loadedPlugins.remove(pluginId);
316 return;
317 }
318
319 Client *client = qobject_cast<Client *>(plugin.instance());
320
321 if (!client) {
322 qCWarning(SONNET_LOG_CORE) << "Sonnet: Invalid plugin loaded" << pluginPath;
323 plugin.unload(); // don't leave it in memory
324 return;
325 }
326
327 addClient(client);
328}
329
330void Loader::addClient(Client *client)
331{
332 const QStringList languages = client->languages();
333 d->clients.append(client->name());
334
335 for (const QString &language : languages) {
336 QList<Client *> &languageClients = d->languageClients[language];
337
338 if (languageClients.isEmpty() //
339 || client->reliability() < languageClients.first()->reliability()) {
340 languageClients.append(client); // less reliable, to the end
341 } else {
342 languageClients.prepend(client); // more reliable, to the front
343 }
344 }
345}
346
347void Loader::changed()
348{
349 Q_EMIT configurationChanged();
350}
351}
352
353#include "moc_loader_p.cpp"
char * toString(const EngineQuery &query)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KEDUVOCDOCUMENT_EXPORT QStringList languages()
The sonnet namespace.
const char * constData() const const
QStringList libraryPaths()
QString completeBaseName() const const
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
T & first()
bool isEmpty() const const
void prepend(parameter_type value)
QList< QStaticPlugin > staticPlugins()
QJsonObject metaData() const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString right(qsizetype n) const const
QByteArray toLatin1() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:56:20 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.