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

KDE's Doxygen guidelines are available online.