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

KDE's Doxygen guidelines are available online.