1 /*
2 SPDX-FileCopyrightText: 2009-2021 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "foldertreewidgetproxymodel.h"
8 #include "foldersettings.h"
9 #include "kernel/mailkernel.h"
10 #include "util/mailutil.h"
11
12 #include <MessageCore/MessageCoreSettings>
13
14 #include <Akonadi/AgentInstance>
15 #include <Akonadi/AgentManager>
16 #include <Akonadi/EntityTreeModel>
17 #include <Akonadi/MimeTypeChecker>
18
19 #include <Akonadi/CollectionQuotaAttribute>
20 #include <KColorScheme>
21 #include <KLocalizedString>
22
23 #include <QIcon>
24 #include <QPalette>
25
26 using namespace MailCommon;
27 class FolderTreeWidgetProxyModel::FolderTreeWidgetProxyModelPrivate
28 {
29 public:
FolderTreeWidgetProxyModelPrivate(FolderTreeWidgetProxyModel * qq)30 FolderTreeWidgetProxyModelPrivate(FolderTreeWidgetProxyModel *qq)
31 : q(qq)
32 {
33 }
34
checkQuotaExcedded(const QModelIndex & index,qreal & percentage)35 bool checkQuotaExcedded(const QModelIndex &index, qreal &percentage)
36 {
37 if (threshold >= 0.0) {
38 if (index.model()->hasChildren(index)) {
39 const int rowCount = index.model()->rowCount(index);
40 for (int row = 0; row < rowCount; row++) {
41 const QModelIndex firstIndex = q->mapToSource(index.model()->index(row, 0, index));
42
43 const auto collectionFirst = q->sourceModel()->data(firstIndex, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
44 if (collectionFirst.isValid() && collectionFirst.hasAttribute<Akonadi::CollectionQuotaAttribute>()) {
45 const auto *quota = collectionFirst.attribute<Akonadi::CollectionQuotaAttribute>();
46
47 if (quota->currentValue() > -1 && quota->maximumValue() > 0) {
48 percentage = (100.0 * quota->currentValue()) / quota->maximumValue();
49 if (percentage >= threshold) {
50 return true;
51 }
52 }
53 }
54 }
55 }
56 }
57 return false;
58 }
59
60 QSet<QString> includedMimeTypes;
61 Akonadi::MimeTypeChecker checker;
62
63 QColor brokenAccountColor;
64 qreal threshold = -1.0;
65 FolderTreeWidgetProxyModel *const q;
66 bool enableCheck = false;
67 bool hideVirtualFolder = false;
68 bool hideSpecificFolder = false;
69 bool hideOutboxFolder = false;
70 };
71
FolderTreeWidgetProxyModel(QObject * parent,FolderTreeWidgetProxyModelOptions option)72 FolderTreeWidgetProxyModel::FolderTreeWidgetProxyModel(QObject *parent, FolderTreeWidgetProxyModelOptions option)
73 : Akonadi::EntityRightsFilterModel(parent)
74 , d(new FolderTreeWidgetProxyModelPrivate(this))
75 {
76 setDynamicSortFilter(true);
77 setFilterCaseSensitivity(Qt::CaseInsensitive);
78
79 if (option & HideVirtualFolder) {
80 d->hideVirtualFolder = true;
81 }
82 if (option & HideSpecificFolder) {
83 d->hideSpecificFolder = true;
84 }
85 if (option & HideOutboxFolder) {
86 d->hideOutboxFolder = true;
87 }
88 readConfig();
89 }
90
91 FolderTreeWidgetProxyModel::~FolderTreeWidgetProxyModel() = default;
92
setWarningThreshold(qreal threshold)93 void FolderTreeWidgetProxyModel::setWarningThreshold(qreal threshold)
94 {
95 d->threshold = threshold;
96 }
97
readConfig()98 void FolderTreeWidgetProxyModel::readConfig()
99 {
100 invalidate();
101 }
102
flags(const QModelIndex & index) const103 Qt::ItemFlags FolderTreeWidgetProxyModel::flags(const QModelIndex &index) const
104 {
105 if (d->enableCheck) {
106 const QModelIndex sourceIndex = mapToSource(index);
107 const QModelIndex rowIndex = sourceIndex.sibling(sourceIndex.row(), 0);
108 const auto collection = sourceModel()->data(rowIndex, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
109 if (!MailCommon::Util::isVirtualCollection(collection)) {
110 const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
111 if (instance.status() == Akonadi::AgentInstance::Broken) {
112 return QSortFilterProxyModel::flags(sourceIndex) & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
113 }
114 }
115 return Akonadi::EntityRightsFilterModel::flags(index);
116 }
117 return QSortFilterProxyModel::flags(index);
118 }
119
setEnabledCheck(bool enable)120 void FolderTreeWidgetProxyModel::setEnabledCheck(bool enable)
121 {
122 if (d->enableCheck == enable) {
123 return;
124 }
125
126 d->enableCheck = enable;
127 if (enable) {
128 setAccessRights(Akonadi::Collection::CanCreateItem | Akonadi::Collection::CanCreateCollection);
129 }
130 }
131
enabledCheck() const132 bool FolderTreeWidgetProxyModel::enabledCheck() const
133 {
134 return d->enableCheck;
135 }
136
setHideVirtualFolder(bool exclude)137 void FolderTreeWidgetProxyModel::setHideVirtualFolder(bool exclude)
138 {
139 if (d->hideVirtualFolder != exclude) {
140 d->hideVirtualFolder = exclude;
141 invalidate();
142 }
143 }
144
hideVirtualFolder() const145 bool FolderTreeWidgetProxyModel::hideVirtualFolder() const
146 {
147 return d->hideVirtualFolder;
148 }
149
setHideSpecificFolder(bool hide)150 void FolderTreeWidgetProxyModel::setHideSpecificFolder(bool hide)
151 {
152 if (d->hideSpecificFolder != hide) {
153 d->hideSpecificFolder = hide;
154 invalidate();
155 }
156 }
157
hideSpecificFolder() const158 bool FolderTreeWidgetProxyModel::hideSpecificFolder() const
159 {
160 return d->hideSpecificFolder;
161 }
162
setHideOutboxFolder(bool hide)163 void FolderTreeWidgetProxyModel::setHideOutboxFolder(bool hide)
164 {
165 if (d->hideOutboxFolder != hide) {
166 d->hideOutboxFolder = hide;
167 invalidate();
168 }
169 }
170
hideOutboxFolder() const171 bool FolderTreeWidgetProxyModel::hideOutboxFolder() const
172 {
173 return d->hideOutboxFolder;
174 }
175
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const176 bool FolderTreeWidgetProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
177 {
178 const QModelIndex modelIndex = sourceModel()->index(sourceRow, 0, sourceParent);
179
180 const auto collection = sourceModel()->data(modelIndex, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
181 if (!d->checker.isWantedCollection(collection)) {
182 return false;
183 }
184
185 if (d->hideVirtualFolder) {
186 if (Util::isVirtualCollection(collection)) {
187 return false;
188 }
189 }
190
191 if (d->hideSpecificFolder) {
192 const QSharedPointer<FolderSettings> col = FolderSettings::forCollection(collection, false);
193 if (col && col->hideInSelectionDialog()) {
194 return false;
195 }
196 }
197
198 if (d->hideOutboxFolder) {
199 if (collection == Kernel::self()->outboxCollectionFolder()) {
200 return false;
201 }
202 }
203
204 return EntityRightsFilterModel::filterAcceptsRow(sourceRow, sourceParent);
205 }
206
data(const QModelIndex & index,int role) const207 QVariant FolderTreeWidgetProxyModel::data(const QModelIndex &index, int role) const
208 {
209 if (role == Qt::ForegroundRole) {
210 const QModelIndex sourceIndex = mapToSource(index);
211 const QModelIndex rowIndex = sourceIndex.sibling(sourceIndex.row(), 0);
212 const auto collection = sourceModel()->data(rowIndex, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
213
214 if (!MailCommon::Util::isVirtualCollection(collection)) {
215 const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
216
217 if (instance.status() == Akonadi::AgentInstance::Broken) {
218 if (!d->brokenAccountColor.isValid()) {
219 const KColorScheme scheme(QPalette::Active, KColorScheme::View);
220 d->brokenAccountColor = scheme.foreground(KColorScheme::NegativeText).color();
221 }
222 return d->brokenAccountColor;
223 }
224 }
225 } else if (role == Qt::DisplayRole) {
226 const QModelIndex sourceIndex = mapToSource(index);
227 const QModelIndex rowIndex = sourceIndex.sibling(sourceIndex.row(), 0);
228 const auto collection = sourceModel()->data(rowIndex, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
229 if (!MailCommon::Util::isVirtualCollection(collection)) {
230 const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
231 if (collection.parentCollection() == Akonadi::Collection::root()) {
232 if (!instance.isOnline()) {
233 return i18n("%1 (Offline)", Akonadi::EntityRightsFilterModel::data(index, role).toString());
234 }
235 qreal percentage = 0.0;
236 if (d->checkQuotaExcedded(index, percentage)) {
237 return i18n("%1 (Reached %2% quota)", Akonadi::EntityRightsFilterModel::data(index, role).toString(), static_cast<int>(percentage));
238 }
239 }
240 }
241 } else if (role == Qt::DecorationRole) {
242 const QModelIndex sourceIndex = mapToSource(index);
243 const QModelIndex rowIndex = sourceIndex.sibling(sourceIndex.row(), 0);
244 const auto collection = sourceModel()->data(rowIndex, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
245 if (!MailCommon::Util::isVirtualCollection(collection)) {
246 if (collection.parentCollection() == Akonadi::Collection::root()) {
247 qreal percentage = 0.0;
248 if (d->checkQuotaExcedded(index, percentage)) {
249 return QIcon::fromTheme(QStringLiteral("emblem-warning"));
250 }
251 }
252 }
253 }
254 return Akonadi::EntityRightsFilterModel::data(index, role);
255 }
256
updatePalette()257 void FolderTreeWidgetProxyModel::updatePalette()
258 {
259 if (d->brokenAccountColor.isValid()) {
260 KColorScheme scheme(QPalette::Active, KColorScheme::View);
261 d->brokenAccountColor = scheme.foreground(KColorScheme::NegativeText).color();
262 invalidate();
263 }
264 }
265
addContentMimeTypeInclusionFilter(const QString & mimeType)266 void FolderTreeWidgetProxyModel::addContentMimeTypeInclusionFilter(const QString &mimeType)
267 {
268 d->includedMimeTypes << mimeType;
269 d->checker.setWantedMimeTypes(d->includedMimeTypes.values());
270 invalidateFilter();
271 }
272