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

KDE's Doxygen guidelines are available online.