Messagelib

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

KDE's Doxygen guidelines are available online.