Akonadi

statisticsproxymodel.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org>
3 2016 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "statisticsproxymodel.h"
9#include "akonadicore_debug.h"
10
11#include "collectionquotaattribute.h"
12#include "collectionstatistics.h"
13#include "collectionutils.h"
14#include "entitydisplayattribute.h"
15#include "entitytreemodel.h"
16
17#include <KFormat>
18#include <KIconLoader>
19#include <KLocalizedString>
20
21#include <QGuiApplication>
22#include <QMetaMethod>
23#include <QPalette>
24
25using namespace Akonadi;
26
27/**
28 * @internal
29 */
30class Akonadi::StatisticsProxyModelPrivate
31{
32public:
33 explicit StatisticsProxyModelPrivate(StatisticsProxyModel *parent)
34 : q(parent)
35 {
36 }
37
38 void getCountRecursive(const QModelIndex &index, qint64 &totalSize) const
39 {
40 auto collection = qvariant_cast<Collection>(index.data(EntityTreeModel::CollectionRole));
41 // Do not assert on invalid collections, since a collection may be deleted
42 // in the meantime and deleted collections are invalid.
43 if (collection.isValid()) {
44 CollectionStatistics statistics = collection.statistics();
45 totalSize += qMax(0LL, statistics.size());
46 if (index.model()->hasChildren(index)) {
47 const int rowCount = index.model()->rowCount(index);
48 for (int row = 0; row < rowCount; row++) {
49 static const int column = 0;
50 getCountRecursive(index.model()->index(row, column, index), totalSize);
51 }
52 }
53 }
54 }
55
56 int sourceColumnCount() const
57 {
58 return q->sourceModel()->columnCount();
59 }
60
61 QString toolTipForCollection(const QModelIndex &index, const Collection &collection) const
62 {
63 QString tip = QStringLiteral("<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">\n");
64 const QString textDirection = (QGuiApplication::layoutDirection() == Qt::LeftToRight) ? QStringLiteral("left") : QStringLiteral("right");
65 tip += QStringLiteral(
66 " <tr>\n"
67 " <td colspan=\"2\" align=\"%2\" valign=\"middle\">\n"
68 " <div style=\"font-weight: bold;\">\n"
69 " %1\n"
70 " </div>\n"
71 " </td>\n"
72 " </tr>\n")
73 .arg(index.data(Qt::DisplayRole).toString(), textDirection);
74
75 tip += QStringLiteral(
76 " <tr>\n"
77 " <td align=\"%1\" valign=\"top\">\n")
78 .arg(textDirection);
79
80 KFormat format;
81 QString tipInfo = QStringLiteral(
82 " <strong>%1</strong>: %2<br>\n"
83 " <strong>%3</strong>: %4<br><br>\n")
84 .arg(i18n("Total Messages"))
85 .arg(collection.statistics().count())
86 .arg(i18n("Unread Messages"))
87 .arg(collection.statistics().unreadCount());
88
89 if (collection.hasAttribute<CollectionQuotaAttribute>()) {
90 const auto quota = collection.attribute<CollectionQuotaAttribute>();
91 if (quota->currentValue() > -1 && quota->maximumValue() > 0) {
92 qreal percentage = (100.0 * quota->currentValue()) / quota->maximumValue();
93 QString percentStr = QString::number(percentage, 'f', 2);
94 tipInfo += i18nc("@info:tooltip Quota: 10% (300 MiB/3 GiB)",
95 "<strong>Quota</strong>: %1% (%2/%3)<br>\n",
96 percentStr,
97 format.formatByteSize(quota->currentValue()),
98 format.formatByteSize(quota->maximumValue()));
99 }
100 }
101
102 qint64 currentFolderSize(collection.statistics().size());
103 tipInfo += QStringLiteral(" <strong>%1</strong>: %2<br>\n").arg(i18n("Storage Size"), format.formatByteSize(currentFolderSize));
104
105 qint64 totalSize = 0;
106 getCountRecursive(index, totalSize);
107 totalSize -= currentFolderSize;
108 if (totalSize > 0) {
109 tipInfo += QStringLiteral("<strong>%1</strong>: %2<br>").arg(i18n("Subfolder Storage Size"), format.formatByteSize(totalSize));
110 }
111
112 QString iconName = CollectionUtils::defaultIconName(collection);
113 if (collection.hasAttribute<EntityDisplayAttribute>() && !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
114 if (!collection.attribute<EntityDisplayAttribute>()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) {
115 iconName = collection.attribute<EntityDisplayAttribute>()->activeIconName();
116 } else {
117 iconName = collection.attribute<EntityDisplayAttribute>()->iconName();
118 }
119 }
120
121 int iconSizes[] = {32, 22};
122 int icon_size_found = 32;
123
124 QString iconPath;
125
126 for (int i = 0; i < 2; ++i) {
127 iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[i], true);
128 if (!iconPath.isEmpty()) {
129 icon_size_found = iconSizes[i];
130 break;
131 }
132 }
133
134 if (iconPath.isEmpty()) {
135 iconPath = KIconLoader::global()->iconPath(QStringLiteral("folder"), -32, false);
136 }
137
138 const QString tipIcon = QStringLiteral(
139 " <table border=\"0\"><tr><td width=\"32\" height=\"32\" align=\"center\" valign=\"middle\">\n"
140 " <img src=\"%1\" width=\"%2\" height=\"32\">\n"
141 " </td></tr></table>\n"
142 " </td>\n")
143 .arg(iconPath)
144 .arg(icon_size_found);
145
147 tip += tipInfo + QStringLiteral("</td><td align=\"%1\" valign=\"top\">").arg(textDirection) + tipIcon;
148 } else {
149 tip += tipIcon + QStringLiteral("</td><td align=\"%1\" valign=\"top\">").arg(textDirection) + tipInfo;
150 }
151
152 tip += QLatin1StringView(
153 " </tr>"
154 "</table>");
155
156 return tip;
157 }
158
159 void _k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles);
160
161 StatisticsProxyModel *const q;
162
163 bool mToolTipEnabled = false;
164 bool mExtraColumnsEnabled = false;
165};
166
167void StatisticsProxyModelPrivate::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles)
168{
169 QModelIndex proxyTopLeft(q->mapFromSource(topLeft));
170 QModelIndex proxyBottomRight(q->mapFromSource(bottomRight));
171 // Emit data changed for the whole row (bug #222292)
172 if (mExtraColumnsEnabled && topLeft.column() == 0) { // in theory we could filter on roles, but ETM doesn't set any yet
173 const int lastColumn = q->columnCount() - 1;
174 proxyBottomRight = proxyBottomRight.sibling(proxyBottomRight.row(), lastColumn);
175 }
176 Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight, roles);
177}
178
179void StatisticsProxyModel::setSourceModel(QAbstractItemModel *model)
180{
181 if (sourceModel()) {
183 }
185 if (model) {
186 connect(model, &QAbstractItemModel::dataChanged, this, [this](const auto &tl, const auto &br, const auto &roles) {
187 d->_k_sourceDataChanged(tl, br, roles);
188 });
189 }
190}
191
194 , d(new StatisticsProxyModelPrivate(this))
195{
197 // Disable the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row
198 setHandleSourceDataChanges(false);
199}
200
202
204{
205 d->mToolTipEnabled = enable;
206}
207
209{
210 return d->mToolTipEnabled;
211}
212
214{
215 if (d->mExtraColumnsEnabled == enable) {
216 return;
217 }
218 d->mExtraColumnsEnabled = enable;
219 if (enable) {
220 KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread"));
221 KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total"));
222 KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size"));
223 } else {
227 }
228}
229
231{
232 return d->mExtraColumnsEnabled;
233}
234
235QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const
236{
237 switch (role) {
238 case Qt::DisplayRole: {
239 const QModelIndex firstColumn = index(row, 0, parent);
240 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
241 if (collection.isValid() && collection.statistics().count() >= 0) {
242 const CollectionStatistics stats = collection.statistics();
243 if (extraColumn == 2) {
244 KFormat format;
245 return format.formatByteSize(stats.size());
246 } else if (extraColumn == 1) {
247 return stats.count();
248 } else if (extraColumn == 0) {
249 if (stats.unreadCount() > 0) {
250 return stats.unreadCount();
251 } else {
252 return QString();
253 }
254 } else {
255 qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size.";
256 }
257 }
258 } break;
260 return Qt::AlignRight;
261 }
262 default:
263 break;
264 }
265 return QVariant();
266}
267
268QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const
269{
270 if (role == Qt::ToolTipRole && d->mToolTipEnabled) {
271 const QModelIndex firstColumn = index.sibling(index.row(), 0);
272 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
273
274 if (collection.isValid()) {
275 return d->toolTipForCollection(firstColumn, collection);
276 }
277 }
278
280}
281
282Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const
283{
284 if (sourceModel() && index_.column() >= d->sourceColumnCount()) {
285 const QModelIndex firstColumn = index_.sibling(index_.row(), 0);
286 return KExtraColumnsProxyModel::flags(firstColumn)
287 & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags
289 }
290
291 return KExtraColumnsProxyModel::flags(index_);
292}
293
294// Not sure this is still necessary....
295QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
296{
297 if (role < Qt::UserRole) {
298 return KExtraColumnsProxyModel::match(start, role, value, hits, flags);
299 }
300
301 QModelIndexList list;
302 QModelIndex proxyIndex;
303 const auto matches = sourceModel()->match(mapToSource(start), role, value, hits, flags);
304 for (const auto &idx : matches) {
305 proxyIndex = mapFromSource(idx);
306 if (proxyIndex.isValid()) {
307 list.push_back(proxyIndex);
308 }
309 }
310
311 return list;
312}
313
314#include "moc_statisticsproxymodel.cpp"
Provides statistics information of a Collection.
qint64 unreadCount() const
Returns the number of unread items in this collection or -1 if this information is not available.
Represents a collection of PIM items.
Definition collection.h:62
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
@ CollectionRole
The collection.
bool isToolTipEnabled() const
Return true if we display tooltips, otherwise false.
~StatisticsProxyModel() override
Destroys the statistics proxy model.
bool isExtraColumnsEnabled() const
Return true if we display extra statistics columns, otherwise false.
StatisticsProxyModel(QObject *parent=nullptr)
Creates a new statistics proxy model.
void removeExtraColumn(int idx)
QModelIndex parent(const QModelIndex &child) const override
void setSourceModel(QAbstractItemModel *model) override
void appendColumn(const QString &header=QString())
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
KExtraColumnsProxyModel(QObject *parent=nullptr)
Qt::ItemFlags flags(const QModelIndex &index) const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QString formatByteSize(double size, int precision=1, KFormat::BinaryUnitDialect dialect=KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units=KFormat::DefaultBinaryUnits) const
static KIconLoader * global()
QString iconPath(const QString &name, int group_or_size, bool canReturnNull, qreal scale) const
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
QAction * statistics(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual bool hasChildren(const QModelIndex &parent) const const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const
virtual int rowCount(const QModelIndex &parent) const const=0
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
void push_back(parameter_type value)
int column() const const
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
AlignRight
DisplayRole
typedef ItemFlags
LeftToRight
typedef MatchFlags
QString toString() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:52:14 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.