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 <QApplication>
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 const QString bckColor = QApplication::palette().color(QPalette::ToolTipBase).name();
64 const QString txtColor = QApplication::palette().color(QPalette::ToolTipText).name();
65
66 QString tip = QStringLiteral("<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">\n");
67 const QString textDirection = (QApplication::layoutDirection() == Qt::LeftToRight) ? QStringLiteral("left") : QStringLiteral("right");
68 tip += QStringLiteral(
69 " <tr>\n"
70 " <td bgcolor=\"%1\" colspan=\"2\" align=\"%4\" valign=\"middle\">\n"
71 " <div style=\"color: %2; font-weight: bold;\">\n"
72 " %3\n"
73 " </div>\n"
74 " </td>\n"
75 " </tr>\n")
76 .arg(txtColor, bckColor, index.data(Qt::DisplayRole).toString(), textDirection);
77
78 tip += QStringLiteral(
79 " <tr>\n"
80 " <td align=\"%1\" valign=\"top\">\n")
81 .arg(textDirection);
82
83 KFormat format;
84 QString tipInfo = QStringLiteral(
85 " <strong>%1</strong>: %2<br>\n"
86 " <strong>%3</strong>: %4<br><br>\n")
87 .arg(i18n("Total Messages"))
88 .arg(collection.statistics().count())
89 .arg(i18n("Unread Messages"))
90 .arg(collection.statistics().unreadCount());
91
92 if (collection.hasAttribute<CollectionQuotaAttribute>()) {
93 const auto quota = collection.attribute<CollectionQuotaAttribute>();
94 if (quota->currentValue() > -1 && quota->maximumValue() > 0) {
95 qreal percentage = (100.0 * quota->currentValue()) / quota->maximumValue();
96 QString percentStr = QString::number(percentage, 'f', 2);
97 tipInfo += i18nc("@info:tooltip Quota: 10% (300 MiB/3 GiB)",
98 "<strong>Quota</strong>: %1% (%2/%3)<br>\n",
99 percentStr,
100 format.formatByteSize(quota->currentValue()),
101 format.formatByteSize(quota->maximumValue()));
102 }
103 }
104
105 qint64 currentFolderSize(collection.statistics().size());
106 tipInfo += QStringLiteral(" <strong>%1</strong>: %2<br>\n").arg(i18n("Storage Size"), format.formatByteSize(currentFolderSize));
107
108 qint64 totalSize = 0;
109 getCountRecursive(index, totalSize);
110 totalSize -= currentFolderSize;
111 if (totalSize > 0) {
112 tipInfo += QStringLiteral("<strong>%1</strong>: %2<br>").arg(i18n("Subfolder Storage Size"), format.formatByteSize(totalSize));
113 }
114
115 QString iconName = CollectionUtils::defaultIconName(collection);
116 if (collection.hasAttribute<EntityDisplayAttribute>() && !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
117 if (!collection.attribute<EntityDisplayAttribute>()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) {
118 iconName = collection.attribute<EntityDisplayAttribute>()->activeIconName();
119 } else {
120 iconName = collection.attribute<EntityDisplayAttribute>()->iconName();
121 }
122 }
123
124 int iconSizes[] = {32, 22};
125 int icon_size_found = 32;
126
127 QString iconPath;
128
129 for (int i = 0; i < 2; ++i) {
130 iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[i], true);
131 if (!iconPath.isEmpty()) {
132 icon_size_found = iconSizes[i];
133 break;
134 }
135 }
136
137 if (iconPath.isEmpty()) {
138 iconPath = KIconLoader::global()->iconPath(QStringLiteral("folder"), -32, false);
139 }
140
141 const QString tipIcon = QStringLiteral(
142 " <table border=\"0\"><tr><td width=\"32\" height=\"32\" align=\"center\" valign=\"middle\">\n"
143 " <img src=\"%1\" width=\"%2\" height=\"32\">\n"
144 " </td></tr></table>\n"
145 " </td>\n")
146 .arg(iconPath)
147 .arg(icon_size_found);
148
150 tip += tipInfo + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipIcon;
151 } else {
152 tip += tipIcon + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipInfo;
153 }
154
155 tip += QLatin1StringView(
156 " </tr>"
157 "</table>");
158
159 return tip;
160 }
161
162 void _k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles);
163
164 StatisticsProxyModel *const q;
165
166 bool mToolTipEnabled = false;
167 bool mExtraColumnsEnabled = false;
168};
169
170void StatisticsProxyModelPrivate::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles)
171{
172 QModelIndex proxyTopLeft(q->mapFromSource(topLeft));
173 QModelIndex proxyBottomRight(q->mapFromSource(bottomRight));
174 // Emit data changed for the whole row (bug #222292)
175 if (mExtraColumnsEnabled && topLeft.column() == 0) { // in theory we could filter on roles, but ETM doesn't set any yet
176 const int lastColumn = q->columnCount() - 1;
177 proxyBottomRight = proxyBottomRight.sibling(proxyBottomRight.row(), lastColumn);
178 }
179 Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight, roles);
180}
181
182void StatisticsProxyModel::setSourceModel(QAbstractItemModel *model)
183{
184 if (sourceModel()) {
186 }
188 if (model) {
189#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
190 // Disconnect the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row
191 disconnect(model,
192 SIGNAL(dataChanged(QModelIndex, QModelIndex, QList<int>)), // clazy:exclude=old-style-connect
193 this,
194 SLOT(_q_sourceDataChanged(QModelIndex, QModelIndex, QList<int>)));
195#endif
196 connect(model, &QAbstractItemModel::dataChanged, this, [this](const auto &tl, const auto &br, const auto &roles) {
197 d->_k_sourceDataChanged(tl, br, roles);
198 });
199 }
200}
201
204 , d(new StatisticsProxyModelPrivate(this))
205{
207#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
208 // Disable the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row
209 setHandleSourceDataChanges(false);
210#endif
211}
212
214
216{
217 d->mToolTipEnabled = enable;
218}
219
221{
222 return d->mToolTipEnabled;
223}
224
226{
227 if (d->mExtraColumnsEnabled == enable) {
228 return;
229 }
230 d->mExtraColumnsEnabled = enable;
231 if (enable) {
232 KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread"));
233 KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total"));
234 KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size"));
235 } else {
239 }
240}
241
243{
244 return d->mExtraColumnsEnabled;
245}
246
247QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const
248{
249 switch (role) {
250 case Qt::DisplayRole: {
251 const QModelIndex firstColumn = index(row, 0, parent);
252 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
253 if (collection.isValid() && collection.statistics().count() >= 0) {
254 const CollectionStatistics stats = collection.statistics();
255 if (extraColumn == 2) {
256 KFormat format;
257 return format.formatByteSize(stats.size());
258 } else if (extraColumn == 1) {
259 return stats.count();
260 } else if (extraColumn == 0) {
261 if (stats.unreadCount() > 0) {
262 return stats.unreadCount();
263 } else {
264 return QString();
265 }
266 } else {
267 qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size.";
268 }
269 }
270 } break;
272 return Qt::AlignRight;
273 }
274 default:
275 break;
276 }
277 return QVariant();
278}
279
280QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const
281{
282 if (role == Qt::ToolTipRole && d->mToolTipEnabled) {
283 const QModelIndex firstColumn = index.sibling(index.row(), 0);
284 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
285
286 if (collection.isValid()) {
287 return d->toolTipForCollection(firstColumn, collection);
288 }
289 }
290
292}
293
294Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const
295{
296 if (sourceModel() && index_.column() >= d->sourceColumnCount()) {
297 const QModelIndex firstColumn = index_.sibling(index_.row(), 0);
298 return KExtraColumnsProxyModel::flags(firstColumn)
299 & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags
301 }
302
303 return KExtraColumnsProxyModel::flags(index_);
304}
305
306// Not sure this is still necessary....
307QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
308{
309 if (role < Qt::UserRole) {
310 return KExtraColumnsProxyModel::match(start, role, value, hits, flags);
311 }
312
313 QModelIndexList list;
314 QModelIndex proxyIndex;
315 const auto matches = sourceModel()->match(mapToSource(start), role, value, hits, flags);
316 for (const auto &idx : matches) {
317 proxyIndex = mapFromSource(idx);
318 if (proxyIndex.isValid()) {
319 list.push_back(proxyIndex);
320 }
321 }
322
323 return list;
324}
325
326#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.
qint64 count() const
Returns the number of items in this collection or -1 if this information is not available.
qint64 size() const
Returns the total size of the items in this collection or -1 if this information is not available.
Represents a collection of PIM items.
Definition collection.h:62
CollectionStatistics statistics() const
Returns the collection statistics of the collection.
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
QString name(NameFormat format) const const
QPalette palette()
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
const QColor & color(ColorGroup group, ColorRole role) 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 Jan 24 2025 11:49:57 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.