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 QString tipInfo = QStringLiteral(
84 " <strong>%1</strong>: %2<br>\n"
85 " <strong>%3</strong>: %4<br><br>\n")
86 .arg(i18n("Total Messages"))
87 .arg(collection.statistics().count())
88 .arg(i18n("Unread Messages"))
89 .arg(collection.statistics().unreadCount());
90
91 if (collection.hasAttribute<CollectionQuotaAttribute>()) {
92 const auto quota = collection.attribute<CollectionQuotaAttribute>();
93 if (quota->currentValue() > -1 && quota->maximumValue() > 0) {
94 qreal percentage = (100.0 * quota->currentValue()) / quota->maximumValue();
95
96 if (qAbs(percentage) >= 0.01) {
97 QString percentStr = QString::number(percentage, 'f', 2);
98 tipInfo += i18n("<strong>Quota</strong>: %1%<br>\n", percentStr);
99 }
100 }
101 }
102
103 qint64 currentFolderSize(collection.statistics().size());
104 KFormat format;
105 tipInfo += QStringLiteral(" <strong>%1</strong>: %2<br>\n").arg(i18n("Storage Size"), format.formatByteSize(currentFolderSize));
106
107 qint64 totalSize = 0;
108 getCountRecursive(index, totalSize);
109 totalSize -= currentFolderSize;
110 if (totalSize > 0) {
111 tipInfo += QStringLiteral("<strong>%1</strong>: %2<br>").arg(i18n("Subfolder Storage Size"), format.formatByteSize(totalSize));
112 }
113
114 QString iconName = CollectionUtils::defaultIconName(collection);
115 if (collection.hasAttribute<EntityDisplayAttribute>() && !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
116 if (!collection.attribute<EntityDisplayAttribute>()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) {
117 iconName = collection.attribute<EntityDisplayAttribute>()->activeIconName();
118 } else {
119 iconName = collection.attribute<EntityDisplayAttribute>()->iconName();
120 }
121 }
122
123 int iconSizes[] = {32, 22};
124 int icon_size_found = 32;
125
126 QString iconPath;
127
128 for (int i = 0; i < 2; ++i) {
129 iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[i], true);
130 if (!iconPath.isEmpty()) {
131 icon_size_found = iconSizes[i];
132 break;
133 }
134 }
135
136 if (iconPath.isEmpty()) {
137 iconPath = KIconLoader::global()->iconPath(QStringLiteral("folder"), -32, false);
138 }
139
140 QString tipIcon = QStringLiteral(
141 " <table border=\"0\"><tr><td width=\"32\" height=\"32\" align=\"center\" valign=\"middle\">\n"
142 " <img src=\"%1\" width=\"%2\" height=\"32\">\n"
143 " </td></tr></table>\n"
144 " </td>\n")
145 .arg(iconPath)
146 .arg(icon_size_found);
147
149 tip += tipInfo + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipIcon;
150 } else {
151 tip += tipIcon + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipInfo;
152 }
153
154 tip += QLatin1StringView(
155 " </tr>"
156 "</table>");
157
158 return tip;
159 }
160
161 void _k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles);
162
163 StatisticsProxyModel *const q;
164
165 bool mToolTipEnabled = false;
166 bool mExtraColumnsEnabled = false;
167};
168
169void StatisticsProxyModelPrivate::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles)
170{
171 QModelIndex proxyTopLeft(q->mapFromSource(topLeft));
172 QModelIndex proxyBottomRight(q->mapFromSource(bottomRight));
173 // Emit data changed for the whole row (bug #222292)
174 if (mExtraColumnsEnabled && topLeft.column() == 0) { // in theory we could filter on roles, but ETM doesn't set any yet
175 const int lastColumn = q->columnCount() - 1;
176 proxyBottomRight = proxyBottomRight.sibling(proxyBottomRight.row(), lastColumn);
177 }
178 Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight, roles);
179}
180
181void StatisticsProxyModel::setSourceModel(QAbstractItemModel *model)
182{
183 if (sourceModel()) {
185 }
187 if (model) {
188 // Disconnect the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row
189 disconnect(model,
190 SIGNAL(dataChanged(QModelIndex, QModelIndex, QList<int>)), // clazy:exclude=old-style-connect
191 this,
193 connect(model, &QAbstractItemModel::dataChanged, this, [this](const auto &tl, const auto &br, const auto &roles) {
194 d->_k_sourceDataChanged(tl, br, roles);
195 });
196 }
197}
198
201 , d(new StatisticsProxyModelPrivate(this))
202{
204}
205
207
209{
210 d->mToolTipEnabled = enable;
211}
212
214{
215 return d->mToolTipEnabled;
216}
217
219{
220 if (d->mExtraColumnsEnabled == enable) {
221 return;
222 }
223 d->mExtraColumnsEnabled = enable;
224 if (enable) {
225 KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread"));
226 KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total"));
227 KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size"));
228 } else {
232 }
233}
234
236{
237 return d->mExtraColumnsEnabled;
238}
239
240QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const
241{
242 switch (role) {
243 case Qt::DisplayRole: {
244 const QModelIndex firstColumn = index(row, 0, parent);
245 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
246 if (collection.isValid() && collection.statistics().count() >= 0) {
247 const CollectionStatistics stats = collection.statistics();
248 if (extraColumn == 2) {
249 KFormat format;
250 return format.formatByteSize(stats.size());
251 } else if (extraColumn == 1) {
252 return stats.count();
253 } else if (extraColumn == 0) {
254 if (stats.unreadCount() > 0) {
255 return stats.unreadCount();
256 } else {
257 return QString();
258 }
259 } else {
260 qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size.";
261 }
262 }
263 } break;
265 return Qt::AlignRight;
266 }
267 default:
268 break;
269 }
270 return QVariant();
271}
272
273QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const
274{
275 if (role == Qt::ToolTipRole && d->mToolTipEnabled) {
276 const QModelIndex firstColumn = index.sibling(index.row(), 0);
277 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
278
279 if (collection.isValid()) {
280 return d->toolTipForCollection(firstColumn, collection);
281 }
282 }
283
285}
286
287Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const
288{
289 if (sourceModel() && index_.column() >= d->sourceColumnCount()) {
290 const QModelIndex firstColumn = index_.sibling(index_.row(), 0);
291 return KExtraColumnsProxyModel::flags(firstColumn)
292 & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags
294 }
295
297}
298
299// Not sure this is still necessary....
300QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
301{
302 if (role < Qt::UserRole) {
303 return KExtraColumnsProxyModel::match(start, role, value, hits, flags);
304 }
305
306 QModelIndexList list;
308 const auto matches = sourceModel()->match(mapToSource(start), role, value, hits, flags);
309 for (const auto &idx : matches) {
311 if (proxyIndex.isValid()) {
313 }
314 }
315
316 return list;
317}
318
319#include "moc_statisticsproxymodel.cpp"
Attribute that provides quota information for a collection.
qint64 maximumValue() const
Returns the maximum quota value in bytes.
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
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
T qobject_cast(QObject *object)
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 Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.