summaryrefslogtreecommitdiff
path: root/meshmc/launcher/VersionProxyModel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'meshmc/launcher/VersionProxyModel.cpp')
-rw-r--r--meshmc/launcher/VersionProxyModel.cpp453
1 files changed, 453 insertions, 0 deletions
diff --git a/meshmc/launcher/VersionProxyModel.cpp b/meshmc/launcher/VersionProxyModel.cpp
new file mode 100644
index 0000000000..8b299834b9
--- /dev/null
+++ b/meshmc/launcher/VersionProxyModel.cpp
@@ -0,0 +1,453 @@
+/* SPDX-FileCopyrightText: 2026 Project Tick
+ * SPDX-FileContributor: Project Tick
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * MeshMC - A Custom Launcher for Minecraft
+ * Copyright (C) 2026 Project Tick
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/>.
+ */
+
+#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;
+ if (!QPixmapCache::find("placeholder", &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 m_columns.size();
+}
+
+int VersionProxyModel::rowCount(const QModelIndex& parent) const
+{
+ if (sourceModel()) {
+ return sourceModel()->rowCount();
+ }
+ 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);
+ endResetModel();
+ 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"