1 /*
2 
3   SPDX-FileCopyrightText: 2010-2021 Laurent Montel <montel@kde.org>
4 
5   SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "entitycollectionorderproxymodel.h"
9 #include "hierarchicalfoldermatcher_p.h"
10 #include "kernel/mailkernel.h"
11 #include "mailcommon_debug.h"
12 #include "util/mailutil.h"
13 #include <Akonadi/AgentManager>
14 #include <Akonadi/Collection>
15 #include <Akonadi/EntityTreeModel>
16 #include <Akonadi/KMime/SpecialMailCollections>
17 
18 #include <QRegularExpression>
19 
20 using namespace MailCommon;
21 class Q_DECL_HIDDEN MailCommon::EntityCollectionOrderProxyModel::EntityCollectionOrderProxyModelPrivate
22 {
23 public:
EntityCollectionOrderProxyModelPrivate()24     EntityCollectionOrderProxyModelPrivate()
25     {
26     }
27 
collectionRank(const Akonadi::Collection & collection)28     int collectionRank(const Akonadi::Collection &collection)
29     {
30         const Akonadi::Collection::Id id = collection.id();
31         const int cachedRank = collectionRanks.value(id, -1);
32         if (cachedRank != -1) {
33             return cachedRank;
34         }
35 
36         int rank = 100;
37         if (Kernel::folderIsInbox(collection)) {
38             rank = 1;
39         } else if (Kernel::self()->folderIsDraftOrOutbox(collection)) {
40             if (Kernel::self()->folderIsDrafts(collection)) {
41                 rank = 5;
42             } else {
43                 rank = 2;
44             }
45         } else if (Kernel::self()->folderIsSentMailFolder(collection)) {
46             rank = 3;
47         } else if (Kernel::self()->folderIsTrash(collection)) {
48             rank = 4;
49         } else if (Kernel::self()->folderIsTemplates(collection)) {
50             rank = 6;
51         } else if (MailCommon::Util::isVirtualCollection(collection)) {
52             rank = 200;
53         } else if (collection.parentCollection() == Akonadi::Collection::root() && MailCommon::Util::isUnifiedMailboxesAgent(collection)) {
54             // special treatment for Unified Mailboxes: they are *always* on top
55             rank = 0;
56         } else if (!topLevelOrder.isEmpty()) {
57             if (collection.parentCollection() == Akonadi::Collection::root()) {
58                 const QString resource = collection.resource();
59                 if (resource.isEmpty()) {
60                     qCDebug(MAILCOMMON_LOG) << " collection has not resource: " << collection;
61                     // Don't save in collectionranks because we don't have resource name => pb.
62                     return rank;
63                 }
64                 const int order = topLevelOrder.indexOf(resource);
65                 if (order != -1) {
66                     rank = order + 1; /* top-level rank "0" belongs to Unified Mailboxes */
67                 }
68             }
69         }
70         collectionRanks.insert(id, rank);
71         return rank;
72     }
73 
74     QMap<Akonadi::Collection::Id, int> collectionRanks;
75     QStringList topLevelOrder;
76     HierarchicalFolderMatcher matcher;
77     bool manualSortingActive = false;
78 };
79 
EntityCollectionOrderProxyModel(QObject * parent)80 EntityCollectionOrderProxyModel::EntityCollectionOrderProxyModel(QObject *parent)
81     : EntityOrderProxyModel(parent)
82     , d(new EntityCollectionOrderProxyModelPrivate())
83 {
84     setSortCaseSensitivity(Qt::CaseInsensitive);
85     connect(Akonadi::SpecialMailCollections::self(),
86             &Akonadi::SpecialMailCollections::defaultCollectionsChanged,
87             this,
88             &EntityCollectionOrderProxyModel::slotSpecialCollectionsChanged);
89     connect(Akonadi::SpecialMailCollections::self(),
90             &Akonadi::SpecialMailCollections::collectionsChanged,
91             this,
92             &EntityCollectionOrderProxyModel::slotSpecialCollectionsChanged);
93 }
94 
~EntityCollectionOrderProxyModel()95 EntityCollectionOrderProxyModel::~EntityCollectionOrderProxyModel()
96 {
97     if (d->manualSortingActive) {
98         saveOrder();
99     }
100 }
101 
slotSpecialCollectionsChanged()102 void EntityCollectionOrderProxyModel::slotSpecialCollectionsChanged()
103 {
104     if (!d->manualSortingActive) {
105         d->collectionRanks.clear();
106         invalidate();
107     }
108 }
109 
setTopLevelOrder(const QStringList & list)110 void EntityCollectionOrderProxyModel::setTopLevelOrder(const QStringList &list)
111 {
112     d->topLevelOrder = list;
113     clearRanks();
114 }
115 
clearRanks()116 void EntityCollectionOrderProxyModel::clearRanks()
117 {
118     d->collectionRanks.clear();
119     invalidate();
120 }
121 
lessThan(const QModelIndex & left,const QModelIndex & right) const122 bool EntityCollectionOrderProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
123 {
124     const auto leftData = left.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
125     const auto rightData = right.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
126     if (!d->manualSortingActive) {
127         const int rankLeft = d->collectionRank(leftData);
128         const int rankRight = d->collectionRank(rightData);
129 
130         if (rankLeft < rankRight) {
131             return true;
132         } else if (rankLeft > rankRight) {
133             return false;
134         }
135 
136         return QSortFilterProxyModel::lessThan(left, right);
137     }
138 
139     if (MailCommon::Util::isUnifiedMailboxesAgent(leftData)) {
140         return true;
141     } else {
142         return EntityOrderProxyModel::lessThan(left, right);
143     }
144 }
145 
setManualSortingActive(bool active)146 void EntityCollectionOrderProxyModel::setManualSortingActive(bool active)
147 {
148     if (d->manualSortingActive == active) {
149         return;
150     }
151 
152     d->manualSortingActive = active;
153     d->collectionRanks.clear();
154     invalidate();
155 }
156 
isManualSortingActive() const157 bool EntityCollectionOrderProxyModel::isManualSortingActive() const
158 {
159     return d->manualSortingActive;
160 }
161 
setFolderMatcher(const HierarchicalFolderMatcher & matcher)162 void EntityCollectionOrderProxyModel::setFolderMatcher(const HierarchicalFolderMatcher &matcher)
163 {
164     d->matcher = matcher;
165     invalidateFilter();
166 }
167 
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const168 bool EntityCollectionOrderProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
169 {
170     if (d->matcher.isNull()) {
171         return EntityOrderProxyModel::filterAcceptsRow(sourceRow, sourceParent);
172     }
173     const QModelIndex sourceIndex = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent);
174     return d->matcher.matches(sourceModel(), sourceIndex, filterRole());
175 }
176