/* Copyright (c) 2009 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "statisticsproxymodel.h" #include "entitytreemodel.h" #include "collectionutils_p.h" #include #include #include #include #include #include #include #include #include #include using namespace Akonadi; /** * @internal */ class StatisticsProxyModel::Private { public: Private(StatisticsProxyModel *parent) : mParent(parent) , mToolTipEnabled(false) , mExtraColumnsEnabled(true) { } int sourceColumnCount(const QModelIndex &parent) { return mParent->sourceModel()->columnCount(mParent->mapToSource(parent)); } void getCountRecursive(const QModelIndex &index, qint64 &totalSize) const { Collection collection = qvariant_cast(index.data(EntityTreeModel::CollectionRole)); // Do not assert on invalid collections, since a collection may be deleted // in the meantime and deleted collections are invalid. if (collection.isValid()) { CollectionStatistics statistics = collection.statistics(); totalSize += qMax(0LL, statistics.size()); if (index.model()->hasChildren(index)) { const int rowCount = index.model()->rowCount(index); for (int row = 0; row < rowCount; row++) { static const int column = 0; getCountRecursive(index.model()->index(row, column, index), totalSize); } } } } QString toolTipForCollection(const QModelIndex &index, const Collection &collection) { QString bckColor = QApplication::palette().color(QPalette::ToolTipBase).name(); QString txtColor = QApplication::palette().color(QPalette::ToolTipText).name(); QString tip = QString::fromLatin1( "\n" ); const QString textDirection = (QApplication::layoutDirection() == Qt::LeftToRight) ? QLatin1String("left") : QLatin1String("right"); tip += QString::fromLatin1( " \n" " \n" " \n" ).arg(txtColor).arg(bckColor).arg(index.data(Qt::DisplayRole).toString()).arg(textDirection); tip += QString::fromLatin1( " \n" " \n" ).arg(iconPath).arg(icon_size_found) ; if (QApplication::layoutDirection() == Qt::LeftToRight) { tip += tipInfo + QString::fromLatin1("" \ "
\n" "
\n" " %3\n" "
\n" "
\n" ).arg(textDirection); QString tipInfo; tipInfo += QString::fromLatin1( " %1: %2
\n" " %3: %4

\n" ).arg(i18n("Total Messages")).arg(collection.statistics().count()) .arg(i18n("Unread Messages")).arg(collection.statistics().unreadCount()); if (collection.hasAttribute()) { CollectionQuotaAttribute *quota = collection.attribute(); if (quota->currentValue() > -1 && quota->maximumValue() > 0) { qreal percentage = (100.0 * quota->currentValue()) / quota->maximumValue(); if (qAbs(percentage) >= 0.01) { QString percentStr = QString::number(percentage, 'f', 2); tipInfo += QString::fromLatin1( " %1: %2%
\n" ).arg(i18n("Quota")).arg(percentStr); } } } qint64 currentFolderSize(collection.statistics().size()); tipInfo += QString::fromLatin1( " %1: %2
\n" ).arg(i18n("Storage Size")).arg(KIO::convertSize((KIO::filesize_t)(currentFolderSize))); qint64 totalSize = 0; getCountRecursive(index, totalSize); totalSize -= currentFolderSize; if (totalSize > 0) { tipInfo += QString::fromLatin1( "%1: %2
" ).arg(i18n("Subfolder Storage Size")).arg(KIO::convertSize((KIO::filesize_t)(totalSize))); } QString iconName = CollectionUtils::defaultIconName(collection); if (collection.hasAttribute() && !collection.attribute()->iconName().isEmpty()) { if (!collection.attribute()->activeIconName().isEmpty() && collection.statistics().unreadCount() > 0) { iconName = collection.attribute()->activeIconName(); } else { iconName = collection.attribute()->iconName(); } } int iconSizes[] = { 32, 22 }; int icon_size_found = 32; QString iconPath; for (int i = 0; i < 2; i++) { iconPath = KIconLoader::global()->iconPath(iconName, -iconSizes[i], true); if (!iconPath.isEmpty()) { icon_size_found = iconSizes[i]; break; } } if (iconPath.isEmpty()) { iconPath = KIconLoader::global()->iconPath(QLatin1String("folder"), -32, false); } QString tipIcon = QString::fromLatin1( "
\n" " \n" "
\n" "
").arg(textDirection) + tipIcon; } else { tip += tipIcon + QString::fromLatin1("").arg(textDirection) + tipInfo; } tip += QString::fromLatin1( "
" ); return tip; } void proxyDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void sourceLayoutAboutToBeChanged(); void sourceLayoutChanged(); QVector m_nonPersistent; QVector m_nonPersistentFirstColumn; QVector m_persistent; QVector m_persistentFirstColumn; StatisticsProxyModel *mParent; bool mToolTipEnabled; bool mExtraColumnsEnabled; }; void StatisticsProxyModel::Private::proxyDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (mExtraColumnsEnabled) { // Ugly hack. // The proper solution is a KExtraColumnsProxyModel, but this will do for now. QModelIndex parent = topLeft.parent(); int parentColumnCount = mParent->columnCount(parent); QModelIndex extraTopLeft = mParent->index(topLeft.row(), parentColumnCount - 1 - 3 , parent); QModelIndex extraBottomRight = mParent->index(bottomRight.row(), parentColumnCount - 1, parent); mParent->disconnect(mParent, SIGNAL(dataChanged(QModelIndex,QModelIndex)), mParent, SLOT(proxyDataChanged(QModelIndex,QModelIndex))); emit mParent->dataChanged(extraTopLeft, extraBottomRight); // We get this signal when the statistics of a row changes. // However, we need to emit data changed for the statistics of all ancestor rows too // so that recursive totals can be updated. while (parent.isValid()) { emit mParent->dataChanged(parent.sibling(parent.row(), parentColumnCount - 1 - 3), parent.sibling(parent.row(), parentColumnCount - 1)); parent = parent.parent(); parentColumnCount = mParent->columnCount(parent); } mParent->connect(mParent, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(proxyDataChanged(QModelIndex,QModelIndex))); } } void StatisticsProxyModel::Private::sourceLayoutAboutToBeChanged() { QModelIndexList persistent = mParent->persistentIndexList(); const int columnCount = mParent->sourceModel()->columnCount(); foreach (const QModelIndex &idx, persistent) { if (idx.column() >= columnCount) { m_nonPersistent.push_back(idx); m_persistent.push_back(idx); const QModelIndex firstColumn = idx.sibling(0, idx.column()); m_nonPersistentFirstColumn.push_back(firstColumn); m_persistentFirstColumn.push_back(firstColumn); } } } void StatisticsProxyModel::Private::sourceLayoutChanged() { QModelIndexList oldList; QModelIndexList newList; const int columnCount = mParent->sourceModel()->columnCount(); for (int i = 0; i < m_persistent.size(); ++i) { const QModelIndex persistentIdx = m_persistent.at(i); const QModelIndex nonPersistentIdx = m_nonPersistent.at(i); if (m_persistentFirstColumn.at(i) != m_nonPersistentFirstColumn.at(i) && persistentIdx.column() >= columnCount) { oldList.append(nonPersistentIdx); newList.append(persistentIdx); } } mParent->changePersistentIndexList(oldList, newList); } void StatisticsProxyModel::setSourceModel(QAbstractItemModel *sourceModel) { // Order is important here. sourceLayoutChanged must be called *before* any downstreams react // to the layoutChanged so that it can have the QPersistentModelIndexes uptodate in time. disconnect(this, SIGNAL(layoutChanged()), this, SLOT(sourceLayoutChanged())); connect(this, SIGNAL(layoutChanged()), SLOT(sourceLayoutChanged())); QSortFilterProxyModel::setSourceModel(sourceModel); // This one should come *after* any downstream handlers of layoutAboutToBeChanged. // The connectNotify stuff below ensures that it remains the last one. disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged())); connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged())); } void StatisticsProxyModel::connectNotify(const char *signal) { static bool ignore = false; if (ignore || QLatin1String(signal) == SIGNAL(layoutAboutToBeChanged())) { return QSortFilterProxyModel::connectNotify(signal); } ignore = true; disconnect(this, SIGNAL(layoutAboutToBeChanged()), this, SLOT(sourceLayoutAboutToBeChanged())); connect(this, SIGNAL(layoutAboutToBeChanged()), SLOT(sourceLayoutAboutToBeChanged())); ignore = false; QSortFilterProxyModel::connectNotify(signal); } StatisticsProxyModel::StatisticsProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , d(new Private(this)) { connect(this, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(proxyDataChanged(QModelIndex,QModelIndex))); } StatisticsProxyModel::~StatisticsProxyModel() { delete d; } void StatisticsProxyModel::setToolTipEnabled(bool enable) { d->mToolTipEnabled = enable; } bool StatisticsProxyModel::isToolTipEnabled() const { return d->mToolTipEnabled; } void StatisticsProxyModel::setExtraColumnsEnabled(bool enable) { d->mExtraColumnsEnabled = enable; } bool StatisticsProxyModel::isExtraColumnsEnabled() const { return d->mExtraColumnsEnabled; } QModelIndex Akonadi::StatisticsProxyModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } int sourceColumn = column; if (column >= d->sourceColumnCount(parent)) { sourceColumn = 0; } QModelIndex i = QSortFilterProxyModel::index(row, sourceColumn, parent); return createIndex(i.row(), column, i.internalPointer()); } QVariant StatisticsProxyModel::data(const QModelIndex &index, int role) const { if (!sourceModel()) { return QVariant(); } if (role == Qt::DisplayRole && index.column() >= d->sourceColumnCount(index.parent())) { const QModelIndex sourceIndex = mapToSource(index.sibling(index.row(), 0)); Collection collection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value(); if (collection.isValid() && collection.statistics().count() >= 0) { if (index.column() == d->sourceColumnCount(QModelIndex()) + 2) { return KIO::convertSize((KIO::filesize_t)(collection.statistics().size())); } else if (index.column() == d->sourceColumnCount(QModelIndex()) + 1) { return collection.statistics().count(); } else if (index.column() == d->sourceColumnCount(QModelIndex())) { if (collection.statistics().unreadCount() > 0) { return collection.statistics().unreadCount(); } else { return QString(); } } else { kWarning() << "We shouldn't get there for a column which is not total, unread or size."; return QVariant(); } } } else if (role == Qt::TextAlignmentRole && index.column() >= d->sourceColumnCount(index.parent())) { return Qt::AlignRight; } else if (role == Qt::ToolTipRole && d->mToolTipEnabled) { const QModelIndex sourceIndex = mapToSource(index.sibling(index.row(), 0)); Collection collection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value(); if (collection.isValid() && collection.statistics().count() > 0) { return d->toolTipForCollection(index, collection); } } else if (role == Qt::DecorationRole && index.column() == 0) { const QModelIndex sourceIndex = mapToSource(index.sibling(index.row(), 0)); Collection collection = sourceModel()->data(sourceIndex, EntityTreeModel::CollectionRole).value(); if (collection.isValid()) { return KIcon(CollectionUtils::displayIconName(collection)); } else { return QVariant(); } } return QAbstractProxyModel::data(index, role); } QVariant StatisticsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { if (section == d->sourceColumnCount(QModelIndex()) + 2) { return i18nc("collection size", "Size"); } else if (section == d->sourceColumnCount(QModelIndex()) + 1) { return i18nc("number of entities in the collection", "Total"); } else if (section == d->sourceColumnCount(QModelIndex())) { return i18nc("number of unread entities in the collection", "Unread"); } } return QSortFilterProxyModel::headerData(section, orientation, role); } Qt::ItemFlags StatisticsProxyModel::flags(const QModelIndex &index) const { if (index.column() >= d->sourceColumnCount(index.parent())) { return QSortFilterProxyModel::flags(index.sibling(index.row(), 0)) & (Qt::ItemIsSelectable | Qt::ItemIsDragEnabled // Allowed flags | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); } return QSortFilterProxyModel::flags(index); } int StatisticsProxyModel::columnCount(const QModelIndex &parent) const { if (sourceModel() == 0) { return 0; } else { return d->sourceColumnCount(parent) + (d->mExtraColumnsEnabled ? 3 : 0); } } QModelIndexList StatisticsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { if (role < Qt::UserRole) { return QSortFilterProxyModel::match(start, role, value, hits, flags); } QModelIndexList list; QModelIndex proxyIndex; foreach (const QModelIndex &idx, sourceModel()->match(mapToSource(start), role, value, hits, flags)) { proxyIndex = mapFromSource(idx); if (proxyIndex.isValid()) { list << proxyIndex; } } return list; } #include "moc_statisticsproxymodel.cpp"