Sonnet

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

KDE's Doxygen guidelines are available online.