2023-03-02 11:25:36 +00:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
/*
|
|
|
|
* Prism Launcher - Minecraft Launcher
|
|
|
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
|
|
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
#include "FileIgnoreProxy.h"
|
2023-03-02 11:25:36 +00:00
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QFileSystemModel>
|
|
|
|
#include <QSortFilterProxyModel>
|
|
|
|
#include <QStack>
|
|
|
|
#include "FileSystem.h"
|
|
|
|
#include "SeparatorPrefixTree.h"
|
|
|
|
#include "StringUtils.h"
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {}
|
2023-03-02 11:25:36 +00:00
|
|
|
// NOTE: Sadly, we have to do sorting ourselves.
|
2023-03-04 19:55:38 +00:00
|
|
|
bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(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);
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
Qt::ItemFlags FileIgnoreProxy::flags(const QModelIndex& index) const
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
QVariant FileIgnoreProxy::data(const QModelIndex& index, int role) const
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
QModelIndex sourceIndex = mapToSource(index);
|
|
|
|
|
|
|
|
if (index.column() == 0 && role == Qt::CheckStateRole) {
|
|
|
|
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(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);
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, int role)
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
if (index.column() == 0 && role == Qt::CheckStateRole) {
|
|
|
|
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
|
|
|
|
return setFilterState(index, state);
|
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex sourceIndex = mapToSource(index);
|
|
|
|
return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role);
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
QString FileIgnoreProxy::relPath(const QString& path) const
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
QString prefix = QDir().absoluteFilePath(root);
|
|
|
|
prefix += '/';
|
|
|
|
if (!path.startsWith(prefix)) {
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
return path.mid(prefix.size());
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state)
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(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<QModelIndex> 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<QModelIndex> 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;
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
bool FileIgnoreProxy::shouldExpand(QModelIndex index)
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
QModelIndex sourceIndex = mapToSource(index);
|
|
|
|
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
|
|
|
|
if (!fsm) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
auto blockedPath = relPath(fsm->filePath(sourceIndex));
|
|
|
|
auto found = blocked.find(blockedPath);
|
|
|
|
if (found) {
|
|
|
|
return !found->leaf();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
void FileIgnoreProxy::setBlockedPaths(QStringList paths)
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
beginResetModel();
|
|
|
|
blocked.clear();
|
|
|
|
blocked.insert(paths);
|
|
|
|
endResetModel();
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
const SeparatorPrefixTree<'/'>& FileIgnoreProxy::blockedPaths() const
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
return blocked;
|
|
|
|
}
|
|
|
|
|
2023-03-04 19:55:38 +00:00
|
|
|
bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const
|
2023-03-02 11:25:36 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|