Akonadi

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

KDE's Doxygen guidelines are available online.