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 */
65 void switchMessageSorting(SortOrder::MessageSorting messageSorting, SortOrder::SortDirection sortDirection, int logicalHeaderColumnIndex);
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 reset filter => get info before to call setFilter
171 const auto status = f->status();
172 const auto options = f->currentOptions();
173 const auto str = f->searchString();
174 setFilter(f);
175 d->quickSearchLine->searchEdit()->setText(str);
176 d->quickSearchLine->setSearchOptions(options);
177 d->quickSearchLine->setFilterMessageStatus(status);
178}
179
180void Widget::slotSaveFilter()
181{
182 if (d->mFilter) {
183 QPointer<FilterNameDialog> dlg = new FilterNameDialog(this);
184 dlg->setExistingFilterNames(FilterSavedManager::self()->existingFilterNames());
185 if (dlg->exec()) {
186 FilterSavedManager::self()->saveFilter(d->mFilter, dlg->filterName(), dlg->iconName());
187 }
188 delete dlg;
189 } else {
190 KMessageBox::information(this, i18n("No filter defined."), i18nc("@title:window", "Create Filter"));
191 }
192}
193
195{
196 QLineEdit *const lineEdit = d->quickSearchLine->searchEdit();
197 if (!show) {
198 // if we hide it we do not want to apply the filter,
199 // otherwise someone is maybe stuck with x new emails
200 // and cannot read it because of filter
201 lineEdit->clear();
202
203 // we focus the message list if we hide the searchbar
204 d->mView->setFocus(Qt::OtherFocusReason);
205 } else {
206 // on show: we focus the lineedit for fast filtering
208 if (d->mFilter) {
209 resetFilter();
210 }
211 }
212 d->quickSearchLine->changeQuicksearchVisibility(show);
213 MessageListSettings::self()->setShowQuickSearch(show);
214}
215
217{
218 if (d->mStatusFilterComboPopulationInProgress) {
219 return;
220 }
221 d->mStatusFilterComboPopulationInProgress = true;
222 QComboBox *tagFilterComboBox = d->quickSearchLine->tagFilterComboBox();
223 d->mCurrentStatusFilterIndex = (tagFilterComboBox->currentIndex() != -1) ? tagFilterComboBox->currentIndex() : 0;
224 disconnect(tagFilterComboBox, &QComboBox::currentIndexChanged, this, &Widget::statusSelected);
225
226 tagFilterComboBox->clear();
227
229}
230
231void Widget::addMessageTagItem(const QPixmap &icon, const QString &text, const QVariant &data)
232{
233 d->quickSearchLine->tagFilterComboBox()->addItem(icon, text, data);
234}
235
237{
238 d->quickSearchLine->updateComboboxVisibility();
239 connect(d->quickSearchLine->tagFilterComboBox(), &QComboBox::currentIndexChanged, this, &Widget::statusSelected);
240 d->quickSearchLine->tagFilterComboBox()->setCurrentIndex(
241 d->mCurrentStatusFilterIndex >= d->quickSearchLine->tagFilterComboBox()->count() ? 0 : d->mCurrentStatusFilterIndex);
242 d->mStatusFilterComboPopulationInProgress = false;
243}
244
249
251{
252 return d->quickSearchLine->searchOptions();
253}
254
256{
257 if (d->mFilter) {
258 return d->mFilter->status();
259 }
260 return {};
261}
262
264{
265 if (d->mFilter) {
266 return d->mFilter->searchString();
267 }
268 return {};
269}
270
272{
273 if (d->mFilter) {
274 return d->mFilter->tagId();
275 }
276
277 return {};
278}
279
280void Widget::WidgetPrivate::setDefaultAggregationForStorageModel(const StorageModel *storageModel)
281{
282 const Aggregation *opt = Manager::instance()->aggregationForStorageModel(storageModel, &mStorageUsesPrivateAggregation);
283
284 Q_ASSERT(opt);
285
286 delete mAggregation;
287 mAggregation = new Aggregation(*opt);
288
289 mView->setAggregation(mAggregation);
290
291 mLastAggregationId = opt->id();
292}
293
294void Widget::WidgetPrivate::setDefaultThemeForStorageModel(const StorageModel *storageModel)
295{
296 const Theme *opt = Manager::instance()->themeForStorageModel(storageModel, &mStorageUsesPrivateTheme);
297
298 Q_ASSERT(opt);
299
300 delete mTheme;
301 mTheme = new Theme(*opt);
302
303 mView->setTheme(mTheme);
304
305 mLastThemeId = opt->id();
306}
307
308void Widget::WidgetPrivate::checkSortOrder(const StorageModel *storageModel)
309{
310 if (storageModel && mAggregation && !mSortOrder.validForAggregation(mAggregation)) {
311 qCDebug(MESSAGELIST_LOG) << "Could not restore sort order for folder" << storageModel->id();
312 mSortOrder = SortOrder::defaultForAggregation(mAggregation, mSortOrder);
313
314 // Change the global sort order if the sort order didn't fit the global aggregation.
315 // Otherwise, if it is a per-folder aggregation, make the sort order per-folder too.
316 if (mStorageUsesPrivateAggregation) {
317 mStorageUsesPrivateSortOrder = true;
318 }
319 if (mStorageModel) {
320 Manager::instance()->saveSortOrderForStorageModel(storageModel, mSortOrder, mStorageUsesPrivateSortOrder);
321 }
322 switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
323 }
324}
325
326void Widget::WidgetPrivate::setDefaultSortOrderForStorageModel(const StorageModel *storageModel)
327{
328 // Load the sort order from config and update column headers
329 mSortOrder = Manager::instance()->sortOrderForStorageModel(storageModel, &mStorageUsesPrivateSortOrder);
330 switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
331 checkSortOrder(storageModel);
332}
333
334void Widget::saveCurrentSelection()
335{
336 if (d->mStorageModel) {
337 // Save the current selection
338 MessageItem *lastSelectedMessageItem = d->mView->currentMessageItem(false);
339 if (lastSelectedMessageItem) {
340 d->mStorageModel->savePreSelectedMessage(lastSelectedMessageItem->uniqueId());
341 }
342 }
343}
344
346{
347 if (storageModel == d->mStorageModel) {
348 return; // nuthin to do here
349 }
350
351 d->setDefaultAggregationForStorageModel(storageModel);
352 d->setDefaultThemeForStorageModel(storageModel);
353 d->setDefaultSortOrderForStorageModel(storageModel);
354
355 if (!d->quickSearchLine->searchEdit()->locked()) {
356 if (d->mSearchTimer) {
357 d->mSearchTimer->stop();
358 delete d->mSearchTimer;
359 d->mSearchTimer = nullptr;
360 }
361
362 d->quickSearchLine->searchEdit()->clear();
363
364 if (d->mFilter) {
365 resetFilter();
366 }
367 }
368 StorageModel *oldModel = d->mStorageModel;
369
370 d->mStorageModel = storageModel;
371 d->mView->setStorageModel(d->mStorageModel, preSelectionMode);
372
373 delete oldModel;
374
375 d->quickSearchLine->tagFilterComboBox()->setEnabled(d->mStorageModel);
376 d->quickSearchLine->searchEdit()->setEnabled(d->mStorageModel);
377 d->quickSearchLine->setContainsOutboundMessages(d->mStorageModel->containsOutboundMessages());
378}
379
381{
382 return d->mStorageModel;
383}
384
386{
387 return d->quickSearchLine->searchEdit();
388}
389
391{
392 return d->mView;
393}
394
395void Widget::themeMenuAboutToShow()
396{
397 if (!d->mStorageModel) {
398 return;
399 }
400
401 auto menu = qobject_cast<QMenu *>(sender());
402 if (!menu) {
403 return;
404 }
405 themeMenuAboutToShow(menu);
406}
407
408void Widget::themeMenuAboutToShow(QMenu *menu)
409{
410 menu->clear();
411
412 menu->addSection(i18n("Theme"));
413
414 auto grp = new QActionGroup(menu);
415
416 QList<Theme *> sortedThemes = Manager::instance()->themes().values();
417
418 QAction *act;
419
420 std::sort(sortedThemes.begin(), sortedThemes.end(), MessageList::Core::Theme::compareName);
421
422 for (const auto theme : std::as_const(sortedThemes)) {
423 act = menu->addAction(theme->name());
424 act->setCheckable(true);
425 grp->addAction(act);
426 act->setChecked(d->mLastThemeId == theme->id());
427 act->setData(QVariant(theme->id()));
428 connect(act, &QAction::triggered, this, &Widget::themeSelected);
429 }
430
431 menu->addSeparator();
432
433 act = menu->addAction(i18n("Configure..."));
434 connect(act, &QAction::triggered, this, &Widget::configureThemes);
435}
436
437void Widget::setPrivateSortOrderForStorage()
438{
439 if (!d->mStorageModel) {
440 return;
441 }
442
443 d->mStorageUsesPrivateSortOrder = !d->mStorageUsesPrivateSortOrder;
444
445 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
446}
447
448void Widget::configureThemes()
449{
450 auto dialog = new Utils::ConfigureThemesDialog(window());
451 dialog->selectTheme(d->mLastThemeId);
452 dialog->show();
453}
454
455void Widget::themeSelected(bool)
456{
457 if (!d->mStorageModel) {
458 return; // nuthin to do
459 }
460
461 auto act = qobject_cast<QAction *>(sender());
462 if (!act) {
463 return;
464 }
465
466 QVariant v = act->data();
467 const QString id = v.toString();
468
469 if (id.isEmpty()) {
470 return;
471 }
472
473 const Theme *opt = Manager::instance()->theme(id);
474
475 delete d->mTheme;
476 d->mTheme = new Theme(*opt);
477
478 d->mView->setTheme(d->mTheme);
479
480 d->mLastThemeId = opt->id();
481
482 // mStorageUsesPrivateTheme = false;
483
484 Manager::instance()->saveThemeForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateTheme);
485
486 d->mView->reload();
487}
488
489void Widget::aggregationMenuAboutToShow()
490{
491 auto menu = qobject_cast<QMenu *>(sender());
492 if (!menu) {
493 return;
494 }
495 aggregationMenuAboutToShow(menu);
496}
497
498void Widget::aggregationMenuAboutToShow(QMenu *menu)
499{
500 menu->clear();
501
502 menu->addSection(i18n("Aggregation"));
503
504 auto grp = new QActionGroup(menu);
505
506 QList<Aggregation *> sortedAggregations = Manager::instance()->aggregations().values();
507
508 QAction *act;
509
510 std::sort(sortedAggregations.begin(), sortedAggregations.end(), MessageList::Core::Aggregation::compareName);
511
512 for (const auto agg : std::as_const(sortedAggregations)) {
513 act = menu->addAction(agg->name());
514 act->setCheckable(true);
515 grp->addAction(act);
516 act->setChecked(d->mLastAggregationId == agg->id());
517 act->setData(QVariant(agg->id()));
518 connect(act, &QAction::triggered, this, &Widget::aggregationSelected);
519 }
520
521 menu->addSeparator();
522
523 act = menu->addAction(i18n("Configure..."));
524 act->setData(QVariant(QString()));
525 connect(act, &QAction::triggered, this, &Widget::aggregationSelected);
526}
527
528void Widget::aggregationSelected(bool)
529{
530 auto act = qobject_cast<QAction *>(sender());
531 if (!act) {
532 return;
533 }
534
535 QVariant v = act->data();
536 QString id = v.toString();
537
538 if (id.isEmpty()) {
539 auto dialog = new Utils::ConfigureAggregationsDialog(window());
540 dialog->selectAggregation(d->mLastAggregationId);
541 dialog->show();
542 return;
543 }
544
545 if (!d->mStorageModel) {
546 return; // nuthin to do
547 }
548
549 const Aggregation *opt = Manager::instance()->aggregation(id);
550
551 delete d->mAggregation;
552 d->mAggregation = new Aggregation(*opt);
553
554 d->mView->setAggregation(d->mAggregation);
555
556 d->mLastAggregationId = opt->id();
557
558 // mStorageUsesPrivateAggregation = false;
559
560 Manager::instance()->saveAggregationForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateAggregation);
561
562 // The sort order might not be valid anymore for this aggregation
563 d->checkSortOrder(d->mStorageModel);
564
565 d->mView->reload();
566}
567
568void Widget::sortOrderMenuAboutToShow()
569{
570 if (!d->mAggregation) {
571 return;
572 }
573
574 auto menu = qobject_cast<QMenu *>(sender());
575 if (!menu) {
576 return;
577 }
578 sortOrderMenuAboutToShow(menu);
579}
580
581void Widget::sortOrderMenuAboutToShow(QMenu *menu)
582{
583 menu->clear();
584
585 menu->addSection(i18n("Message Sort Order"));
586
587 QActionGroup *grp;
588 QAction *act;
590
591 grp = new QActionGroup(menu);
592
593 options = SortOrder::enumerateMessageSortingOptions(d->mAggregation->threading());
594 for (const auto &opt : std::as_const(options)) {
595 act = menu->addAction(opt.first);
596 act->setCheckable(true);
597 grp->addAction(act);
598 act->setChecked(d->mSortOrder.messageSorting() == opt.second);
599 act->setData(QVariant(opt.second));
600 }
601
602 connect(grp, &QActionGroup::triggered, this, &Widget::messageSortingSelected);
603
604 options = SortOrder::enumerateMessageSortDirectionOptions(d->mSortOrder.messageSorting());
605
606 if (options.size() >= 2) {
607 menu->addSection(i18n("Message Sort Direction"));
608
609 grp = new QActionGroup(menu);
610 for (const auto &opt : std::as_const(options)) {
611 act = menu->addAction(opt.first);
612 act->setCheckable(true);
613 grp->addAction(act);
614 act->setChecked(d->mSortOrder.messageSortDirection() == opt.second);
615 act->setData(QVariant(opt.second));
616 }
617
618 connect(grp, &QActionGroup::triggered, this, &Widget::messageSortDirectionSelected);
619 }
620
621 options = SortOrder::enumerateGroupSortingOptions(d->mAggregation->grouping());
622
623 if (options.size() >= 2) {
624 menu->addSection(i18n("Group Sort Order"));
625
626 grp = new QActionGroup(menu);
627 for (const auto &opt : std::as_const(options)) {
628 act = menu->addAction(opt.first);
629 act->setCheckable(true);
630 grp->addAction(act);
631 act->setChecked(d->mSortOrder.groupSorting() == opt.second);
632 act->setData(QVariant(opt.second));
633 }
634
635 connect(grp, &QActionGroup::triggered, this, &Widget::groupSortingSelected);
636 }
637
638 options = SortOrder::enumerateGroupSortDirectionOptions(d->mAggregation->grouping(), d->mSortOrder.groupSorting());
639
640 if (options.size() >= 2) {
641 menu->addSection(i18n("Group Sort Direction"));
642
643 grp = new QActionGroup(menu);
644 for (const auto &opt : std::as_const(options)) {
645 act = menu->addAction(opt.first);
646 act->setCheckable(true);
647 grp->addAction(act);
648 act->setChecked(d->mSortOrder.groupSortDirection() == opt.second);
649 act->setData(QVariant(opt.second));
650 }
651
652 connect(grp, &QActionGroup::triggered, this, &Widget::groupSortDirectionSelected);
653 }
654
655 menu->addSeparator();
656 act = menu->addAction(i18n("Folder Always Uses This Sort Order"));
657 act->setCheckable(true);
658 act->setChecked(d->mStorageUsesPrivateSortOrder);
659 connect(act, &QAction::triggered, this, &Widget::setPrivateSortOrderForStorage);
660}
661
662void Widget::WidgetPrivate::switchMessageSorting(SortOrder::MessageSorting messageSorting, SortOrder::SortDirection sortDirection, int logicalHeaderColumnIndex)
663{
664 mSortOrder.setMessageSorting(messageSorting);
665 mSortOrder.setMessageSortDirection(sortDirection);
666
667 // If the logicalHeaderColumnIndex was specified then we already know which
668 // column we should set the sort indicator to. If it wasn't specified (it's -1)
669 // then we need to find it out in the theme.
670
671 if (logicalHeaderColumnIndex == -1) {
672 // try to find the specified message sorting in the theme columns
673 const auto columns = mTheme->columns();
674 int idx = 0;
675
676 // First try with a well defined message sorting.
677
678 for (const auto column : std::as_const(columns)) {
679 if (!mView->header()->isSectionHidden(idx)) {
680 if (column->messageSorting() == messageSorting) {
681 // found a visible column with this message sorting
682 logicalHeaderColumnIndex = idx;
683 break;
684 }
685 }
686 ++idx;
687 }
688
689 // if still not found, try again with a wider range
690 if (logicalHeaderColumnIndex == -1) {
691 idx = 0;
692 for (const auto column : std::as_const(columns)) {
693 if (!mView->header()->isSectionHidden(idx)) {
694 if (((column->messageSorting() == SortOrder::SortMessagesBySenderOrReceiver)
695 || (column->messageSorting() == SortOrder::SortMessagesByReceiver) || (column->messageSorting() == SortOrder::SortMessagesBySender))
696 && ((messageSorting == SortOrder::SortMessagesBySenderOrReceiver) || (messageSorting == SortOrder::SortMessagesByReceiver)
697 || (messageSorting == SortOrder::SortMessagesBySender))) {
698 // found a visible column with this message sorting
699 logicalHeaderColumnIndex = idx;
700 break;
701 }
702 }
703 ++idx;
704 }
705 }
706 }
707
708 if (logicalHeaderColumnIndex == -1) {
709 // not found: either not a column-based sorting or the related column is hidden
710 mView->header()->setSortIndicatorShown(false);
711 return;
712 }
713
714 mView->header()->setSortIndicatorShown(true);
715
716 if (sortDirection == SortOrder::Ascending) {
717 mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::AscendingOrder);
718 } else {
719 mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::DescendingOrder);
720 }
721}
722
723void Widget::messageSortingSelected(QAction *action)
724{
725 if (!d->mAggregation) {
726 return;
727 }
728 if (!action) {
729 return;
730 }
731
732 if (!d->mStorageModel) {
733 return;
734 }
735
736 bool ok;
737 auto ord = static_cast<SortOrder::MessageSorting>(action->data().toInt(&ok));
738
739 if (!ok) {
740 return;
741 }
742
743 d->switchMessageSorting(ord, d->mSortOrder.messageSortDirection(), -1);
744 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
745
746 d->mView->reload();
747}
748
749void Widget::messageSortDirectionSelected(QAction *action)
750{
751 if (!d->mAggregation) {
752 return;
753 }
754 if (!action) {
755 return;
756 }
757 if (!d->mStorageModel) {
758 return;
759 }
760
761 bool ok;
762 auto ord = static_cast<SortOrder::SortDirection>(action->data().toInt(&ok));
763
764 if (!ok) {
765 return;
766 }
767
768 d->switchMessageSorting(d->mSortOrder.messageSorting(), ord, -1);
769 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
770
771 d->mView->reload();
772}
773
774void Widget::groupSortingSelected(QAction *action)
775{
776 if (!d->mAggregation) {
777 return;
778 }
779 if (!action) {
780 return;
781 }
782
783 if (!d->mStorageModel) {
784 return;
785 }
786
787 bool ok;
788 auto ord = static_cast<SortOrder::GroupSorting>(action->data().toInt(&ok));
789
790 if (!ok) {
791 return;
792 }
793
794 d->mSortOrder.setGroupSorting(ord);
795 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
796
797 d->mView->reload();
798}
799
800void Widget::groupSortDirectionSelected(QAction *action)
801{
802 if (!d->mAggregation) {
803 return;
804 }
805 if (!action) {
806 return;
807 }
808 if (!d->mStorageModel) {
809 return;
810 }
811
812 bool ok;
813 auto ord = static_cast<SortOrder::SortDirection>(action->data().toInt(&ok));
814
815 if (!ok) {
816 return;
817 }
818
819 d->mSortOrder.setGroupSortDirection(ord);
820 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
821
822 d->mView->reload();
823}
824
825void Widget::setFilter(Filter *filter)
826{
827 resetFilter();
828 d->mFilter = filter;
829 d->mView->model()->setFilter(d->mFilter);
830}
831
832void Widget::resetFilter()
833{
834 delete d->mFilter;
835 d->mFilter = nullptr;
836 d->mView->model()->setFilter(nullptr);
837 d->quickSearchLine->resetFilter();
838 d->quickSearchWarning->animatedHide();
839}
840
842{
843 if (!d->mTheme) {
844 return;
845 }
846
847 if (!d->mAggregation) {
848 return;
849 }
850
851 if (logicalIndex >= d->mTheme->columns().count()) {
852 return;
853 }
854
855 if (!d->mStorageModel) {
856 return;
857 }
858
859 auto column = d->mTheme->column(logicalIndex);
860 if (!column) {
861 return; // should never happen...
862 }
863
864 if (column->messageSorting() == SortOrder::NoMessageSorting) {
865 return; // this is a null op.
866 }
867
868 if (d->mSortOrder.messageSorting() == column->messageSorting()) {
869 // switch sort direction
870 if (d->mSortOrder.messageSortDirection() == SortOrder::Ascending) {
871 d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Descending, logicalIndex);
872 } else {
873 d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Ascending, logicalIndex);
874 }
875 } else {
876 // keep sort direction but switch sort order
877 d->switchMessageSorting(column->messageSorting(), d->mSortOrder.messageSortDirection(), logicalIndex);
878 }
879 Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
880
881 d->mView->reload();
882}
883
885{
886 d->setDefaultThemeForStorageModel(d->mStorageModel);
887
888 d->mView->reload();
889}
890
892{
893 d->setDefaultAggregationForStorageModel(d->mStorageModel);
894 d->checkSortOrder(d->mStorageModel);
895
896 d->mView->reload();
897}
898
900{
901 // nothing here: must be overridden in derived classes
903}
904
905void Widget::tagIdSelected(const QVariant &data)
906{
907 const QString tagId = data.toString();
908
909 if (tagId.isEmpty()) {
910 if (d->mFilter) {
911 if (d->mFilter->isEmpty()) {
912 resetFilter();
913 return;
914 }
915 }
916 } else {
917 if (!d->mFilter) {
918 d->mFilter = new Filter();
919 }
920 d->mFilter->setTagId(tagId);
921 }
922
923 d->mView->model()->setFilter(d->mFilter);
924}
925
926void Widget::setLockTab(bool lock)
927{
928 d->mLockTab = lock;
929 if (lock) {
930 d->tabLockedWarning->animatedShow();
931 } else {
932 d->tabLockedWarning->animatedHide();
933 }
934}
935
936bool Widget::isLocked() const
937{
938 return d->mLockTab;
939}
940
941void Widget::statusSelected(int index)
942{
943 if (index == 0) {
944 resetFilter();
945 return;
946 }
947 tagIdSelected(d->quickSearchLine->tagFilterComboBox()->itemData(index));
948 d->mView->model()->setFilter(d->mFilter);
949}
950
951void Widget::searchEditTextEdited()
952{
953 // This slot is called whenever the user edits the search QLineEdit.
954 // Since the user is likely to type more than one character
955 // so we start the real search after a short delay in order to catch
956 // multiple textEdited() signals.
957
958 if (!d->mSearchTimer) {
959 d->mSearchTimer = new QTimer(this);
960 connect(d->mSearchTimer, &QTimer::timeout, this, &Widget::searchTimerFired);
961 } else {
962 d->mSearchTimer->stop(); // eventually
963 }
964
965 d->mSearchTimer->setSingleShot(true);
966 d->mSearchTimer->start(1s);
967}
968
969void Widget::slotStatusButtonsClicked()
970{
971 // We also arbitrarily set tagId to an empty string, though we *could* allow filtering
972 // by status AND tag...
973 if (d->mFilter) {
974 d->mFilter->setTagId(QString());
975 }
976
977 auto lst = d->quickSearchLine->status();
978 if (lst.isEmpty()) {
979 if (d->mFilter) {
980 d->mFilter->setStatus(lst);
981 if (d->mFilter->isEmpty()) {
982 qCDebug(MESSAGELIST_LOG) << " RESET FILTER";
983 resetFilter();
984 return;
985 }
986 }
987 } else {
988 // don't have this status bit
989 if (!d->mFilter) {
990 d->mFilter = new Filter();
991 }
992 d->mFilter->setStatus(lst);
993 }
994
995 d->mView->model()->setFilter(d->mFilter);
996}
997
998void Widget::searchTimerFired()
999{
1000 // A search is pending.
1001
1002 if (d->mSearchTimer) {
1003 d->mSearchTimer->stop();
1004 }
1005
1006 if (!d->mFilter) {
1007 d->mFilter = new Filter();
1008 }
1009
1010 const QString text = d->quickSearchLine->searchEdit()->text();
1011
1012 if (!text.isEmpty()) {
1013 d->quickSearchLine->addCompletionItem(text);
1014 }
1015
1016 d->mFilter->setCurrentFolder(d->mCurrentFolder);
1017 d->mFilter->setSearchString(text, d->quickSearchLine->searchOptions());
1018 d->quickSearchWarning->setSearchText(text);
1019 if (d->mFilter->isEmpty()) {
1020 resetFilter();
1021 return;
1022 }
1023
1024 d->mView->model()->setFilter(d->mFilter);
1025}
1026
1027void Widget::searchEditClearButtonClicked()
1028{
1029 if (!d->mFilter) {
1030 return;
1031 }
1032
1033 resetFilter();
1034
1035 d->mView->scrollTo(d->mView->currentIndex(), QAbstractItemView::PositionAtCenter);
1036}
1037
1041
1045
1049
1053
1055{
1056}
1057
1061
1065
1069
1073
1075{
1076 Q_UNUSED(msg)
1077 Q_UNUSED(set)
1078 Q_UNUSED(clear)
1079}
1080
1081void Widget::focusQuickSearch(const QString &selectedText)
1082{
1083 d->quickSearchLine->focusQuickSearch(selectedText);
1084}
1085
1087{
1088 return d->mView->isThreaded();
1089}
1090
1092{
1093 return d->mView->selectionEmpty();
1094}
1095
1096Akonadi::Collection Widget::currentFolder() const
1097{
1098 return d->mCurrentFolder;
1099}
1100
1102{
1103 if (!d->mLockTab) {
1104 d->mCurrentFolder = collection;
1105 d->searchCollectionIndexingWarning->setCollection(collection);
1106 }
1107}
1108
1109bool Widget::searchEditHasFocus() const
1110{
1111 return d->quickSearchLine->searchEdit()->hasFocus();
1112}
1113
1114#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
const QString & id() const
Returns the unique id of this OptionSet.
Definition optionset.h:51
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:849
void setAggregation(const Aggregation *aggregation)
Sets the aggregation for this view.
Definition view.cpp:249
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.
Q_SCRIPTABLE CaptureState status()
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...
void setCheckable(bool)
void setChecked(bool)
QVariant data() const const
void setData(const QVariant &data)
void triggered(bool checked)
QAction * addAction(QAction *action)
void clear()
void currentIndexChanged(int index)
void sectionClicked(int logicalIndex)
void clear()
iterator begin()
iterator end()
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 Fri Jul 26 2024 11:54:19 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.