1 /*
2 
3   SPDX-FileCopyrightText: 2009-2021 Laurent Montel <montel@kde.org>
4 
5   SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "foldertreeview.h"
9 #include "kernel/mailkernel.h"
10 #include "util/mailutil_p.h"
11 
12 #include <Akonadi/CollectionStatistics>
13 #include <Akonadi/CollectionStatisticsDelegate>
14 #include <Akonadi/EntityTreeModel>
15 
16 #include <KConfigGroup>
17 #include <KGuiItem>
18 #include <KLocalizedString>
19 #include <KMessageBox>
20 #include <QMenu>
21 
22 #include <QHeaderView>
23 #include <QMouseEvent>
24 
25 using namespace MailCommon;
26 
FolderTreeView(QWidget * parent,bool showUnreadCount)27 FolderTreeView::FolderTreeView(QWidget *parent, bool showUnreadCount)
28     : Akonadi::EntityTreeView(parent)
29 {
30     init(showUnreadCount);
31 }
32 
FolderTreeView(KXMLGUIClient * xmlGuiClient,QWidget * parent,bool showUnreadCount)33 FolderTreeView::FolderTreeView(KXMLGUIClient *xmlGuiClient, QWidget *parent, bool showUnreadCount)
34     : Akonadi::EntityTreeView(xmlGuiClient, parent)
35 {
36     init(showUnreadCount);
37 }
38 
~FolderTreeView()39 FolderTreeView::~FolderTreeView()
40 {
41 }
42 
disableSaveConfig()43 void FolderTreeView::disableSaveConfig()
44 {
45     mbDisableSaveConfig = true;
46 }
47 
setTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy)48 void FolderTreeView::setTooltipsPolicy(FolderTreeWidget::ToolTipDisplayPolicy policy)
49 {
50     if (mToolTipDisplayPolicy == policy) {
51         return;
52     }
53 
54     mToolTipDisplayPolicy = policy;
55     Q_EMIT changeTooltipsPolicy(mToolTipDisplayPolicy);
56     writeConfig();
57 }
58 
disableContextMenuAndExtraColumn()59 void FolderTreeView::disableContextMenuAndExtraColumn()
60 {
61     mbDisableContextMenuAndExtraColumn = true;
62     const int nbColumn = header()->count();
63     for (int i = 1; i < nbColumn; ++i) {
64         setColumnHidden(i, true);
65     }
66 }
67 
init(bool showUnreadCount)68 void FolderTreeView::init(bool showUnreadCount)
69 {
70     setIconSize(QSize(22, 22));
71     setUniformRowHeights(true);
72     mSortingPolicy = FolderTreeWidget::SortByCurrentColumn;
73     mToolTipDisplayPolicy = FolderTreeWidget::DisplayAlways;
74 
75     header()->setContextMenuPolicy(Qt::CustomContextMenu);
76     connect(header(), &QWidget::customContextMenuRequested, this, &FolderTreeView::slotHeaderContextMenuRequested);
77 
78     mCollectionStatisticsDelegate = new Akonadi::CollectionStatisticsDelegate(this);
79     mCollectionStatisticsDelegate->setProgressAnimationEnabled(true);
80     setItemDelegate(mCollectionStatisticsDelegate);
81     mCollectionStatisticsDelegate->setUnreadCountShown(showUnreadCount && !header()->isSectionHidden(1));
82 }
83 
showStatisticAnimation(bool anim)84 void FolderTreeView::showStatisticAnimation(bool anim)
85 {
86     mCollectionStatisticsDelegate->setProgressAnimationEnabled(anim);
87 }
88 
writeConfig()89 void FolderTreeView::writeConfig()
90 {
91     if (mbDisableSaveConfig) {
92         return;
93     }
94 
95     KConfigGroup myGroup(KernelIf->config(), "MainFolderView");
96     myGroup.writeEntry("IconSize", iconSize().width());
97     myGroup.writeEntry("ToolTipDisplayPolicy", (int)mToolTipDisplayPolicy);
98     myGroup.writeEntry("SortingPolicy", (int)mSortingPolicy);
99 }
100 
readConfig()101 void FolderTreeView::readConfig()
102 {
103     KConfigGroup myGroup(KernelIf->config(), "MainFolderView");
104     int iIconSize = myGroup.readEntry("IconSize", iconSize().width());
105     if ((iIconSize < 16) || (iIconSize > 32)) {
106         iIconSize = 22;
107     }
108     setIconSize(QSize(iIconSize, iIconSize));
109     mToolTipDisplayPolicy =
110         static_cast<FolderTreeWidget::ToolTipDisplayPolicy>(myGroup.readEntry("ToolTipDisplayPolicy", static_cast<int>(FolderTreeWidget::DisplayAlways)));
111 
112     Q_EMIT changeTooltipsPolicy(mToolTipDisplayPolicy);
113 
114     setSortingPolicy((FolderTreeWidget::SortingPolicy)myGroup.readEntry("SortingPolicy", (int)FolderTreeWidget::SortByCurrentColumn), false);
115 }
116 
slotHeaderContextMenuRequested(const QPoint & pnt)117 void FolderTreeView::slotHeaderContextMenuRequested(const QPoint &pnt)
118 {
119     if (mbDisableContextMenuAndExtraColumn) {
120         readConfig();
121         return;
122     }
123 
124     // the menu for the columns
125     QMenu menu;
126     QAction *act = nullptr;
127     const int nbColumn = header()->count();
128     if (nbColumn > 1) {
129         menu.addSection(i18n("View Columns"));
130         for (int i = 1; i < nbColumn; ++i) {
131             act = menu.addAction(model()->headerData(i, Qt::Horizontal).toString());
132             act->setCheckable(true);
133             act->setChecked(!header()->isSectionHidden(i));
134             act->setData(QVariant(i));
135             connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeHeader);
136         }
137     }
138 
139     menu.addSection(i18n("Icon Size"));
140 
141     static const int icon_sizes[] = {16, 22, 32};
142 
143     auto grp = new QActionGroup(&menu);
144     for (int i : icon_sizes) {
145         act = menu.addAction(QStringLiteral("%1x%2").arg(i).arg(i));
146         act->setCheckable(true);
147         grp->addAction(act);
148         if (iconSize().width() == i) {
149             act->setChecked(true);
150         }
151         act->setData(QVariant(i));
152 
153         connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeIconSize);
154     }
155     menu.addSection(i18n("Display Tooltips"));
156 
157     grp = new QActionGroup(&menu);
158 
159     act = menu.addAction(i18nc("@action:inmenu Always display tooltips", "Always"));
160     act->setCheckable(true);
161     grp->addAction(act);
162     act->setChecked(mToolTipDisplayPolicy == FolderTreeWidget::DisplayAlways);
163     act->setData(QVariant((int)FolderTreeWidget::DisplayAlways));
164     connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeToolTipDisplayPolicy);
165 
166     act = menu.addAction(i18nc("@action:inmenu Never display tooltips.", "Never"));
167     act->setCheckable(true);
168     grp->addAction(act);
169     act->setChecked(mToolTipDisplayPolicy == FolderTreeWidget::DisplayNever);
170     act->setData(QVariant((int)FolderTreeWidget::DisplayNever));
171     connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeToolTipDisplayPolicy);
172 
173     menu.addSection(i18nc("@action:inmenu", "Sort Items"));
174 
175     grp = new QActionGroup(&menu);
176 
177     act = menu.addAction(i18nc("@action:inmenu", "Automatically, by Current Column"));
178     act->setCheckable(true);
179     grp->addAction(act);
180     act->setChecked(mSortingPolicy == FolderTreeWidget::SortByCurrentColumn);
181     act->setData(QVariant((int)FolderTreeWidget::SortByCurrentColumn));
182     connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeSortingPolicy);
183 
184     act = menu.addAction(i18nc("@action:inmenu", "Manually, by Drag And Drop"));
185     act->setCheckable(true);
186     grp->addAction(act);
187     act->setChecked(mSortingPolicy == FolderTreeWidget::SortByDragAndDropKey);
188     act->setData(QVariant((int)FolderTreeWidget::SortByDragAndDropKey));
189     connect(act, &QAction::triggered, this, &FolderTreeView::slotHeaderContextMenuChangeSortingPolicy);
190 
191     menu.exec(header()->mapToGlobal(pnt));
192 }
193 
slotHeaderContextMenuChangeSortingPolicy(bool)194 void FolderTreeView::slotHeaderContextMenuChangeSortingPolicy(bool)
195 {
196     auto act = qobject_cast<QAction *>(sender());
197     if (!act) {
198         return;
199     }
200 
201     QVariant data = act->data();
202 
203     bool ok;
204     int policy = data.toInt(&ok);
205     if (!ok) {
206         return;
207     }
208 
209     setSortingPolicy((FolderTreeWidget::SortingPolicy)policy, true);
210 }
211 
setSortingPolicy(FolderTreeWidget::SortingPolicy policy,bool writeInConfig)212 void FolderTreeView::setSortingPolicy(FolderTreeWidget::SortingPolicy policy, bool writeInConfig)
213 {
214     if (mSortingPolicy == policy) {
215         return;
216     }
217 
218     mSortingPolicy = policy;
219     switch (mSortingPolicy) {
220     case FolderTreeWidget::SortByCurrentColumn:
221         header()->setSectionsClickable(true);
222         header()->setSortIndicatorShown(true);
223         setSortingEnabled(true);
224         Q_EMIT manualSortingChanged(false);
225         break;
226 
227     case FolderTreeWidget::SortByDragAndDropKey:
228         header()->setSectionsClickable(false);
229         header()->setSortIndicatorShown(false);
230 
231         setSortingEnabled(false); // hack for qutie bug: this call shouldn't be here at all
232         Q_EMIT manualSortingChanged(true);
233 
234         break;
235     default:
236         // should never happen
237         break;
238     }
239     if (writeInConfig) {
240         writeConfig();
241     }
242 }
243 
slotHeaderContextMenuChangeToolTipDisplayPolicy(bool)244 void FolderTreeView::slotHeaderContextMenuChangeToolTipDisplayPolicy(bool)
245 {
246     auto act = qobject_cast<QAction *>(sender());
247     if (!act) {
248         return;
249     }
250 
251     QVariant data = act->data();
252 
253     bool ok;
254     const int id = data.toInt(&ok);
255     if (!ok) {
256         return;
257     }
258     Q_EMIT changeTooltipsPolicy((FolderTreeWidget::ToolTipDisplayPolicy)id);
259 }
260 
slotHeaderContextMenuChangeHeader(bool)261 void FolderTreeView::slotHeaderContextMenuChangeHeader(bool)
262 {
263     auto act = qobject_cast<QAction *>(sender());
264     if (!act) {
265         return;
266     }
267 
268     QVariant data = act->data();
269 
270     bool ok;
271     const int id = data.toInt(&ok);
272     if (!ok) {
273         return;
274     }
275 
276     if (id >= header()->count()) {
277         return;
278     }
279 
280     if (id == 1) {
281         mCollectionStatisticsDelegate->setUnreadCountShown(!act->isChecked());
282     }
283 
284     setColumnHidden(id, !act->isChecked());
285 }
286 
slotHeaderContextMenuChangeIconSize(bool)287 void FolderTreeView::slotHeaderContextMenuChangeIconSize(bool)
288 {
289     auto act = qobject_cast<QAction *>(sender());
290     if (!act) {
291         return;
292     }
293 
294     QVariant data = act->data();
295 
296     bool ok;
297     const int size = data.toInt(&ok);
298     if (!ok) {
299         return;
300     }
301 
302     const QSize newIconSize(QSize(size, size));
303     if (newIconSize == iconSize()) {
304         return;
305     }
306     setIconSize(newIconSize);
307 
308     writeConfig();
309 }
310 
setCurrentModelIndex(const QModelIndex & index)311 void FolderTreeView::setCurrentModelIndex(const QModelIndex &index)
312 {
313     if (index.isValid()) {
314         clearSelection();
315         scrollTo(index);
316         selectionModel()->setCurrentIndex(index, QItemSelectionModel::Rows);
317     }
318 }
319 
selectModelIndex(const QModelIndex & index)320 void FolderTreeView::selectModelIndex(const QModelIndex &index)
321 {
322     if (index.isValid()) {
323         scrollTo(index);
324         selectionModel()->select(index, QItemSelectionModel::Rows | QItemSelectionModel::Select | QItemSelectionModel::Current | QItemSelectionModel::Clear);
325     }
326 }
327 
slotSelectFocusFolder()328 void FolderTreeView::slotSelectFocusFolder()
329 {
330     const QModelIndex index = currentIndex();
331     if (index.isValid()) {
332         setCurrentIndex(index);
333     }
334 }
335 
slotFocusNextFolder()336 void FolderTreeView::slotFocusNextFolder()
337 {
338     const QModelIndex nextFolder = selectNextFolder(currentIndex());
339 
340     if (nextFolder.isValid()) {
341         expand(nextFolder);
342         setCurrentModelIndex(nextFolder);
343     }
344 }
345 
selectNextFolder(const QModelIndex & current)346 QModelIndex FolderTreeView::selectNextFolder(const QModelIndex &current)
347 {
348     QModelIndex below;
349     if (current.isValid()) {
350         model()->fetchMore(current);
351         if (model()->hasChildren(current)) {
352             expand(current);
353             below = indexBelow(current);
354         } else if (current.row() < model()->rowCount(model()->parent(current)) - 1) {
355             below = model()->index(current.row() + 1, current.column(), model()->parent(current));
356         } else {
357             below = indexBelow(current);
358         }
359     }
360     return below;
361 }
362 
slotFocusPrevFolder()363 void FolderTreeView::slotFocusPrevFolder()
364 {
365     const QModelIndex current = currentIndex();
366     if (current.isValid()) {
367         QModelIndex above = indexAbove(current);
368         setCurrentModelIndex(above);
369     }
370 }
371 
slotFocusFirstFolder()372 void FolderTreeView::slotFocusFirstFolder()
373 {
374     const QModelIndex first = moveCursor(QAbstractItemView::MoveHome, Qt::NoModifier);
375     if (first.isValid()) {
376         setCurrentModelIndex(first);
377     }
378 }
379 
slotFocusLastFolder()380 void FolderTreeView::slotFocusLastFolder()
381 {
382     const QModelIndex last = moveCursor(QAbstractItemView::MoveEnd, Qt::NoModifier);
383     if (last.isValid()) {
384         setCurrentModelIndex(last);
385     }
386 }
387 
selectNextUnreadFolder(bool confirm)388 void FolderTreeView::selectNextUnreadFolder(bool confirm)
389 {
390     // find next unread collection starting from current position
391     if (!trySelectNextUnreadFolder(currentIndex(), ForwardSearch, confirm)) {
392         // if there is none, jump to the last collection and try again
393         trySelectNextUnreadFolder(model()->index(0, 0), ForwardSearch, confirm);
394     }
395 }
396 
397 // helper method to find last item in the model tree
lastChildOf(QAbstractItemModel * model,const QModelIndex & current)398 static QModelIndex lastChildOf(QAbstractItemModel *model, const QModelIndex &current)
399 {
400     if (model->rowCount(current) == 0) {
401         return current;
402     }
403 
404     return lastChildOf(model, model->index(model->rowCount(current) - 1, 0, current));
405 }
406 
selectPrevUnreadFolder(bool confirm)407 void FolderTreeView::selectPrevUnreadFolder(bool confirm)
408 {
409     // find next unread collection starting from current position
410     if (!trySelectNextUnreadFolder(currentIndex(), BackwardSearch, confirm)) {
411         // if there is none, jump to top and try again
412         const QModelIndex index = lastChildOf(model(), QModelIndex());
413         trySelectNextUnreadFolder(index, BackwardSearch, confirm);
414     }
415 }
416 
trySelectNextUnreadFolder(const QModelIndex & current,SearchDirection direction,bool confirm)417 bool FolderTreeView::trySelectNextUnreadFolder(const QModelIndex &current, SearchDirection direction, bool confirm)
418 {
419     QModelIndex index = current;
420     while (true) {
421         index = nextUnreadCollection(index, direction);
422 
423         if (!index.isValid()) {
424             return false;
425         }
426 
427         const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
428         if (collection == Kernel::self()->trashCollectionFolder() || collection == Kernel::self()->outboxCollectionFolder()) {
429             continue;
430         }
431 
432         if (ignoreUnreadFolder(collection, confirm)) {
433             continue;
434         }
435 
436         if (allowedToEnterFolder(collection, confirm)) {
437             expand(index);
438             setCurrentIndex(index);
439             selectModelIndex(index);
440             return true;
441         } else {
442             return false;
443         }
444     }
445 
446     return false;
447 }
448 
ignoreUnreadFolder(const Akonadi::Collection & collection,bool confirm) const449 bool FolderTreeView::ignoreUnreadFolder(const Akonadi::Collection &collection, bool confirm) const
450 {
451     if (!confirm) {
452         return false;
453     }
454 
455     // Skip drafts, sent mail and templates as well, when reading mail with the
456     // space bar - but not when changing into the next folder with unread mail
457     // via ctrl+ or ctrl- so we do this only if (confirm == true), which means
458     // we are doing readOn.
459 
460     return collection == Kernel::self()->draftsCollectionFolder() || collection == Kernel::self()->templatesCollectionFolder()
461         || collection == Kernel::self()->sentCollectionFolder();
462 }
463 
allowedToEnterFolder(const Akonadi::Collection & collection,bool confirm) const464 bool FolderTreeView::allowedToEnterFolder(const Akonadi::Collection &collection, bool confirm) const
465 {
466     if (!confirm) {
467         return true;
468     }
469 
470     // warn user that going to next folder - but keep track of
471     // whether he wishes to be notified again in "AskNextFolder"
472     // parameter (kept in the config file for kmail)
473     const int result = KMessageBox::questionYesNo(const_cast<FolderTreeView *>(this),
474                                                   i18n("<qt>Go to the next unread message in folder <b>%1</b>?</qt>", collection.name()),
475                                                   i18n("Go to Next Unread Message"),
476                                                   KGuiItem(i18n("Go To")),
477                                                   KGuiItem(i18n("Do Not Go To")), // defaults
478                                                   QStringLiteral(":kmail_AskNextFolder"),
479                                                   KMessageBox::Option());
480 
481     return result == KMessageBox::Yes;
482 }
483 
isUnreadFolder(const QModelIndex & current,QModelIndex & index,FolderTreeView::Move move,bool confirm)484 bool FolderTreeView::isUnreadFolder(const QModelIndex &current, QModelIndex &index, FolderTreeView::Move move, bool confirm)
485 {
486     if (current.isValid()) {
487         if (move == FolderTreeView::Next) {
488             index = selectNextFolder(current);
489         } else if (move == FolderTreeView::Previous) {
490             index = indexAbove(current);
491         }
492 
493         if (index.isValid()) {
494             const auto collection = index.model()->data(current, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
495 
496             if (collection.isValid()) {
497                 if (collection.statistics().unreadCount() > 0) {
498                     if (!confirm) {
499                         selectModelIndex(current);
500                         return true;
501                     } else {
502                         // Skip drafts, sent mail and templates as well, when reading mail with the
503                         // space bar - but not when changing into the next folder with unread mail
504                         // via ctrl+ or ctrl- so we do this only if (confirm == true), which means
505                         // we are doing readOn.
506 
507                         if (collection == Kernel::self()->draftsCollectionFolder() || collection == Kernel::self()->templatesCollectionFolder()
508                             || collection == Kernel::self()->sentCollectionFolder()) {
509                             return false;
510                         }
511 
512                         // warn user that going to next folder - but keep track of
513                         // whether he wishes to be notified again in "AskNextFolder"
514                         // parameter (kept in the config file for kmail)
515                         if (KMessageBox::questionYesNo(this,
516                                                        i18n("<qt>Go to the next unread message in folder <b>%1</b>?</qt>", collection.name()),
517                                                        i18n("Go to Next Unread Message"),
518                                                        KGuiItem(i18n("Go To")),
519                                                        KGuiItem(i18n("Do Not Go To")), // defaults
520                                                        QStringLiteral(":kmail_AskNextFolder"),
521                                                        KMessageBox::Option())
522                             == KMessageBox::No) {
523                             return true; // assume selected (do not continue looping)
524                         }
525 
526                         selectModelIndex(current);
527                         return true;
528                     }
529                 }
530             }
531         }
532     }
533     return false;
534 }
535 
currentFolder() const536 Akonadi::Collection FolderTreeView::currentFolder() const
537 {
538     const QModelIndex current = currentIndex();
539     if (current.isValid()) {
540         const auto collection = current.model()->data(current, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
541         return collection;
542     }
543     return Akonadi::Collection();
544 }
545 
mousePressEvent(QMouseEvent * e)546 void FolderTreeView::mousePressEvent(QMouseEvent *e)
547 {
548     const bool buttonPressedIsMiddle = (e->button() == Qt::MiddleButton);
549     Q_EMIT newTabRequested(buttonPressedIsMiddle);
550     EntityTreeView::mousePressEvent(e);
551 }
552 
restoreHeaderState(const QByteArray & data)553 void FolderTreeView::restoreHeaderState(const QByteArray &data)
554 {
555     if (data.isEmpty()) {
556         const int nbColumn = header()->count();
557         for (int i = 1; i < nbColumn; ++i) {
558             setColumnHidden(i, true);
559         }
560     } else {
561         header()->restoreState(data);
562     }
563     mCollectionStatisticsDelegate->setUnreadCountShown(header()->isSectionHidden(1));
564 }
565 
updatePalette()566 void FolderTreeView::updatePalette()
567 {
568     mCollectionStatisticsDelegate->updatePalette();
569 }
570 
keyboardSearch(const QString &)571 void FolderTreeView::keyboardSearch(const QString &)
572 {
573     // Disable keyboardSearch: it interfers with filtering in the
574     // FolderSelectionDialog. We don't want it in KMail main window
575     // either because KMail has one-letter keyboard shortcuts.
576 }
577 
indexBelow(const QModelIndex & current) const578 QModelIndex FolderTreeView::indexBelow(const QModelIndex &current) const
579 {
580     // if we have children, return first child
581     if (model()->rowCount(current) > 0) {
582         return model()->index(0, 0, current);
583     }
584 
585     // if we have siblings, return next sibling
586     const QModelIndex parent = model()->parent(current);
587     const QModelIndex sibling = model()->index(current.row() + 1, 0, parent);
588 
589     if (sibling.isValid()) { // found valid sibling
590         return sibling;
591     }
592 
593     if (!parent.isValid()) { // our parent is the tree root and we have no siblings
594         return QModelIndex(); // we reached the bottom of the tree
595     }
596 
597     // We are the last child, the next index to check is our uncle, parent's first sibling
598     const QModelIndex parentsSibling = parent.sibling(parent.row() + 1, 0);
599     if (parentsSibling.isValid()) {
600         return parentsSibling;
601     }
602 
603     // iterate over our parents back to root until we find a parent with a valid sibling
604     QModelIndex currentParent = parent;
605     QModelIndex grandParent = model()->parent(currentParent);
606     while (currentParent.isValid()) {
607         // check if the parent has children except from us
608         if (model()->rowCount(grandParent) > currentParent.row() + 1) {
609             const auto index = indexBelow(model()->index(currentParent.row() + 1, 0, grandParent));
610             if (index.isValid()) {
611                 return index;
612             }
613         }
614 
615         currentParent = grandParent;
616         grandParent = model()->parent(currentParent);
617     }
618 
619     return QModelIndex(); // nothing found -> end of tree
620 }
621 
lastChild(const QModelIndex & current) const622 QModelIndex FolderTreeView::lastChild(const QModelIndex &current) const
623 {
624     if (model()->rowCount(current) == 0) {
625         return current;
626     }
627 
628     return lastChild(model()->index(model()->rowCount(current) - 1, 0, current));
629 }
630 
indexAbove(const QModelIndex & current) const631 QModelIndex FolderTreeView::indexAbove(const QModelIndex &current) const
632 {
633     const QModelIndex parent = model()->parent(current);
634 
635     if (current.row() == 0) {
636         // we have no previous siblings -> our parent is the next item above us
637         return parent;
638     }
639 
640     // find previous sibling
641     const QModelIndex previousSibling = model()->index(current.row() - 1, 0, parent);
642 
643     // the item above us is the last child (or grandchild, or grandgrandchild... etc)
644     // of our previous sibling
645     return lastChild(previousSibling);
646 }
647 
nextUnreadCollection(const QModelIndex & current,SearchDirection direction) const648 QModelIndex FolderTreeView::nextUnreadCollection(const QModelIndex &current, SearchDirection direction) const
649 {
650     QModelIndex index = current;
651     while (true) {
652         if (direction == ForwardSearch) {
653             index = indexBelow(index);
654         } else if (direction == BackwardSearch) {
655             index = indexAbove(index);
656         }
657 
658         if (!index.isValid()) { // reach end or top of the model
659             return QModelIndex();
660         }
661 
662         // check if the index is a collection
663         const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
664 
665         if (collection.isValid()) {
666             // check if it is unread
667             if (collection.statistics().unreadCount() > 0) {
668                 if (!MailCommon::Util::ignoreNewMailInFolder(collection)) {
669                     return index; // we found the next unread collection
670                 }
671             }
672         }
673     }
674 
675     return QModelIndex(); // no unread collection found
676 }
677