Messagelib

filter.cpp
1/******************************************************************************
2 *
3 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 *
7 *******************************************************************************/
8
9#include "core/filter.h"
10#include "config-messagelist.h"
11#include "core/messageitem.h"
12#include <MessageCore/StringUtil>
13#include <TextUtils/ConvertText>
14
15#include <KRandom>
16#if !FORCE_DISABLE_AKONADI_SEARCH
17#include <PIM/emailquery.h>
18#include <PIM/resultiterator.h>
19#endif
20using namespace MessageList::Core;
21
22Filter::Filter(QObject *parent)
23 : QObject(parent)
24{
25 generateRandomIdentifier();
26}
27
28bool Filter::containString(const QString &searchInString) const
29{
30 bool found = false;
31 const QString searchInStringNormalize{TextUtils::ConvertText::normalize(searchInString)};
32 for (const QString &str : std::as_const(mSearchList)) {
33 if (searchInStringNormalize.contains(TextUtils::ConvertText::normalize(str), Qt::CaseInsensitive)) {
34 found = true;
35 } else {
36 found = false;
37 break;
38 }
39 }
40 return found;
41}
42
43const QString &Filter::iconName() const
44{
45 return mIconName;
46}
47
48void Filter::setIconName(const QString &newIconName)
49{
50 mIconName = newIconName;
51}
52
53void Filter::setOptions(SearchMessageByButtons::SearchOptions newOptions)
54{
55 mOptions = newOptions;
56}
57
58const QString &Filter::filterName() const
59{
60 return mFilterName;
61}
62
63void Filter::setFilterName(const QString &newFilterName)
64{
65 mFilterName = newFilterName;
66}
67
68void Filter::setIdentifier(const QString &newIdentifier)
69{
70 mIdentifier = newIdentifier;
71}
72
73bool Filter::match(const MessageItem *item) const
74{
75 if (!mStatus.isEmpty()) {
76 for (Akonadi::MessageStatus status : std::as_const(mStatus)) {
77 if (!(status & item->status())) {
78 return false;
79 }
80 }
81 }
82
83 if (!mSearchString.isEmpty()) {
84 if (mMatchingItemIds.contains(item->itemId())) {
85 return true;
86 }
87
88 bool searchMatches = false;
89 bool searchEveryWhere = (mOptions & SearchMessageByButtons::SearchEveryWhere);
90 if (containString(item->subject()) && ((mOptions & SearchMessageByButtons::SearchAgainstSubject) || searchEveryWhere)) {
91 searchMatches = true;
92 } else if (containString(item->sender()) && ((mOptions & SearchMessageByButtons::SearchAgainstFrom) || searchEveryWhere)) {
93 searchMatches = true;
94 } else if (containString(item->receiver()) && ((mOptions & SearchMessageByButtons::SearchAgainstTo) || searchEveryWhere)) {
95 searchMatches = true;
96 }
97 if (!searchMatches) {
98 return false;
99 }
100 }
101
102 if (!mTagId.isEmpty()) {
103 // mTagId is a Akonadi::Tag::url
104 const bool tagMatches = item->findTag(mTagId) != nullptr;
105 if (!tagMatches) {
106 return false;
107 }
108 }
109
110 return true;
111}
112
113QList<Akonadi::MessageStatus> Filter::status() const
114{
115 return mStatus;
116}
117
118void Filter::setStatus(const QList<Akonadi::MessageStatus> &lstStatus)
119{
120 mStatus = lstStatus;
121}
122
123bool Filter::isEmpty() const
124{
125 if (!mStatus.isEmpty()) {
126 return false;
127 }
128
129 if (!mSearchString.isEmpty()) {
130 return false;
131 }
132
133 if (!mTagId.isEmpty()) {
134 return false;
135 }
136
137 return true;
138}
139
140void Filter::clear()
141{
142 mStatus.clear();
143 mSearchString.clear();
144 mTagId.clear();
145 mMatchingItemIds.clear();
146 mSearchList.clear();
147}
148
149void Filter::setCurrentFolder(const Akonadi::Collection &folder)
150{
151 mCurrentFolder = folder;
152}
153
154const QString &Filter::searchString() const
155{
156 return mSearchString;
157}
158
159SearchMessageByButtons::SearchOptions Filter::currentOptions() const
160{
161 return mOptions;
162}
163
164void Filter::save(const KSharedConfig::Ptr &config, const QString &filtername, const QString &iconName, int numFilter)
165{
166 KConfigGroup grp(config, QStringLiteral("General"));
167 int numberFilter = (numFilter == -1) ? grp.readEntry("NumberFilter").toInt() : numFilter;
168 KConfigGroup newGroup(config, QStringLiteral("Filter_%1").arg(numberFilter++));
169 newGroup.writeEntry("name", filtername);
170 if (!iconName.isEmpty()) {
171 newGroup.writeEntry("iconName", iconName);
172 }
173 newGroup.writeEntry("searchString", mSearchString);
174 newGroup.writeEntry("searchOptions", static_cast<int>(mOptions));
175 newGroup.writeEntry("tagId", mTagId);
176 newGroup.writeEntry("identifier", mIdentifier);
177 QList<qint32> lst;
178 lst.reserve(mStatus.count());
179 for (const auto s : std::as_const(mStatus)) {
180 lst << s.toQInt32();
181 }
182 newGroup.writeEntry("status", lst);
183 newGroup.sync();
184 grp.writeEntry("NumberFilter", numberFilter);
185 grp.sync();
186 config->reparseConfiguration();
187}
188
189Filter *Filter::load(const KSharedConfig::Ptr &config, int filternumber)
190{
191 KConfigGroup grp(config, QStringLiteral("General"));
192 int numberFilter = grp.readEntry("NumberFilter").toInt();
193 if (filternumber < numberFilter) {
194 KConfigGroup newGroup(config, QStringLiteral("Filter_%1").arg(filternumber));
195 return loadFromConfigGroup(newGroup);
196 }
197 return nullptr;
198}
199
200Filter *Filter::loadFromConfigGroup(const KConfigGroup &newGroup)
201{
202 auto filter = new Filter();
203 filter->setSearchString(newGroup.readEntry("searchString"),
204 static_cast<SearchMessageByButtons::SearchOptions>(newGroup.readEntry("searchOptions").toInt()));
205 filter->setTagId(newGroup.readEntry("tagId"));
206 filter->setIdentifier(newGroup.readEntry("identifier"));
207 filter->setFilterName(newGroup.readEntry("name"));
208 filter->setIconName(newGroup.readEntry("iconName"));
209 const QList<qint32> lst = newGroup.readEntry("status", QList<qint32>());
210 QList<Akonadi::MessageStatus> messageStatusLst;
211 messageStatusLst.reserve(lst.count());
212 for (const auto s : std::as_const(lst)) {
215 messageStatusLst << status;
216 }
217 filter->setStatus(messageStatusLst);
218 filter->setOptions(static_cast<SearchMessageByButtons::SearchOptions>(newGroup.readEntry("searchOptions").toInt()));
219 return filter;
220}
221
222void Filter::setSearchString(const SearchLineCommand &command)
223{
224#if !FORCE_DISABLE_AKONADI_SEARCH
225 mMatchingItemIds.clear();
226 if (command.isEmpty()) {
227 return;
228 }
229 const QList<SearchLineCommand::SearchLineInfo> infos = command.searchLineInfo();
232 for (const auto &info : infos) {
233 switch (info.type) {
234 case SearchLineCommand::Literal: {
235 QString newStr = info.argument;
236 const QStringList searchListTmp = info.argument.split(QLatin1Char(' '), Qt::SkipEmptyParts);
237 bool needToSplitString = false;
238 for (const QString &text : searchListTmp) {
239 if (text.size() >= 3) {
240 mSearchList << text;
241 if (!newStr.isEmpty()) {
242 newStr += QLatin1Char(' ');
243 }
244 newStr += text;
245 }
246 }
247 needToSplitString = true;
248
249 mSearchString = newStr;
250 query.matches(newStr);
251 query.setSplitSearchMatchString(needToSplitString);
252 break;
253 }
254 case SearchLineCommand::Subject: {
255 mSearchString = info.argument;
256 query.subjectMatches(mSearchString);
257 break;
258 }
259 case SearchLineCommand::Body: {
260 mSearchString = info.argument;
261 query.bodyMatches(mSearchString);
262 break;
263 }
264 case SearchLineCommand::Unknown:
265 case SearchLineCommand::HasStateOrAttachment:
266 // Nothing
267 break;
268 case SearchLineCommand::Larger:
269 case SearchLineCommand::Smaller:
270 case SearchLineCommand::OlderThan:
271 case SearchLineCommand::NewerThan:
272 case SearchLineCommand::Date:
273 case SearchLineCommand::Size:
274 case SearchLineCommand::Category:
275 // TODO implement tag support
276 break;
277 case SearchLineCommand::HasAttachment: {
280 lstStatus.append(status);
281 break;
282 }
283 case SearchLineCommand::HasInvitation: {
286 lstStatus.append(status);
287 break;
288 }
289 case SearchLineCommand::IsImportant: {
291 status.setImportant(true);
292 lstStatus.append(status);
293 break;
294 }
295 case SearchLineCommand::IsRead: {
297 status.setRead(true);
298 lstStatus.append(status);
299 break;
300 }
301 case SearchLineCommand::IsUnRead: {
302 // TODO verify
304 status.setRead(false);
305 lstStatus.append(status);
306 break;
307 }
308 case SearchLineCommand::IsIgnored: {
310 status.setIgnored(true);
311 lstStatus.append(status);
312 break;
313 }
314 case SearchLineCommand::IsHam: {
316 status.setHam(true);
317 lstStatus.append(status);
318 break;
319 }
320 case SearchLineCommand::IsSpam: {
322 status.setSpam(true);
323 lstStatus.append(status);
324 break;
325 }
326 case SearchLineCommand::IsWatched: {
328 status.setWatched(true);
329 lstStatus.append(status);
330 break;
331 }
332 case SearchLineCommand::IsReplied: {
334 status.setReplied(true);
335 lstStatus.append(status);
336 break;
337 }
338 case SearchLineCommand::IsForwarded: {
340 status.setForwarded(true);
341 lstStatus.append(status);
342 break;
343 }
344 case SearchLineCommand::To:
345 mSearchString = info.argument;
346 query.addTo(info.argument);
347 break;
348 case SearchLineCommand::Bcc:
349 mSearchString = info.argument;
350 query.addBcc(info.argument);
351 break;
352 case SearchLineCommand::From:
353 mSearchString = info.argument;
354 query.addFrom(info.argument);
355 break;
356 case SearchLineCommand::Cc:
357 mSearchString = info.argument;
358 query.addCc(info.argument);
359 break;
360 }
361 }
362
363 setStatus(lstStatus);
364 // If the collection is virtual we're probably trying to filter the search collection, so we just search globally
365 if (mCurrentFolder.isValid() && !mCurrentFolder.isVirtual()) {
366 query.addCollection(mCurrentFolder.id());
367 }
368
370 while (it.next()) {
371 mMatchingItemIds << it.id();
372 }
373 Q_EMIT finished();
374#endif
375}
376
377void Filter::setSearchString(const QString &search, SearchMessageByButtons::SearchOptions options)
378{
379#if !FORCE_DISABLE_AKONADI_SEARCH
380 const QString trimStr = search.trimmed();
381 if ((mSearchString == trimStr) && (mOptions == options)) {
382 return;
383 }
384 mOptions = options;
385 mSearchString = trimStr;
386 mMatchingItemIds.clear();
387
388 if (mSearchString.isEmpty()) {
389 return;
390 }
391 bool needToSplitString = false;
392 QString newStr = mSearchString;
393 if (mSearchString.startsWith(QLatin1Char('"')) && mSearchString.startsWith(QLatin1Char('"'))) {
394 newStr.remove(0, 1);
395 newStr.remove(newStr.length() - 1, 1);
396 mSearchList = QStringList() << newStr;
397 } else {
398 const QStringList searchListTmp = mSearchString.split(QLatin1Char(' '), Qt::SkipEmptyParts);
399 mSearchList.clear();
400 newStr.clear();
401 for (const QString &text : searchListTmp) {
402 if (text.size() >= 3) {
403 mSearchList << text;
404 if (!newStr.isEmpty()) {
405 newStr += QLatin1Char(' ');
406 }
407 newStr += text;
408 }
409 }
410 needToSplitString = true;
411 }
412 if (!newStr.trimmed().isEmpty()) {
414 if (options & SearchMessageByButtons::SearchEveryWhere) {
415 query.matches(newStr);
416 query.setSplitSearchMatchString(needToSplitString);
417 } else if (options & SearchMessageByButtons::SearchAgainstSubject) {
418 query.subjectMatches(newStr);
419 } else if (options & SearchMessageByButtons::SearchAgainstBody) {
420 query.bodyMatches(newStr);
421 } else if (options & SearchMessageByButtons::SearchAgainstFrom) {
422 query.setFrom(newStr);
423 } else if (options & SearchMessageByButtons::SearchAgainstBcc) {
424 query.setBcc(QStringList() << newStr);
425 } else if (options & SearchMessageByButtons::SearchAgainstCc) {
426 query.setCc(QStringList() << newStr);
427 } else if (options & SearchMessageByButtons::SearchAgainstTo) {
428 query.setTo(QStringList() << newStr);
429 }
430
431 // If the collection is virtual we're probably trying to filter the search collection, so we just search globally
432 if (mCurrentFolder.isValid() && !mCurrentFolder.isVirtual()) {
433 query.addCollection(mCurrentFolder.id());
434 }
435
436 Akonadi::Search::PIM::ResultIterator it = query.exec();
437 while (it.next()) {
438 mMatchingItemIds << it.id();
439 }
440 }
441 Q_EMIT finished();
442#endif
443}
444
445const QString &Filter::tagId() const
446{
447 return mTagId;
448}
449
450void Filter::setTagId(const QString &tagId)
451{
452 mTagId = tagId;
453}
454
455void Filter::generateRandomIdentifier()
456{
457 mIdentifier = KRandom::randomString(16);
458}
459
460QString Filter::identifier() const
461{
462 return mIdentifier;
463}
464
465QDebug operator<<(QDebug d, const MessageList::Core::Filter &t)
466{
467 d << "filtername " << t.filterName();
468 d << "identifier " << t.identifier();
469 d << "search string " << t.searchString();
470 d << "search option " << t.currentOptions();
471 d << "status " << t.status();
472 return d;
473}
474
475#include "moc_filter.cpp"
void setHam(bool ham=true)
void fromQInt32(qint32 status)
void setRead(bool read=true)
void setHasInvitation(bool hasInvitation=true)
void setForwarded(bool forwarded=true)
void setSpam(bool spam=true)
void setIgnored(bool ignored=true)
void setReplied(bool replied=true)
void setHasAttachment(bool hasAttachment=true)
void setImportant(bool important=true)
void setWatched(bool watched=true)
QString readEntry(const char *key, const char *aDefault=nullptr) const
This class is responsible of matching messages that should be displayed in the View.
Definition filter.h:34
QList< Akonadi::MessageStatus > status() const
Returns the currently set status mask.
Definition filter.cpp:113
const QString & searchString() const
Returns the currently set search string.
Definition filter.cpp:154
const QString & receiver() const
Returns the receiver associated to this item.
Definition item.cpp:501
const Akonadi::MessageStatus & status() const
Returns the status associated to this Item.
Definition item.cpp:446
const QString & sender() const
Returns the sender associated to this item.
Definition item.cpp:486
const QString & subject() const
Returns the subject associated to this Item.
Definition item.cpp:531
The MessageItem class.
Definition messageitem.h:36
const Tag * findTag(const QString &szTagId) const
Returns Tag associated to this message that has the specified id or 0 if no such tag exists.
Q_SCRIPTABLE CaptureState status()
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KCOREADDONS_EXPORT QString randomString(int length)
The implementation independent part of the MessageList library.
Definition aggregation.h:22
void append(QList< T > &&value)
void clear()
qsizetype count() const const
void reserve(qsizetype size)
Q_EMITQ_EMIT
void clear()
bool isEmpty() const const
qsizetype length() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
int toInt(bool *ok, int base) const const
QString trimmed() const const
CaseInsensitive
SkipEmptyParts
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:07:25 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.