File indexing completed on 2024-05-19 09:22:09

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 <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)
0027     : AbstractRunner(parent, metaData)
0028 {
0029 }
0030 
0031 void DictionaryRunner::reloadConfiguration()
0032 {
0033     KConfigGroup c = config();
0034     m_triggerWord = c.readEntry(CONFIG_TRIGGERWORD, i18nc("Trigger word before word to define", "define"));
0035     if (!m_triggerWord.isEmpty()) {
0036         m_triggerWord.append(QLatin1Char(' '));
0037         setTriggerWords({m_triggerWord});
0038     } else {
0039         setMatchRegex(QRegularExpression());
0040     }
0041     setSyntaxes({RunnerSyntax(i18nc("Dictionary keyword", "%1:q:", m_triggerWord), i18n("Finds the definition of :q:."))});
0042 }
0043 
0044 void DictionaryRunner::match(RunnerContext &context)
0045 {
0046     QString query = context.query();
0047     if (query.startsWith(m_triggerWord, Qt::CaseInsensitive)) {
0048         query.remove(0, m_triggerWord.length());
0049     }
0050     if (query.isEmpty()) {
0051         return;
0052     }
0053 
0054     {
0055         QEventLoop loop;
0056         QTimer::singleShot(400, &loop, [&loop]() {
0057             loop.quit();
0058         });
0059         loop.exec();
0060     }
0061     if (!context.isValid()) {
0062         return;
0063     }
0064     QString returnedQuery;
0065     QMetaObject::invokeMethod(&m_dictEngine, "requestDefinition", Qt::QueuedConnection, Q_ARG(const QString &, query));
0066     QEventLoop loop;
0067     connect(&m_dictEngine, &DictEngine::definitionRecieved, &loop, [&loop, &query, &returnedQuery, &context](const QString &html) {
0068         returnedQuery = html;
0069         loop.quit();
0070     });
0071     loop.exec();
0072     if (!context.isValid() || returnedQuery.isEmpty()) {
0073         return;
0074     }
0075 
0076     static const QRegularExpression removeHtml(QLatin1String("<[^>]*>"));
0077     QString definitions(returnedQuery);
0078     definitions.remove(QLatin1Char('\r')).remove(removeHtml);
0079     while (definitions.contains(QLatin1String("  "))) {
0080         definitions.replace(QLatin1String("  "), QLatin1String(" "));
0081     }
0082     QStringList lines = definitions.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0083     if (lines.length() < 2) {
0084         return;
0085     }
0086     lines.removeFirst();
0087 
0088     QList<QueryMatch> matches;
0089     int item = 0;
0090     static const QRegularExpression partOfSpeech(QLatin1String("(?: ([a-z]{1,5})){0,1} [0-9]{1,2}: (.*)"));
0091     QString lastPartOfSpeech;
0092     for (const QString &line : std::as_const(lines)) {
0093         const auto reMatch = partOfSpeech.match(line);
0094         if (!reMatch.hasMatch()) {
0095             continue;
0096         }
0097         if (!reMatch.capturedView(1).isEmpty()) {
0098             lastPartOfSpeech = reMatch.captured(1);
0099         }
0100         QueryMatch match(this);
0101         match.setMultiLine(true);
0102         match.setText(lastPartOfSpeech + QLatin1String(": ") + reMatch.captured(2));
0103         match.setRelevance(1 - (static_cast<double>(++item) / static_cast<double>(lines.length())));
0104         match.setCategoryRelevance(QueryMatch::CategoryRelevance::Moderate);
0105         match.setIconName(QStringLiteral("accessories-dictionary"));
0106         matches.append(match);
0107     }
0108     context.addMatches(matches);
0109 }
0110 
0111 void DictionaryRunner::run(const RunnerContext &context, const QueryMatch &match)
0112 {
0113     QString query = context.query();
0114     if (query.startsWith(m_triggerWord, Qt::CaseInsensitive)) {
0115         query.remove(0, m_triggerWord.length());
0116     }
0117     QGuiApplication::clipboard()->setText(query + QLatin1Char(' ') + match.text());
0118     KNotification::event(KNotification::Notification, name(), i18n("Definition for \"%1\" has been copied to clipboard", query), metadata().iconName());
0119 }
0120 
0121 K_PLUGIN_CLASS_WITH_JSON(DictionaryRunner, "plasma-runner-dictionary.json")
0122 
0123 #include "dictionaryrunner.moc"