// SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * * 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, version 3. * * 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 <https://www.gnu.org/licenses/>. * * This file incorporates work covered by the following copyright and * permission notice: * * Copyright 2013-2021 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "VersionProxyModel.h" #include "Application.h" #include <QSortFilterProxyModel> #include <QPixmapCache> #include <Version.h> #include <meta/VersionList.h> class VersionFilterModel : public QSortFilterProxyModel { Q_OBJECT public: VersionFilterModel(VersionProxyModel *parent) : QSortFilterProxyModel(parent) { m_parent = parent; setSortRole(BaseVersionList::SortRole); sort(0, Qt::DescendingOrder); } bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { const auto &filters = m_parent->filters(); for (auto it = filters.begin(); it != filters.end(); ++it) { auto idx = sourceModel()->index(source_row, 0, source_parent); auto data = sourceModel()->data(idx, it.key()); auto match = data.toString(); if(!it.value()->accepts(match)) { return false; } } return true; } void filterChanged() { invalidateFilter(); } private: VersionProxyModel *m_parent; }; VersionProxyModel::VersionProxyModel(QObject *parent) : QAbstractProxyModel(parent) { filterModel = new VersionFilterModel(this); connect(filterModel, &QAbstractItemModel::dataChanged, this, &VersionProxyModel::sourceDataChanged); connect(filterModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &VersionProxyModel::sourceRowsAboutToBeInserted); connect(filterModel, &QAbstractItemModel::rowsInserted, this, &VersionProxyModel::sourceRowsInserted); connect(filterModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &VersionProxyModel::sourceRowsAboutToBeRemoved); connect(filterModel, &QAbstractItemModel::rowsRemoved, this, &VersionProxyModel::sourceRowsRemoved); // FIXME: implement when needed /* connect(replacing, &QAbstractItemModel::rowsAboutToBeMoved, this, &VersionProxyModel::sourceRowsAboutToBeMoved); connect(replacing, &QAbstractItemModel::rowsMoved, this, &VersionProxyModel::sourceRowsMoved); connect(replacing, &QAbstractItemModel::layoutAboutToBeChanged, this, &VersionProxyModel::sourceLayoutAboutToBeChanged); connect(replacing, &QAbstractItemModel::layoutChanged, this, &VersionProxyModel::sourceLayoutChanged); */ connect(filterModel, &QAbstractItemModel::modelAboutToBeReset, this, &VersionProxyModel::sourceAboutToBeReset); connect(filterModel, &QAbstractItemModel::modelReset, this, &VersionProxyModel::sourceReset); QAbstractProxyModel::setSourceModel(filterModel); } QVariant VersionProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { if(section < 0 || section >= m_columns.size()) return QVariant(); if(orientation != Qt::Horizontal) return QVariant(); auto column = m_columns[section]; if(role == Qt::DisplayRole) { switch(column) { case Name: return tr("Version"); case ParentVersion: return tr("Minecraft"); //FIXME: this should come from metadata case Branch: return tr("Branch"); case Type: return tr("Type"); case Architecture: return tr("Architecture"); case Path: return tr("Path"); case Time: return tr("Released"); } } else if(role == Qt::ToolTipRole) { switch(column) { case Name: return tr("The name of the version."); case ParentVersion: return tr("Minecraft version"); //FIXME: this should come from metadata case Branch: return tr("The version's branch"); case Type: return tr("The version's type"); case Architecture: return tr("CPU Architecture"); case Path: return tr("Filesystem path to this version"); case Time: return tr("Release date of this version"); } } return QVariant(); } QVariant VersionProxyModel::data(const QModelIndex &index, int role) const { if(!index.isValid()) { return QVariant(); } auto column = m_columns[index.column()]; auto parentIndex = mapToSource(index); switch(role) { case Qt::DisplayRole: { switch(column) { case Name: { QString version = sourceModel()->data(parentIndex, BaseVersionList::VersionRole).toString(); if(version == m_currentVersion) { return tr("%1 (installed)").arg(version); } return version; } case ParentVersion: return sourceModel()->data(parentIndex, BaseVersionList::ParentVersionRole); case Branch: return sourceModel()->data(parentIndex, BaseVersionList::BranchRole); case Type: return sourceModel()->data(parentIndex, BaseVersionList::TypeRole); case Architecture: return sourceModel()->data(parentIndex, BaseVersionList::ArchitectureRole); case Path: return sourceModel()->data(parentIndex, BaseVersionList::PathRole); case Time: return sourceModel()->data(parentIndex, Meta::VersionList::TimeRole).toDate(); default: return QVariant(); } } case Qt::ToolTipRole: { switch(column) { case Name: { if(hasRecommended) { auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); if(value.toBool()) { return tr("Recommended"); } else if(hasLatest) { auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); if(value.toBool()) { return tr("Latest"); } } else if(index.row() == 0) { return tr("Latest"); } } } default: { return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); } } } case Qt::DecorationRole: { switch(column) { case Name: { if(hasRecommended) { auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); if(value.toBool()) { return APPLICATION->getThemedIcon("star"); } else if(hasLatest) { auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); if(value.toBool()) { return APPLICATION->getThemedIcon("bug"); } } else if(index.row() == 0) { return APPLICATION->getThemedIcon("bug"); } QPixmap pixmap; QPixmapCache::find("placeholder", &pixmap); if(!pixmap) { QPixmap px(16,16); px.fill(Qt::transparent); QPixmapCache::insert("placeholder", px); return px; } return pixmap; } } default: { return QVariant(); } } } default: { if(roles.contains((BaseVersionList::ModelRoles)role)) { return sourceModel()->data(parentIndex, role); } return QVariant(); } } } QModelIndex VersionProxyModel::parent(const QModelIndex &child) const { return QModelIndex(); } QModelIndex VersionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const { if(sourceIndex.isValid()) { return index(sourceIndex.row(), 0); } return QModelIndex(); } QModelIndex VersionProxyModel::mapToSource(const QModelIndex &proxyIndex) const { if(proxyIndex.isValid()) { return sourceModel()->index(proxyIndex.row(), 0); } return QModelIndex(); } QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &parent) const { // no trees here... shoo if(parent.isValid()) { return QModelIndex(); } if(row < 0 || row >= sourceModel()->rowCount()) return QModelIndex(); if(column < 0 || column >= columnCount()) return QModelIndex(); return QAbstractItemModel::createIndex(row, column); } int VersionProxyModel::columnCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_columns.size(); } int VersionProxyModel::rowCount(const QModelIndex &parent) const { if(sourceModel()) { return sourceModel()->rowCount(parent); } return 0; } void VersionProxyModel::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right) { if(source_top_left.parent() != source_bottom_right.parent()) return; // whole row is getting changed auto topLeft = createIndex(source_top_left.row(), 0); auto bottomRight = createIndex(source_bottom_right.row(), columnCount() - 1); emit dataChanged(topLeft, bottomRight); } void VersionProxyModel::setSourceModel(QAbstractItemModel *replacingRaw) { auto replacing = dynamic_cast<BaseVersionList *>(replacingRaw); beginResetModel(); m_columns.clear(); if(!replacing) { roles.clear(); filterModel->setSourceModel(replacing); return; } roles = replacing->providesRoles(); if(roles.contains(BaseVersionList::VersionRole)) { m_columns.push_back(Name); } /* if(roles.contains(BaseVersionList::ParentVersionRole)) { m_columns.push_back(ParentVersion); } */ if(roles.contains(BaseVersionList::ArchitectureRole)) { m_columns.push_back(Architecture); } if(roles.contains(BaseVersionList::PathRole)) { m_columns.push_back(Path); } if(roles.contains(Meta::VersionList::TimeRole)) { m_columns.push_back(Time); } if(roles.contains(BaseVersionList::BranchRole)) { m_columns.push_back(Branch); } if(roles.contains(BaseVersionList::TypeRole)) { m_columns.push_back(Type); } if(roles.contains(BaseVersionList::RecommendedRole)) { hasRecommended = true; } if(roles.contains(BaseVersionList::LatestRole)) { hasLatest = true; } filterModel->setSourceModel(replacing); endResetModel(); } QModelIndex VersionProxyModel::getRecommended() const { if(!roles.contains(BaseVersionList::RecommendedRole)) { return index(0, 0); } int recommended = 0; for (int i = 0; i < rowCount(); i++) { auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::RecommendedRole); if (value.toBool()) { recommended = i; } } return index(recommended, 0); } QModelIndex VersionProxyModel::getVersion(const QString& version) const { int found = -1; for (int i = 0; i < rowCount(); i++) { auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::VersionRole); if (value.toString() == version) { found = i; } } if(found == -1) { return QModelIndex(); } return index(found, 0); } void VersionProxyModel::clearFilters() { m_filters.clear(); filterModel->filterChanged(); } void VersionProxyModel::setFilter(const BaseVersionList::ModelRoles column, Filter * f) { m_filters[column].reset(f); filterModel->filterChanged(); } const VersionProxyModel::FilterMap &VersionProxyModel::filters() const { return m_filters; } void VersionProxyModel::sourceAboutToBeReset() { beginResetModel(); } void VersionProxyModel::sourceReset() { endResetModel(); } void VersionProxyModel::sourceRowsAboutToBeInserted(const QModelIndex& parent, int first, int last) { beginInsertRows(parent, first, last); } void VersionProxyModel::sourceRowsInserted(const QModelIndex& parent, int first, int last) { endInsertRows(); } void VersionProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { beginRemoveRows(parent, first, last); } void VersionProxyModel::sourceRowsRemoved(const QModelIndex& parent, int first, int last) { endRemoveRows(); } void VersionProxyModel::setCurrentVersion(const QString &version) { m_currentVersion = version; } #include "VersionProxyModel.moc"