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

KDE's Doxygen guidelines are available online.