Mailcommon

kmfilterlistbox.cpp
1 /*
2  SPDX-FileCopyrightText: 2014-2022 Laurent Montel <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "kmfilterlistbox.h"
8 #include "filteractions/filteractiondict.h"
9 #include "filtermanager.h"
10 #include "invalidfilters/invalidfilterdialog.h"
11 #include "invalidfilters/invalidfilterinfo.h"
12 #include "mailcommon_debug.h"
13 #include "mailfilter.h"
14 #include <KListWidgetSearchLine>
15 #include <KLocalizedString>
16 #include <KMessageBox>
17 #include <QHBoxLayout>
18 #include <QInputDialog>
19 #include <QKeyEvent>
20 #include <QListWidget>
21 #include <QPointer>
22 #include <QPushButton>
23 #include <QShortcut>
24 #include <QVBoxLayout>
25 
26 //=============================================================================
27 //
28 // class KMFilterListBox (the filter list manipulator)
29 //
30 //=============================================================================
31 using namespace MailCommon;
32 KMFilterListBox::KMFilterListBox(const QString &title, QWidget *parent)
33  : QGroupBox(title, parent)
34 {
35  auto layout = new QVBoxLayout(this);
36 
37  //----------- the list box
38  mListWidget = new QListWidget(this);
39  mListWidget->setMinimumWidth(150);
40  mListWidget->setWhatsThis(
41  i18n("<qt><p>This is the list of defined filters. "
42  "They are processed top-to-bottom.</p>"
43  "<p>Click on any filter to edit it "
44  "using the controls in the right-hand half "
45  "of the dialog.</p></qt>"));
46  mListWidget->setDragDropMode(QAbstractItemView::InternalMove);
47  mListWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
48  connect(mListWidget->model(), &QAbstractItemModel::rowsMoved, this, &KMFilterListBox::slotRowsMoved);
49 
50  mSearchListWidget = new KListWidgetSearchLine(this, mListWidget);
51  mSearchListWidget->setPlaceholderText(i18nc("@info Displayed grayed-out inside the textbox, verb to search", "Search"));
52  mSearchListWidget->installEventFilter(this);
53  layout->addWidget(mSearchListWidget);
54  layout->addWidget(mListWidget);
55 
56  //----------- the first row of buttons
57  auto hb = new QWidget(this);
58  auto hbHBoxLayout = new QHBoxLayout(hb);
59  hbHBoxLayout->setContentsMargins({});
60  hbHBoxLayout->setSpacing(4);
61 
62  mBtnTop = new QPushButton(QString(), hb);
63  hbHBoxLayout->addWidget(mBtnTop);
64  mBtnTop->setIcon(QIcon::fromTheme(QStringLiteral("go-top")));
65  mBtnTop->setMinimumSize(mBtnTop->sizeHint() * 1.2);
66 
67  mBtnUp = new QPushButton(QString(), hb);
68  hbHBoxLayout->addWidget(mBtnUp);
69  mBtnUp->setAutoRepeat(true);
70  mBtnUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
71  mBtnUp->setMinimumSize(mBtnUp->sizeHint() * 1.2);
72  mBtnDown = new QPushButton(QString(), hb);
73  hbHBoxLayout->addWidget(mBtnDown);
74  mBtnDown->setAutoRepeat(true);
75  mBtnDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
76  mBtnDown->setMinimumSize(mBtnDown->sizeHint() * 1.2);
77 
78  mBtnBottom = new QPushButton(QString(), hb);
79  hbHBoxLayout->addWidget(mBtnBottom);
80  mBtnBottom->setIcon(QIcon::fromTheme(QStringLiteral("go-bottom")));
81  mBtnBottom->setMinimumSize(mBtnBottom->sizeHint() * 1.2);
82 
83  mBtnUp->setToolTip(i18nc("Move selected filter up.", "Up"));
84  mBtnDown->setToolTip(i18nc("Move selected filter down.", "Down"));
85  mBtnTop->setToolTip(i18nc("Move selected filter to the top.", "Top"));
86  mBtnBottom->setToolTip(i18nc("Move selected filter to the bottom.", "Bottom"));
87  mBtnUp->setWhatsThis(
88  i18n("<qt><p>Click this button to move the currently-"
89  "selected filter <em>up</em> one in the list above.</p>"
90  "<p>This is useful since the order of the filters in the list "
91  "determines the order in which they are tried on messages: "
92  "The topmost filter gets tried first.</p>"
93  "<p>If you have clicked this button accidentally, you can undo this "
94  "by clicking on the <em>Down</em> button.</p></qt>"));
95  mBtnDown->setWhatsThis(
96  i18n("<qt><p>Click this button to move the currently-"
97  "selected filter <em>down</em> one in the list above.</p>"
98  "<p>This is useful since the order of the filters in the list "
99  "determines the order in which they are tried on messages: "
100  "The topmost filter gets tried first.</p>"
101  "<p>If you have clicked this button accidentally, you can undo this "
102  "by clicking on the <em>Up</em> button.</p></qt>"));
103  mBtnBottom->setWhatsThis(
104  i18n("<qt><p>Click this button to move the currently-"
105  "selected filter to bottom of list.</p>"
106  "<p>This is useful since the order of the filters in the list "
107  "determines the order in which they are tried on messages: "
108  "The topmost filter gets tried first.</p></qt>"));
109  mBtnTop->setWhatsThis(
110  i18n("<qt><p>Click this button to move the currently-"
111  "selected filter to top of list.</p>"
112  "<p>This is useful since the order of the filters in the list "
113  "determines the order in which they are tried on messages: "
114  "The topmost filter gets tried first.</p></qt>"));
115 
116  layout->addWidget(hb);
117 
118  //----------- the second row of buttons
119  hb = new QWidget(this);
120  hbHBoxLayout = new QHBoxLayout(hb);
121  hbHBoxLayout->setContentsMargins({});
122  hbHBoxLayout->setSpacing(4);
123  mBtnNew = new QPushButton(hb);
124  hbHBoxLayout->addWidget(mBtnNew);
125  mBtnNew->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
126  mBtnNew->setMinimumSize(mBtnNew->sizeHint() * 1.2);
127  mBtnCopy = new QPushButton(hb);
128  hbHBoxLayout->addWidget(mBtnCopy);
129  mBtnCopy->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
130  mBtnCopy->setMinimumSize(mBtnCopy->sizeHint() * 1.2);
131  mBtnDelete = new QPushButton(hb);
132  hbHBoxLayout->addWidget(mBtnDelete);
133  mBtnDelete->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
134  mBtnDelete->setMinimumSize(mBtnDelete->sizeHint() * 1.2);
135  mBtnRename = new QPushButton(hb);
136  mBtnRename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
137  mBtnRename->setMinimumSize(mBtnDelete->sizeHint() * 1.2);
138 
139  hbHBoxLayout->addWidget(mBtnRename);
140  mBtnNew->setToolTip(i18nc("@action:button in filter list manipulator", "New"));
141  mBtnCopy->setToolTip(i18n("Copy"));
142  mBtnDelete->setToolTip(i18n("Delete"));
143  mBtnRename->setToolTip(i18n("Rename"));
144  mBtnNew->setWhatsThis(
145  i18n("<qt><p>Click this button to create a new filter.</p>"
146  "<p>The filter will be inserted just before the currently-"
147  "selected one, but you can always change that "
148  "later on.</p>"
149  "<p>If you have clicked this button accidentally, you can undo this "
150  "by clicking on the <em>Delete</em> button.</p></qt>"));
151  mBtnCopy->setWhatsThis(
152  i18n("<qt><p>Click this button to copy a filter.</p>"
153  "<p>If you have clicked this button accidentally, you can undo this "
154  "by clicking on the <em>Delete</em> button.</p></qt>"));
155  mBtnDelete->setWhatsThis(
156  i18n("<qt><p>Click this button to <em>delete</em> the currently-"
157  "selected filter from the list above.</p>"
158  "<p>There is no way to get the filter back once "
159  "it is deleted, but you can always leave the "
160  "dialog by clicking <em>Cancel</em> to discard the "
161  "changes made.</p></qt>"));
162  mBtnRename->setWhatsThis(
163  i18n("<qt><p>Click this button to rename the currently-selected filter.</p>"
164  "<p>Filters are named automatically, as long as they start with "
165  "\"&lt;\".</p>"
166  "<p>If you have renamed a filter accidentally and want automatic "
167  "naming back, click this button and select <em>Clear</em> followed "
168  "by <em>OK</em> in the appearing dialog.</p></qt>"));
169 
170  layout->addWidget(hb);
171 
172  auto shortcut = new QShortcut(this);
173  shortcut->setKey(Qt::Key_Delete);
174  connect(shortcut, &QShortcut::activated, this, &KMFilterListBox::slotDelete);
175 
176  //----------- now connect everything
177  connect(mListWidget, &QListWidget::currentRowChanged, this, &KMFilterListBox::slotSelected);
178  connect(mListWidget, &QListWidget::itemDoubleClicked, this, &KMFilterListBox::slotRename);
179  connect(mListWidget, &QListWidget::itemChanged, this, &KMFilterListBox::slotFilterEnabledChanged);
180 
181  connect(mListWidget, &QListWidget::itemSelectionChanged, this, &KMFilterListBox::slotSelectionChanged);
182 
183  connect(mBtnUp, &QPushButton::clicked, this, &KMFilterListBox::slotUp);
184  connect(mBtnDown, &QPushButton::clicked, this, &KMFilterListBox::slotDown);
185  connect(mBtnTop, &QPushButton::clicked, this, &KMFilterListBox::slotTop);
186  connect(mBtnBottom, &QPushButton::clicked, this, &KMFilterListBox::slotBottom);
187 
188  connect(mBtnNew, &QPushButton::clicked, this, &KMFilterListBox::slotNew);
189  connect(mBtnCopy, &QPushButton::clicked, this, &KMFilterListBox::slotCopy);
190  connect(mBtnDelete, &QPushButton::clicked, this, &KMFilterListBox::slotDelete);
191  connect(mBtnRename, &QPushButton::clicked, this, &KMFilterListBox::slotRename);
192 
193  // the dialog should call loadFilterList()
194  // when all signals are connected.
195  enableControls();
196 }
197 
198 KMFilterListBox::~KMFilterListBox() = default;
199 
200 bool KMFilterListBox::eventFilter(QObject *obj, QEvent *event)
201 {
202  if (event->type() == QEvent::KeyPress && obj == mSearchListWidget) {
203  auto key = static_cast<QKeyEvent *>(event);
204  if ((key->key() == Qt::Key_Enter) || (key->key() == Qt::Key_Return)) {
205  event->accept();
206  return true;
207  }
208  }
209  return QGroupBox::eventFilter(obj, event);
210 }
211 
212 bool KMFilterListBox::itemIsValid(QListWidgetItem *item) const
213 {
214  if (!item) {
215  qCDebug(MAILCOMMON_LOG) << "Called while no filter is selected, ignoring.";
216  return false;
217  }
218  if (item->isHidden()) {
219  return false;
220  }
221  return true;
222 }
223 
224 void KMFilterListBox::slotFilterEnabledChanged(QListWidgetItem *item)
225 {
226  if (!item) {
227  qCDebug(MAILCOMMON_LOG) << "Called while no filter is selected, ignoring.";
228  return;
229  }
230  auto itemFilter = static_cast<QListWidgetFilterItem *>(item);
231  MailCommon::MailFilter *filter = itemFilter->filter();
232  filter->setEnabled((item->checkState() == Qt::Checked));
233  Q_EMIT filterUpdated(filter);
234 }
235 
236 void KMFilterListBox::slotRowsMoved(const QModelIndex &, int sourcestart, int sourceEnd, const QModelIndex &, int destinationRow)
237 {
238  Q_UNUSED(sourceEnd)
239  Q_UNUSED(sourcestart)
240  Q_UNUSED(destinationRow)
241  enableControls();
242 
243  Q_EMIT filterOrderAltered();
244 }
245 
246 void KMFilterListBox::createFilter(const QByteArray &field, const QString &value)
247 {
248  SearchRule::Ptr newRule = SearchRule::createInstance(field, SearchRule::FuncContains, value);
249 
250  auto newFilter = new MailFilter();
251  newFilter->pattern()->append(newRule);
252  newFilter->pattern()->setName(QStringLiteral("<%1>: %2").arg(QString::fromLatin1(field), value));
253 
254  FilterActionDesc *desc = MailCommon::FilterManager::filterActionDict()->value(QStringLiteral("transfer"));
255  if (desc) {
256  newFilter->actions()->append(desc->create());
257  }
258 
259  insertFilter(newFilter);
260  enableControls();
261 }
262 
263 void KMFilterListBox::slotUpdateFilterName()
264 {
265  QListWidgetItem *item = mListWidget->currentItem();
266  if (!item) {
267  qCDebug(MAILCOMMON_LOG) << "Called while no filter is selected, ignoring.";
268  return;
269  }
270  auto itemFilter = static_cast<QListWidgetFilterItem *>(item);
271  MailCommon::MailFilter *filter = itemFilter->filter();
272 
273  SearchPattern *p = filter->pattern();
274  if (!p) {
275  return;
276  }
277 
278  QString shouldBeName = p->name();
279  QString displayedName = itemFilter->text();
280 
281  if (shouldBeName.trimmed().isEmpty()) {
282  filter->setAutoNaming(true);
283  }
284 
285  if (filter->isAutoNaming()) {
286  // auto-naming of patterns
287  if (!p->isEmpty() && p->first() && !p->first()->field().trimmed().isEmpty()) {
288  shouldBeName = QStringLiteral("<%1>: %2").arg(QString::fromLatin1(p->first()->field()), p->first()->contents());
289  } else {
290  shouldBeName = QLatin1Char('<') + i18n("unnamed") + QLatin1Char('>');
291  }
292  p->setName(shouldBeName);
293  }
294 
295  if (displayedName == shouldBeName) {
296  return;
297  }
298 
299  filter->setToolbarName(shouldBeName);
300 
301  mListWidget->blockSignals(true);
302  itemFilter->setText(shouldBeName);
303  mListWidget->blockSignals(false);
304 }
305 
306 void KMFilterListBox::slotAccepted()
307 {
308  applyFilterChanged(true);
309 }
310 
311 void KMFilterListBox::slotApplied()
312 {
313  applyFilterChanged(false);
314 }
315 
316 void KMFilterListBox::applyFilterChanged(bool closeAfterSaving)
317 {
318  if (mListWidget->currentItem()) {
319  Q_EMIT applyWidgets();
320  slotSelected(mListWidget->currentRow());
321  }
322 
323  // by now all edit widgets should have written back
324  // their widget's data into our filter list.
325 
326  bool wasCanceled = false;
327  const QVector<MailFilter *> newFilters = filtersForSaving(closeAfterSaving, wasCanceled);
328  if (!wasCanceled) {
330  }
331 }
332 
333 QVector<MailFilter *> KMFilterListBox::filtersForSaving(bool closeAfterSaving, bool &wasCanceled) const
334 {
335  Q_EMIT const_cast<KMFilterListBox *>(this)->applyWidgets(); // signals aren't const
336  QVector<MailFilter *> filters;
337  QStringList emptyFilters;
338  QVector<MailCommon::InvalidFilterInfo> listInvalidFilters;
339  const int numberOfFilter(mListWidget->count());
340  for (int i = 0; i < numberOfFilter; ++i) {
341  auto *itemFilter = static_cast<QListWidgetFilterItem *>(mListWidget->item(i));
342  auto f = new MailFilter(*itemFilter->filter()); // deep copy
343 
344  const QString information = f->purify();
345  if (!f->isEmpty() && information.isEmpty()) {
346  // the filter is valid:
347  filters.append(f);
348  } else {
349  // the filter is invalid:
350  emptyFilters << f->name();
351  listInvalidFilters.append(MailCommon::InvalidFilterInfo(f->name(), information));
352  delete f;
353  }
354  }
355 
356  // report on invalid filters:
357  if (!emptyFilters.empty()) {
358  QPointer<MailCommon::InvalidFilterDialog> dlg = new MailCommon::InvalidFilterDialog(nullptr);
359  dlg->setInvalidFilters(listInvalidFilters);
360  if (!dlg->exec()) {
361  if (closeAfterSaving) {
362  Q_EMIT abortClosing();
363  }
364  wasCanceled = true;
365  }
366  delete dlg;
367  }
368  return filters;
369 }
370 
371 void KMFilterListBox::slotSelectionChanged()
372 {
373  if (mListWidget->selectedItems().count() > 1) {
374  Q_EMIT resetWidgets();
375  }
376  enableControls();
377 }
378 
379 void KMFilterListBox::slotSelected(int aIdx)
380 {
381  if (aIdx >= 0 && aIdx < mListWidget->count()) {
382  auto *itemFilter = static_cast<QListWidgetFilterItem *>(mListWidget->item(aIdx));
383  MailFilter *f = itemFilter->filter();
384 
385  if (f) {
386  Q_EMIT filterSelected(f);
387  } else {
388  Q_EMIT resetWidgets();
389  }
390  } else {
391  Q_EMIT resetWidgets();
392  }
393  enableControls();
394 }
395 
396 void KMFilterListBox::slotNew()
397 {
398  QListWidgetItem *item = mListWidget->currentItem();
399  if (item && item->isHidden()) {
400  return;
401  }
402  // just insert a new filter.
403  insertFilter(new MailFilter());
404  enableControls();
405 }
406 
407 void KMFilterListBox::slotCopy()
408 {
409  QListWidgetItem *item = mListWidget->currentItem();
410  if (!itemIsValid(item)) {
411  return;
412  }
413 
414  // make sure that all changes are written to the filter before we copy it
415  Q_EMIT applyWidgets();
416  auto itemFilter = static_cast<QListWidgetFilterItem *>(item);
417 
418  MailFilter *filter = itemFilter->filter();
419 
420  // enableControls should make sure this method is
421  // never called when no filter is selected.
422  Q_ASSERT(filter);
423 
424  // inserts a copy of the current filter.
425  auto copyFilter = new MailFilter(*filter);
426  copyFilter->generateRandomIdentifier();
427  copyFilter->setShortcut(QKeySequence());
428 
429  insertFilter(copyFilter);
430  enableControls();
431 }
432 
433 void KMFilterListBox::slotDelete()
434 {
435  QListWidgetItem *itemFirst = mListWidget->currentItem();
436  if (!itemIsValid(itemFirst)) {
437  return;
438  }
439  const bool uniqFilterSelected = (mListWidget->selectedItems().count() == 1);
440 
441  auto itemFilter = static_cast<QListWidgetFilterItem *>(itemFirst);
442  MailCommon::MailFilter *filter = itemFilter->filter();
443  const QString question =
444  uniqFilterSelected ? i18n("Do you want to remove the filter \"%1\"?", filter->pattern()->name()) : i18n("Do you want to remove selected filters?");
445  const QString dialogTitle = uniqFilterSelected ? i18n("Remove Filter") : i18n("Remove Filters");
446  const int answer = KMessageBox::questionYesNo(this, question, dialogTitle, KStandardGuiItem::remove(), KStandardGuiItem::cancel());
447  if (answer == KMessageBox::No) {
448  return;
449  }
450 
451  const int oIdxSelItem = mListWidget->currentRow();
453 
454  Q_EMIT resetWidgets();
455 
456  const QList<QListWidgetItem *> lstItems = mListWidget->selectedItems();
457  for (QListWidgetItem *item : lstItems) {
458  auto itemFilter = static_cast<QListWidgetFilterItem *>(item);
459 
460  MailCommon::MailFilter *filter = itemFilter->filter();
461  lst << filter;
462 
463  // remove the filter from both the listbox
464  QListWidgetItem *item2 = mListWidget->takeItem(mListWidget->row(item));
465  delete item2;
466  }
467  const int count = mListWidget->count();
468  // and set the new current item.
469  if (count > oIdxSelItem) {
470  // oIdxItem is still a valid index
471  mListWidget->setCurrentRow(oIdxSelItem);
472  } else if (count) {
473  // oIdxSelIdx is no longer valid, but the
474  // list box isn't empty
475  mListWidget->setCurrentRow(count - 1);
476  }
477 
478  // work around a problem when deleting the first item in a QListWidget:
479  // after takeItem, slotSelectionChanged is emitted with 1, but the row 0
480  // remains selected and another selectCurrentRow(0) does not trigger the
481  // selectionChanged signal
482  // (qt-copy as of 2006-12-22 / gungl)
483  if (oIdxSelItem == 0) {
484  slotSelected(0);
485  }
486  enableControls();
487 
488  Q_EMIT filterRemoved(lst);
489 }
490 
491 void KMFilterListBox::slotTop()
492 {
493  QList<QListWidgetItem *> listWidgetItem = selectedFilter();
494  if (listWidgetItem.isEmpty()) {
495  return;
496  }
497 
498  const int numberOfItem(listWidgetItem.count());
499  if ((numberOfItem == 1) && (mListWidget->currentRow() == 0)) {
500  qCDebug(MAILCOMMON_LOG) << "Called while the _topmost_ filter is selected, ignoring.";
501  return;
502  }
503 
504  QListWidgetItem *item = nullptr;
505  bool wasMoved = false;
506  for (int i = 0; i < numberOfItem; ++i) {
507  const int posItem = mListWidget->row(listWidgetItem.at(i));
508  if (posItem == i) {
509  continue;
510  }
511  item = mListWidget->takeItem(mListWidget->row(listWidgetItem.at(i)));
512  mListWidget->insertItem(i, item);
513  wasMoved = true;
514  }
515 
516  if (wasMoved) {
517  enableControls();
518  Q_EMIT filterOrderAltered();
519  }
520 }
521 
522 QList<QListWidgetItem *> KMFilterListBox::selectedFilter()
523 {
524  QList<QListWidgetItem *> listWidgetItem;
525  const int numberOfFilters = mListWidget->count();
526  for (int i = 0; i < numberOfFilters; ++i) {
527  if (mListWidget->item(i)->isSelected() && !mListWidget->item(i)->isHidden()) {
528  listWidgetItem << mListWidget->item(i);
529  }
530  }
531  return listWidgetItem;
532 }
533 
534 QStringList KMFilterListBox::selectedFilterId(SearchRule::RequiredPart &requiredPart, const QString &resource) const
535 {
536  QStringList listFilterId;
537  requiredPart = SearchRule::Envelope;
538  const int numberOfFilters = mListWidget->count();
539  for (int i = 0; i < numberOfFilters; ++i) {
540  if (mListWidget->item(i)->isSelected() && !mListWidget->item(i)->isHidden()) {
541  MailFilter *filter = static_cast<QListWidgetFilterItem *>(mListWidget->item(i))->filter();
542  if (!filter->isEmpty()) {
543  const QString id = filter->identifier();
544  listFilterId << id;
545  requiredPart = qMax(requiredPart, static_cast<QListWidgetFilterItem *>(mListWidget->item(i))->filter()->requiredPart(resource));
546  }
547  }
548  }
549  return listFilterId;
550 }
551 
552 void KMFilterListBox::slotBottom()
553 {
554  const QList<QListWidgetItem *> listWidgetItem = selectedFilter();
555  if (listWidgetItem.isEmpty()) {
556  return;
557  }
558 
559  const int numberOfElement(mListWidget->count());
560  const int numberOfItem(listWidgetItem.count());
561  if ((numberOfItem == 1) && (mListWidget->currentRow() == numberOfElement - 1)) {
562  qCDebug(MAILCOMMON_LOG) << "Called while the _last_ filter is selected, ignoring.";
563  return;
564  }
565 
566  QListWidgetItem *item = nullptr;
567  int j = 0;
568  bool wasMoved = false;
569  for (int i = numberOfItem - 1; i >= 0; --i, j++) {
570  const int posItem = mListWidget->row(listWidgetItem.at(i));
571  if (posItem == (numberOfElement - 1 - j)) {
572  continue;
573  }
574  item = mListWidget->takeItem(mListWidget->row(listWidgetItem.at(i)));
575  mListWidget->insertItem(numberOfElement - j, item);
576  wasMoved = true;
577  }
578 
579  if (wasMoved) {
580  enableControls();
581  Q_EMIT filterOrderAltered();
582  }
583 }
584 
585 void KMFilterListBox::slotUp()
586 {
587  const QList<QListWidgetItem *> listWidgetItem = selectedFilter();
588  if (listWidgetItem.isEmpty()) {
589  return;
590  }
591 
592  const int numberOfItem(listWidgetItem.count());
593  if ((numberOfItem == 1) && (mListWidget->currentRow() == 0)) {
594  qCDebug(MAILCOMMON_LOG) << "Called while the _topmost_ filter is selected, ignoring.";
595  return;
596  }
597  bool wasMoved = false;
598 
599  for (int i = 0; i < numberOfItem; ++i) {
600  const int posItem = mListWidget->row(listWidgetItem.at(i));
601  if (posItem == i) {
602  continue;
603  }
604  swapNeighbouringFilters(posItem, posItem - 1);
605  wasMoved = true;
606  }
607  if (wasMoved) {
608  enableControls();
609  Q_EMIT filterOrderAltered();
610  }
611 }
612 
613 void KMFilterListBox::slotDown()
614 {
615  const QList<QListWidgetItem *> listWidgetItem = selectedFilter();
616  if (listWidgetItem.isEmpty()) {
617  return;
618  }
619 
620  const int numberOfElement(mListWidget->count());
621  const int numberOfItem(listWidgetItem.count());
622  if ((numberOfItem == 1) && (mListWidget->currentRow() == numberOfElement - 1)) {
623  qCDebug(MAILCOMMON_LOG) << "Called while the _last_ filter is selected, ignoring.";
624  return;
625  }
626 
627  int j = 0;
628  bool wasMoved = false;
629  for (int i = numberOfItem - 1; i >= 0; --i, j++) {
630  const int posItem = mListWidget->row(listWidgetItem.at(i));
631  if (posItem == (numberOfElement - 1 - j)) {
632  continue;
633  }
634  swapNeighbouringFilters(posItem, posItem + 1);
635  wasMoved = true;
636  }
637 
638  if (wasMoved) {
639  enableControls();
640  Q_EMIT filterOrderAltered();
641  }
642 }
643 
644 void KMFilterListBox::slotRename()
645 {
646  QListWidgetItem *item = mListWidget->currentItem();
647  if (!itemIsValid(item)) {
648  return;
649  }
650  auto itemFilter = static_cast<QListWidgetFilterItem *>(item);
651 
652  bool okPressed = false;
653  MailFilter *filter = itemFilter->filter();
654 
655  // enableControls should make sure this method is
656  // never called when no filter is selected.
657  Q_ASSERT(filter);
658 
659  // allow empty names - those will turn auto-naming on again
661  i18n("Rename Filter"),
662  i18n("Rename filter \"%1\" to:\n(leave the field empty for automatic naming)", filter->pattern()->name()), /*label*/
664  filter->pattern()->name(), /* initial value */
665  &okPressed);
666 
667  if (!okPressed) {
668  return;
669  }
670 
671  if (newName.isEmpty()) {
672  // bait for slotUpdateFilterName to
673  // use automatic naming again.
674  filter->pattern()->setName(QStringLiteral("<>"));
675  filter->setAutoNaming(true);
676  } else {
677  filter->pattern()->setName(newName);
678  filter->setAutoNaming(false);
679  }
680 
681  slotUpdateFilterName();
682 
683  Q_EMIT filterUpdated(filter);
684 }
685 
686 void KMFilterListBox::enableControls()
687 {
688  const int currentIndex = mListWidget->currentRow();
689  const bool theFirst = (currentIndex == 0);
690  const int numberOfElement(mListWidget->count());
691  const bool theLast = (currentIndex >= numberOfElement - 1);
692  const bool aFilterIsSelected = (currentIndex >= 0);
693 
694  const int numberOfSelectedItem(mListWidget->selectedItems().count());
695  const bool uniqFilterSelected = (numberOfSelectedItem == 1);
696  const bool allItemSelected = (numberOfSelectedItem == numberOfElement);
697 
698  mBtnUp->setEnabled(aFilterIsSelected && ((uniqFilterSelected && !theFirst) || (!uniqFilterSelected)) && !allItemSelected);
699  mBtnDown->setEnabled(aFilterIsSelected && ((uniqFilterSelected && !theLast) || (!uniqFilterSelected)) && !allItemSelected);
700 
701  mBtnCopy->setEnabled(aFilterIsSelected && uniqFilterSelected);
702  mBtnDelete->setEnabled(aFilterIsSelected);
703  mBtnRename->setEnabled(aFilterIsSelected && uniqFilterSelected);
704 
705  mBtnTop->setEnabled(aFilterIsSelected && ((uniqFilterSelected && !theFirst) || (!uniqFilterSelected)) && !allItemSelected);
706 
707  mBtnBottom->setEnabled(aFilterIsSelected && ((uniqFilterSelected && !theLast) || (!uniqFilterSelected)) && !allItemSelected);
708  if (aFilterIsSelected) {
709  mListWidget->scrollToItem(mListWidget->currentItem());
710  }
711 }
712 
713 void KMFilterListBox::loadFilterList(bool createDummyFilter)
714 {
715  Q_ASSERT(mListWidget);
716  setEnabled(false);
717  Q_EMIT resetWidgets();
718  // we don't want the insertion to
719  // cause flicker in the edit widgets.
720  blockSignals(true);
721 
722  // clear both lists
723  mListWidget->clear();
724 
726  for (MailFilter *filter : filters) {
727  auto *item = new QListWidgetFilterItem(filter->pattern()->name(), mListWidget);
728  item->setFilter(new MailFilter(*filter));
729  mListWidget->addItem(item);
730  }
731 
732  blockSignals(false);
733  setEnabled(true);
734 
735  // create an empty filter when there's none, to avoid a completely
736  // disabled dialog (usability tests indicated that the new-filter
737  // button is too hard to find that way):
738  const int numberOfItem(mListWidget->count());
739  if (numberOfItem == 0) {
740  if (createDummyFilter) {
741  slotNew();
742  }
743  } else {
744  mListWidget->setCurrentRow(0);
745  }
746 
747  enableControls();
748 }
749 
750 void KMFilterListBox::insertFilter(MailFilter *aFilter)
751 {
752  // must be really a filter...
753  Q_ASSERT(aFilter);
754  const int currentIndex = mListWidget->currentRow();
755  // if mIdxSelItem < 0, QListBox::insertItem will append.
756  auto item = new QListWidgetFilterItem(aFilter->pattern()->name());
757  item->setFilter(aFilter);
758  mListWidget->insertItem(currentIndex, item);
759  mListWidget->clearSelection();
760  if (currentIndex < 0) {
761  mListWidget->setCurrentRow(mListWidget->count() - 1);
762  } else {
763  // insert just before selected
764  mListWidget->setCurrentRow(currentIndex);
765  }
766 
767  Q_EMIT filterCreated();
768  Q_EMIT filterOrderAltered();
769 }
770 
771 void KMFilterListBox::appendFilter(MailFilter *aFilter)
772 {
773  auto *item = new QListWidgetFilterItem(aFilter->pattern()->name(), mListWidget);
774 
775  item->setFilter(aFilter);
776  mListWidget->addItem(item);
777 
778  Q_EMIT filterCreated();
779 }
780 
781 void KMFilterListBox::swapNeighbouringFilters(int untouchedOne, int movedOne)
782 {
783  // must be neighbours...
784  Q_ASSERT(untouchedOne - movedOne == 1 || movedOne - untouchedOne == 1);
785 
786  // untouchedOne is at idx. to move it down(up),
787  // remove item at idx+(-)1 w/o deleting it.
788  QListWidgetItem *item = mListWidget->takeItem(movedOne);
789  // now selected item is at idx(idx-1), so
790  // insert the other item at idx, ie. above(below).
791  mListWidget->insertItem(untouchedOne, item);
792 }
793 
794 QListWidgetFilterItem::QListWidgetFilterItem(const QString &text, QListWidget *parent)
795  : QListWidgetItem(text, parent)
796 {
797 }
798 
799 QListWidgetFilterItem::~QListWidgetFilterItem()
800 {
801  delete mFilter;
802 }
803 
804 void QListWidgetFilterItem::setFilter(MailCommon::MailFilter *filter)
805 {
806  mFilter = filter;
807  setCheckState(filter->isEnabled() ? Qt::Checked : Qt::Unchecked);
808 }
809 
810 MailCommon::MailFilter *QListWidgetFilterItem::filter() const
811 {
812  return mFilter;
813 }
T & first()
void setFilters(const QVector< MailCommon::MailFilter * > &filters)
Replace the list of filters of the filter manager with the given list of filters.
std::shared_ptr< SearchRule > Ptr
Defines a pointer to a search rule.
Definition: searchrule.h:29
const QList< QKeySequence > & shortcut(StandardShortcut id)
Qt::CheckState checkState() const const
void activated()
int count(const T &value) const const
QString trimmed() const const
void append(const T &value)
void clicked(bool checked)
QIcon fromTheme(const QString &name)
The MailFilter class.
Definition: mailfilter.h:28
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QVector< MailCommon::MailFilter * > filters() const
Returns the filter list of the manager.
void currentRowChanged(int currentRow)
KGuiItem cancel()
void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row)
virtual bool eventFilter(QObject *watched, QEvent *event)
bool empty() const const
QString i18n(const char *text, const TYPE &arg...)
bool isHidden() const const
RequiredPart
Possible required parts.
Definition: searchrule.h:68
bool isEmpty() const const
static SearchRule::Ptr createInstance(const QByteArray &field=QByteArray(), Function function=FuncContains, const QString &contents=QString())
Creates a new search rule of a certain type by instantiating the appropriate subclass depending on th...
Definition: searchrule.cpp:75
static FilterManager * instance()
Returns the global filter manager object.
const T & at(int i) const const
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
bool isEmpty() const const
KGuiItem remove()
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void setName(const QString &newName)
Sets the name of the search pattern.
Key_Delete
void itemDoubleClicked(QListWidgetItem *item)
static FilterActionDict * filterActionDict()
Returns the global filter action dictionary.
SearchPattern * pattern()
Provides a reference to the internal pattern.
Definition: mailfilter.cpp:171
This class is an abstraction of a search over messages.
Definition: searchpattern.h:58
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void itemSelectionChanged()
QString name() const
Returns the name of the search pattern.
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
KJOBWIDGETS_EXPORT QWidget * window(KJob *job)
ButtonCode questionYesNo(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Notify)
void itemChanged(QListWidgetItem *item)
Auxiliary struct for FilterActionDict.
void accept()
The filter dialog.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Oct 1 2022 04:00:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.