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 {
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"
Attribute that provides quota information for a collection.
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.
Attribute that stores the properties that are used to display an entity.
QString activeIconName() const
Returns the icon name of an active item.
QString iconName() const
Returns the icon name of the icon returned by icon().
@ CollectionRole
The collection.
A proxy model that exposes collection statistics through extra columns.
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)
int columnCount(const QModelIndex &parent=QModelIndex()) 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
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-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:01:42 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.