File indexing completed on 2024-05-05 17:34:05

0001 /*
0002  * SPDX-License-Identifier: GPL-2.0-or-later
0003  * SPDX-FileCopyrightText: 2010, 2012 Jason A. Donenfeld <Jason@zx2c4.com>
0004  */
0005 
0006 #include "dictionaryrunner.h"
0007 
0008 #include <KConfigGroup>
0009 #include <KLocalizedString>
0010 #include <KNotifications/KNotification>
0011 #include <QClipboard>
0012 #include <QEventLoop>
0013 #include <QGuiApplication>
0014 #include <QIcon>
0015 #include <QMutex>
0016 #include <QMutexLocker>
0017 #include <QStringList>
0018 #include <QTimer>
0019 
0020 namespace
0021 {
0022 const char CONFIG_TRIGGERWORD[] = "triggerWord";
0023 QMutex s_initMutex;
0024 }
0025 
0026 DictionaryRunner::DictionaryRunner(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
0027     : AbstractRunner(parent, metaData, args)
0028 {
0029     setPriority(LowPriority);
0030     setObjectName(QLatin1String("Dictionary"));
0031 }
0032 
0033 void DictionaryRunner::reloadConfiguration()
0034 {
0035     KConfigGroup c = config();
0036     m_triggerWord = c.readEntry(CONFIG_TRIGGERWORD, i18nc("Trigger word before word to define", "define"));
0037     if (!m_triggerWord.isEmpty()) {
0038         m_triggerWord.append(QLatin1Char(' '));
0039         setTriggerWords({m_triggerWord});
0040     } else {
0041         setMatchRegex(QRegularExpression());
0042     }
0043     setSyntaxes({RunnerSyntax(i18nc("Dictionary keyword", "%1:q:", m_triggerWord), i18n("Finds the definition of :q:."))});
0044 }
0045 
0046 void DictionaryRunner::match(RunnerContext &context)
0047 {
0048     QString query = context.query();
0049     if (query.startsWith(m_triggerWord, Qt::CaseInsensitive)) {
0050         query.remove(0, m_triggerWord.length());
0051     }
0052     if (query.isEmpty()) {
0053         return;
0054     }
0055 
0056     {
0057         QEventLoop loop;
0058         QTimer::singleShot(400, &loop, [&loop]() {
0059             loop.quit();
0060         });
0061         loop.exec();
0062     }
0063     if (!context.isValid()) {
0064         return;
0065     }
0066     QString returnedQuery;
0067     QMetaObject::invokeMethod(&m_dictEngine, "requestDefinition", Qt::QueuedConnection, Q_ARG(const QString &, query));
0068     QEventLoop loop;
0069     connect(&m_dictEngine, &DictEngine::definitionRecieved, &loop, [&loop, &query, &returnedQuery, &context](const QString &html) {
0070         returnedQuery = html;
0071         loop.quit();
0072     });
0073     loop.exec();
0074     if (!context.isValid() || returnedQuery.isEmpty()) {
0075         return;
0076     }
0077 
0078     static const QRegularExpression removeHtml(QLatin1String("<[^>]*>"));
0079     QString definitions(returnedQuery);
0080     definitions.remove(QLatin1Char('\r')).remove(removeHtml);
0081     while (definitions.contains(QLatin1String("  "))) {
0082         definitions.replace(QLatin1String("  "), QLatin1String(" "));
0083     }
0084     QStringList lines = definitions.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0085     if (lines.length() < 2) {
0086         return;
0087     }
0088     lines.removeFirst();
0089 
0090     QList<QueryMatch> matches;
0091     int item = 0;
0092     static const QRegularExpression partOfSpeech(QLatin1String("(?: ([a-z]{1,5})){0,1} [0-9]{1,2}: (.*)"));
0093     QString lastPartOfSpeech;
0094     for (const QString &line : std::as_const(lines)) {
0095         const auto reMatch = partOfSpeech.match(line);
0096         if (!reMatch.hasMatch()) {
0097             continue;
0098         }
0099         if (!reMatch.capturedView(1).isEmpty()) {
0100             lastPartOfSpeech = reMatch.captured(1);
0101         }
0102         QueryMatch match(this);
0103         match.setMultiLine(true);
0104         match.setText(lastPartOfSpeech + QLatin1String(": ") + reMatch.captured(2));
0105         match.setRelevance(1 - (static_cast<double>(++item) / static_cast<double>(lines.length())));
0106         match.setType(QueryMatch::HelperMatch);
0107         match.setIconName(QStringLiteral("accessories-dictionary"));
0108         matches.append(match);
0109     }
0110     context.addMatches(matches);
0111 }
0112 
0113 void DictionaryRunner::run(const RunnerContext &context, const QueryMatch &match)
0114 {
0115     QString query = context.query();
0116     if (query.startsWith(m_triggerWord, Qt::CaseInsensitive)) {
0117         query.remove(0, m_triggerWord.length());
0118     }
0119     QGuiApplication::clipboard()->setText(query + QLatin1Char(' ') + match.text());
0120     KNotification::event(KNotification::Notification, name(), i18n("Definition for \"%1\" has been copied to clipboard", query), icon().name());
0121 }
0122 
0123 K_PLUGIN_CLASS_WITH_JSON(DictionaryRunner, "plasma-runner-dictionary.json")
0124 
0125 #include "dictionaryrunner.moc"