1/*
2 SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2020-2023 Alexander Lohnau <alexander.lohnau@gmx.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "abstractrunner.h"
9#include "abstractrunner_p.h"
10
11#include <QHash>
12#include <QIcon>
13#include <QMimeData>
14#include <QRegularExpression>
15#include <QTimer>
16
17#include <KConfigGroup>
18#include <KLocalizedString>
19#include <KSharedConfig>
20
21namespace KRunner
22{
23AbstractRunner::AbstractRunner(QObject *parent, const KPluginMetaData &pluginMetaData)
24 : QObject(nullptr)
25 , d(new AbstractRunnerPrivate(this, pluginMetaData))
26{
27 // By now, runners who do "qobject_cast<Krunner::RunnerManager*>(parent)" should have saved the value
28 // By setting the parent to a nullptr, we are allowed to move the object to another thread
29 Q_ASSERT(parent);
30 setObjectName(pluginMetaData.pluginId()); // Only for debugging purposes
31
32 // Suspend matching while we initialize the runner. Once it is ready, the last query will be run
33 QTimer::singleShot(interval: 0, receiver: this, slot: [this]() {
34 init();
35 // In case the runner didn't specify anything explicitly, we resume matching after the initialization
36 bool doesNotHaveExplicitSuspend = true;
37 {
38 QReadLocker l(&d->lock);
39 doesNotHaveExplicitSuspend = !d->suspendMatching.has_value();
40 }
41 if (doesNotHaveExplicitSuspend) {
42 suspendMatching(suspend: false);
43 }
44 });
45}
46
47AbstractRunner::~AbstractRunner() = default;
48
49KConfigGroup AbstractRunner::config() const
50{
51 KConfigGroup runners(KSharedConfig::openConfig(QStringLiteral("krunnerrc")), QStringLiteral("Runners"));
52 return runners.group(group: id());
53}
54
55void AbstractRunner::reloadConfiguration()
56{
57}
58
59void AbstractRunner::addSyntax(const RunnerSyntax &syntax)
60{
61 d->syntaxes.append(t: syntax);
62}
63
64void AbstractRunner::setSyntaxes(const QList<RunnerSyntax> &syntaxes)
65{
66 d->syntaxes = syntaxes;
67}
68
69QList<RunnerSyntax> AbstractRunner::syntaxes() const
70{
71 return d->syntaxes;
72}
73
74QMimeData *AbstractRunner::mimeDataForMatch(const QueryMatch &match)
75{
76 if (match.urls().isEmpty()) {
77 return nullptr;
78 }
79 QMimeData *result = new QMimeData();
80 result->setUrls(match.urls());
81 return result;
82}
83
84void AbstractRunner::run(const KRunner::RunnerContext & /*search*/, const KRunner::QueryMatch & /*action*/)
85{
86}
87
88QString AbstractRunner::name() const
89{
90 return d->translatedName;
91}
92
93QString AbstractRunner::id() const
94{
95 return d->runnerDescription.pluginId();
96}
97
98KPluginMetaData AbstractRunner::metadata() const
99{
100 return d->runnerDescription;
101}
102
103void AbstractRunner::init()
104{
105 reloadConfiguration();
106}
107
108bool AbstractRunner::isMatchingSuspended() const
109{
110 QReadLocker lock(&d->lock);
111 return d->suspendMatching.value_or(u: true);
112}
113
114void AbstractRunner::suspendMatching(bool suspend)
115{
116 QWriteLocker lock(&d->lock);
117 if (d->suspendMatching.has_value() && d->suspendMatching.value() == suspend) {
118 return;
119 }
120
121 d->suspendMatching = suspend;
122 if (!suspend) {
123 Q_EMIT matchingResumed();
124 }
125}
126
127int AbstractRunner::minLetterCount() const
128{
129 return d->minLetterCount;
130}
131
132void AbstractRunner::setMinLetterCount(int count)
133{
134 d->minLetterCount = count;
135}
136
137QRegularExpression AbstractRunner::matchRegex() const
138{
139 return d->matchRegex;
140}
141
142void AbstractRunner::setMatchRegex(const QRegularExpression &regex)
143{
144 d->matchRegex = regex;
145 d->hasMatchRegex = regex.isValid() && !regex.pattern().isEmpty();
146}
147
148void AbstractRunner::setTriggerWords(const QStringList &triggerWords)
149{
150 int minTriggerWordLetters = 0;
151 QString constructedRegex = QStringLiteral("^");
152 for (const QString &triggerWord : triggerWords) {
153 // We want to link them with an or
154 if (constructedRegex.length() > 1) {
155 constructedRegex += QLatin1Char('|');
156 }
157 constructedRegex += QRegularExpression::escape(str: triggerWord);
158 if (minTriggerWordLetters == 0 || triggerWord.length() < minTriggerWordLetters) {
159 minTriggerWordLetters = triggerWord.length();
160 }
161 }
162 // If we can reject the query because of the length we don't need the regex
163 setMinLetterCount(minTriggerWordLetters);
164 setMatchRegex(QRegularExpression(constructedRegex));
165}
166
167bool AbstractRunner::hasMatchRegex() const
168{
169 return d->hasMatchRegex;
170}
171
172void AbstractRunner::matchInternal(KRunner::RunnerContext context)
173{
174 if (context.isValid()) { // Otherwise, we would just waste resources
175 match(context);
176 }
177 Q_EMIT matchInternalFinished(jobId: context.runnerJobId(runner: this));
178}
179// Suspend the runner while reloading the config
180void AbstractRunner::reloadConfigurationInternal()
181{
182 bool isSuspended = isMatchingSuspended();
183 suspendMatching(suspend: true);
184 reloadConfiguration();
185 suspendMatching(suspend: isSuspended);
186}
187
188} // KRunner namespace
189
190#include "moc_abstractrunner.cpp"
191

source code of krunner/src/abstractrunner.cpp