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

KDE's Doxygen guidelines are available online.