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