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"