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
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
154QList<SearchLineCommand::SearchLineInfo> Filter::searchLineCommands() const
155{
156 const QString text = mSearchString;
158 if (mOptions & SearchMessageByButtons::SearchAgainstBody) {
159 SearchLineCommand::SearchLineInfo i;
160 i.type = SearchLineCommand::SearchLineType::Body;
161 i.argument = mSearchString;
162 if (i.isValid()) {
163 infos.append(std::move(i));
164 }
165 }
166 if (mOptions & SearchMessageByButtons::SearchAgainstSubject) {
167 SearchLineCommand::SearchLineInfo i;
168 i.type = SearchLineCommand::SearchLineType::Subject;
169 i.argument = mSearchString;
170 if (i.isValid()) {
171 infos.append(std::move(i));
172 }
173 }
174 if (mOptions & SearchMessageByButtons::SearchAgainstBcc) {
175 SearchLineCommand::SearchLineInfo i;
176 i.type = SearchLineCommand::SearchLineType::Bcc;
177 i.argument = mSearchString;
178 if (i.isValid()) {
179 infos.append(std::move(i));
180 }
181 }
182 if (mOptions & SearchMessageByButtons::SearchAgainstCc) {
183 SearchLineCommand::SearchLineInfo i;
184 i.type = SearchLineCommand::SearchLineType::Cc;
185 i.argument = mSearchString;
186 if (i.isValid()) {
187 infos.append(std::move(i));
188 }
189 }
190
192 if (status.hasAttachment()) {
193 SearchLineCommand::SearchLineInfo i;
194 i.type = SearchLineCommand::SearchLineType::HasAttachment;
195 if (i.isValid()) {
196 infos.append(std::move(i));
197 }
198 } else if (status.hasInvitation()) {
199 SearchLineCommand::SearchLineInfo i;
200 i.type = SearchLineCommand::SearchLineType::HasInvitation;
201 if (i.isValid()) {
202 infos.append(std::move(i));
203 }
204 } else if (status.isImportant()) {
205 SearchLineCommand::SearchLineInfo i;
206 i.type = SearchLineCommand::SearchLineType::IsImportant;
207 if (i.isValid()) {
208 infos.append(std::move(i));
209 }
210 } else if (status.isEncrypted()) {
211 SearchLineCommand::SearchLineInfo i;
212 i.type = SearchLineCommand::SearchLineType::IsEncrypted;
213 if (i.isValid()) {
214 infos.append(std::move(i));
215 }
216 } else if (status.isReplied()) {
217 SearchLineCommand::SearchLineInfo i;
218 i.type = SearchLineCommand::SearchLineType::IsReplied;
219 if (i.isValid()) {
220 infos.append(std::move(i));
221 }
222 } else if (status.isForwarded()) {
223 SearchLineCommand::SearchLineInfo i;
224 i.type = SearchLineCommand::SearchLineType::IsForwarded;
225 if (i.isValid()) {
226 infos.append(std::move(i));
227 }
228 } else if (status.isRead()) {
229 SearchLineCommand::SearchLineInfo i;
230 i.type = SearchLineCommand::SearchLineType::IsRead;
231 if (i.isValid()) {
232 infos.append(std::move(i));
233 }
234 } else if (status.isIgnored()) {
235 SearchLineCommand::SearchLineInfo i;
236 i.type = SearchLineCommand::SearchLineType::IsIgnored;
237 if (i.isValid()) {
238 infos.append(std::move(i));
239 }
240 } else if (status.isSpam()) {
241 SearchLineCommand::SearchLineInfo i;
242 i.type = SearchLineCommand::SearchLineType::IsSpam;
243 if (i.isValid()) {
244 infos.append(std::move(i));
245 }
246 } else if (status.isHam()) {
247 SearchLineCommand::SearchLineInfo i;
248 i.type = SearchLineCommand::SearchLineType::IsHam;
249 if (i.isValid()) {
250 infos.append(std::move(i));
251 }
252 } else if (status.isQueued()) {
253 SearchLineCommand::SearchLineInfo i;
254 i.type = SearchLineCommand::SearchLineType::IsQueued;
255 if (i.isValid()) {
256 infos.append(std::move(i));
257 }
258 } else if (status.isSent()) {
259 SearchLineCommand::SearchLineInfo i;
260 i.type = SearchLineCommand::SearchLineType::IsSent;
261 if (i.isValid()) {
262 infos.append(std::move(i));
263 }
264 }
265 }
266
267#if 0
268
269 if (mContainsOutboundMessages) {
270 mButtonGroup->button(SearchMessageByButtons::SearchAgainstTo)->setChecked(opts & SearchMessageByButtons::SearchAgainstTo);
271 } else {
272 mButtonGroup->button(SearchMessageByButtons::SearchAgainstTo)->setChecked(opts & SearchMessageByButtons::SearchAgainstFrom);
273 }
274#endif
275 SearchLineCommand command;
276 command.parseSearchLineCommand(text);
277 infos += command.searchLineInfo();
278 return infos;
279}
280
281const QString &Filter::searchString() const
282{
283 return mSearchString;
284}
285
286SearchMessageByButtons::SearchOptions Filter::currentOptions() const
287{
288 return mOptions;
289}
290
291void Filter::save(const KSharedConfig::Ptr &config, const QString &filtername, const QString &iconName, int numFilter)
292{
293 KConfigGroup grp(config, QStringLiteral("General"));
294 int numberFilter = (numFilter == -1) ? grp.readEntry("NumberFilter").toInt() : numFilter;
295 KConfigGroup newGroup(config, QStringLiteral("Filter_%1").arg(numberFilter++));
296 newGroup.writeEntry("name", filtername);
297 if (!iconName.isEmpty()) {
298 newGroup.writeEntry("iconName", iconName);
299 }
300 newGroup.writeEntry("searchString", mSearchString);
301 newGroup.writeEntry("searchOptions", static_cast<int>(mOptions));
302 newGroup.writeEntry("tagId", mTagId);
303 newGroup.writeEntry("identifier", mIdentifier);
304 QList<qint32> lst;
305 lst.reserve(mStatus.count());
306 for (const auto s : std::as_const(mStatus)) {
307 lst << s.toQInt32();
308 }
309 newGroup.writeEntry("status", lst);
310 newGroup.sync();
311 grp.writeEntry("NumberFilter", numberFilter);
312 grp.sync();
313 config->reparseConfiguration();
314}
315
316Filter *Filter::load(const KSharedConfig::Ptr &config, int filternumber)
317{
318 KConfigGroup grp(config, QStringLiteral("General"));
319 int numberFilter = grp.readEntry("NumberFilter").toInt();
320 if (filternumber < numberFilter) {
321 KConfigGroup newGroup(config, QStringLiteral("Filter_%1").arg(filternumber));
322 return loadFromConfigGroup(newGroup);
323 }
324 return nullptr;
325}
326
327Filter *Filter::loadFromConfigGroup(const KConfigGroup &newGroup)
328{
329 auto filter = new Filter();
330 filter->setSearchString(newGroup.readEntry("searchString"),
331 static_cast<SearchMessageByButtons::SearchOptions>(newGroup.readEntry("searchOptions").toInt()));
332 filter->setTagId(newGroup.readEntry("tagId"));
333 filter->setIdentifier(newGroup.readEntry("identifier"));
334 filter->setFilterName(newGroup.readEntry("name"));
335 filter->setIconName(newGroup.readEntry("iconName"));
336 const QList<qint32> lst = newGroup.readEntry("status", QList<qint32>());
337 QList<Akonadi::MessageStatus> messageStatusLst;
338 messageStatusLst.reserve(lst.count());
339 for (const auto s : std::as_const(lst)) {
342 messageStatusLst << status;
343 }
344 filter->setStatus(messageStatusLst);
345 filter->setOptions(static_cast<SearchMessageByButtons::SearchOptions>(newGroup.readEntry("searchOptions").toInt()));
346 return filter;
347}
348
349void Filter::setSearchString(const SearchLineCommand &command)
350{
351#if !FORCE_DISABLE_AKONADI_SEARCH
352 mMatchingItemIds.clear();
353 if (command.isEmpty()) {
354 return;
355 }
356 const QList<SearchLineCommand::SearchLineInfo> infos = command.searchLineInfo();
359 for (const auto &info : infos) {
360 switch (info.type) {
361 case SearchLineCommand::Literal: {
362 QString newStr;
363 const QStringList searchListTmp = info.argument.split(QLatin1Char(' '), Qt::SkipEmptyParts);
364 bool needToSplitString = false;
365 for (const QString &text : searchListTmp) {
366 if (text.size() >= 3) {
367 if (!newStr.isEmpty()) {
368 newStr += QLatin1Char(' ');
369 }
370 newStr += text;
371 }
372 }
373 needToSplitString = true;
374
375 mSearchString = newStr;
376 query.matches(newStr);
377 query.setSplitSearchMatchString(needToSplitString);
378 break;
379 }
380 case SearchLineCommand::Subject: {
381 mSearchString = info.argument;
382 query.subjectMatches(mSearchString);
383 break;
384 }
385 case SearchLineCommand::Body: {
386 mSearchString = info.argument;
387 query.bodyMatches(mSearchString);
388 break;
389 }
390 case SearchLineCommand::Unknown:
391 case SearchLineCommand::HasStateOrAttachment:
392 // Nothing
393 break;
394 case SearchLineCommand::Larger:
395 case SearchLineCommand::Smaller:
396 case SearchLineCommand::OlderThan:
397 case SearchLineCommand::NewerThan:
398 case SearchLineCommand::Date:
399 case SearchLineCommand::Size:
400 case SearchLineCommand::Category:
401 // TODO implement tag support
402 break;
403 case SearchLineCommand::HasAttachment: {
406 lstStatus.append(status);
407 break;
408 }
409 case SearchLineCommand::HasInvitation: {
412 lstStatus.append(status);
413 break;
414 }
415 case SearchLineCommand::IsImportant: {
417 status.setImportant(true);
418 lstStatus.append(status);
419 break;
420 }
421 case SearchLineCommand::IsRead: {
423 status.setRead(true);
424 lstStatus.append(status);
425 break;
426 }
427 case SearchLineCommand::IsUnRead: {
428 // TODO verify
430 status.setRead(false);
431 lstStatus.append(status);
432 break;
433 }
434 case SearchLineCommand::IsIgnored: {
436 status.setIgnored(true);
437 lstStatus.append(status);
438 break;
439 }
440 case SearchLineCommand::IsHam: {
442 status.setHam(true);
443 lstStatus.append(status);
444 break;
445 }
446 case SearchLineCommand::IsSpam: {
448 status.setSpam(true);
449 lstStatus.append(status);
450 break;
451 }
452 case SearchLineCommand::IsWatched: {
454 status.setWatched(true);
455 lstStatus.append(status);
456 break;
457 }
458 case SearchLineCommand::IsReplied: {
460 status.setReplied(true);
461 lstStatus.append(status);
462 break;
463 }
464 case SearchLineCommand::IsEncrypted: {
466 status.setEncrypted(true);
467 lstStatus.append(status);
468 break;
469 }
470 case SearchLineCommand::IsQueued: {
472 status.setQueued(true);
473 lstStatus.append(status);
474 break;
475 }
476 case SearchLineCommand::IsSent: {
478 status.setSent(true);
479 lstStatus.append(status);
480 break;
481 }
482 case SearchLineCommand::IsForwarded: {
484 status.setForwarded(true);
485 lstStatus.append(status);
486 break;
487 }
488 case SearchLineCommand::To:
489 mSearchString = info.argument;
490 query.addTo(info.argument);
491 break;
492 case SearchLineCommand::Bcc:
493 mSearchString = info.argument;
494 query.addBcc(info.argument);
495 break;
496 case SearchLineCommand::From:
497 mSearchString = info.argument;
498 query.addFrom(info.argument);
499 break;
500 case SearchLineCommand::Cc:
501 mSearchString = info.argument;
502 query.addCc(info.argument);
503 break;
504 }
505 }
506
507 setStatus(lstStatus);
508 // If the collection is virtual we're probably trying to filter the search collection, so we just search globally
509 if (mCurrentFolder.isValid() && !mCurrentFolder.isVirtual()) {
510 query.addCollection(mCurrentFolder.id());
511 }
512
514 while (it.next()) {
515 mMatchingItemIds << it.id();
516 }
517 Q_EMIT finished();
518#endif
519}
520
521void Filter::setSearchString(const QString &search, SearchMessageByButtons::SearchOptions options)
522{
523#if !FORCE_DISABLE_AKONADI_SEARCH
524 const QString trimStr = search.trimmed();
525 if ((mSearchString == trimStr) && (mOptions == options)) {
526 return;
527 }
528 mOptions = options;
529 mSearchString = trimStr;
530 mMatchingItemIds.clear();
531
532 if (mSearchString.isEmpty()) {
533 return;
534 }
535 bool needToSplitString = false;
536 QString newStr = mSearchString;
537 if (mSearchString.startsWith(QLatin1Char('"')) && mSearchString.startsWith(QLatin1Char('"'))) {
538 newStr.remove(0, 1);
539 newStr.remove(newStr.length() - 1, 1);
540 mSearchList = QStringList() << newStr;
541 } else {
542 const QStringList searchListTmp = mSearchString.split(QLatin1Char(' '), Qt::SkipEmptyParts);
543 mSearchList.clear();
544 newStr.clear();
545 for (const QString &text : searchListTmp) {
546 if (text.size() >= 3) {
547 mSearchList << text;
548 if (!newStr.isEmpty()) {
549 newStr += QLatin1Char(' ');
550 }
551 newStr += text;
552 }
553 }
554 needToSplitString = true;
555 }
556 if (!newStr.trimmed().isEmpty()) {
558 if (options & SearchMessageByButtons::SearchEveryWhere) {
559 query.matches(newStr);
560 query.setSplitSearchMatchString(needToSplitString);
561 } else if (options & SearchMessageByButtons::SearchAgainstSubject) {
562 query.subjectMatches(newStr);
563 } else if (options & SearchMessageByButtons::SearchAgainstBody) {
564 query.bodyMatches(newStr);
565 } else if (options & SearchMessageByButtons::SearchAgainstFrom) {
566 query.setFrom(newStr);
567 } else if (options & SearchMessageByButtons::SearchAgainstBcc) {
568 query.setBcc(QStringList() << newStr);
569 } else if (options & SearchMessageByButtons::SearchAgainstCc) {
570 query.setCc(QStringList() << newStr);
571 } else if (options & SearchMessageByButtons::SearchAgainstTo) {
572 query.setTo(QStringList() << newStr);
573 }
574
575 // If the collection is virtual we're probably trying to filter the search collection, so we just search globally
576 if (mCurrentFolder.isValid() && !mCurrentFolder.isVirtual()) {
577 query.addCollection(mCurrentFolder.id());
578 }
579
580 Akonadi::Search::PIM::ResultIterator it = query.exec();
581 while (it.next()) {
582 mMatchingItemIds << it.id();
583 }
584 }
585 Q_EMIT finished();
586#endif
587}
588
589const QString &Filter::tagId() const
590{
591 return mTagId;
592}
593
594void Filter::setTagId(const QString &tagId)
595{
596 mTagId = tagId;
597}
598
599void Filter::generateRandomIdentifier()
600{
601 mIdentifier = KRandom::randomString(16);
602}
603
604QString Filter::identifier() const
605{
606 return mIdentifier;
607}
608
609QDebug operator<<(QDebug d, const MessageList::Core::Filter &t)
610{
611 d << "filtername " << t.filterName();
612 d << "identifier " << t.identifier();
613 d << "search string " << t.searchString();
614 d << "search option " << t.currentOptions();
615 d << "status " << t.status();
616 return d;
617}
618
619#include "moc_filter.cpp"
void setQueued(bool queued=true)
void setHam(bool ham=true)
void fromQInt32(qint32 status)
void setEncrypted(bool value=true)
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 setSent(bool sent=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:281
const QString & receiver() const
Returns the receiver associated to this item.
Definition item.cpp:504
const Akonadi::MessageStatus & status() const
Returns the status associated to this Item.
Definition item.cpp:449
const QString & sender() const
Returns the sender associated to this item.
Definition item.cpp:489
const QString & subject() const
Returns the subject associated to this Item.
Definition item.cpp:534
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()
std::optional< QSqlQuery > query(const QString &queryStatement)
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 Sat Dec 21 2024 16:59:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.