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