File indexing completed on 2024-12-15 04:54:43

0001 /******************************************************************************
0002  *
0003  *  SPDX-FileCopyrightText: 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  *
0007  *******************************************************************************/
0008 
0009 #include "core/widgetbase.h"
0010 #include "core/aggregation.h"
0011 #include "core/filter.h"
0012 #include "core/filtersavedmanager.h"
0013 #include "core/manager.h"
0014 #include "core/messageitem.h"
0015 #include "core/model.h"
0016 #include "core/optionset.h"
0017 #include "core/storagemodelbase.h"
0018 #include "core/theme.h"
0019 #include "core/view.h"
0020 #include "core/widgets/quicksearchwarning.h"
0021 #include "core/widgets/searchcollectionindexingwarning.h"
0022 #include "core/widgets/tablockedwarning.h"
0023 #include "messagelistsettings.h"
0024 #include "widgets/searchlinestatus.h"
0025 
0026 #include "utils/configureaggregationsdialog.h"
0027 #include "utils/configurethemesdialog.h"
0028 
0029 #include <QActionGroup>
0030 #include <QHeaderView>
0031 #include <QPointer>
0032 #include <QTimer>
0033 #include <QVBoxLayout>
0034 #include <QVariant>
0035 
0036 #include "messagelist_debug.h"
0037 #include <KLocalizedString>
0038 #include <KMessageBox>
0039 #include <QAction>
0040 #include <QComboBox>
0041 #include <QMenu>
0042 
0043 #include "core/widgets/filternamedialog.h"
0044 #include <Akonadi/Collection>
0045 #include <Akonadi/MessageStatus>
0046 #include <chrono>
0047 
0048 using namespace std::chrono_literals;
0049 
0050 using namespace MessageList::Core;
0051 
0052 class Widget::WidgetPrivate
0053 {
0054 public:
0055     WidgetPrivate(Widget *owner)
0056         : q(owner)
0057     {
0058     }
0059 
0060     /**
0061      * Small helper for switching SortOrder::MessageSorting and SortOrder::SortDirection
0062      * on the fly.
0063      * After doing this, the sort indicator in the header is updated.
0064      */
0065     void switchMessageSorting(SortOrder::MessageSorting messageSorting, SortOrder::SortDirection sortDirection, int logicalHeaderColumnIndex);
0066 
0067     /**
0068      * Check if our sort order can still be used with this aggregation.
0069      * This can happen if the global aggregation changed, for example we can now
0070      * have "most recent in subtree" sorting with an aggregation without threading.
0071      * If this happens, reset to the default sort order and don't use the global sort
0072      * order.
0073      */
0074     void checkSortOrder(const StorageModel *storageModel);
0075 
0076     void setDefaultAggregationForStorageModel(const StorageModel *storageModel);
0077     void setDefaultThemeForStorageModel(const StorageModel *storageModel);
0078     void setDefaultSortOrderForStorageModel(const StorageModel *storageModel);
0079     void applyFilter();
0080 
0081     Widget *const q;
0082 
0083     QuickSearchWarning *quickSearchWarning = nullptr;
0084     SearchCollectionIndexingWarning *searchCollectionIndexingWarning = nullptr;
0085     TabLockedWarning *tabLockedWarning = nullptr;
0086     QuickSearchLine *quickSearchLine = nullptr;
0087     View *mView = nullptr;
0088     QString mLastAggregationId;
0089     QString mLastThemeId;
0090     QTimer *mSearchTimer = nullptr;
0091     StorageModel *mStorageModel = nullptr; ///< The currently displayed storage. The storage itself
0092     ///  is owned by MessageList::Widget.
0093     Aggregation *mAggregation = nullptr; ///< The currently set aggregation mode, a deep copy
0094     Theme *mTheme = nullptr; ///< The currently set theme, a deep copy
0095     SortOrder mSortOrder; ///< The currently set sort order
0096     Filter *mFilter = nullptr; ///< The currently applied filter, owned by us.
0097     bool mStorageUsesPrivateTheme = false; ///< true if the current folder does not use the global theme
0098     bool mStorageUsesPrivateAggregation = false; ///< true if the current folder does not use the global aggregation
0099     bool mStorageUsesPrivateSortOrder = false; ///< true if the current folder does not use the global sort order
0100     Akonadi::Collection mCurrentFolder; ///< The current folder
0101     int mCurrentStatusFilterIndex = 0;
0102     bool mStatusFilterComboPopulationInProgress = false;
0103     bool mLockTab = false;
0104 };
0105 
0106 Widget::Widget(QWidget *pParent)
0107     : QWidget(pParent)
0108     , d(new WidgetPrivate(this))
0109 {
0110     Manager::registerWidget(this);
0111     connect(Manager::instance(), &Manager::aggregationsChanged, this, &Widget::aggregationsChanged);
0112     connect(Manager::instance(), &Manager::themesChanged, this, &Widget::themesChanged);
0113 
0114     setAutoFillBackground(true);
0115     setObjectName(QLatin1StringView("messagelistwidget"));
0116 
0117     auto g = new QVBoxLayout(this);
0118     g->setContentsMargins({});
0119     g->setSpacing(0);
0120 
0121     d->quickSearchLine = new QuickSearchLine;
0122     d->quickSearchLine->setObjectName(QLatin1StringView("quicksearchline"));
0123     connect(d->quickSearchLine, &QuickSearchLine::clearButtonClicked, this, &Widget::searchEditClearButtonClicked);
0124 
0125     connect(d->quickSearchLine, &QuickSearchLine::searchEditTextEdited, this, &Widget::searchEditTextEdited);
0126     connect(d->quickSearchLine, &QuickSearchLine::searchOptionChanged, this, &Widget::searchEditTextEdited);
0127     connect(d->quickSearchLine, &QuickSearchLine::statusButtonsClicked, this, &Widget::slotStatusButtonsClicked);
0128     connect(d->quickSearchLine, &QuickSearchLine::forceLostFocus, this, &Widget::forceLostFocus);
0129     connect(d->quickSearchLine, &QuickSearchLine::saveFilter, this, &Widget::slotSaveFilter);
0130     connect(d->quickSearchLine, &QuickSearchLine::activateFilter, this, &Widget::slotActivateFilter);
0131     g->addWidget(d->quickSearchLine, 0);
0132     d->quickSearchWarning = new QuickSearchWarning(this);
0133     g->addWidget(d->quickSearchWarning, 0);
0134     d->searchCollectionIndexingWarning = new SearchCollectionIndexingWarning(this);
0135     g->addWidget(d->searchCollectionIndexingWarning, 0);
0136 
0137     d->tabLockedWarning = new TabLockedWarning(this);
0138     g->addWidget(d->tabLockedWarning, 0);
0139     connect(d->tabLockedWarning, &TabLockedWarning::unlockTabRequested, this, [this]() {
0140         setLockTab(false);
0141         Q_EMIT unlockTabRequested();
0142         // Fix icon!
0143     });
0144 
0145     d->mView = new View(this);
0146     d->mView->setFrameStyle(QFrame::NoFrame);
0147     d->mView->setSortOrder(&d->mSortOrder);
0148     d->mView->setObjectName(QLatin1StringView("messagealistview"));
0149     g->addWidget(d->mView, 1);
0150 
0151     connect(d->mView->header(), &QHeaderView::sectionClicked, this, &Widget::slotViewHeaderSectionClicked);
0152     d->mSearchTimer = nullptr;
0153 }
0154 
0155 Widget::~Widget()
0156 {
0157     d->mView->setStorageModel(nullptr);
0158 
0159     Manager::unregisterWidget(this);
0160 
0161     delete d->mSearchTimer;
0162     delete d->mTheme;
0163     delete d->mAggregation;
0164     delete d->mFilter;
0165     delete d->mStorageModel;
0166 }
0167 
0168 void Widget::slotActivateFilter(Filter *f)
0169 {
0170     setFilter(f);
0171     d->quickSearchLine->searchEdit()->setText(f->searchString());
0172     d->quickSearchLine->setSearchOptions(f->currentOptions());
0173     d->quickSearchLine->setFilterMessageStatus(f->status());
0174 }
0175 
0176 void Widget::slotSaveFilter()
0177 {
0178     if (d->mFilter) {
0179         QPointer<FilterNameDialog> dlg = new FilterNameDialog(this);
0180         dlg->setExistingFilterNames(FilterSavedManager::self()->existingFilterNames());
0181         if (dlg->exec()) {
0182             FilterSavedManager::self()->saveFilter(d->mFilter, dlg->filterName(), dlg->iconName());
0183         }
0184         delete dlg;
0185     } else {
0186         KMessageBox::information(this, i18n("Any filter defined."), i18nc("@title:window", "Create Filter"));
0187     }
0188 }
0189 
0190 void Widget::changeQuicksearchVisibility(bool show)
0191 {
0192     QLineEdit *const lineEdit = d->quickSearchLine->searchEdit();
0193     if (!show) {
0194         // if we hide it we do not want to apply the filter,
0195         // otherwise someone is maybe stuck with x new emails
0196         // and cannot read it because of filter
0197         lineEdit->clear();
0198 
0199         // we focus the message list if we hide the searchbar
0200         d->mView->setFocus(Qt::OtherFocusReason);
0201     } else {
0202         // on show: we focus the lineedit for fast filtering
0203         lineEdit->setFocus(Qt::OtherFocusReason);
0204         if (d->mFilter) {
0205             resetFilter();
0206         }
0207     }
0208     d->quickSearchLine->changeQuicksearchVisibility(show);
0209     MessageListSettings::self()->setShowQuickSearch(show);
0210 }
0211 
0212 void Widget::populateStatusFilterCombo()
0213 {
0214     if (d->mStatusFilterComboPopulationInProgress) {
0215         return;
0216     }
0217     d->mStatusFilterComboPopulationInProgress = true;
0218     QComboBox *tagFilterComboBox = d->quickSearchLine->tagFilterComboBox();
0219     d->mCurrentStatusFilterIndex = (tagFilterComboBox->currentIndex() != -1) ? tagFilterComboBox->currentIndex() : 0;
0220     disconnect(tagFilterComboBox, &QComboBox::currentIndexChanged, this, &Widget::statusSelected);
0221 
0222     tagFilterComboBox->clear();
0223 
0224     fillMessageTagCombo();
0225 }
0226 
0227 void Widget::addMessageTagItem(const QPixmap &icon, const QString &text, const QVariant &data)
0228 {
0229     d->quickSearchLine->tagFilterComboBox()->addItem(icon, text, data);
0230 }
0231 
0232 void Widget::setCurrentStatusFilterItem()
0233 {
0234     d->quickSearchLine->updateComboboxVisibility();
0235     connect(d->quickSearchLine->tagFilterComboBox(), &QComboBox::currentIndexChanged, this, &Widget::statusSelected);
0236     d->quickSearchLine->tagFilterComboBox()->setCurrentIndex(
0237         d->mCurrentStatusFilterIndex >= d->quickSearchLine->tagFilterComboBox()->count() ? 0 : d->mCurrentStatusFilterIndex);
0238     d->mStatusFilterComboPopulationInProgress = false;
0239 }
0240 
0241 MessageItem *Widget::currentMessageItem() const
0242 {
0243     return view()->currentMessageItem();
0244 }
0245 
0246 MessageList::Core::QuickSearchLine::SearchOptions Widget::currentOptions() const
0247 {
0248     return d->quickSearchLine->searchOptions();
0249 }
0250 
0251 QList<Akonadi::MessageStatus> Widget::currentFilterStatus() const
0252 {
0253     if (d->mFilter) {
0254         return d->mFilter->status();
0255     }
0256     return {};
0257 }
0258 
0259 QString Widget::currentFilterSearchString() const
0260 {
0261     if (d->mFilter) {
0262         return d->mFilter->searchString();
0263     }
0264     return {};
0265 }
0266 
0267 QString Widget::currentFilterTagId() const
0268 {
0269     if (d->mFilter) {
0270         return d->mFilter->tagId();
0271     }
0272 
0273     return {};
0274 }
0275 
0276 void Widget::WidgetPrivate::setDefaultAggregationForStorageModel(const StorageModel *storageModel)
0277 {
0278     const Aggregation *opt = Manager::instance()->aggregationForStorageModel(storageModel, &mStorageUsesPrivateAggregation);
0279 
0280     Q_ASSERT(opt);
0281 
0282     delete mAggregation;
0283     mAggregation = new Aggregation(*opt);
0284 
0285     mView->setAggregation(mAggregation);
0286 
0287     mLastAggregationId = opt->id();
0288 }
0289 
0290 void Widget::WidgetPrivate::setDefaultThemeForStorageModel(const StorageModel *storageModel)
0291 {
0292     const Theme *opt = Manager::instance()->themeForStorageModel(storageModel, &mStorageUsesPrivateTheme);
0293 
0294     Q_ASSERT(opt);
0295 
0296     delete mTheme;
0297     mTheme = new Theme(*opt);
0298 
0299     mView->setTheme(mTheme);
0300 
0301     mLastThemeId = opt->id();
0302 }
0303 
0304 void Widget::WidgetPrivate::checkSortOrder(const StorageModel *storageModel)
0305 {
0306     if (storageModel && mAggregation && !mSortOrder.validForAggregation(mAggregation)) {
0307         qCDebug(MESSAGELIST_LOG) << "Could not restore sort order for folder" << storageModel->id();
0308         mSortOrder = SortOrder::defaultForAggregation(mAggregation, mSortOrder);
0309 
0310         // Change the global sort order if the sort order didn't fit the global aggregation.
0311         // Otherwise, if it is a per-folder aggregation, make the sort order per-folder too.
0312         if (mStorageUsesPrivateAggregation) {
0313             mStorageUsesPrivateSortOrder = true;
0314         }
0315         if (mStorageModel) {
0316             Manager::instance()->saveSortOrderForStorageModel(storageModel, mSortOrder, mStorageUsesPrivateSortOrder);
0317         }
0318         switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
0319     }
0320 }
0321 
0322 void Widget::WidgetPrivate::setDefaultSortOrderForStorageModel(const StorageModel *storageModel)
0323 {
0324     // Load the sort order from config and update column headers
0325     mSortOrder = Manager::instance()->sortOrderForStorageModel(storageModel, &mStorageUsesPrivateSortOrder);
0326     switchMessageSorting(mSortOrder.messageSorting(), mSortOrder.messageSortDirection(), -1);
0327     checkSortOrder(storageModel);
0328 }
0329 
0330 void Widget::saveCurrentSelection()
0331 {
0332     if (d->mStorageModel) {
0333         // Save the current selection
0334         MessageItem *lastSelectedMessageItem = d->mView->currentMessageItem(false);
0335         if (lastSelectedMessageItem) {
0336             d->mStorageModel->savePreSelectedMessage(lastSelectedMessageItem->uniqueId());
0337         }
0338     }
0339 }
0340 
0341 void Widget::setStorageModel(StorageModel *storageModel, PreSelectionMode preSelectionMode)
0342 {
0343     if (storageModel == d->mStorageModel) {
0344         return; // nuthin to do here
0345     }
0346 
0347     d->setDefaultAggregationForStorageModel(storageModel);
0348     d->setDefaultThemeForStorageModel(storageModel);
0349     d->setDefaultSortOrderForStorageModel(storageModel);
0350 
0351     if (!d->quickSearchLine->searchEdit()->locked()) {
0352         if (d->mSearchTimer) {
0353             d->mSearchTimer->stop();
0354             delete d->mSearchTimer;
0355             d->mSearchTimer = nullptr;
0356         }
0357 
0358         d->quickSearchLine->searchEdit()->clear();
0359 
0360         if (d->mFilter) {
0361             resetFilter();
0362         }
0363     }
0364     StorageModel *oldModel = d->mStorageModel;
0365 
0366     d->mStorageModel = storageModel;
0367     d->mView->setStorageModel(d->mStorageModel, preSelectionMode);
0368 
0369     delete oldModel;
0370 
0371     d->quickSearchLine->tagFilterComboBox()->setEnabled(d->mStorageModel);
0372     d->quickSearchLine->searchEdit()->setEnabled(d->mStorageModel);
0373     d->quickSearchLine->setContainsOutboundMessages(d->mStorageModel->containsOutboundMessages());
0374 }
0375 
0376 StorageModel *Widget::storageModel() const
0377 {
0378     return d->mStorageModel;
0379 }
0380 
0381 QLineEdit *Widget::quickSearch() const
0382 {
0383     return d->quickSearchLine->searchEdit();
0384 }
0385 
0386 View *Widget::view() const
0387 {
0388     return d->mView;
0389 }
0390 
0391 void Widget::themeMenuAboutToShow()
0392 {
0393     if (!d->mStorageModel) {
0394         return;
0395     }
0396 
0397     auto menu = qobject_cast<QMenu *>(sender());
0398     if (!menu) {
0399         return;
0400     }
0401     themeMenuAboutToShow(menu);
0402 }
0403 
0404 void Widget::themeMenuAboutToShow(QMenu *menu)
0405 {
0406     menu->clear();
0407 
0408     menu->addSection(i18n("Theme"));
0409 
0410     auto grp = new QActionGroup(menu);
0411 
0412     QList<Theme *> sortedThemes = Manager::instance()->themes().values();
0413 
0414     QAction *act;
0415 
0416     std::sort(sortedThemes.begin(), sortedThemes.end(), MessageList::Core::Theme::compareName);
0417 
0418     for (const auto theme : std::as_const(sortedThemes)) {
0419         act = menu->addAction(theme->name());
0420         act->setCheckable(true);
0421         grp->addAction(act);
0422         act->setChecked(d->mLastThemeId == theme->id());
0423         act->setData(QVariant(theme->id()));
0424         connect(act, &QAction::triggered, this, &Widget::themeSelected);
0425     }
0426 
0427     menu->addSeparator();
0428 
0429     act = menu->addAction(i18n("Configure..."));
0430     connect(act, &QAction::triggered, this, &Widget::configureThemes);
0431 }
0432 
0433 void Widget::setPrivateSortOrderForStorage()
0434 {
0435     if (!d->mStorageModel) {
0436         return;
0437     }
0438 
0439     d->mStorageUsesPrivateSortOrder = !d->mStorageUsesPrivateSortOrder;
0440 
0441     Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
0442 }
0443 
0444 void Widget::configureThemes()
0445 {
0446     auto dialog = new Utils::ConfigureThemesDialog(window());
0447     dialog->selectTheme(d->mLastThemeId);
0448     dialog->show();
0449 }
0450 
0451 void Widget::themeSelected(bool)
0452 {
0453     if (!d->mStorageModel) {
0454         return; // nuthin to do
0455     }
0456 
0457     auto act = qobject_cast<QAction *>(sender());
0458     if (!act) {
0459         return;
0460     }
0461 
0462     QVariant v = act->data();
0463     const QString id = v.toString();
0464 
0465     if (id.isEmpty()) {
0466         return;
0467     }
0468 
0469     const Theme *opt = Manager::instance()->theme(id);
0470 
0471     delete d->mTheme;
0472     d->mTheme = new Theme(*opt);
0473 
0474     d->mView->setTheme(d->mTheme);
0475 
0476     d->mLastThemeId = opt->id();
0477 
0478     // mStorageUsesPrivateTheme = false;
0479 
0480     Manager::instance()->saveThemeForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateTheme);
0481 
0482     d->mView->reload();
0483 }
0484 
0485 void Widget::aggregationMenuAboutToShow()
0486 {
0487     auto menu = qobject_cast<QMenu *>(sender());
0488     if (!menu) {
0489         return;
0490     }
0491     aggregationMenuAboutToShow(menu);
0492 }
0493 
0494 void Widget::aggregationMenuAboutToShow(QMenu *menu)
0495 {
0496     menu->clear();
0497 
0498     menu->addSection(i18n("Aggregation"));
0499 
0500     auto grp = new QActionGroup(menu);
0501 
0502     QList<Aggregation *> sortedAggregations = Manager::instance()->aggregations().values();
0503 
0504     QAction *act;
0505 
0506     std::sort(sortedAggregations.begin(), sortedAggregations.end(), MessageList::Core::Aggregation::compareName);
0507 
0508     for (const auto agg : std::as_const(sortedAggregations)) {
0509         act = menu->addAction(agg->name());
0510         act->setCheckable(true);
0511         grp->addAction(act);
0512         act->setChecked(d->mLastAggregationId == agg->id());
0513         act->setData(QVariant(agg->id()));
0514         connect(act, &QAction::triggered, this, &Widget::aggregationSelected);
0515     }
0516 
0517     menu->addSeparator();
0518 
0519     act = menu->addAction(i18n("Configure..."));
0520     act->setData(QVariant(QString()));
0521     connect(act, &QAction::triggered, this, &Widget::aggregationSelected);
0522 }
0523 
0524 void Widget::aggregationSelected(bool)
0525 {
0526     auto act = qobject_cast<QAction *>(sender());
0527     if (!act) {
0528         return;
0529     }
0530 
0531     QVariant v = act->data();
0532     QString id = v.toString();
0533 
0534     if (id.isEmpty()) {
0535         auto dialog = new Utils::ConfigureAggregationsDialog(window());
0536         dialog->selectAggregation(d->mLastAggregationId);
0537         dialog->show();
0538         return;
0539     }
0540 
0541     if (!d->mStorageModel) {
0542         return; // nuthin to do
0543     }
0544 
0545     const Aggregation *opt = Manager::instance()->aggregation(id);
0546 
0547     delete d->mAggregation;
0548     d->mAggregation = new Aggregation(*opt);
0549 
0550     d->mView->setAggregation(d->mAggregation);
0551 
0552     d->mLastAggregationId = opt->id();
0553 
0554     // mStorageUsesPrivateAggregation = false;
0555 
0556     Manager::instance()->saveAggregationForStorageModel(d->mStorageModel, opt->id(), d->mStorageUsesPrivateAggregation);
0557 
0558     // The sort order might not be valid anymore for this aggregation
0559     d->checkSortOrder(d->mStorageModel);
0560 
0561     d->mView->reload();
0562 }
0563 
0564 void Widget::sortOrderMenuAboutToShow()
0565 {
0566     if (!d->mAggregation) {
0567         return;
0568     }
0569 
0570     auto menu = qobject_cast<QMenu *>(sender());
0571     if (!menu) {
0572         return;
0573     }
0574     sortOrderMenuAboutToShow(menu);
0575 }
0576 
0577 void Widget::sortOrderMenuAboutToShow(QMenu *menu)
0578 {
0579     menu->clear();
0580 
0581     menu->addSection(i18n("Message Sort Order"));
0582 
0583     QActionGroup *grp;
0584     QAction *act;
0585     QList<QPair<QString, int>> options;
0586 
0587     grp = new QActionGroup(menu);
0588 
0589     options = SortOrder::enumerateMessageSortingOptions(d->mAggregation->threading());
0590     for (const auto &opt : std::as_const(options)) {
0591         act = menu->addAction(opt.first);
0592         act->setCheckable(true);
0593         grp->addAction(act);
0594         act->setChecked(d->mSortOrder.messageSorting() == opt.second);
0595         act->setData(QVariant(opt.second));
0596     }
0597 
0598     connect(grp, &QActionGroup::triggered, this, &Widget::messageSortingSelected);
0599 
0600     options = SortOrder::enumerateMessageSortDirectionOptions(d->mSortOrder.messageSorting());
0601 
0602     if (options.size() >= 2) {
0603         menu->addSection(i18n("Message Sort Direction"));
0604 
0605         grp = new QActionGroup(menu);
0606         for (const auto &opt : std::as_const(options)) {
0607             act = menu->addAction(opt.first);
0608             act->setCheckable(true);
0609             grp->addAction(act);
0610             act->setChecked(d->mSortOrder.messageSortDirection() == opt.second);
0611             act->setData(QVariant(opt.second));
0612         }
0613 
0614         connect(grp, &QActionGroup::triggered, this, &Widget::messageSortDirectionSelected);
0615     }
0616 
0617     options = SortOrder::enumerateGroupSortingOptions(d->mAggregation->grouping());
0618 
0619     if (options.size() >= 2) {
0620         menu->addSection(i18n("Group Sort Order"));
0621 
0622         grp = new QActionGroup(menu);
0623         for (const auto &opt : std::as_const(options)) {
0624             act = menu->addAction(opt.first);
0625             act->setCheckable(true);
0626             grp->addAction(act);
0627             act->setChecked(d->mSortOrder.groupSorting() == opt.second);
0628             act->setData(QVariant(opt.second));
0629         }
0630 
0631         connect(grp, &QActionGroup::triggered, this, &Widget::groupSortingSelected);
0632     }
0633 
0634     options = SortOrder::enumerateGroupSortDirectionOptions(d->mAggregation->grouping(), d->mSortOrder.groupSorting());
0635 
0636     if (options.size() >= 2) {
0637         menu->addSection(i18n("Group Sort Direction"));
0638 
0639         grp = new QActionGroup(menu);
0640         for (const auto &opt : std::as_const(options)) {
0641             act = menu->addAction(opt.first);
0642             act->setCheckable(true);
0643             grp->addAction(act);
0644             act->setChecked(d->mSortOrder.groupSortDirection() == opt.second);
0645             act->setData(QVariant(opt.second));
0646         }
0647 
0648         connect(grp, &QActionGroup::triggered, this, &Widget::groupSortDirectionSelected);
0649     }
0650 
0651     menu->addSeparator();
0652     act = menu->addAction(i18n("Folder Always Uses This Sort Order"));
0653     act->setCheckable(true);
0654     act->setChecked(d->mStorageUsesPrivateSortOrder);
0655     connect(act, &QAction::triggered, this, &Widget::setPrivateSortOrderForStorage);
0656 }
0657 
0658 void Widget::WidgetPrivate::switchMessageSorting(SortOrder::MessageSorting messageSorting, SortOrder::SortDirection sortDirection, int logicalHeaderColumnIndex)
0659 {
0660     mSortOrder.setMessageSorting(messageSorting);
0661     mSortOrder.setMessageSortDirection(sortDirection);
0662 
0663     // If the logicalHeaderColumnIndex was specified then we already know which
0664     // column we should set the sort indicator to. If it wasn't specified (it's -1)
0665     // then we need to find it out in the theme.
0666 
0667     if (logicalHeaderColumnIndex == -1) {
0668         // try to find the specified message sorting in the theme columns
0669         const auto columns = mTheme->columns();
0670         int idx = 0;
0671 
0672         // First try with a well defined message sorting.
0673 
0674         for (const auto column : std::as_const(columns)) {
0675             if (!mView->header()->isSectionHidden(idx)) {
0676                 if (column->messageSorting() == messageSorting) {
0677                     // found a visible column with this message sorting
0678                     logicalHeaderColumnIndex = idx;
0679                     break;
0680                 }
0681             }
0682             ++idx;
0683         }
0684 
0685         // if still not found, try again with a wider range
0686         if (logicalHeaderColumnIndex == -1) {
0687             idx = 0;
0688             for (const auto column : std::as_const(columns)) {
0689                 if (!mView->header()->isSectionHidden(idx)) {
0690                     if (((column->messageSorting() == SortOrder::SortMessagesBySenderOrReceiver)
0691                          || (column->messageSorting() == SortOrder::SortMessagesByReceiver) || (column->messageSorting() == SortOrder::SortMessagesBySender))
0692                         && ((messageSorting == SortOrder::SortMessagesBySenderOrReceiver) || (messageSorting == SortOrder::SortMessagesByReceiver)
0693                             || (messageSorting == SortOrder::SortMessagesBySender))) {
0694                         // found a visible column with this message sorting
0695                         logicalHeaderColumnIndex = idx;
0696                         break;
0697                     }
0698                 }
0699                 ++idx;
0700             }
0701         }
0702     }
0703 
0704     if (logicalHeaderColumnIndex == -1) {
0705         // not found: either not a column-based sorting or the related column is hidden
0706         mView->header()->setSortIndicatorShown(false);
0707         return;
0708     }
0709 
0710     mView->header()->setSortIndicatorShown(true);
0711 
0712     if (sortDirection == SortOrder::Ascending) {
0713         mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::AscendingOrder);
0714     } else {
0715         mView->header()->setSortIndicator(logicalHeaderColumnIndex, Qt::DescendingOrder);
0716     }
0717 }
0718 
0719 void Widget::messageSortingSelected(QAction *action)
0720 {
0721     if (!d->mAggregation) {
0722         return;
0723     }
0724     if (!action) {
0725         return;
0726     }
0727 
0728     if (!d->mStorageModel) {
0729         return;
0730     }
0731 
0732     bool ok;
0733     auto ord = static_cast<SortOrder::MessageSorting>(action->data().toInt(&ok));
0734 
0735     if (!ok) {
0736         return;
0737     }
0738 
0739     d->switchMessageSorting(ord, d->mSortOrder.messageSortDirection(), -1);
0740     Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
0741 
0742     d->mView->reload();
0743 }
0744 
0745 void Widget::messageSortDirectionSelected(QAction *action)
0746 {
0747     if (!d->mAggregation) {
0748         return;
0749     }
0750     if (!action) {
0751         return;
0752     }
0753     if (!d->mStorageModel) {
0754         return;
0755     }
0756 
0757     bool ok;
0758     auto ord = static_cast<SortOrder::SortDirection>(action->data().toInt(&ok));
0759 
0760     if (!ok) {
0761         return;
0762     }
0763 
0764     d->switchMessageSorting(d->mSortOrder.messageSorting(), ord, -1);
0765     Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
0766 
0767     d->mView->reload();
0768 }
0769 
0770 void Widget::groupSortingSelected(QAction *action)
0771 {
0772     if (!d->mAggregation) {
0773         return;
0774     }
0775     if (!action) {
0776         return;
0777     }
0778 
0779     if (!d->mStorageModel) {
0780         return;
0781     }
0782 
0783     bool ok;
0784     auto ord = static_cast<SortOrder::GroupSorting>(action->data().toInt(&ok));
0785 
0786     if (!ok) {
0787         return;
0788     }
0789 
0790     d->mSortOrder.setGroupSorting(ord);
0791     Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
0792 
0793     d->mView->reload();
0794 }
0795 
0796 void Widget::groupSortDirectionSelected(QAction *action)
0797 {
0798     if (!d->mAggregation) {
0799         return;
0800     }
0801     if (!action) {
0802         return;
0803     }
0804     if (!d->mStorageModel) {
0805         return;
0806     }
0807 
0808     bool ok;
0809     auto ord = static_cast<SortOrder::SortDirection>(action->data().toInt(&ok));
0810 
0811     if (!ok) {
0812         return;
0813     }
0814 
0815     d->mSortOrder.setGroupSortDirection(ord);
0816     Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
0817 
0818     d->mView->reload();
0819 }
0820 
0821 void Widget::setFilter(Filter *filter)
0822 {
0823     resetFilter();
0824     d->mFilter = filter;
0825     d->mView->model()->setFilter(d->mFilter);
0826 }
0827 
0828 void Widget::resetFilter()
0829 {
0830     delete d->mFilter;
0831     d->mFilter = nullptr;
0832     d->mView->model()->setFilter(nullptr);
0833     d->quickSearchLine->resetFilter();
0834     d->quickSearchWarning->animatedHide();
0835 }
0836 
0837 void Widget::slotViewHeaderSectionClicked(int logicalIndex)
0838 {
0839     if (!d->mTheme) {
0840         return;
0841     }
0842 
0843     if (!d->mAggregation) {
0844         return;
0845     }
0846 
0847     if (logicalIndex >= d->mTheme->columns().count()) {
0848         return;
0849     }
0850 
0851     if (!d->mStorageModel) {
0852         return;
0853     }
0854 
0855     auto column = d->mTheme->column(logicalIndex);
0856     if (!column) {
0857         return; // should never happen...
0858     }
0859 
0860     if (column->messageSorting() == SortOrder::NoMessageSorting) {
0861         return; // this is a null op.
0862     }
0863 
0864     if (d->mSortOrder.messageSorting() == column->messageSorting()) {
0865         // switch sort direction
0866         if (d->mSortOrder.messageSortDirection() == SortOrder::Ascending) {
0867             d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Descending, logicalIndex);
0868         } else {
0869             d->switchMessageSorting(d->mSortOrder.messageSorting(), SortOrder::Ascending, logicalIndex);
0870         }
0871     } else {
0872         // keep sort direction but switch sort order
0873         d->switchMessageSorting(column->messageSorting(), d->mSortOrder.messageSortDirection(), logicalIndex);
0874     }
0875     Manager::instance()->saveSortOrderForStorageModel(d->mStorageModel, d->mSortOrder, d->mStorageUsesPrivateSortOrder);
0876 
0877     d->mView->reload();
0878 }
0879 
0880 void Widget::themesChanged()
0881 {
0882     d->setDefaultThemeForStorageModel(d->mStorageModel);
0883 
0884     d->mView->reload();
0885 }
0886 
0887 void Widget::aggregationsChanged()
0888 {
0889     d->setDefaultAggregationForStorageModel(d->mStorageModel);
0890     d->checkSortOrder(d->mStorageModel);
0891 
0892     d->mView->reload();
0893 }
0894 
0895 void Widget::fillMessageTagCombo()
0896 {
0897     // nothing here: must be overridden in derived classes
0898     setCurrentStatusFilterItem();
0899 }
0900 
0901 void Widget::tagIdSelected(const QVariant &data)
0902 {
0903     const QString tagId = data.toString();
0904 
0905     if (tagId.isEmpty()) {
0906         if (d->mFilter) {
0907             if (d->mFilter->isEmpty()) {
0908                 resetFilter();
0909                 return;
0910             }
0911         }
0912     } else {
0913         if (!d->mFilter) {
0914             d->mFilter = new Filter();
0915         }
0916         d->mFilter->setTagId(tagId);
0917     }
0918 
0919     d->mView->model()->setFilter(d->mFilter);
0920 }
0921 
0922 void Widget::setLockTab(bool lock)
0923 {
0924     d->mLockTab = lock;
0925     if (lock) {
0926         d->tabLockedWarning->animatedShow();
0927     } else {
0928         d->tabLockedWarning->animatedHide();
0929     }
0930 }
0931 
0932 bool Widget::isLocked() const
0933 {
0934     return d->mLockTab;
0935 }
0936 
0937 void Widget::statusSelected(int index)
0938 {
0939     if (index == 0) {
0940         resetFilter();
0941         return;
0942     }
0943     tagIdSelected(d->quickSearchLine->tagFilterComboBox()->itemData(index));
0944     d->mView->model()->setFilter(d->mFilter);
0945 }
0946 
0947 void Widget::searchEditTextEdited()
0948 {
0949     // This slot is called whenever the user edits the search QLineEdit.
0950     // Since the user is likely to type more than one character
0951     // so we start the real search after a short delay in order to catch
0952     // multiple textEdited() signals.
0953 
0954     if (!d->mSearchTimer) {
0955         d->mSearchTimer = new QTimer(this);
0956         connect(d->mSearchTimer, &QTimer::timeout, this, &Widget::searchTimerFired);
0957     } else {
0958         d->mSearchTimer->stop(); // eventually
0959     }
0960 
0961     d->mSearchTimer->setSingleShot(true);
0962     d->mSearchTimer->start(1s);
0963 }
0964 
0965 void Widget::slotStatusButtonsClicked()
0966 {
0967     // We also arbitrarily set tagId to an empty string, though we *could* allow filtering
0968     // by status AND tag...
0969     if (d->mFilter) {
0970         d->mFilter->setTagId(QString());
0971     }
0972 
0973     auto lst = d->quickSearchLine->status();
0974     if (lst.isEmpty()) {
0975         if (d->mFilter) {
0976             d->mFilter->setStatus(lst);
0977             if (d->mFilter->isEmpty()) {
0978                 qCDebug(MESSAGELIST_LOG) << " RESET FILTER";
0979                 resetFilter();
0980                 return;
0981             }
0982         }
0983     } else {
0984         // don't have this status bit
0985         if (!d->mFilter) {
0986             d->mFilter = new Filter();
0987         }
0988         d->mFilter->setStatus(lst);
0989     }
0990 
0991     d->mView->model()->setFilter(d->mFilter);
0992 }
0993 
0994 void Widget::searchTimerFired()
0995 {
0996     // A search is pending.
0997 
0998     if (d->mSearchTimer) {
0999         d->mSearchTimer->stop();
1000     }
1001 
1002     if (!d->mFilter) {
1003         d->mFilter = new Filter();
1004     }
1005 
1006     const QString text = d->quickSearchLine->searchEdit()->text();
1007 
1008     if (!text.isEmpty()) {
1009         d->quickSearchLine->addCompletionItem(text);
1010     }
1011 
1012     d->mFilter->setCurrentFolder(d->mCurrentFolder);
1013     d->mFilter->setSearchString(text, d->quickSearchLine->searchOptions());
1014     d->quickSearchWarning->setSearchText(text);
1015     if (d->mFilter->isEmpty()) {
1016         resetFilter();
1017         return;
1018     }
1019 
1020     d->mView->model()->setFilter(d->mFilter);
1021 }
1022 
1023 void Widget::searchEditClearButtonClicked()
1024 {
1025     if (!d->mFilter) {
1026         return;
1027     }
1028 
1029     resetFilter();
1030 
1031     d->mView->scrollTo(d->mView->currentIndex(), QAbstractItemView::PositionAtCenter);
1032 }
1033 
1034 void Widget::viewMessageSelected(MessageItem *)
1035 {
1036 }
1037 
1038 void Widget::viewMessageActivated(MessageItem *)
1039 {
1040 }
1041 
1042 void Widget::viewSelectionChanged()
1043 {
1044 }
1045 
1046 void Widget::viewMessageListContextPopupRequest(const QList<MessageItem *> &, const QPoint &)
1047 {
1048 }
1049 
1050 void Widget::viewGroupHeaderContextPopupRequest(GroupHeaderItem *, const QPoint &)
1051 {
1052 }
1053 
1054 void Widget::viewDragEnterEvent(QDragEnterEvent *)
1055 {
1056 }
1057 
1058 void Widget::viewDragMoveEvent(QDragMoveEvent *)
1059 {
1060 }
1061 
1062 void Widget::viewDropEvent(QDropEvent *)
1063 {
1064 }
1065 
1066 void Widget::viewStartDragRequest()
1067 {
1068 }
1069 
1070 void Widget::viewMessageStatusChangeRequest(MessageItem *msg, Akonadi::MessageStatus set, Akonadi::MessageStatus clear)
1071 {
1072     Q_UNUSED(msg)
1073     Q_UNUSED(set)
1074     Q_UNUSED(clear)
1075 }
1076 
1077 void Widget::focusQuickSearch(const QString &selectedText)
1078 {
1079     d->quickSearchLine->focusQuickSearch(selectedText);
1080 }
1081 
1082 bool Widget::isThreaded() const
1083 {
1084     return d->mView->isThreaded();
1085 }
1086 
1087 bool Widget::selectionEmpty() const
1088 {
1089     return d->mView->selectionEmpty();
1090 }
1091 
1092 Akonadi::Collection Widget::currentFolder() const
1093 {
1094     return d->mCurrentFolder;
1095 }
1096 
1097 void Widget::setCurrentFolder(const Akonadi::Collection &collection)
1098 {
1099     if (!d->mLockTab) {
1100         d->mCurrentFolder = collection;
1101         d->searchCollectionIndexingWarning->setCollection(collection);
1102     }
1103 }
1104 
1105 bool Widget::searchEditHasFocus() const
1106 {
1107     return d->quickSearchLine->searchEdit()->hasFocus();
1108 }
1109 
1110 #include "moc_widgetbase.cpp"