// SPDX-License-Identifier: GPL-3.0-only /* * Prism Launcher - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu * Copyright (C) 2023 TheKodeToad * * 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 . * * 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 "FileIgnoreProxy.h" #include #include #include #include #include #include "FileSystem.h" #include "SeparatorPrefixTree.h" #include "StringUtils.h" FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {} // NOTE: Sadly, we have to do sorting ourselves. bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const { QFileSystemModel* fsm = qobject_cast(sourceModel()); if (!fsm) { return QSortFilterProxyModel::lessThan(left, right); } bool asc = sortOrder() == Qt::AscendingOrder ? true : false; QFileInfo leftFileInfo = fsm->fileInfo(left); QFileInfo rightFileInfo = fsm->fileInfo(right); if (!leftFileInfo.isDir() && rightFileInfo.isDir()) { return !asc; } if (leftFileInfo.isDir() && !rightFileInfo.isDir()) { return asc; } // sort and proxy model breaks the original model... if (sortColumn() == 0) { return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0; } if (sortColumn() == 1) { auto leftSize = leftFileInfo.size(); auto rightSize = rightFileInfo.size(); if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) { return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0 ? asc : !asc; } return leftSize < rightSize; } return QSortFilterProxyModel::lessThan(left, right); } Qt::ItemFlags FileIgnoreProxy::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::NoItemFlags; auto sourceIndex = mapToSource(index); Qt::ItemFlags flags = sourceIndex.flags(); if (index.column() == 0) { flags |= Qt::ItemIsUserCheckable; if (sourceIndex.model()->hasChildren(sourceIndex)) { flags |= Qt::ItemIsAutoTristate; } } return flags; } QVariant FileIgnoreProxy::data(const QModelIndex& index, int role) const { QModelIndex sourceIndex = mapToSource(index); if (index.column() == 0 && role == Qt::CheckStateRole) { QFileSystemModel* fsm = qobject_cast(sourceModel()); auto blockedPath = relPath(fsm->filePath(sourceIndex)); auto cover = blocked.cover(blockedPath); if (!cover.isNull()) { return QVariant(Qt::Unchecked); } else if (blocked.exists(blockedPath)) { return QVariant(Qt::PartiallyChecked); } else { return QVariant(Qt::Checked); } } return sourceIndex.data(role); } bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, int role) { if (index.column() == 0 && role == Qt::CheckStateRole) { Qt::CheckState state = static_cast(value.toInt()); return setFilterState(index, state); } QModelIndex sourceIndex = mapToSource(index); return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role); } QString FileIgnoreProxy::relPath(const QString& path) const { return QDir(root).relativeFilePath(path); } bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state) { QFileSystemModel* fsm = qobject_cast(sourceModel()); if (!fsm) { return false; } QModelIndex sourceIndex = mapToSource(index); auto blockedPath = relPath(fsm->filePath(sourceIndex)); bool changed = false; if (state == Qt::Unchecked) { // blocking a path auto& node = blocked.insert(blockedPath); // get rid of all blocked nodes below node.clear(); changed = true; } else if (state == Qt::Checked || state == Qt::PartiallyChecked) { if (!blocked.remove(blockedPath)) { auto cover = blocked.cover(blockedPath); qDebug() << "Blocked by cover" << cover; // uncover blocked.remove(cover); // block all contents, except for any cover QModelIndex rootIndex = fsm->index(FS::PathCombine(root, cover)); QModelIndex doing = rootIndex; int row = 0; QStack todo; while (1) { auto node = fsm->index(row, 0, doing); if (!node.isValid()) { if (!todo.size()) { break; } else { doing = todo.pop(); row = 0; continue; } } auto relpath = relPath(fsm->filePath(node)); if (blockedPath.startsWith(relpath)) // cover found? { // continue processing cover later todo.push(node); } else { // or just block this one. blocked.insert(relpath); } row++; } } changed = true; } if (changed) { // update the thing emit dataChanged(index, index, { Qt::CheckStateRole }); // update everything above index QModelIndex up = index.parent(); while (1) { if (!up.isValid()) break; emit dataChanged(up, up, { Qt::CheckStateRole }); up = up.parent(); } // and everything below the index QModelIndex doing = index; int row = 0; QStack todo; while (1) { auto node = this->index(row, 0, doing); if (!node.isValid()) { if (!todo.size()) { break; } else { doing = todo.pop(); row = 0; continue; } } emit dataChanged(node, node, { Qt::CheckStateRole }); todo.push(node); row++; } // siblings and unrelated nodes are ignored } return true; } bool FileIgnoreProxy::shouldExpand(QModelIndex index) { QModelIndex sourceIndex = mapToSource(index); QFileSystemModel* fsm = qobject_cast(sourceModel()); if (!fsm) { return false; } auto blockedPath = relPath(fsm->filePath(sourceIndex)); auto found = blocked.find(blockedPath); if (found) { return !found->leaf(); } return false; } void FileIgnoreProxy::setBlockedPaths(QStringList paths) { beginResetModel(); blocked.clear(); blocked.insert(paths); endResetModel(); } bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const { Q_UNUSED(source_parent) // adjust the columns you want to filter out here // return false for those that will be hidden if (source_column == 2 || source_column == 3) return false; return true; } bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); QFileSystemModel* fsm = qobject_cast(sourceModel()); auto fileInfo = fsm->fileInfo(index); return !ignoreFile(fileInfo); } bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const { auto fileName = fileInfo.fileName(); auto path = relPath(fileInfo.absoluteFilePath()); return (path.startsWith("..") || // just in case ignore files outside the gameroot std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) || m_ignoreFilePaths.covers(path)); } bool FileIgnoreProxy::filterFile(const QString& fileName) const { return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName)); }