Messagelib

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

KDE's Doxygen guidelines are available online.