Messagelib

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

KDE's Doxygen guidelines are available online.