Messagelib

widgetbase.cpp
1/******************************************************************************
2 *
3 * SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 *
7 *******************************************************************************/
8
9#include "core/widgetbase.h"
10#include "core/aggregation.h"
11#include "core/filter.h"
12#include "core/filtersavedmanager.h"
13#include "core/manager.h"
14#include "core/messageitem.h"
15#include "core/model.h"
16#include "core/optionset.h"
17#include "core/storagemodelbase.h"
18#include "core/theme.h"
19#include "core/view.h"
20#include "core/widgets/quicksearchwarning.h"
21#include "core/widgets/searchcollectionindexingwarning.h"
22#include "core/widgets/tablockedwarning.h"
23#include "messagelistsettings.h"
24#include "widgets/searchlinestatus.h"
25
26#include "utils/configureaggregationsdialog.h"
27#include "utils/configurethemesdialog.h"
28
29#include <QActionGroup>
30#include <QHeaderView>
31#include <QPointer>
32#include <QTimer>
33#include <QVBoxLayout>
34#include <QVariant>
35
36#include "messagelist_debug.h"
37#include <KLocalizedString>
38#include <KMessageBox>
39#include <QAction>
40#include <QComboBox>
41#include <QMenu>
42
43#include "core/widgets/filternamedialog.h"
44#include <Akonadi/Collection>
45#include <Akonadi/MessageStatus>
46#include <chrono>
47
48using namespace std::chrono_literals;
49
50using namespace MessageList::Core;
51
52class Widget::WidgetPrivate
53{
54public:
55 WidgetPrivate(Widget *owner)
56 : q(owner)
57 {
58 }
59
60 /**
61 * Small helper for switching SortOrder::MessageSorting and SortOrder::SortDirection
62 * on the fly.
63 * After doing this, the sort indicator in the header is updated.
64 */
66
67 /**
68 * Check if our sort order can still be used with this aggregation.
69 * This can happen if the global aggregation changed, for example we can now
70 * have "most recent in subtree" sorting with an aggregation without threading.
71 * If this happens, reset to the default sort order and don't use the global sort
72 * order.
73 */
74 void checkSortOrder(const StorageModel *storageModel);
75
76 void setDefaultAggregationForStorageModel(const StorageModel *storageModel);
77 void setDefaultThemeForStorageModel(const StorageModel *storageModel);
78 void setDefaultSortOrderForStorageModel(const StorageModel *storageModel);
79 void applyFilter();
80
81 Widget *const q;
82
83 QuickSearchWarning *quickSearchWarning = nullptr;
84 SearchCollectionIndexingWarning *searchCollectionIndexingWarning = nullptr;
85 TabLockedWarning *tabLockedWarning = nullptr;
86 QuickSearchLine *quickSearchLine = nullptr;
87 View *mView = nullptr;
88 QString mLastAggregationId;
89 QString mLastThemeId;
90 QTimer *mSearchTimer = nullptr;
91 StorageModel *mStorageModel = nullptr; ///< The currently displayed storage. The storage itself
92 /// is owned by MessageList::Widget.
93 Aggregation *mAggregation = nullptr; ///< The currently set aggregation mode, a deep copy
94 Theme *mTheme = nullptr; ///< The currently set theme, a deep copy
95 SortOrder mSortOrder; ///< The currently set sort order
96 Filter *mFilter = nullptr; ///< The currently applied filter, owned by us.
97 bool mStorageUsesPrivateTheme = false; ///< true if the current folder does not use the global theme
98 bool mStorageUsesPrivateAggregation = false; ///< true if the current folder does not use the global aggregation
99 bool mStorageUsesPrivateSortOrder = false; ///< true if the current folder does not use the global sort order
100 Akonadi::Collection mCurrentFolder; ///< The current folder
101 int mCurrentStatusFilterIndex = 0;
102 bool mStatusFilterComboPopulationInProgress = false;
103 bool mLockTab = false;
104};
105
106Widget::Widget(QWidget *pParent)
107 : QWidget(pParent)
108 , d(new WidgetPrivate(this))
109{
110 Manager::registerWidget(this);
111 connect(Manager::instance(), &Manager::aggregationsChanged, this, &Widget::aggregationsChanged);
112 connect(Manager::instance(), &Manager::themesChanged, this, &Widget::themesChanged);
113
114 setAutoFillBackground(true);
115 setObjectName(QLatin1StringView("messagelistwidget"));
116
117 auto g = new QVBoxLayout(this);
118 g->setContentsMargins({});
119 g->setSpacing(0);
120
121 d->quickSearchLine = new QuickSearchLine;
122 d->quickSearchLine->setObjectName(QLatin1StringView("quicksearchline"));
123 connect(d->quickSearchLine, &QuickSearchLine::clearButtonClicked, this, &Widget::searchEditClearButtonClicked);
124
125 connect(d->quickSearchLine, &QuickSearchLine::searchEditTextEdited, this, &Widget::searchEditTextEdited);
126 connect(d->quickSearchLine, &QuickSearchLine::searchOptionChanged, this, &Widget::searchEditTextEdited);
127 connect(d->quickSearchLine, &QuickSearchLine::statusButtonsClicked, this, &Widget::slotStatusButtonsClicked);
128 connect(d->quickSearchLine, &QuickSearchLine::forceLostFocus, this, &Widget::forceLostFocus);
129 connect(d->quickSearchLine, &QuickSearchLine::saveFilter, this, &Widget::slotSaveFilter);
130 connect(d->quickSearchLine, &QuickSearchLine::activateFilter, this, &Widget::slotActivateFilter);
131 g->addWidget(d->quickSearchLine, 0);
132 d->quickSearchWarning = new QuickSearchWarning(this);
133 g->addWidget(d->quickSearchWarning, 0);
134 d->searchCollectionIndexingWarning = new SearchCollectionIndexingWarning(this);
135 g->addWidget(d->searchCollectionIndexingWarning, 0);
136
137 d->tabLockedWarning = new TabLockedWarning(this);
138 g->addWidget(d->tabLockedWarning, 0);
139 connect(d->tabLockedWarning, &TabLockedWarning::unlockTabRequested, this, [this]() {
140 setLockTab(false);
141 Q_EMIT unlockTabRequested();
142 // Fix icon!
143 });
144
145 d->mView = new View(this);
146 d->mView->setFrameStyle(QFrame::NoFrame);
147 d->mView->setSortOrder(&d->mSortOrder);
148 d->mView->setObjectName(QLatin1StringView("messagealistview"));
149 g->addWidget(d->mView, 1);
150
152 d->mSearchTimer = nullptr;
153}
154
155Widget::~Widget()
156{
157 d->mView->setStorageModel(nullptr);
158
159 Manager::unregisterWidget(this);
160
161 delete d->mSearchTimer;
162 delete d->mTheme;
163 delete d->mAggregation;
164 delete d->mFilter;
165 delete d->mStorageModel;
166}
167
168void Widget::slotActivateFilter(Filter *f)
169{
170 setFilter(f);
171 d->quickSearchLine->searchEdit()->setText(f->searchString());
172 d->quickSearchLine->setSearchOptions(f->currentOptions());
173 d->quickSearchLine->setFilterMessageStatus(f->status());
174}
175
176void Widget::slotSaveFilter()
177{
178 if (d->mFilter) {
179 QPointer<FilterNameDialog> dlg = new FilterNameDialog(this);
180 dlg->setExistingFilterNames(FilterSavedManager::self()->existingFilterNames());
181 if (dlg->exec()) {
182 FilterSavedManager::self()->saveFilter(d->mFilter, dlg->filterName(), dlg->iconName());
183 }
184 delete dlg;
185 } else {
186 KMessageBox::information(this, i18n("No filter defined."), i18nc("@title:window", "Create Filter"));
187 }
188}
189
191{
192 QLineEdit *const lineEdit = d->quickSearchLine->searchEdit();
193 if (!show) {
194 // if we hide it we do not want to apply the filter,
195 // otherwise someone is maybe stuck with x new emails
196 // and cannot read it because of filter
197 lineEdit->clear();
198
199 // we focus the message list if we hide the searchbar
200 d->mView->setFocus(Qt::OtherFocusReason);
201 } else {
202 // on show: we focus the lineedit for fast filtering
204 if (d->mFilter) {
205 resetFilter();
206 }
207 }
208 d->quickSearchLine->changeQuicksearchVisibility(show);
209 MessageListSettings::self()->setShowQuickSearch(show);
210}
211
213{
214 if (d->mStatusFilterComboPopulationInProgress) {
215 return;
216 }
217 d->mStatusFilterComboPopulationInProgress = true;
218 QComboBox *tagFilterComboBox = d->quickSearchLine->tagFilterComboBox();
219 d->mCurrentStatusFilterIndex = (tagFilterComboBox->currentIndex() != -1) ? tagFilterComboBox->currentIndex() : 0;
220 disconnect(tagFilterComboBox, &QComboBox::currentIndexChanged, this, &Widget::statusSelected);
221
222 tagFilterComboBox->clear();
223
225}
226
227void Widget::addMessageTagItem(const QPixmap &icon, const QString &text, const QVariant &data)
228{
229 d->quickSearchLine->tagFilterComboBox()->addItem(icon, text, data);
230}
231
233{
234 d->quickSearchLine->updateComboboxVisibility();
235 connect(d->quickSearchLine->tagFilterComboBox(), &QComboBox::currentIndexChanged, this, &Widget::statusSelected);
236 d->quickSearchLine->tagFilterComboBox()->setCurrentIndex(
237 d->mCurrentStatusFilterIndex >= d->quickSearchLine->tagFilterComboBox()->count() ? 0 : d->mCurrentStatusFilterIndex);
238 d->mStatusFilterComboPopulationInProgress = false;
239}
240
245
246MessageList::Core::QuickSearchLine::SearchOptions Widget::currentOptions() const
247{
248 return d->quickSearchLine->searchOptions();
249}
250
252{
253 if (d->mFilter) {
254 return d->mFilter->status();
255 }
256 return {};
257}
258
260{
261 if (d->mFilter) {
262 return d->mFilter->searchString();
263 }
264 return {};
265}
266
268{
269 if (d->mFilter) {
270 return d->mFilter->tagId();
271 }
272
273 return {};
274}
275
276void Widget::WidgetPrivate::setDefaultAggregationForStorageModel(const StorageModel *storageModel)
277{
278 const Aggregation *opt = Manager::instance()->aggregationForStorageModel(storageModel, &mStorageUsesPrivateAggregation);
279
280 Q_ASSERT(opt);
281
282 delete mAggregation;
283 mAggregation = new Aggregation(*opt);
284
285 mView->setAggregation(mAggregation);
286
287 mLastAggregationId = opt->id();
288}
289
290void Widget::WidgetPrivate::setDefaultThemeForStorageModel(const StorageModel *storageModel)
291{
292 const Theme *opt = Manager::instance()->themeForStorageModel(storageModel, &mStorageUsesPrivateTheme);
293
294 Q_ASSERT(opt);
295
296 delete mTheme;
297 mTheme = new Theme(*opt);
298
299 mView->setTheme(mTheme);
300
301 mLastThemeId = opt->id();
302}
303
304void Widget::WidgetPrivate::checkSortOrder(const StorageModel *storageModel)
305{
306 if (storageModel && mAggregation && !mSortOrder.validForAggregation(mAggregation)) {
307 qCDebug(MESSAGELIST_LOG) << "Could not restore sort order for folder" << storageModel->id();
308 mSortOrder = SortOrder::defaultForAggregation(mAggregation, mSortOrder);
309
310 // Change the global sort order if the sort order didn't fit the global aggregation.
311 // Otherwise, if it is a per-folder aggregation, make the sort order per-folder too.
312 if (mStorageUsesPrivateAggregation) {
313 mStorageUsesPrivateSortOrder = true;
314 }
315 if (mStorageModel) {
316 Manager::instance()->saveSortOrderForStorageModel(storageModel, mSortOrder, mStorageUsesPrivateSortOrder);
317 }
318 switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
319 }
320}
321
322void Widget::WidgetPrivate::setDefaultSortOrderForStorageModel(const StorageModel *storageModel)
323{
324 // Load the sort order from config and update column headers
325 mSortOrder = Manager::instance()->sortOrderForStorageModel(storageModel, &mStorageUsesPrivateSortOrder);
326 switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
327 checkSortOrder(storageModel);
328}
329
330void Widget::saveCurrentSelection()
331{
332 if (d->mStorageModel) {
333 // Save the current selection
334 MessageItem *lastSelectedMessageItem = d->mView->currentMessageItem(false);
336 d->mStorageModel->savePreSelectedMessage(lastSelectedMessageItem->uniqueId());
337 }
338 }
339}
340
342{
343 if (storageModel == d->mStorageModel) {
344 return; // nuthin to do here
345 }
346
347 d->setDefaultAggregationForStorageModel(storageModel);
348 d->setDefaultThemeForStorageModel(storageModel);
349 d->setDefaultSortOrderForStorageModel(storageModel);
350
351 if (!d->quickSearchLine->searchEdit()->locked()) {
352 if (d->mSearchTimer) {
353 d->mSearchTimer->stop();
354 delete d->mSearchTimer;
355 d->mSearchTimer = nullptr;
356 }
357
358 d->quickSearchLine->searchEdit()->clear();
359
360 if (d->mFilter) {
361 resetFilter();
362 }
363 }
364 StorageModel *oldModel = d->mStorageModel;
365
366 d->mStorageModel = storageModel;
367 d->mView->setStorageModel(d->mStorageModel, preSelectionMode);
368
369 delete oldModel;
370
371 d->quickSearchLine->tagFilterComboBox()->setEnabled(d->mStorageModel);
372 d->quickSearchLine->searchEdit()->setEnabled(d->mStorageModel);
373 d->quickSearchLine->setContainsOutboundMessages(d->mStorageModel->containsOutboundMessages());
374}
375
377{
378 return d->mStorageModel;
379}
380
382{
383 return d->quickSearchLine->searchEdit();
384}
385
387{
388 return d->mView;
389}
390
391void Widget::themeMenuAboutToShow()
392{
393 if (!d->mStorageModel) {
394 return;
395 }
396
397 auto menu = qobject_cast<QMenu *>(sender());
398 if (!menu) {
399 return;
400 }
401 themeMenuAboutToShow(menu);
402}
403
404void Widget::themeMenuAboutToShow(QMenu *menu)
405{
406 menu->clear();
407
408 menu->addSection(i18n("Theme"));
409
410 auto grp = new QActionGroup(menu);
411
412 QList<Theme *> sortedThemes = Manager::instance()->themes().values();
413
414 QAction *act;
415
416 std::sort(sortedThemes.begin(), sortedThemes.end(), MessageList::Core::Theme::compareName);
417
418 for (const auto theme : std::as_const(sortedThemes)) {
419 act = menu->addAction(theme->name());
420 act->setCheckable(true);
421 grp->addAction(act);
422 act->setChecked(d->mLastThemeId == theme->id());
423 act->setData(QVariant(theme->id()));
424 connect(act, &QAction::triggered, this, &Widget::themeSelected);
425 }
426
427 menu->addSeparator();
428
429 act = menu->addAction(i18n("Configure..."));
430 connect(act, &QAction::triggered, this, &Widget::configureThemes);
431}
432
433void Widget::setPrivateSortOrderForStorage()
434{
435 if (!d->mStorageModel) {
436 return;
437 }
438
439 d->mStorageUsesPrivateSortOrder = !d->mStorageUsesPrivateSortOrder;
440
441 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
442}
443
444void Widget::configureThemes()
445{
446 auto dialog = new Utils::ConfigureThemesDialog(window());
447 dialog->selectTheme(d->mLastThemeId);
448 dialog->show();
449}
450
451void Widget::themeSelected(bool)
452{
453 if (!d->mStorageModel) {
454 return; // nuthin to do
455 }
456
458 if (!act) {
459 return;
460 }
461
462 QVariant v = act->data();
463 const QString id = v.toString();
464
465 if (id.isEmpty()) {
466 return;
467 }
468
469 const Theme *opt = Manager::instance()->theme(id);
470
471 delete d->mTheme;
472 d->mTheme = new Theme(*opt);
473
474 d->mView->setTheme(d->mTheme);
475
476 d->mLastThemeId = opt->id();
477
478 // mStorageUsesPrivateTheme = false;
479
480 Manager::instance()->saveThemeForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateTheme);
481
482 d->mView->reload();
483}
484
485void Widget::aggregationMenuAboutToShow()
486{
487 auto menu = qobject_cast<QMenu *>(sender());
488 if (!menu) {
489 return;
490 }
491 aggregationMenuAboutToShow(menu);
492}
493
494void Widget::aggregationMenuAboutToShow(QMenu *menu)
495{
496 menu->clear();
497
498 menu->addSection(i18n("Aggregation"));
499
500 auto grp = new QActionGroup(menu);
501
502 QList<Aggregation *> sortedAggregations = Manager::instance()->aggregations().values();
503
504 QAction *act;
505
506 std::sort(sortedAggregations.begin(), sortedAggregations.end(), MessageList::Core::Aggregation::compareName);
507
508 for (const auto agg : std::as_const(sortedAggregations)) {
509 act = menu->addAction(agg->name());
510 act->setCheckable(true);
511 grp->addAction(act);
512 act->setChecked(d->mLastAggregationId == agg->id());
513 act->setData(QVariant(agg->id()));
514 connect(act, &QAction::triggered, this, &Widget::aggregationSelected);
515 }
516
517 menu->addSeparator();
518
519 act = menu->addAction(i18n("Configure..."));
520 act->setData(QVariant(QString()));
521 connect(act, &QAction::triggered, this, &Widget::aggregationSelected);
522}
523
524void Widget::aggregationSelected(bool)
525{
527 if (!act) {
528 return;
529 }
530
531 QVariant v = act->data();
532 QString id = v.toString();
533
534 if (id.isEmpty()) {
535 auto dialog = new Utils::ConfigureAggregationsDialog(window());
536 dialog->selectAggregation(d->mLastAggregationId);
537 dialog->show();
538 return;
539 }
540
541 if (!d->mStorageModel) {
542 return; // nuthin to do
543 }
544
545 const Aggregation *opt = Manager::instance()->aggregation(id);
546
547 delete d->mAggregation;
548 d->mAggregation = new Aggregation(*opt);
549
550 d->mView->setAggregation(d->mAggregation);
551
552 d->mLastAggregationId = opt->id();
553
554 // mStorageUsesPrivateAggregation = false;
555
556 Manager::instance()->saveAggregationForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateAggregation);
557
558 // The sort order might not be valid anymore for this aggregation
559 d->checkSortOrder(d->mStorageModel);
560
561 d->mView->reload();
562}
563
564void Widget::sortOrderMenuAboutToShow()
565{
566 if (!d->mAggregation) {
567 return;
568 }
569
570 auto menu = qobject_cast<QMenu *>(sender());
571 if (!menu) {
572 return;
573 }
574 sortOrderMenuAboutToShow(menu);
575}
576
577void Widget::sortOrderMenuAboutToShow(QMenu *menu)
578{
579 menu->clear();
580
581 menu->addSection(i18n("Message Sort Order"));
582
584 QAction *act;
586
587 grp = new QActionGroup(menu);
588
589 options = SortOrder::enumerateMessageSortingOptions(d->mAggregation->threading());
590 for (const auto &opt : std::as_const(options)) {
591 act = menu->addAction(opt.first);
592 act->setCheckable(true);
593 grp->addAction(act);
594 act->setChecked(d->mSortOrder.messageSorting() == opt.second);
595 act->setData(QVariant(opt.second));
596 }
597
598 connect(grp, &QActionGroup::triggered, this, &Widget::messageSortingSelected);
599
600 options = SortOrder::enumerateMessageSortDirectionOptions(d->mSortOrder.messageSorting());
601
602 if (options.size() >= 2) {
603 menu->addSection(i18n("Message Sort Direction"));
604
605 grp = new QActionGroup(menu);
606 for (const auto &opt : std::as_const(options)) {
607 act = menu->addAction(opt.first);
608 act->setCheckable(true);
609 grp->addAction(act);
610 act->setChecked(d->mSortOrder.messageSortDirection() == opt.second);
611 act->setData(QVariant(opt.second));
612 }
613
614 connect(grp, &QActionGroup::triggered, this, &Widget::messageSortDirectionSelected);
615 }
616
617 options = SortOrder::enumerateGroupSortingOptions(d->mAggregation->grouping());
618
619 if (options.size() >= 2) {
620 menu->addSection(i18n("Group Sort Order"));
621
622 grp = new QActionGroup(menu);
623 for (const auto &opt : std::as_const(options)) {
624 act = menu->addAction(opt.first);
625 act->setCheckable(true);
626 grp->addAction(act);
627 act->setChecked(d->mSortOrder.groupSorting() == opt.second);
628 act->setData(QVariant(opt.second));
629 }
630
631 connect(grp, &QActionGroup::triggered, this, &Widget::groupSortingSelected);
632 }
633
634 options = SortOrder::enumerateGroupSortDirectionOptions(d->mAggregation->grouping(), d->mSortOrder.groupSorting());
635
636 if (options.size() >= 2) {
637 menu->addSection(i18n("Group Sort Direction"));
638
639 grp = new QActionGroup(menu);
640 for (const auto &opt : std::as_const(options)) {
641 act = menu->addAction(opt.first);
642 act->setCheckable(true);
643 grp->addAction(act);
644 act->setChecked(d->mSortOrder.groupSortDirection() == opt.second);
645 act->setData(QVariant(opt.second));
646 }
647
648 connect(grp, &QActionGroup::triggered, this, &Widget::groupSortDirectionSelected);
649 }
650
651 menu->addSeparator();
652 act = menu->addAction(i18n("Folder Always Uses This Sort Order"));
653 act->setCheckable(true);
654 act->setChecked(d->mStorageUsesPrivateSortOrder);
655 connect(act, &QAction::triggered, this, &Widget::setPrivateSortOrderForStorage);
656}
657
658void Widget::WidgetPrivate::switchMessageSorting(SortOrder::MessageSorting messageSorting, SortOrder::SortDirection sortDirection, int logicalHeaderColumnIndex)
659{
660 mSortOrder.setMessageSorting(messageSorting);
661 mSortOrder.setMessageSortDirection(sortDirection);
662
663 // If the logicalHeaderColumnIndex was specified then we already know which
664 // column we should set the sort indicator to. If it wasn't specified (it's -1)
665 // then we need to find it out in the theme.
666
667 if (logicalHeaderColumnIndex == -1) {
668 // try to find the specified message sorting in the theme columns
669 const auto columns = mTheme->columns();
670 int idx = 0;
671
672 // First try with a well defined message sorting.
673
674 for (const auto column : std::as_const(columns)) {
675 if (!mView->header()->isSectionHidden(idx)) {
676 if (column->messageSorting() == messageSorting) {
677 // found a visible column with this message sorting
679 break;
680 }
681 }
682 ++idx;
683 }
684
685 // if still not found, try again with a wider range
686 if (logicalHeaderColumnIndex == -1) {
687 idx = 0;
688 for (const auto column : std::as_const(columns)) {
689 if (!mView->header()->isSectionHidden(idx)) {
690 if (((column->messageSorting() == SortOrder::SortMessagesBySenderOrReceiver)
691 || (column->messageSorting() == SortOrder::SortMessagesByReceiver) || (column->messageSorting() == SortOrder::SortMessagesBySender))
692 && ((messageSorting == SortOrder::SortMessagesBySenderOrReceiver) || (messageSorting == SortOrder::SortMessagesByReceiver)
693 || (messageSorting == SortOrder::SortMessagesBySender))) {
694 // found a visible column with this message sorting
696 break;
697 }
698 }
699 ++idx;
700 }
701 }
702 }
703
704 if (logicalHeaderColumnIndex == -1) {
705 // not found: either not a column-based sorting or the related column is hidden
706 mView->header()->setSortIndicatorShown(false);
707 return;
708 }
709
710 mView->header()->setSortIndicatorShown(true);
711
712 if (sortDirection == SortOrder::Ascending) {
713 mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::AscendingOrder);
714 } else {
715 mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::DescendingOrder);
716 }
717}
718
719void Widget::messageSortingSelected(QAction *action)
720{
721 if (!d->mAggregation) {
722 return;
723 }
724 if (!action) {
725 return;
726 }
727
728 if (!d->mStorageModel) {
729 return;
730 }
731
732 bool ok;
733 auto ord = static_cast<SortOrder::MessageSorting>(action->data().toInt(&ok));
734
735 if (!ok) {
736 return;
737 }
738
739 d->switchMessageSorting(ord, d->mSortOrder.messageSortDirection(), -1);
740 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
741
742 d->mView->reload();
743}
744
745void Widget::messageSortDirectionSelected(QAction *action)
746{
747 if (!d->mAggregation) {
748 return;
749 }
750 if (!action) {
751 return;
752 }
753 if (!d->mStorageModel) {
754 return;
755 }
756
757 bool ok;
758 auto ord = static_cast<SortOrder::SortDirection>(action->data().toInt(&ok));
759
760 if (!ok) {
761 return;
762 }
763
764 d->switchMessageSorting(d->mSortOrder.messageSorting(), ord, -1);
765 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
766
767 d->mView->reload();
768}
769
770void Widget::groupSortingSelected(QAction *action)
771{
772 if (!d->mAggregation) {
773 return;
774 }
775 if (!action) {
776 return;
777 }
778
779 if (!d->mStorageModel) {
780 return;
781 }
782
783 bool ok;
784 auto ord = static_cast<SortOrder::GroupSorting>(action->data().toInt(&ok));
785
786 if (!ok) {
787 return;
788 }
789
790 d->mSortOrder.setGroupSorting(ord);
791 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
792
793 d->mView->reload();
794}
795
796void Widget::groupSortDirectionSelected(QAction *action)
797{
798 if (!d->mAggregation) {
799 return;
800 }
801 if (!action) {
802 return;
803 }
804 if (!d->mStorageModel) {
805 return;
806 }
807
808 bool ok;
809 auto ord = static_cast<SortOrder::SortDirection>(action->data().toInt(&ok));
810
811 if (!ok) {
812 return;
813 }
814
815 d->mSortOrder.setGroupSortDirection(ord);
816 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
817
818 d->mView->reload();
819}
820
821void Widget::setFilter(Filter *filter)
822{
823 resetFilter();
824 d->mFilter = filter;
825 d->mView->model()->setFilter(d->mFilter);
826}
827
828void Widget::resetFilter()
829{
830 delete d->mFilter;
831 d->mFilter = nullptr;
832 d->mView->model()->setFilter(nullptr);
833 d->quickSearchLine->resetFilter();
834 d->quickSearchWarning->animatedHide();
835}
836
838{
839 if (!d->mTheme) {
840 return;
841 }
842
843 if (!d->mAggregation) {
844 return;
845 }
846
847 if (logicalIndex >= d->mTheme->columns().count()) {
848 return;
849 }
850
851 if (!d->mStorageModel) {
852 return;
853 }
854
855 auto column = d->mTheme->column(logicalIndex);
856 if (!column) {
857 return; // should never happen...
858 }
859
860 if (column->messageSorting() == SortOrder::NoMessageSorting) {
861 return; // this is a null op.
862 }
863
864 if (d->mSortOrder.messageSorting() == column->messageSorting()) {
865 // switch sort direction
866 if (d->mSortOrder.messageSortDirection() == SortOrder::Ascending) {
867 d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Descending, logicalIndex);
868 } else {
869 d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Ascending, logicalIndex);
870 }
871 } else {
872 // keep sort direction but switch sort order
873 d->switchMessageSorting(column->messageSorting(), d->mSortOrder.messageSortDirection(), logicalIndex);
874 }
875 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
876
877 d->mView->reload();
878}
879
881{
882 d->setDefaultThemeForStorageModel(d->mStorageModel);
883
884 d->mView->reload();
885}
886
888{
889 d->setDefaultAggregationForStorageModel(d->mStorageModel);
890 d->checkSortOrder(d->mStorageModel);
891
892 d->mView->reload();
893}
894
896{
897 // nothing here: must be overridden in derived classes
899}
900
901void Widget::tagIdSelected(const QVariant &data)
902{
903 const QString tagId = data.toString();
904
905 if (tagId.isEmpty()) {
906 if (d->mFilter) {
907 if (d->mFilter->isEmpty()) {
908 resetFilter();
909 return;
910 }
911 }
912 } else {
913 if (!d->mFilter) {
914 d->mFilter = new Filter();
915 }
916 d->mFilter->setTagId(tagId);
917 }
918
919 d->mView->model()->setFilter(d->mFilter);
920}
921
922void Widget::setLockTab(bool lock)
923{
924 d->mLockTab = lock;
925 if (lock) {
926 d->tabLockedWarning->animatedShow();
927 } else {
928 d->tabLockedWarning->animatedHide();
929 }
930}
931
932bool Widget::isLocked() const
933{
934 return d->mLockTab;
935}
936
937void Widget::statusSelected(int index)
938{
939 if (index == 0) {
940 resetFilter();
941 return;
942 }
943 tagIdSelected(d->quickSearchLine->tagFilterComboBox()->itemData(index));
944 d->mView->model()->setFilter(d->mFilter);
945}
946
947void Widget::searchEditTextEdited()
948{
949 // This slot is called whenever the user edits the search QLineEdit.
950 // Since the user is likely to type more than one character
951 // so we start the real search after a short delay in order to catch
952 // multiple textEdited() signals.
953
954 if (!d->mSearchTimer) {
955 d->mSearchTimer = new QTimer(this);
956 connect(d->mSearchTimer, &QTimer::timeout, this, &Widget::searchTimerFired);
957 } else {
958 d->mSearchTimer->stop(); // eventually
959 }
960
961 d->mSearchTimer->setSingleShot(true);
962 d->mSearchTimer->start(1s);
963}
964
965void Widget::slotStatusButtonsClicked()
966{
967 // We also arbitrarily set tagId to an empty string, though we *could* allow filtering
968 // by status AND tag...
969 if (d->mFilter) {
970 d->mFilter->setTagId(QString());
971 }
972
973 auto lst = d->quickSearchLine->status();
974 if (lst.isEmpty()) {
975 if (d->mFilter) {
976 d->mFilter->setStatus(lst);
977 if (d->mFilter->isEmpty()) {
978 qCDebug(MESSAGELIST_LOG) << " RESET FILTER";
979 resetFilter();
980 return;
981 }
982 }
983 } else {
984 // don't have this status bit
985 if (!d->mFilter) {
986 d->mFilter = new Filter();
987 }
988 d->mFilter->setStatus(lst);
989 }
990
991 d->mView->model()->setFilter(d->mFilter);
992}
993
994void Widget::searchTimerFired()
995{
996 // A search is pending.
997
998 if (d->mSearchTimer) {
999 d->mSearchTimer->stop();
1000 }
1001
1002 if (!d->mFilter) {
1003 d->mFilter = new Filter();
1004 }
1005
1006 const QString text = d->quickSearchLine->searchEdit()->text();
1007
1008 if (!text.isEmpty()) {
1009 d->quickSearchLine->addCompletionItem(text);
1010 }
1011
1012 d->mFilter->setCurrentFolder(d->mCurrentFolder);
1013 d->mFilter->setSearchString(text, d->quickSearchLine->searchOptions());
1014 d->quickSearchWarning->setSearchText(text);
1015 if (d->mFilter->isEmpty()) {
1016 resetFilter();
1017 return;
1018 }
1019
1020 d->mView->model()->setFilter(d->mFilter);
1021}
1022
1023void Widget::searchEditClearButtonClicked()
1024{
1025 if (!d->mFilter) {
1026 return;
1027 }
1028
1029 resetFilter();
1030
1031 d->mView->scrollTo(d->mView->currentIndex(), QAbstractItemView::PositionAtCenter);
1032}
1033
1037
1041
1045
1049
1051{
1052}
1053
1057
1061
1065
1069
1076
1077void Widget::focusQuickSearch(const QString &selectedText)
1078{
1079 d->quickSearchLine->focusQuickSearch(selectedText);
1080}
1081
1083{
1084 return d->mView->isThreaded();
1085}
1086
1088{
1089 return d->mView->selectionEmpty();
1090}
1091
1092Akonadi::Collection Widget::currentFolder() const
1093{
1094 return d->mCurrentFolder;
1095}
1096
1098{
1099 if (!d->mLockTab) {
1100 d->mCurrentFolder = collection;
1101 d->searchCollectionIndexingWarning->setCollection(collection);
1102 }
1103}
1104
1105bool Widget::searchEditHasFocus() const
1106{
1107 return d->quickSearchLine->searchEdit()->hasFocus();
1108}
1109
1110#include "moc_widgetbase.cpp"
constexpr bool isEmpty() const
A set of aggregation options that can be applied to the MessageList::Model in a single shot.
Definition aggregation.h:29
The MessageItem class.
Definition messageitem.h:35
The QuickSearchLine class.
A class which holds information about sorting, e.g.
Definition sortorder.h:23
SortDirection
The "generic" sort direction: used for groups and for messages If you add values here please look at ...
Definition sortorder.h:50
static QList< QPair< QString, int > > enumerateGroupSortingOptions(Aggregation::Grouping g)
Enumerates the group sorting options compatible with the specified Grouping.
Definition sortorder.cpp:98
static QList< QPair< QString, int > > enumerateMessageSortDirectionOptions(MessageSorting ms)
Enumerates the available message sorting directions for the specified MessageSorting option.
Definition sortorder.cpp:80
MessageSorting
The available message sorting options.
Definition sortorder.h:60
@ NoMessageSorting
Don't sort the messages at all.
Definition sortorder.h:61
@ SortMessagesBySenderOrReceiver
Sort the messages by sender or receiver.
Definition sortorder.h:64
@ SortMessagesBySender
Sort the messages by sender.
Definition sortorder.h:65
@ SortMessagesByReceiver
Sort the messages by receiver.
Definition sortorder.h:66
static QList< QPair< QString, int > > enumerateMessageSortingOptions(Aggregation::Threading t)
Enumerates the message sorting options compatible with the specified Threading setting.
Definition sortorder.cpp:60
GroupSorting
How to sort the groups If you add values here please look at the implementations of the enumerate* fu...
Definition sortorder.h:35
static SortOrder defaultForAggregation(const Aggregation *aggregation, SortOrder oldSortOrder)
Returns the default sort order for the given aggregation.
static QList< QPair< QString, int > > enumerateGroupSortDirectionOptions(Aggregation::Grouping g, GroupSorting groupSorting)
Enumerates the group sort direction options compatible with the specified Grouping and GroupSorting.
The QAbstractItemModel based interface that you need to provide for your storage to work with Message...
virtual QString id() const =0
Returns an unique id for this Storage collection.
The Theme class defines the visual appearance of the MessageList.
Definition theme.h:48
The MessageList::View is the real display of the message list.
Definition view.h:47
MessageItem * currentMessageItem(bool selectIfNeeded=true) const
Returns the current MessageItem (that is bound to current StorageModel).
Definition view.cpp:851
void setAggregation(const Aggregation *aggregation)
Sets the aggregation for this view.
Definition view.cpp:251
Provides a widget which has the messagelist and the most important helper widgets,...
Definition widgetbase.h:41
virtual void viewDragMoveEvent(QDragMoveEvent *e)
This is called by View when a drag move event is received.
bool isThreaded() const
Returns true if the current Aggregation is threaded, false otherwise (or if there is no current Aggre...
virtual void fillMessageTagCombo()
Called when the "Message Status/Tag" filter menu is opened by the user.
virtual void viewMessageStatusChangeRequest(MessageItem *msg, Akonadi::MessageStatus set, Akonadi::MessageStatus clear)
This is called by View when a message item is manipulated by the user in a way that it's status shoul...
View * view() const
Returns the View attached to this Widget.
bool selectionEmpty() const
Fast function that determines if the selection is empty.
void changeQuicksearchVisibility(bool)
Shows or hides the quicksearch field, the filter combobox and the toolbutton for advanced search.
StorageModel * storageModel() const
Returns the StorageModel currently set.
virtual void viewDropEvent(QDropEvent *e)
This is called by View when a drop event is received.
void slotViewHeaderSectionClicked(int logicalIndex)
Handles header section clicks switching the Aggregation MessageSorting on-the-fly.
virtual void viewMessageListContextPopupRequest(const QList< MessageItem * > &selectedItems, const QPoint &globalPos)
This is called by View when a message is right clicked.
void setCurrentFolder(const Akonadi::Collection &collection)
Sets the current folder.
virtual void viewSelectionChanged()
This is called by View when selection changes.
virtual void viewMessageActivated(MessageItem *msg)
This is called by View when a message is double-clicked or activated by other input means.
virtual void viewStartDragRequest()
This is called by View when a drag can possibly be started.
virtual void viewGroupHeaderContextPopupRequest(GroupHeaderItem *group, const QPoint &globalPos)
This is called by View when a group header is right clicked.
void setCurrentStatusFilterItem()
Must be called by fillMessageTagCombo()
void populateStatusFilterCombo()
This is called to setup the status filter's QComboBox.
QLineEdit * quickSearch() const
Returns the search line of this widget.
void aggregationsChanged()
This is called by Manager when the option sets stored within have changed.
void themesChanged()
This is called by Manager when the option sets stored within have changed.
QString currentFilterTagId() const
Returns the id of the MessageItem::Tag currently set in the quicksearch field.
Core::MessageItem * currentMessageItem() const
Returns the current MessageItem in the current folder.
void setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode=PreSelectLastSelected)
Sets the storage model for this Widget.
QList< Akonadi::MessageStatus > currentFilterStatus() const
Returns the Akonadi::MessageStatus in the current quicksearch field.
virtual void viewMessageSelected(MessageItem *msg)
This is called by View when a message is single-clicked (thus selected and made current)
virtual void viewDragEnterEvent(QDragEnterEvent *e)
This is called by View when a drag enter event is received.
void focusQuickSearch(const QString &selectedText)
Sets the focus on the quick search line of the currently active tab.
QString currentFilterSearchString() const
Returns the search term in the current quicksearch field.
The Akonadi specific implementation of the Core::StorageModel.
The dialog used for configuring MessageList::Aggregation sets.
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)
The implementation independent part of the MessageList library.
Definition aggregation.h:22
PreSelectionMode
Pre-selection is the action of automatically selecting a message just after the folder has finished l...
QVariant data() const const
void triggered(bool checked)
void clear()
void currentIndexChanged(int index)
void sectionClicked(int logicalIndex)
void clear()
qsizetype size() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addSection(const QIcon &icon, const QString &text)
QAction * addSeparator()
void clear()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
T qobject_cast(QObject *object)
QObject * sender() const const
void setObjectName(QAnyStringView name)
bool isEmpty() const const
OtherFocusReason
AscendingOrder
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
int toInt(bool *ok) const const
QString toString() const const
void setFocus()
void show()
QWidget * window() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.