Messagelib

searchlinestatus.cpp
1/*
2 SPDX-FileCopyrightText: 2016-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "searchlinestatus.h"
8#include "configurefiltersdialog.h"
9#include "core/filtersavedmanager.h"
10#include "filtersavedmenu.h"
11#include "messagelist_debug.h"
12#include <KStatefulBrush>
13
14#include <KColorScheme>
15#include <KIconEngine>
16#include <KIconLoader>
17#include <KLocalizedString>
18#include <QAbstractItemView>
19#include <QAction>
20#include <QActionGroup>
21#include <QCompleter>
22#include <QContextMenuEvent>
23#include <QMenu>
24#include <QPushButton>
25#include <QStandardPaths>
26#include <QStringListModel>
27#include <QWidgetAction>
28
29static const char qLineEditclearButtonActionNameC[] = "_q_qlineeditclearaction";
30#define MAX_COMPLETION_ITEMS 20
31using namespace MessageList::Core;
32SearchLineStatus::SearchLineStatus(QWidget *parent)
33 : QLineEdit(parent)
34 , mCompleter(new QCompleter(this))
35 , mCompleterListModel(new QStringListModel(this))
36{
37 setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::BottomEdge}));
38 mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
39 mCompleter->setModel(mCompleterListModel);
40 setCompleter(mCompleter);
41
42 setClearButtonEnabled(true);
43 initializeActions();
44 createMenuSearch();
45 auto act = findChild<QAction *>(QLatin1StringView(qLineEditclearButtonActionNameC));
46 if (act) {
47 connect(act, &QAction::triggered, this, &SearchLineStatus::slotClear);
48 } else {
49 qCWarning(MESSAGELIST_LOG) << "Clear button name was changed ! Please verify qt code";
50 }
51 connect(FilterSavedManager::self(), &FilterSavedManager::activateFilter, this, &SearchLineStatus::slotActivateFilter);
52}
53
54SearchLineStatus::~SearchLineStatus() = default;
55
56void SearchLineStatus::keyPressEvent(QKeyEvent *e)
57{
58 if (e->key() == Qt::Key_Escape) {
59 if (mCompleter->popup()->isVisible()) {
61 } else {
62 Q_EMIT forceLostFocus();
63 }
64 } else if (e->key() == Qt::Key_Q && (e->modifiers().testFlag(Qt::ShiftModifier) && e->modifiers().testFlag(Qt::AltModifier))) {
65 mLockAction->trigger();
66 } else {
68 }
69}
70
71void SearchLineStatus::slotClear()
72{
73 Q_EMIT clearButtonClicked();
74}
75
76void SearchLineStatus::updateLockAction()
77{
78 if (mLocked) {
79 mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked")));
80 mLockAction->setToolTip(i18nc("@info:tooltip", "Prevent the quick search field from being cleared when changing folders"));
81 } else {
82 mLockAction->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
83 mLockAction->setToolTip(i18nc("@info:tooltip", "Clear the quick search field when changing folders"));
84 }
85}
86
87void SearchLineStatus::setLocked(bool b)
88{
89 if (mLocked != b) {
90 slotToggledLockAction();
91 }
92}
93
94bool SearchLineStatus::locked() const
95{
96 return mLocked;
97}
98
99void SearchLineStatus::initializeActions()
100{
101 mLockAction = addAction(QIcon::fromTheme(QStringLiteral("object-locked")), QLineEdit::TrailingPosition);
102 mLockAction->setWhatsThis(i18nc("@info:whatsthis",
103 "Toggle this button if you want to keep your quick search "
104 "locked when moving to other folders or when narrowing the search "
105 "by message status."));
106
107 connect(mLockAction, &QAction::triggered, this, &SearchLineStatus::slotToggledLockAction);
108 updateLockAction();
109
110 const QStringList overlays = QStringList() << QStringLiteral("list-add");
111 mWithFilter = QIcon(new KIconEngine(QStringLiteral("view-filter"), KIconLoader::global(), overlays));
112 mWithoutFilter = QIcon::fromTheme(QStringLiteral("view-filter"));
113 mFiltersAction = addAction(mWithoutFilter, QLineEdit::LeadingPosition);
114 mFiltersAction->setToolTip(i18n("Filter Mails by Status"));
115 connect(mFiltersAction, &QAction::triggered, this, &SearchLineStatus::showMenu);
116
117 mSaveFilterAction = addAction(QIcon::fromTheme(QStringLiteral("edit-find")), QLineEdit::LeadingPosition);
118 mSaveFilterAction->setToolTip(i18n("Saved Filter"));
119 mFilterSavedMenu = new FilterSavedMenu(this);
120 mSaveFilterAction->setMenu(mFilterSavedMenu);
121 connect(mSaveFilterAction, &QAction::triggered, this, &SearchLineStatus::showSavedFiltersMenu);
122 connect(mFilterSavedMenu, &FilterSavedMenu::saveFilter, this, &SearchLineStatus::saveFilter);
123 connect(mFilterSavedMenu, &FilterSavedMenu::configureFilters, this, &SearchLineStatus::slotConfigureFilters);
124}
125
126void SearchLineStatus::slotActivateFilter(const QString &identifier)
127{
128 Filter *f = FilterSavedManager::self()->loadFilter(identifier);
129 if (f) {
130 Q_EMIT activateFilter(f);
131 } else {
132 qCWarning(MESSAGELIST_LOG) << "Impossible to load Filter from identifier :" << identifier;
133 }
134}
135
136void SearchLineStatus::slotConfigureFilters()
137{
138 ConfigureFiltersDialog dlg(this);
139 dlg.exec();
140}
141
142void SearchLineStatus::slotToggledLockAction()
143{
144 mLocked = !mLocked;
145 updateLockAction();
146}
147
148void SearchLineStatus::updateFilters()
149{
151
152 for (QAction *act : std::as_const(mFilterListActions)) {
153 if (act->isChecked()) {
155 status.fromQInt32(static_cast<qint32>(act->data().toInt()));
156 lstStatus.append(status);
157 }
158 }
159 mHasFilter = !lstStatus.isEmpty();
160 Q_EMIT filterActionChanged(lstStatus);
161 updateFilterActionIcon();
162}
163
164void SearchLineStatus::showSavedFiltersMenu()
165{
166 mFilterSavedMenu->exec(mapToGlobal(QPoint(0, height())));
167}
168
169void SearchLineStatus::showMenu()
170{
171 if (mFilterMenu->exec(mapToGlobal(QPoint(0, height())))) {
172 updateFilters();
173 }
174}
175
176void SearchLineStatus::clearFilterAction()
177{
178 for (QAction *act : std::as_const(mFilterListActions)) {
179 act->setChecked(false);
180 }
181 mHasFilter = false;
182 updateFilterActionIcon();
183}
184
185void SearchLineStatus::createFilterAction(const QIcon &icon, const QString &text, int value)
186{
187 auto act = new QAction(icon, text, this);
188 act->setCheckable(true);
189 act->setData(value);
190 mFilterMenu->addAction(act);
191 mFilterListActions.append(act);
192}
193
194void SearchLineStatus::updateFilterActionIcon()
195{
196 mFiltersAction->setIcon(mHasFilter ? mWithFilter : mWithoutFilter);
197 if (mColorName.isEmpty()) {
200 mColorName = bgBrush.brush(palette()).color().name();
201 }
202 setStyleSheet(mHasFilter ? QStringLiteral("QLineEdit{ background-color:%1 }").arg(mColorName) : QString());
203}
204
205void SearchLineStatus::clearFilterButtonClicked()
206{
207 clearFilterAction();
208 clearFilterByAction();
209 updateFilters();
210}
211
212void SearchLineStatus::createMenuSearch()
213{
214 mFilterMenu = new QMenu(this);
215 mFilterMenu->setObjectName(QLatin1StringView("filtermenu"));
216 auto clearWidgetAction = new QWidgetAction(mFilterMenu);
217 auto clearFilterButton = new QPushButton(i18n("Clear Filter"), mFilterMenu);
218 connect(clearFilterButton, &QPushButton::clicked, this, &SearchLineStatus::clearFilterButtonClicked);
219
220 clearWidgetAction->setDefaultWidget(clearFilterButton);
221 mFilterMenu->addAction(clearWidgetAction);
222 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-unread")),
223 i18nc("@action:inmenu Status of a message", "Unread"),
225
226 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-replied")),
227 i18nc("@action:inmenu Status of a message", "Replied"),
229
230 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-forwarded")),
231 i18nc("@action:inmenu Status of a message", "Forwarded"),
233
234 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-important")),
235 i18nc("@action:inmenu Status of a message", "Important"),
237
238 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-task")),
239 i18nc("@action:inmenu Status of a message", "Action Item"),
241
242 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-watch")),
243 i18nc("@action:inmenu Status of a message", "Watched"),
245
246 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-thread-ignored")),
247 i18nc("@action:inmenu Status of a message", "Ignored"),
249
250 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-attachment")),
251 i18nc("@action:inmenu Status of a message", "Has Attachment"),
253
254 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-invitation")),
255 i18nc("@action:inmenu Status of a message", "Has Invitation"),
257
258 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-junk")),
259 i18nc("@action:inmenu Status of a message", "Spam"),
261
262 createFilterAction(QIcon::fromTheme(QStringLiteral("mail-mark-notjunk")),
263 i18nc("@action:inmenu Status of a message", "Ham"),
265 createFilterByAction();
266}
267
268void SearchLineStatus::createFilterByAction()
269{
270 mFilterMenu->addSeparator();
271 auto grp = new QActionGroup(mFilterMenu);
272
273 mSearchEveryWhereAction = new QAction(i18n("Full Message"), mFilterMenu);
274 mSearchEveryWhereAction->setCheckable(true);
275 mSearchEveryWhereAction->setChecked(true);
276
277 mFilterMenu->addAction(mSearchEveryWhereAction);
278 grp->addAction(mSearchEveryWhereAction);
279
280 mSearchAgainstBodyAction = new QAction(i18n("Body"), mFilterMenu);
281 grp->addAction(mSearchAgainstBodyAction);
282 mFilterMenu->addAction(mSearchAgainstBodyAction);
283 mSearchAgainstBodyAction->setCheckable(true);
284
285 mSearchAgainstSubjectAction = new QAction(i18n("Subject"), mFilterMenu);
286 grp->addAction(mSearchAgainstSubjectAction);
287 mFilterMenu->addAction(mSearchAgainstSubjectAction);
288 mSearchAgainstSubjectAction->setCheckable(true);
289
290 mSearchAgainstFromOrToAction = new QAction(mFilterMenu);
291 changeSearchAgainstFromOrToText();
292 grp->addAction(mSearchAgainstFromOrToAction);
293 mFilterMenu->addAction(mSearchAgainstFromOrToAction);
294 mSearchAgainstFromOrToAction->setCheckable(true);
295
296 mSearchAgainstBccAction = new QAction(i18n("BCC"), mFilterMenu);
297 grp->addAction(mSearchAgainstBccAction);
298 mFilterMenu->addAction(mSearchAgainstBccAction);
299 mSearchAgainstBccAction->setCheckable(true);
300 connect(grp, &QActionGroup::triggered, this, &SearchLineStatus::slotFilterActionClicked);
301}
302
303void SearchLineStatus::clearFilterByAction()
304{
305 mSearchEveryWhereAction->setChecked(true);
306}
307
308bool SearchLineStatus::containsOutboundMessages() const
309{
310 return mContainsOutboundMessages;
311}
312
313void SearchLineStatus::setContainsOutboundMessages(bool containsOutboundMessages)
314{
315 if (mContainsOutboundMessages != containsOutboundMessages) {
316 mContainsOutboundMessages = containsOutboundMessages;
317 changeSearchAgainstFromOrToText();
318 }
319}
320
321void SearchLineStatus::changeSearchAgainstFromOrToText()
322{
323 if (mContainsOutboundMessages) {
324 mSearchAgainstFromOrToAction->setText(i18n("To"));
325 } else {
326 mSearchAgainstFromOrToAction->setText(i18n("From"));
327 }
328}
329
330QuickSearchLine::SearchOptions SearchLineStatus::searchOptions() const
331{
332 QuickSearchLine::SearchOptions searchOptions;
333 if (mSearchEveryWhereAction->isChecked()) {
334 searchOptions |= QuickSearchLine::SearchEveryWhere;
335 }
336 if (mSearchAgainstBodyAction->isChecked()) {
337 searchOptions |= QuickSearchLine::SearchAgainstBody;
338 }
339 if (mSearchAgainstSubjectAction->isChecked()) {
340 searchOptions |= QuickSearchLine::SearchAgainstSubject;
341 }
342 if (mSearchAgainstFromOrToAction->isChecked()) {
343 if (mContainsOutboundMessages) {
344 searchOptions |= QuickSearchLine::SearchAgainstTo;
345 } else {
346 searchOptions |= QuickSearchLine::SearchAgainstFrom;
347 }
348 }
349 if (mSearchAgainstBccAction->isChecked()) {
350 searchOptions |= QuickSearchLine::SearchAgainstBcc;
351 }
352 return searchOptions;
353}
354
355void SearchLineStatus::setSearchOptions(QuickSearchLine::SearchOptions opts)
356{
357 mSearchEveryWhereAction->setChecked(opts & QuickSearchLine::SearchEveryWhere);
358 mSearchAgainstBodyAction->setChecked(opts & QuickSearchLine::SearchAgainstBody);
359 mSearchAgainstSubjectAction->setChecked(opts & QuickSearchLine::SearchAgainstSubject);
360 mSearchAgainstBccAction->setChecked(opts & QuickSearchLine::SearchAgainstBcc);
361 if (mContainsOutboundMessages) {
362 mSearchAgainstFromOrToAction->setChecked(opts & QuickSearchLine::SearchAgainstTo);
363 } else {
364 mSearchAgainstFromOrToAction->setChecked(opts & QuickSearchLine::SearchAgainstFrom);
365 }
366}
367
368void SearchLineStatus::setFilterMessageStatus(const QList<Akonadi::MessageStatus> &newLstStatus)
369{
370 clearFilterAction();
371 clearFilterByAction();
372 // TODO unchecked all checkbox
374 for (QAction *act : std::as_const(mFilterListActions)) {
375 if (static_cast<qint32>(act->data().toInt()) == status.toQInt32()) {
376 act->setChecked(true);
377 mHasFilter = true;
378 break;
379 }
380 }
381 }
382 updateFilterActionIcon();
383}
384
385void SearchLineStatus::slotFilterActionClicked(QAction *act)
386{
388 Q_EMIT searchOptionChanged();
389}
390
391void SearchLineStatus::addCompletionItem(const QString &str)
392{
393 mListCompetion.removeAll(str);
394 mListCompetion.prepend(str);
395 while (mListCompetion.size() > MAX_COMPLETION_ITEMS) {
396 mListCompetion.removeLast();
397 }
398 mCompleterListModel->setStringList(mListCompetion);
399}
400
401void SearchLineStatus::contextMenuEvent(QContextMenuEvent *e)
402{
404 if (popup) {
405 popup->addSeparator();
406 popup->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl")), i18n("Clear History"), this, &SearchLineStatus::slotClearHistory);
407 popup->exec(e->globalPos());
408 delete popup;
409 }
410}
411
412void SearchLineStatus::slotClearHistory()
413{
414 mListCompetion.clear();
415 mCompleterListModel->setStringList(mListCompetion);
416}
417
418#include "moc_searchlinestatus.cpp"
void fromQInt32(qint32 status)
static const MessageStatus statusUnread()
static const MessageStatus statusHasAttachment()
static const MessageStatus statusSpam()
static const MessageStatus statusHasInvitation()
static const MessageStatus statusReplied()
static const MessageStatus statusImportant()
static const MessageStatus statusWatched()
static const MessageStatus statusForwarded()
static const MessageStatus statusToAct()
static const MessageStatus statusIgnored()
static const MessageStatus statusHam()
static KIconLoader * global()
Q_SCRIPTABLE CaptureState status()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
The implementation independent part of the MessageList library.
Definition aggregation.h:22
QCA_EXPORT void setProperty(const QString &name, const QVariant &value)
void clicked(bool checked)
void setCheckable(bool)
void setChecked(bool)
void setIcon(const QIcon &icon)
void setMenu(QMenu *menu)
void setText(const QString &text)
void setToolTip(const QString &tip)
void trigger()
void triggered(bool checked)
void setWhatsThis(const QString &what)
void triggered(QAction *action)
QAbstractItemView * popup() const const
const QPoint & globalPos() const const
QIcon fromTheme(const QString &name)
int key() const const
Qt::KeyboardModifiers modifiers() const const
void addAction(QAction *action, QLineEdit::ActionPosition position)
QMenu * createStandardContextMenu()
virtual void keyPressEvent(QKeyEvent *event) override
void append(const T &value)
void clear()
void prepend(const T &value)
int removeAll(const T &value)
void removeLast()
int size() const const
QAction * addAction(const QString &text)
QAction * addSeparator()
QAction * exec()
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setObjectName(const QString &name)
T qobject_cast(QObject *object)
bool isEmpty() const const
void setStringList(const QStringList &strings)
CaseInsensitive
BottomEdge
Key_Escape
ShiftModifier
QVariant fromValue(const T &value)
QPoint mapToGlobal(const QPoint &pos) const const
void setStyleSheet(const QString &styleSheet)
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sun Feb 25 2024 18:37:31 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.