File indexing completed on 2024-11-10 04:40:36
0001 /* 0002 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org> 0003 2016 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "statisticsproxymodel.h" 0009 #include "akonadicore_debug.h" 0010 0011 #include "collectionquotaattribute.h" 0012 #include "collectionstatistics.h" 0013 #include "collectionutils.h" 0014 #include "entitydisplayattribute.h" 0015 #include "entitytreemodel.h" 0016 0017 #include <KIO/Global> 0018 #include <KIconLoader> 0019 #include <KLocalizedString> 0020 0021 #include <QApplication> 0022 #include <QMetaMethod> 0023 #include <QPalette> 0024 0025 using namespace Akonadi; 0026 0027 /** 0028 * @internal 0029 */ 0030 class Akonadi::StatisticsProxyModelPrivate 0031 { 0032 public: 0033 explicit StatisticsProxyModelPrivate(StatisticsProxyModel *parent) 0034 : q(parent) 0035 { 0036 } 0037 0038 void getCountRecursive(const QModelIndex &index, qint64 &totalSize) const 0039 { 0040 auto collection = qvariant_cast<Collection>(index.data(EntityTreeModel::CollectionRole)); 0041 // Do not assert on invalid collections, since a collection may be deleted 0042 // in the meantime and deleted collections are invalid. 0043 if (collection.isValid()) { 0044 CollectionStatistics statistics = collection.statistics(); 0045 totalSize += qMax(0LL, statistics.size()); 0046 if (index.model()->hasChildren(index)) { 0047 const int rowCount = index.model()->rowCount(index); 0048 for (int row = 0; row < rowCount; row++) { 0049 static const int column = 0; 0050 getCountRecursive(index.model()->index(row, column, index), totalSize); 0051 } 0052 } 0053 } 0054 } 0055 0056 int sourceColumnCount() const 0057 { 0058 return q->sourceModel()->columnCount(); 0059 } 0060 0061 QString toolTipForCollection(const QModelIndex &index, const Collection &collection) const 0062 { 0063 const QString bckColor = QApplication::palette().color(QPalette::ToolTipBase).name(); 0064 const QString txtColor = QApplication::palette().color(QPalette::ToolTipText).name(); 0065 0066 QString tip = QStringLiteral("<table width=\"100%\" border=\"0\" cellpadding=\"2\" cellspacing=\"0\">\n"); 0067 const QString textDirection = (QApplication::layoutDirection() == Qt::LeftToRight) ? QStringLiteral("left") : QStringLiteral("right"); 0068 tip += QStringLiteral( 0069 " <tr>\n" 0070 " <td bgcolor=\"%1\" colspan=\"2\" align=\"%4\" valign=\"middle\">\n" 0071 " <div style=\"color: %2; font-weight: bold;\">\n" 0072 " %3\n" 0073 " </div>\n" 0074 " </td>\n" 0075 " </tr>\n") 0076 .arg(txtColor, bckColor, index.data(Qt::DisplayRole).toString(), textDirection); 0077 0078 tip += QStringLiteral( 0079 " <tr>\n" 0080 " <td align=\"%1\" valign=\"top\">\n") 0081 .arg(textDirection); 0082 0083 QString tipInfo = QStringLiteral( 0084 " <strong>%1</strong>: %2<br>\n" 0085 " <strong>%3</strong>: %4<br><br>\n") 0086 .arg(i18n("Total Messages")) 0087 .arg(collection.statistics().count()) 0088 .arg(i18n("Unread Messages")) 0089 .arg(collection.statistics().unreadCount()); 0090 0091 if (collection.hasAttribute<CollectionQuotaAttribute>()) { 0092 const auto quota = collection.attribute<CollectionQuotaAttribute>(); 0093 if (quota->currentValue() > -1 && quota->maximumValue() > 0) { 0094 qreal percentage = (100.0 * quota->currentValue()) / quota->maximumValue(); 0095 0096 if (qAbs(percentage) >= 0.01) { 0097 QString percentStr = QString::number(percentage, 'f', 2); 0098 tipInfo += QStringLiteral(" <strong>%1</strong>: %2%<br>\n").arg(i18n("Quota"), percentStr); 0099 } 0100 } 0101 } 0102 0103 qint64 currentFolderSize(collection.statistics().size()); 0104 tipInfo += QStringLiteral(" <strong>%1</strong>: %2<br>\n").arg(i18n("Storage Size"), KIO::convertSize(currentFolderSize)); 0105 0106 qint64 totalSize = 0; 0107 getCountRecursive(index, totalSize); 0108 totalSize -= currentFolderSize; 0109 if (totalSize > 0) { 0110 tipInfo += QStringLiteral("<strong>%1</strong>: %2<br>").arg(i18n("Subfolder Storage Size"), KIO::convertSize(totalSize)); 0111 } 0112 0113 QString iconName = CollectionUtils::defaultIconName(collection); 0114 if (collection.hasAttribute<EntityDisplayAttribute>() && !collection.attribute<EntityDisplayAttribute>()->iconName().isEmpty()) { 0115 if (!collection.attribute<EntityDisplayAttribute>()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) { 0116 iconName = collection.attribute<EntityDisplayAttribute>()->activeIconName(); 0117 } else { 0118 iconName = collection.attribute<EntityDisplayAttribute>()->iconName(); 0119 } 0120 } 0121 0122 int iconSizes[] = {32, 22}; 0123 int icon_size_found = 32; 0124 0125 QString iconPath; 0126 0127 for (int i = 0; i < 2; ++i) { 0128 iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[i], true); 0129 if (!iconPath.isEmpty()) { 0130 icon_size_found = iconSizes[i]; 0131 break; 0132 } 0133 } 0134 0135 if (iconPath.isEmpty()) { 0136 iconPath = KIconLoader::global()->iconPath(QStringLiteral("folder"), -32, false); 0137 } 0138 0139 QString tipIcon = QStringLiteral( 0140 " <table border=\"0\"><tr><td width=\"32\" height=\"32\" align=\"center\" valign=\"middle\">\n" 0141 " <img src=\"%1\" width=\"%2\" height=\"32\">\n" 0142 " </td></tr></table>\n" 0143 " </td>\n") 0144 .arg(iconPath) 0145 .arg(icon_size_found); 0146 0147 if (QApplication::layoutDirection() == Qt::LeftToRight) { 0148 tip += tipInfo + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipIcon; 0149 } else { 0150 tip += tipIcon + QStringLiteral("</td><td align=\"%3\" valign=\"top\">").arg(textDirection) + tipInfo; 0151 } 0152 0153 tip += QLatin1StringView( 0154 " </tr>" 0155 "</table>"); 0156 0157 return tip; 0158 } 0159 0160 void _k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles); 0161 0162 StatisticsProxyModel *const q; 0163 0164 bool mToolTipEnabled = false; 0165 bool mExtraColumnsEnabled = false; 0166 }; 0167 0168 void StatisticsProxyModelPrivate::_k_sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles) 0169 { 0170 QModelIndex proxyTopLeft(q->mapFromSource(topLeft)); 0171 QModelIndex proxyBottomRight(q->mapFromSource(bottomRight)); 0172 // Emit data changed for the whole row (bug #222292) 0173 if (mExtraColumnsEnabled && topLeft.column() == 0) { // in theory we could filter on roles, but ETM doesn't set any yet 0174 const int lastColumn = q->columnCount() - 1; 0175 proxyBottomRight = proxyBottomRight.sibling(proxyBottomRight.row(), lastColumn); 0176 } 0177 Q_EMIT q->dataChanged(proxyTopLeft, proxyBottomRight, roles); 0178 } 0179 0180 void StatisticsProxyModel::setSourceModel(QAbstractItemModel *model) 0181 { 0182 if (sourceModel()) { 0183 disconnect(sourceModel(), &QAbstractItemModel::dataChanged, this, nullptr); 0184 } 0185 KExtraColumnsProxyModel::setSourceModel(model); 0186 if (model) { 0187 // Disconnect the default handling of dataChanged in QIdentityProxyModel, so we can extend it to the whole row 0188 disconnect(model, 0189 SIGNAL(dataChanged(QModelIndex, QModelIndex, QList<int>)), // clazy:exclude=old-style-connect 0190 this, 0191 SLOT(_q_sourceDataChanged(QModelIndex, QModelIndex, QList<int>))); 0192 connect(model, &QAbstractItemModel::dataChanged, this, [this](const auto &tl, const auto &br, const auto &roles) { 0193 d->_k_sourceDataChanged(tl, br, roles); 0194 }); 0195 } 0196 } 0197 0198 StatisticsProxyModel::StatisticsProxyModel(QObject *parent) 0199 : KExtraColumnsProxyModel(parent) 0200 , d(new StatisticsProxyModelPrivate(this)) 0201 { 0202 setExtraColumnsEnabled(true); 0203 } 0204 0205 StatisticsProxyModel::~StatisticsProxyModel() = default; 0206 0207 void StatisticsProxyModel::setToolTipEnabled(bool enable) 0208 { 0209 d->mToolTipEnabled = enable; 0210 } 0211 0212 bool StatisticsProxyModel::isToolTipEnabled() const 0213 { 0214 return d->mToolTipEnabled; 0215 } 0216 0217 void StatisticsProxyModel::setExtraColumnsEnabled(bool enable) 0218 { 0219 if (d->mExtraColumnsEnabled == enable) { 0220 return; 0221 } 0222 d->mExtraColumnsEnabled = enable; 0223 if (enable) { 0224 KExtraColumnsProxyModel::appendColumn(i18nc("number of unread entities in the collection", "Unread")); 0225 KExtraColumnsProxyModel::appendColumn(i18nc("number of entities in the collection", "Total")); 0226 KExtraColumnsProxyModel::appendColumn(i18nc("collection size", "Size")); 0227 } else { 0228 KExtraColumnsProxyModel::removeExtraColumn(2); 0229 KExtraColumnsProxyModel::removeExtraColumn(1); 0230 KExtraColumnsProxyModel::removeExtraColumn(0); 0231 } 0232 } 0233 0234 bool StatisticsProxyModel::isExtraColumnsEnabled() const 0235 { 0236 return d->mExtraColumnsEnabled; 0237 } 0238 0239 QVariant StatisticsProxyModel::extraColumnData(const QModelIndex &parent, int row, int extraColumn, int role) const 0240 { 0241 switch (role) { 0242 case Qt::DisplayRole: { 0243 const QModelIndex firstColumn = index(row, 0, parent); 0244 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>(); 0245 if (collection.isValid() && collection.statistics().count() >= 0) { 0246 const CollectionStatistics stats = collection.statistics(); 0247 if (extraColumn == 2) { 0248 return KIO::convertSize(stats.size()); 0249 } else if (extraColumn == 1) { 0250 return stats.count(); 0251 } else if (extraColumn == 0) { 0252 if (stats.unreadCount() > 0) { 0253 return stats.unreadCount(); 0254 } else { 0255 return QString(); 0256 } 0257 } else { 0258 qCWarning(AKONADICORE_LOG) << "We shouldn't get there for a column which is not total, unread or size."; 0259 } 0260 } 0261 } break; 0262 case Qt::TextAlignmentRole: { 0263 return Qt::AlignRight; 0264 } 0265 default: 0266 break; 0267 } 0268 return QVariant(); 0269 } 0270 0271 QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const 0272 { 0273 if (role == Qt::ToolTipRole && d->mToolTipEnabled) { 0274 const QModelIndex firstColumn = index.sibling(index.row(), 0); 0275 const auto collection = data(firstColumn, EntityTreeModel::CollectionRole).value<Collection>(); 0276 0277 if (collection.isValid()) { 0278 return d->toolTipForCollection(firstColumn, collection); 0279 } 0280 } 0281 0282 return KExtraColumnsProxyModel::data(index, role); 0283 } 0284 0285 Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index_) const 0286 { 0287 if (index_.column() >= d->sourceColumnCount()) { 0288 const QModelIndex firstColumn = index_.sibling(index_.row(), 0); 0289 return KExtraColumnsProxyModel::flags(firstColumn) 0290 & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags 0291 | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); 0292 } 0293 0294 return KExtraColumnsProxyModel::flags(index_); 0295 } 0296 0297 // Not sure this is still necessary.... 0298 QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const 0299 { 0300 if (role < Qt::UserRole) { 0301 return KExtraColumnsProxyModel::match(start, role, value, hits, flags); 0302 } 0303 0304 QModelIndexList list; 0305 QModelIndex proxyIndex; 0306 const auto matches = sourceModel()->match(mapToSource(start), role, value, hits, flags); 0307 for (const auto &idx : matches) { 0308 proxyIndex = mapFromSource(idx); 0309 if (proxyIndex.isValid()) { 0310 list.push_back(proxyIndex); 0311 } 0312 } 0313 0314 return list; 0315 } 0316 0317 #include "moc_statisticsproxymodel.cpp"