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 QString &search, SearchMessageByButtons::SearchOptions options)
220{
221 const QString trimStr = search.trimmed();
222 if ((mSearchString == trimStr) && (mOptions == options)) {
223 return;
224 }
225 mOptions = options;
226 mSearchString = trimStr;
227 mMatchingItemIds.clear();
228
229 if (mSearchString.isEmpty()) {
230 return;
231 }
232 bool needToSplitString = false;
233 QString newStr = mSearchString;
234 if (mSearchString.startsWith(QLatin1Char('"')) && mSearchString.startsWith(QLatin1Char('"'))) {
235 newStr.remove(0, 1);
236 newStr.remove(newStr.length() - 1, 1);
237 mSearchList = QStringList() << newStr;
238 } else {
239 const QStringList searchListTmp = mSearchString.split(QLatin1Char(' '), Qt::SkipEmptyParts);
240 mSearchList.clear();
241 newStr.clear();
242 for (const QString &text : searchListTmp) {
243 if (text.size() >= 3) {
244 mSearchList << text;
245 if (!newStr.isEmpty()) {
246 newStr += QLatin1Char(' ');
247 }
248 newStr += text;
249 }
250 }
251 needToSplitString = true;
252 }
253 if (!newStr.trimmed().isEmpty()) {
255 if (options & SearchMessageByButtons::SearchEveryWhere) {
256 query.matches(newStr);
257 query.setSplitSearchMatchString(needToSplitString);
258 } else if (options & SearchMessageByButtons::SearchAgainstSubject) {
259 query.subjectMatches(newStr);
260 } else if (options & SearchMessageByButtons::SearchAgainstBody) {
261 query.bodyMatches(newStr);
262 } else if (options & SearchMessageByButtons::SearchAgainstFrom) {
263 query.setFrom(newStr);
264 } else if (options & SearchMessageByButtons::SearchAgainstBcc) {
265 query.setBcc(QStringList() << newStr);
266 } else if (options & SearchMessageByButtons::SearchAgainstTo) {
267 query.setTo(QStringList() << newStr);
268 }
269
270 // If the collection is virtual we're probably trying to filter the search collection, so we just search globally
271 if (mCurrentFolder.isValid() && !mCurrentFolder.isVirtual()) {
272 query.addCollection(mCurrentFolder.id());
273 }
274
275 Akonadi::Search::PIM::ResultIterator it = query.exec();
276 while (it.next()) {
277 mMatchingItemIds << it.id();
278 }
279 }
280 Q_EMIT finished();
281}
282
283const QString &Filter::tagId() const
284{
285 return mTagId;
286}
287
288void Filter::setTagId(const QString &tagId)
289{
290 mTagId = tagId;
291}
292
293void Filter::generateRandomIdentifier()
294{
295 mIdentifier = KRandom::randomString(16);
296}
297
298QString Filter::identifier() const
299{
300 return mIdentifier;
301}
302
303QDebug operator<<(QDebug d, const MessageList::Core::Filter &t)
304{
305 d << "filtername " << t.filterName();
306 d << "identifier " << t.identifier();
307 d << "search string " << t.searchString();
308 d << "search option " << t.currentOptions();
309 d << "status " << t.status();
310 return d;
311}
312
313#include "moc_filter.cpp"
void fromQInt32(qint32 status)
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:33
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()
KCOREADDONS_EXPORT QString randomString(int length)
The implementation independent part of the MessageList library.
Definition aggregation.h:22
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 Fri Jul 26 2024 11:54:19 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.