Messagelib

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

KDE's Doxygen guidelines are available online.