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 
11 #include "collectionquotaattribute.h"
12 #include "collectionstatistics.h"
13 #include "collectionutils.h"
14 #include "entitydisplayattribute.h"
15 #include "entitytreemodel.h"
16 
17 #include <KIO/Global>
18 #include <KIconLoader>
19 #include <KLocalizedString>
20 
21 #include <QApplication>
22 #include <QMetaMethod>
23 #include <QPalette>
24 
25 using namespace Akonadi;
26 
27 /**
28  * @internal
29  */
30 class Akonadi::StatisticsProxyModelPrivate
31 {
32 public:
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 += QStringLiteral(" <strong>%1</strong>: %2%<br>\n").arg(i18n("Quota"), percentStr);
99  }
100  }
101  }
102 
103  qint64 currentFolderSize(collection.statistics().size());
104  tipInfo += QStringLiteral(" <strong>%1</strong>: %2<br>\n").arg(i18n("Storage Size"), KIO::convertSize(currentFolderSize));
105 
106  qint64 totalSize = 0;
107  getCountRecursive(index, totalSize);
108  totalSize -= currentFolderSize;
109  if (totalSize > 0) {
110  tipInfo += QStringLiteral("<strong>%1</strong>: %2<br>").arg(i18n("Subfolder Storage Size"), KIO::convertSize(totalSize));
111  }
112 
113  QString iconName = CollectionUtils::defaultIconName(collection);
114  if (collection.hasAttribute<EntityDisplayAttribute>() && !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) {
115  if (!collection.attribute<EntityDisplayAttribute>()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) {
116  iconName = collection.attribute<EntityDisplayAttribute>()->activeIconName();
117  } else {
118  iconName = collection.attribute<EntityDisplayAttribute>()->iconName();
119  }
120  }
121 
122  int iconSizes[] = {32, 22};
123  int icon_size_found = 32;
124 
125  QString iconPath;
126 
127  for (int i = 0; i < 2; ++i) {
128  iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[i], true);
129  if (!iconPath.isEmpty()) {
130  icon_size_found = iconSizes[i];
131  break;
132  }
133  }
134 
135  if (iconPath.isEmpty()) {
136  iconPath = KIconLoader::global()->iconPath(QStringLiteral("folder"), -32, false);
137  }
138 
139  QString tipIcon = QStringLiteral(
140  " <table border=\"0\"><tr><td width=\"32\" height=\"32\" align=\"center\" valign=\"middle\">\n"
141  " <img src=\"%1\" width=\"%2\" height=\"32\">\n"
142  " </td></tr></table>\n"
143  " </td>\n")
144  .arg(iconPath)
145  .arg(icon_size_found);
146 
148  tip += tipInfo + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipIcon;
149  } else {
150  tip += tipIcon + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipInfo;
151  }
152 
153  tip += QLatin1String(
154  " </tr>"
155  "</table>");
156 
157  return tip;
158  }
159 
160  void _k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles);
161 
162  StatisticsProxyModel *const q;
163 
164  bool mToolTipEnabled = false;
165  bool mExtraColumnsEnabled = false;
166 };
167 
168 void StatisticsProxyModelPrivate::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
169 {
170  QModelIndex proxyTopLeft(q->mapFromSource(topLeft));
171  QModelIndex proxyBottomRight(q->mapFromSource(bottomRight));
172  // Emit data changed for the whole row (bug #222292)
173  if (mExtraColumnsEnabled && topLeft.column() == 0) { // in theory we could filter on roles, but ETM doesn't set any yet
174  const int lastColumn = q->columnCount() - 1;
175  proxyBottomRight = proxyBottomRight.sibling(proxyBottomRight.row(), lastColumn);
176  }
177  Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight, roles);
178 }
179 
180 void StatisticsProxyModel::setSourceModel(QAbstractItemModel *model)
181 {
182  if (sourceModel()) {
184  }
186  if (model) {
187  // Disconnect the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row
188  disconnect(model,
189  SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), // clazy:exclude=old-style-connect
190  this,
191  SLOT(_q_sourceDataChanged(QModelIndex, QModelIndex, QVector<int>)));
192  connect(model, &QAbstractItemModel::dataChanged, this, [this](const auto &tl, const auto &br, const auto &roles) {
193  d->_k_sourceDataChanged(tl, br, roles);
194  });
195  }
196 }
197 
199  : KExtraColumnsProxyModel(parent)
200  , d(new StatisticsProxyModelPrivate(this))
201 {
203 }
204 
206 
208 {
209  d->mToolTipEnabled = enable;
210 }
211 
213 {
214  return d->mToolTipEnabled;
215 }
216 
218 {
219  if (d->mExtraColumnsEnabled == enable) {
220  return;
221  }
222  d->mExtraColumnsEnabled = enable;
223  if (enable) {
224  KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread"));
225  KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total"));
226  KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size"));
227  } else {
231  }
232 }
233 
235 {
236  return d->mExtraColumnsEnabled;
237 }
238 
239 QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const
240 {
241  switch (role) {
242  case Qt::DisplayRole: {
243  const QModelIndex firstColumn = index(row, 0, parent);
244  const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
245  if (collection.isValid() && collection.statistics().count() >= 0) {
246  const CollectionStatistics stats = collection.statistics();
247  if (extraColumn == 2) {
248  return KIO::convertSize(stats.size());
249  } else if (extraColumn == 1) {
250  return stats.count();
251  } else if (extraColumn == 0) {
252  if (stats.unreadCount() > 0) {
253  return stats.unreadCount();
254  } else {
255  return QString();
256  }
257  } else {
258  qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size.";
259  }
260  }
261  } break;
262  case Qt::TextAlignmentRole: {
263  return Qt::AlignRight;
264  }
265  default:
266  break;
267  }
268  return QVariant();
269 }
270 
271 QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const
272 {
273  if (role == Qt::ToolTipRole && d->mToolTipEnabled) {
274  const QModelIndex firstColumn = index.sibling(index.row(), 0);
275  const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>();
276 
277  if (collection.isValid()) {
278  return d->toolTipForCollection(firstColumn, collection);
279  }
280  }
281 
282  return KExtraColumnsProxyModel::data(index, role);
283 }
284 
285 Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const
286 {
287  if (index_.column() >= d->sourceColumnCount()) {
288  const QModelIndex firstColumn = index_.sibling(index_.row(), 0);
289  return KExtraColumnsProxyModel::flags(firstColumn)
290  & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags
292  }
293 
294  return KExtraColumnsProxyModel::flags(index_);
295 }
296 
297 // Not sure this is still necessary....
298 QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
299 {
300  if (role < Qt::UserRole) {
301  return KExtraColumnsProxyModel::match(start, role, value, hits, flags);
302  }
303 
304  QModelIndexList list;
305  QModelIndex proxyIndex;
306  const auto matches = sourceModel()->match(mapToSource(start), role, value, hits, flags);
307  for (const auto &idx : matches) {
308  proxyIndex = mapFromSource(idx);
309  if (proxyIndex.isValid()) {
310  list.push_back(proxyIndex);
311  }
312  }
313 
314  return list;
315 }
316 
317 #include "moc_statisticsproxymodel.cpp"
const QColor & color(QPalette::ColorGroup group, QPalette::ColorRole role) const const
virtual bool hasChildren(const QModelIndex &parent) const const
Provides statistics information of a Collection.
AlignRight
DisplayRole
KIOCORE_EXPORT QString convertSize(KIO::filesize_t size)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QString number(int n, int base)
virtual int rowCount(const QModelIndex &parent) const const=0
QModelIndex sibling(int row, int column) const const
int column() const const
T value() const const
void setSourceModel(QAbstractItemModel *model) override
QString name() const const
StatisticsProxyModel(QObject *parent=nullptr)
Creates a new statistics proxy model.
Q_SCRIPTABLE Q_NOREPLY void start()
~StatisticsProxyModel() override
Destroys the statistics proxy model.
Represents a collection of PIM items.
Definition: collection.h:61
void push_back(const T &value)
Attribute that provides quota information for a collection.
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.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
QString iconName() const
Returns the icon name of the icon returned by icon().
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
qint64 count() const
Returns the number of items in this collection or -1 if this information is not available.
bool isExtraColumnsEnabled() const
Return true if we display extra statistics columns, otherwise false.
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
Definition: collection.cpp:176
void appendColumn(const QString &header=QString())
QVariant data(int role) const const
QAction * statistics(const QObject *recvr, const char *slot, QObject *parent)
typedef ItemFlags
QString i18n(const char *text, const TYPE &arg...)
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
@ CollectionRole
The collection.
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
bool isEmpty() const const
qint64 unreadCount() const
Returns the number of unread items in this collection or -1 if this information is not available.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
Definition: collection.cpp:161
static KIconLoader * global()
QString activeIconName() const
Returns the icon name of an active item.
bool isValid() const const
int row() const const
Qt::ItemFlags flags(const QModelIndex &index) const override
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isToolTipEnabled() const
Return true if we display tooltips, otherwise false.
CollectionStatistics statistics() const
Returns the collection statistics of the collection.
Definition: collection.cpp:326
LeftToRight
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
void removeExtraColumn(int idx)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString iconPath(const QString &name, int group_or_size, bool canReturnNull, qreal scale) const
QPalette palette()
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
typedef MatchFlags
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const override
QObject * parent() const const
A proxy model that exposes collection statistics through extra columns.
const QAbstractItemModel * model() const const
qint64 maximumValue() const
Returns the maximum quota value in bytes.
QString toString() const const
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jun 25 2022 06:00:33 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.