diff --git a/src/PackageModel/PackageDelegate.cpp b/src/PackageModel/PackageDelegate.cpp index da74b117..3c5fb4f0 100644 --- a/src/PackageModel/PackageDelegate.cpp +++ b/src/PackageModel/PackageDelegate.cpp @@ -1,310 +1,310 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "PackageDelegate.h" // Qt #include #include // KDE #include #include // Own #include "muonapt/MuonStrings.h" #include "PackageModel.h" PackageDelegate::PackageDelegate(QObject *parent) : QAbstractItemDelegate(parent) , m_icon(QIcon::fromTheme("muon")) , m_supportedEmblem(QIcon::fromTheme("ubuntu-logo").pixmap(QSize(12,12))) , m_lockedEmblem(QIcon::fromTheme("object-locked").pixmap(QSize(12,12))) { m_spacing = 4; m_iconSize = KIconLoader::SizeSmallMedium; } void PackageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } paintBackground(painter, option); switch (index.column()) { case 0: paintPackageName(painter, option, index); break; case 1: // Status case 2: // Action case 3: // InstalledSize case 4: // InstalledVersion case 5: // AvailableVersion paintText(painter, option, index); break; default: break; } } void PackageDelegate::paintBackground(QPainter *painter, const QStyleOptionViewItem &option) const { QStyle *style = option.widget ? option.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget); } void PackageDelegate::paintPackageName(QPainter *painter, const QStyleOptionViewItem &option , const QModelIndex &index) const { int left = option.rect.left(); int top = option.rect.top(); int width = option.rect.width(); bool leftToRight = (painter->layoutDirection() == Qt::LeftToRight); QColor foregroundColor = (option.state.testFlag(QStyle::State_Selected)) ? option.palette.color(QPalette::HighlightedText) : option.palette.color(QPalette::Text); const qreal dpr = painter->device()->devicePixelRatioF(); // Pixmap that the text/icon goes in QPixmap pixmap(option.rect.size() * dpr); pixmap.setDevicePixelRatio(dpr); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.translate(-option.rect.topLeft()); m_icon.paint(&p, leftToRight ? left + m_spacing : left + width - m_spacing - m_iconSize, top + m_spacing, m_iconSize, m_iconSize, Qt::AlignCenter, QIcon::Normal); int state = index.data(PackageModel::StatusRole).toInt(); if (state & QApt::Package::IsPinned) { p.drawPixmap(left + m_iconSize - m_lockedEmblem.width()/2, top + option.rect.height() - 1.5*m_lockedEmblem.height(), m_lockedEmblem); } else if (index.data(PackageModel::SupportRole).toBool()) { p.drawPixmap(left + m_iconSize - m_lockedEmblem.width()/2, top + option.rect.height() - 1.5*m_lockedEmblem.height(), m_supportedEmblem); } // Text QStyleOptionViewItem name_item(option); QStyleOptionViewItem description_item(option); description_item.font.setPointSize(name_item.font.pointSize() - 1); int textInner = 2 * m_spacing + m_iconSize; const int itemHeight = calcItemHeight(option); p.setPen(foregroundColor); p.drawText(left + (leftToRight ? textInner : 0), top + 1, width - textInner, itemHeight / 2, Qt::AlignBottom | Qt::AlignLeft, index.data(PackageModel::NameRole).toString()); p.drawText(left + (leftToRight ? textInner : 0) + 10, top + itemHeight / 2, width - textInner, itemHeight / 2, Qt::AlignTop | Qt::AlignLeft, index.data(PackageModel::DescriptionRole).toString()); QLinearGradient gradient; // Gradient part of the background - fading of the text at the end if (leftToRight) { gradient = QLinearGradient(left + width - m_spacing - 16 /*fade length*/, 0, left + width - m_spacing, 0); gradient.setColorAt(0, Qt::white); gradient.setColorAt(1, Qt::transparent); } else { gradient = QLinearGradient(left + m_spacing, 0, left + m_spacing + 16, 0); gradient.setColorAt(0, Qt::transparent); gradient.setColorAt(1, Qt::white); } QRect paintRect = option.rect; p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(paintRect, gradient); p.end(); painter->drawPixmap(option.rect.topLeft(), pixmap); } void PackageDelegate::paintText(QPainter *painter, const QStyleOptionViewItem &option , const QModelIndex &index) const { int state; QString text; QPen pen; KColorScheme color(option.palette.currentColorGroup()); QColor foregroundColor = (option.state.testFlag(QStyle::State_Selected)) ? option.palette.color(QPalette::HighlightedText) : option.palette.color(QPalette::Text); switch (index.column()) { case 1: state = index.data(PackageModel::StatusRole).toInt(); if (state & QApt::Package::NowBroken) { text = MuonStrings::global()->packageStateName(QApt::Package::NowBroken); pen.setBrush(color.foreground(KColorScheme::NegativeText)); break; } if (state & QApt::Package::Installed) { text = MuonStrings::global()->packageStateName(QApt::Package::Installed); pen.setBrush(color.foreground(KColorScheme::PositiveText)); if (state & QApt::Package::Upgradeable) { text = MuonStrings::global()->packageStateName(QApt::Package::Upgradeable); pen.setBrush(color.foreground(KColorScheme::LinkText)); } } else { text = MuonStrings::global()->packageStateName(QApt::Package::NotInstalled); pen.setBrush(color.foreground(KColorScheme::NeutralText)); } break; case 2: state = index.data(PackageModel::ActionRole).toInt(); if (state & QApt::Package::ToKeep) { text = MuonStrings::global()->packageStateName(QApt::Package::ToKeep); pen.setBrush(color.foreground(KColorScheme::NeutralText)); // No other "To" flag will be set if we are keeping break; } if (state & QApt::Package::ToInstall) { text = MuonStrings::global()->packageStateName(QApt::Package::ToInstall); pen.setBrush(color.foreground(KColorScheme::PositiveText)); } if (state & QApt::Package::ToUpgrade) { text = MuonStrings::global()->packageStateName(QApt::Package::ToUpgrade); pen.setBrush(color.foreground(KColorScheme::LinkText)); break; } if (state & QApt::Package::ToRemove) { text = MuonStrings::global()->packageStateName(QApt::Package::ToRemove); pen.setBrush(color.foreground(KColorScheme::NegativeText)); } if (state & QApt::Package::ToPurge) { text = MuonStrings::global()->packageStateName(QApt::Package::ToPurge); pen.setBrush(color.foreground(KColorScheme::NegativeText)); break; } if (state & QApt::Package::ToReInstall) { text = MuonStrings::global()->packageStateName(QApt::Package::ToReInstall); pen.setBrush(color.foreground(KColorScheme::PositiveText)); break; } if (state & QApt::Package::ToDowngrade) { text = MuonStrings::global()->packageStateName(QApt::Package::ToDowngrade); pen.setBrush(color.foreground(KColorScheme::LinkText)); break; } break; case 3: - text = index.data(PackageModel::InstalledSizeRole).toString(); + text = index.data(PackageModel::InstalledSizeDisplayRole).toString(); pen.setBrush(foregroundColor); break; case 4: text = index.data(PackageModel::InstalledVersionRole).toString(); pen.setBrush(foregroundColor); break; case 5: text = index.data(PackageModel::AvailableVersionRole).toString(); pen.setBrush(foregroundColor); break; } QFont font = option.font; QFontMetrics fontMetrics(font); int x = option.rect.x() + m_spacing; int y = option.rect.y() + calcItemHeight(option) / 4 + fontMetrics.height() -1; int width = option.rect.width(); painter->setPen(pen); painter->drawText(x, y, fontMetrics.elidedText(text, option.textElideMode, width)); } QSize PackageDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); QSize size; QFontMetrics metric = QFontMetrics(option.font); switch (index.column()) { case 0: size.setWidth(metric.width(index.data(PackageModel::DescriptionRole).toString())); break; case 1: size.setWidth(metric.width(index.data(PackageModel::StatusRole).toString())); break; case 2: size.setWidth(metric.width(index.data(PackageModel::ActionRole).toString())); break; case 3: - size.setWidth(metric.width(index.data(PackageModel::InstalledSizeRole).toString())); + size.setWidth(metric.width(index.data(PackageModel::InstalledSizeDisplayRole).toString())); break; case 4: size.setWidth(metric.width(index.data(PackageModel::InstalledVersionRole).toString())); break; case 5: size.setWidth(metric.width(index.data(PackageModel::AvailableVersionRole).toString())); break; default: break; } size.setHeight(option.fontMetrics.height() * 2 + m_spacing); return size; } int PackageDelegate::calcItemHeight(const QStyleOptionViewItem &option) const { // Painting main column QStyleOptionViewItem name_item(option); QStyleOptionViewItem description_item(option); description_item.font.setPointSize(name_item.font.pointSize() - 1); int textHeight = QFontInfo(name_item.font).pixelSize() + QFontInfo(description_item.font).pixelSize(); return qMax(textHeight, m_iconSize) + 2 * m_spacing; } diff --git a/src/PackageModel/PackageModel.cpp b/src/PackageModel/PackageModel.cpp index a896afca..00ab56b3 100644 --- a/src/PackageModel/PackageModel.cpp +++ b/src/PackageModel/PackageModel.cpp @@ -1,135 +1,137 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "PackageModel.h" #include #include #include #include PackageModel::PackageModel(QObject *parent) : QAbstractListModel(parent) , m_packages(QApt::PackageList()) { } int PackageModel::rowCount(const QModelIndex & /*parent*/) const { return m_packages.size(); } int PackageModel::columnCount(const QModelIndex & /*parent*/) const { return 6; } QVariant PackageModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return false; } QApt::Package *package = m_packages.at(index.row()); switch (role) { case NameRole: if (package->isForeignArch()) { return QString(package->name() % QLatin1String(" (") % package->architecture() % ')'); } return package->name(); case IconRole: return QIcon::fromTheme("application-x-deb"); case DescriptionRole: return package->shortDescription(); case StatusRole: case ActionRole: return package->state(); case SupportRole: return package->isSupported(); case InstalledSizeRole: + return package->currentInstalledSize(); + case InstalledSizeDisplayRole: if (package->isInstalled()) { return KFormat().formatByteSize(package->currentInstalledSize()); } return QVariant(); case InstalledVersionRole: return package->installedVersion(); case AvailableVersionRole: return package->availableVersion(); case Qt::ToolTipRole: return QVariant(); } return QVariant(); } QVariant PackageModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role == Qt::DisplayRole) { switch (section) { case 0: return QVariant(i18n("Package")); case 1: return QVariant(i18n("Status")); case 2: return QVariant(i18n("Requested")); case 3: return QVariant(i18n("Installed Size")); case 4: return QVariant(i18n("Installed Version")); case 5: return QVariant(i18n("Available Version")); } } return QVariant(); } void PackageModel::setPackages(const QApt::PackageList &list) { beginResetModel(); m_packages = list; endResetModel(); } void PackageModel::clear() { beginRemoveRows(QModelIndex(), 0, m_packages.size() - 1); m_packages.clear(); endRemoveRows(); } void PackageModel::externalDataChanged() { // A package being changed means that any number of other packages can have // changed, so say everything changed to trigger refreshes. emit dataChanged(index(0, 0), index(m_packages.size() - 1, 0)); } QApt::Package *PackageModel::packageAt(const QModelIndex &index) const { return m_packages.at(index.row()); } QApt::PackageList PackageModel::packages() const { return m_packages; } #include "PackageModel.moc" diff --git a/src/PackageModel/PackageModel.h b/src/PackageModel/PackageModel.h index 7bd83d1c..e490389f 100644 --- a/src/PackageModel/PackageModel.h +++ b/src/PackageModel/PackageModel.h @@ -1,62 +1,63 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef PACKAGEMODEL_H #define PACKAGEMODEL_H #include #include class PackageModel: public QAbstractListModel { Q_OBJECT public: enum { NameRole = Qt::UserRole, IconRole = Qt::UserRole + 1, DescriptionRole = Qt::UserRole + 2, ActionRole = Qt::UserRole + 3, StatusRole = Qt::UserRole + 4, SupportRole = Qt::UserRole + 5, InstalledSizeRole = Qt::UserRole + 6, - InstalledVersionRole = Qt::UserRole + 7, - AvailableVersionRole = Qt::UserRole + 8 + InstalledSizeDisplayRole = Qt::UserRole + 7, + InstalledVersionRole = Qt::UserRole + 8, + AvailableVersionRole = Qt::UserRole + 9 }; explicit PackageModel(QObject *parent = 0); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const; void setPackages(const QApt::PackageList &list); void clear(); QApt::Package *packageAt(const QModelIndex &index) const; QApt::PackageList packages() const; private: QApt::PackageList m_packages; public slots: void externalDataChanged(); }; #endif diff --git a/src/PackageModel/PackageProxyModel.cpp b/src/PackageModel/PackageProxyModel.cpp index 5bafaf2d..71421b08 100644 --- a/src/PackageModel/PackageProxyModel.cpp +++ b/src/PackageModel/PackageProxyModel.cpp @@ -1,213 +1,223 @@ /*************************************************************************** * Copyright © 2010 Jonathan Thomas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "PackageProxyModel.h" // KDE includes #include // QApt includes #include // Own includes #include "PackageModel.h" #include "MuonSettings.h" constexpr int status_sort_magic = (QApt::Package::Installed | QApt::Package::Upgradeable | QApt::Package::NowBroken | QApt::Package::New); bool packageStatusLessThan(QApt::Package *p1, QApt::Package *p2) { return (p1->state() & (status_sort_magic)) < (p2->state() & (status_sort_magic)); } constexpr int requested_sort_magic = (QApt::Package::ToInstall | QApt::Package::ToUpgrade | QApt::Package::ToRemove | QApt::Package::ToPurge | QApt::Package::ToReInstall | QApt::Package::ToDowngrade | QApt::Package::ToKeep); bool packageRequestedLessThan(QApt::Package *p1, QApt::Package *p2) { return (p1->state() & (requested_sort_magic)) < (p2->state() & (requested_sort_magic)); } PackageProxyModel::PackageProxyModel(QObject *parent) : QSortFilterProxyModel(parent) , m_backend(0) , m_stateFilter((QApt::Package::State)0) , m_sortByRelevancy(false) , m_useSearchResults(false) { } void PackageProxyModel::setBackend(QApt::Backend *backend) { m_backend = backend; m_packages = static_cast(sourceModel())->packages(); } void PackageProxyModel::search(const QString &searchText) { // 1-character searches are painfully slow. >= 2 chars are fine, though if (searchText.size() > 1) { m_searchPackages = m_backend->search(searchText); if (!m_useSearchResults) { m_sortByRelevancy = true; } m_useSearchResults = true; } else { m_searchPackages.clear(); m_packages = static_cast(sourceModel())->packages(); m_sortByRelevancy = false; m_useSearchResults = false; } invalidate(); } void PackageProxyModel::setSortByRelevancy(bool enabled) { m_sortByRelevancy = enabled; invalidate(); } bool PackageProxyModel::isSortedByRelevancy() const { return m_sortByRelevancy; } void PackageProxyModel::setGroupFilter(const QString &filterText) { m_groupFilter = filterText; invalidate(); } void PackageProxyModel::setStateFilter(QApt::Package::State state) { m_stateFilter = state; invalidate(); } void PackageProxyModel::setOriginFilter(const QString &origin) { m_originFilter = origin; invalidate(); } void PackageProxyModel::setArchFilter(const QString &arch) { m_archFilter = arch; invalidate(); } bool PackageProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { // Our "main"-method QApt::Package *package = static_cast(sourceModel())->packageAt(sourceModel()->index(sourceRow, 1, sourceParent)); // We have a package as internal pointer if (!package) { return false; } if (!m_groupFilter.isEmpty()) { if (!QString(package->section()).contains(m_groupFilter)) { return false; } } if (!m_stateFilter == 0) { if ((bool)(package->state() & m_stateFilter) == false) { return false; } } if (!m_originFilter.isEmpty()) { if (!(package->origin() == m_originFilter)) { return false; } } if (!m_archFilter.isEmpty()) { if (!(package->architecture() == m_archFilter)) { return false; } } if (!MuonSettings::self()->showMultiArchDupes()) { if (package->isMultiArchDuplicate()) return false; } if (m_useSearchResults) return m_searchPackages.contains(package); return true; } QApt::Package *PackageProxyModel::packageAt(const QModelIndex &index) const { // Since our representation is almost bound to change, we need to grab the parent model's index QModelIndex sourceIndex = mapToSource(index); QApt::Package *package = static_cast(sourceModel())->packageAt(sourceIndex); return package; } void PackageProxyModel::reset() { beginRemoveRows(QModelIndex(), 0, m_packages.size()); m_packages = static_cast(sourceModel())->packages(); endRemoveRows(); invalidate(); } bool PackageProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { PackageModel *model = static_cast(sourceModel()); QApt::Package *leftPackage = model->packageAt(left); QApt::Package *rightPackage = model->packageAt(right); switch (left.column()) { case 0: if (m_sortByRelevancy) { // This is expensive for very large datasets. It takes about 3 seconds with 30,000 packages // The order in m_packages is based on relevancy when returned by m_backend->search() // Use this order to determine less than return (m_searchPackages.indexOf(leftPackage) > m_searchPackages.indexOf(rightPackage)); } else { QString leftString = left.data(PackageModel::NameRole).toString(); QString rightString = right.data(PackageModel::NameRole).toString(); return leftString > rightString; } case 1: return packageStatusLessThan(leftPackage, rightPackage); case 2: return packageRequestedLessThan(leftPackage, rightPackage); + case 3: /* Installed size */ + { + qlonglong leftSize = left.data(PackageModel::InstalledSizeRole).toLongLong(); + qlonglong rightSize = right.data(PackageModel::InstalledSizeRole).toLongLong(); + return leftSize < rightSize; + } + case 4: /* Installed version */ + return QApt::Package::compareVersion(leftPackage->installedVersion(), rightPackage->installedVersion()) < 0; + case 5: /* Available version */ + return QApt::Package::compareVersion(leftPackage->availableVersion(), rightPackage->availableVersion()) < 0; } return false; }