Mailcommon

kmfilterlistbox.cpp
1/*
2 SPDX-FileCopyrightText: 2014-2024 Laurent Montel <montel@kde.org>
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//=============================================================================
31using namespace MailCommon;
32KMFilterListBox::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(i18nc("@info:tooltip", "Copy"));
142 mBtnDelete->setToolTip(i18nc("@info:tooltip", "Delete"));
143 mBtnRename->setToolTip(i18nc("@info:tooltip", "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
198KMFilterListBox::~KMFilterListBox() = default;
199
200bool 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
212bool 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
224void 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
236void 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
246void 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
263void 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
306void KMFilterListBox::slotAccepted()
307{
308 applyFilterChanged(true);
309}
310
311void KMFilterListBox::slotApplied()
312{
313 applyFilterChanged(false);
314}
315
316void 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 QList<MailFilter *> newFilters = filtersForSaving(closeAfterSaving, wasCanceled);
328 if (!wasCanceled) {
330 }
331}
332
333QList<MailFilter *> KMFilterListBox::filtersForSaving(bool closeAfterSaving, bool &wasCanceled) const
334{
335 Q_EMIT const_cast<KMFilterListBox *>(this)->applyWidgets(); // signals aren't const
336 QList<MailFilter *> filters;
337 QStringList emptyFilters;
338 QList<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
371void KMFilterListBox::slotSelectionChanged()
372{
373 if (mListWidget->selectedItems().count() > 1) {
374 Q_EMIT resetWidgets();
375 }
376 enableControls();
377}
378
379void 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
396void 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
407void 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
433void 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 ? i18nc("@title:window", "Remove Filter") : i18nc("@title:window", "Remove Filters");
446 const int answer = KMessageBox::questionTwoActions(this, question, dialogTitle, KStandardGuiItem::remove(), KStandardGuiItem::cancel());
447 if (answer == KMessageBox::ButtonCode::SecondaryAction) {
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
491void 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
522QList<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
534QStringList 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
552void 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
585void 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
613void 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
644void 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
686void 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
713void 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
750void 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
771void 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
781void 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
794QListWidgetFilterItem::QListWidgetFilterItem(const QString &text, QListWidget *parent)
795 : QListWidgetItem(text, parent)
796{
797}
798
799QListWidgetFilterItem::~QListWidgetFilterItem()
800{
801 delete mFilter;
802}
803
804void QListWidgetFilterItem::setFilter(MailCommon::MailFilter *filter)
805{
806 mFilter = filter;
808}
809
810MailCommon::MailFilter *QListWidgetFilterItem::filter() const
811{
812 return mFilter;
813}
814
815#include "moc_kmfilterlistbox.cpp"
static FilterActionDict * filterActionDict()
Returns the global filter action dictionary.
void setFilters(const QList< MailCommon::MailFilter * > &filters)
Replace the list of filters of the filter manager with the given list of filters.
QList< MailCommon::MailFilter * > filters() const
Returns the filter list of the manager.
static FilterManager * instance()
Returns the global filter manager object.
The MailFilter class.
Definition mailfilter.h:29
SearchPattern * pattern()
Provides a reference to the internal pattern.
This class is an abstraction of a search over messages.
void setName(const QString &newName)
Sets the name of the search pattern.
QString name() const
Returns the name of the search pattern.
std::shared_ptr< SearchRule > Ptr
Defines a pointer to a search rule.
Definition searchrule.h:29
RequiredPart
Possible required parts.
Definition searchrule.h:68
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...
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
KGuiItem remove()
KGuiItem cancel()
const QList< QKeySequence > & shortcut(StandardShortcut id)
The filter dialog.
void clicked(bool checked)
void rowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
virtual bool event(QEvent *e) override
QIcon fromTheme(const QString &name)
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool empty() const const
T & first()
bool isEmpty() const const
void addItem(QListWidgetItem *item)
void clear()
QListWidgetItem * currentItem() const const
void currentRowChanged(int currentRow)
void insertItem(int row, QListWidgetItem *item)
QListWidgetItem * item(int row) const const
void itemChanged(QListWidgetItem *item)
void itemDoubleClicked(QListWidgetItem *item)
void itemSelectionChanged()
int row(const QListWidgetItem *item) const const
void scrollToItem(const QListWidgetItem *item, QAbstractItemView::ScrollHint hint)
QList< QListWidgetItem * > selectedItems() const const
QListWidgetItem * takeItem(int row)
Qt::CheckState checkState() const const
bool isHidden() const const
bool isSelected() const const
void setCheckState(Qt::CheckState state)
T value(const Key &key) const const
Q_EMITQ_EMIT
bool blockSignals(bool block)
virtual bool eventFilter(QObject *watched, QEvent *event)
void activated()
QString arg(Args &&... args) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString trimmed() const const
Key_Delete
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setEnabled(bool)
QWidget * window() const const
Auxiliary struct for FilterActionDict.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:02:04 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.