File indexing completed on 2024-09-22 04:41:03

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"