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 const 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#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
189 // Disconnect the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row
190 disconnect(model,
191 SIGNAL(dataChanged(QModelIndex, QModelIndex, QList<int>)), // clazy:exclude=old-style-connect
192 this,
193 SLOT(_q_sourceDataChanged(QModelIndex, QModelIndex, QList<int>)));
194#endif
195 connect(model, &QAbstractItemModel::dataChanged, this, [this](const auto &tl, const auto &br, const auto &roles) {
196 d->_k_sourceDataChanged(tl, br, roles);
197 });
198 }
199}
200
203 , d(new StatisticsProxyModelPrivate(this))
204{
206#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
207 // Disable the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row
208 setHandleSourceDataChanges(false);
209#endif
210}
211
213
215{
216 d->mToolTipEnabled = enable;
217}
218
220{
221 return d->mToolTipEnabled;
222}
223
225{
226 if (d->mExtraColumnsEnabled == enable) {
227 return;
228 }
229 d->mExtraColumnsEnabled = enable;
230 if (enable) {
231 KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread"));
232 KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total"));
233 KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size"));
234 } else {
238 }
239}
240
242{
243 return d->mExtraColumnsEnabled;
244}
245
246QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const
247{
248 switch (role) {
249 case Qt::DisplayRole: {
250 const QModelIndex firstColumn = index(row, 0, parent);
251 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
252 if (collection.isValid() && collection.statistics().count() >= 0) {
253 const CollectionStatistics stats = collection.statistics();
254 if (extraColumn == 2) {
255 KFormat format;
256 return format.formatByteSize(stats.size());
257 } else if (extraColumn == 1) {
258 return stats.count();
259 } else if (extraColumn == 0) {
260 if (stats.unreadCount() > 0) {
261 return stats.unreadCount();
262 } else {
263 return QString();
264 }
265 } else {
266 qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size.";
267 }
268 }
269 } break;
271 return Qt::AlignRight;
272 }
273 default:
274 break;
275 }
276 return QVariant();
277}
278
279QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const
280{
281 if (role == Qt::ToolTipRole && d->mToolTipEnabled) {
282 const QModelIndex firstColumn = index.sibling(index.row(), 0);
283 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
284
285 if (collection.isValid()) {
286 return d->toolTipForCollection(firstColumn, collection);
287 }
288 }
289
291}
292
293Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const
294{
295 if (sourceModel() && index_.column() >= d->sourceColumnCount()) {
296 const QModelIndex firstColumn = index_.sibling(index_.row(), 0);
297 return KExtraColumnsProxyModel::flags(firstColumn)
298 & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags
300 }
301
302 return KExtraColumnsProxyModel::flags(index_);
303}
304
305// Not sure this is still necessary....
306QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
307{
308 if (role < Qt::UserRole) {
309 return KExtraColumnsProxyModel::match(start, role, value, hits, flags);
310 }
311
312 QModelIndexList list;
313 QModelIndex proxyIndex;
314 const auto matches = sourceModel()->match(mapToSource(start), role, value, hits, flags);
315 for (const auto &idx : matches) {
316 proxyIndex = mapFromSource(idx);
317 if (proxyIndex.isValid()) {
318 list.push_back(proxyIndex);
319 }
320 }
321
322 return list;
323}
324
325#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 Fri Nov 22 2024 12:03:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.