Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into change_version

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2024-06-16 00:18:11 +03:00
182 changed files with 4843 additions and 2070 deletions

View File

@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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/>.
*/
#include "CheckComboBox.h"
#include <QAbstractItemView>
#include <QBoxLayout>
#include <QEvent>
#include <QIdentityProxyModel>
#include <QKeyEvent>
#include <QLineEdit>
#include <QListView>
#include <QMouseEvent>
#include <QStringList>
#include <QStylePainter>
class CheckComboModel : public QIdentityProxyModel {
Q_OBJECT
public:
explicit CheckComboModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {}
virtual Qt::ItemFlags flags(const QModelIndex& index) const { return QIdentityProxyModel::flags(index) | Qt::ItemIsUserCheckable; }
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const
{
if (role == Qt::CheckStateRole) {
auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString();
return checked.contains(txt) ? Qt::Checked : Qt::Unchecked;
}
if (role == Qt::DisplayRole)
return QIdentityProxyModel::data(index, Qt::DisplayRole);
return {};
}
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole)
{
if (role == Qt::CheckStateRole) {
auto txt = QIdentityProxyModel::data(index, Qt::DisplayRole).toString();
if (checked.contains(txt)) {
checked.removeOne(txt);
} else {
checked.push_back(txt);
}
emit dataChanged(index, index);
emit checkStateChanged();
return true;
}
return QIdentityProxyModel::setData(index, value, role);
}
QStringList getChecked() { return checked; }
signals:
void checkStateChanged();
private:
QStringList checked;
};
CheckComboBox::CheckComboBox(QWidget* parent) : QComboBox(parent), m_separator(", ")
{
view()->installEventFilter(this);
view()->window()->installEventFilter(this);
view()->viewport()->installEventFilter(this);
this->installEventFilter(this);
}
void CheckComboBox::setSourceModel(QAbstractItemModel* new_model)
{
auto proxy = new CheckComboModel(this);
proxy->setSourceModel(new_model);
model()->disconnect(this);
QComboBox::setModel(proxy);
connect(this, QOverload<int>::of(&QComboBox::activated), this, &CheckComboBox::toggleCheckState);
connect(proxy, &CheckComboModel::checkStateChanged, this, &CheckComboBox::emitCheckedItemsChanged);
connect(model(), &CheckComboModel::rowsInserted, this, &CheckComboBox::emitCheckedItemsChanged);
connect(model(), &CheckComboModel::rowsRemoved, this, &CheckComboBox::emitCheckedItemsChanged);
}
void CheckComboBox::hidePopup()
{
if (!containerMousePress)
QComboBox::hidePopup();
}
void CheckComboBox::emitCheckedItemsChanged()
{
emit checkedItemsChanged(checkedItems());
}
QString CheckComboBox::defaultText() const
{
return m_default_text;
}
void CheckComboBox::setDefaultText(const QString& text)
{
m_default_text = text;
}
QString CheckComboBox::separator() const
{
return m_separator;
}
void CheckComboBox::setSeparator(const QString& separator)
{
m_separator = separator;
}
bool CheckComboBox::eventFilter(QObject* receiver, QEvent* event)
{
switch (event->type()) {
case QEvent::KeyPress:
case QEvent::KeyRelease: {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (receiver == this && (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down)) {
showPopup();
return true;
} else if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Escape) {
QComboBox::hidePopup();
return (keyEvent->key() != Qt::Key_Escape);
}
break;
}
case QEvent::MouseButtonPress: {
auto ev = static_cast<QMouseEvent*>(event);
containerMousePress = ev && view()->indexAt(ev->pos()).isValid();
break;
}
case QEvent::Wheel:
return receiver == this;
default:
break;
}
return false;
}
void CheckComboBox::toggleCheckState(int index)
{
QVariant value = itemData(index, Qt::CheckStateRole);
if (value.isValid()) {
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
setItemData(index, (state == Qt::Unchecked ? Qt::Checked : Qt::Unchecked), Qt::CheckStateRole);
}
emitCheckedItemsChanged();
}
Qt::CheckState CheckComboBox::itemCheckState(int index) const
{
return static_cast<Qt::CheckState>(itemData(index, Qt::CheckStateRole).toInt());
}
void CheckComboBox::setItemCheckState(int index, Qt::CheckState state)
{
setItemData(index, state, Qt::CheckStateRole);
}
QStringList CheckComboBox::checkedItems() const
{
if (model())
return dynamic_cast<CheckComboModel*>(model())->getChecked();
return {};
}
void CheckComboBox::setCheckedItems(const QStringList& items)
{
foreach (auto text, items) {
auto index = findText(text);
setItemCheckState(index, index != -1 ? Qt::Checked : Qt::Unchecked);
}
}
void CheckComboBox::paintEvent(QPaintEvent*)
{
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
// draw the combobox frame, focusrect and selected etc.
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStringList items = checkedItems();
if (items.isEmpty())
opt.currentText = defaultText();
else
opt.currentText = items.join(separator());
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
// draw the icon and text
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
#include "CheckComboBox.moc"

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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/>.
*/
#pragma once
#include <QComboBox>
#include <QLineEdit>
class CheckComboBox : public QComboBox {
Q_OBJECT
public:
explicit CheckComboBox(QWidget* parent = nullptr);
virtual ~CheckComboBox() = default;
void hidePopup() override;
QString defaultText() const;
void setDefaultText(const QString& text);
Qt::CheckState itemCheckState(int index) const;
void setItemCheckState(int index, Qt::CheckState state);
QString separator() const;
void setSeparator(const QString& separator);
QStringList checkedItems() const;
void setSourceModel(QAbstractItemModel* model);
public slots:
void setCheckedItems(const QStringList& items);
signals:
void checkedItemsChanged(const QStringList& items);
protected:
void paintEvent(QPaintEvent*) override;
private:
void emitCheckedItemsChanged();
bool eventFilter(QObject* receiver, QEvent* event) override;
void toggleCheckState(int index);
private:
QString m_default_text;
QString m_separator;
bool containerMousePress;
};

View File

@ -36,6 +36,8 @@
#include <QLabel>
#include <QMessageBox>
#include <QTextCursor>
#include <QTextDocument>
#include <QToolTip>
#include "InfoFrame.h"
@ -274,12 +276,27 @@ void InfoFrame::setDescription(QString text)
}
QString labeltext;
labeltext.reserve(300);
if (finaltext.length() > 290) {
// elide rich text by getting characters without formatting
const int maxCharacterElide = 290;
QTextDocument doc;
doc.setHtml(text);
if (doc.characterCount() > maxCharacterElide) {
ui->descriptionLabel->setOpenExternalLinks(false);
ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText);
ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); // This allows injecting HTML here.
m_description = text;
// This allows injecting HTML here.
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
// move the cursor to the character elide, doesn't see html
QTextCursor cursor(&doc);
cursor.movePosition(QTextCursor::End);
cursor.setPosition(maxCharacterElide, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
// insert the post fix at the cursor
cursor.insertHtml("<a href=\"#mod_desc\">...</a>");
labeltext.append(doc.toHtml());
QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler);
} else {
ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText);
@ -316,7 +333,7 @@ void InfoFrame::setLicense(QString text)
if (finaltext.length() > 290) {
ui->licenseLabel->setOpenExternalLinks(false);
ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
m_description = text;
m_license = text;
// This allows injecting HTML here.
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler);

View File

@ -1,13 +1,139 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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 "ModFilterWidget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QListWidget>
#include <algorithm>
#include <list>
#include "BaseVersionList.h"
#include "Version.h"
#include "meta/Index.h"
#include "modplatform/ModIndex.h"
#include "ui/widgets/CheckComboBox.h"
#include "ui_ModFilterWidget.h"
#include "Application.h"
#include "minecraft/PackProfile.h"
unique_qobject_ptr<ModFilterWidget> ModFilterWidget::create(Version default_version, QWidget* parent)
unique_qobject_ptr<ModFilterWidget> ModFilterWidget::create(MinecraftInstance* instance, bool extended, QWidget* parent)
{
auto filter_widget = new ModFilterWidget(default_version, parent);
return unique_qobject_ptr<ModFilterWidget>(new ModFilterWidget(instance, extended, parent));
}
if (!filter_widget->versionList()->isLoaded()) {
class VersionBasicModel : public QIdentityProxyModel {
Q_OBJECT
public:
explicit VersionBasicModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {}
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
{
if (role == Qt::DisplayRole)
return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole);
return {};
}
};
ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent)
: QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter())
{
ui->setupUi(this);
m_versions_proxy = new VersionProxyModel(this);
m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release"));
auto proxy = new VersionBasicModel(this);
proxy->setSourceModel(m_versions_proxy);
if (extended) {
ui->versions->setSourceModel(proxy);
ui->versions->setSeparator(", ");
ui->version->hide();
} else {
ui->version->setModel(proxy);
ui->versions->hide();
ui->showAllVersions->hide();
ui->environmentGroup->hide();
}
ui->versions->setStyleSheet("combobox-popup: 0;");
ui->version->setStyleSheet("combobox-popup: 0;");
connect(ui->showAllVersions, &QCheckBox::stateChanged, this, &ModFilterWidget::onShowAllVersionsChanged);
connect(ui->versions, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ModFilterWidget::onVersionFilterChanged);
connect(ui->versions, &CheckComboBox::checkedItemsChanged, this, [this] { onVersionFilterChanged(0); });
connect(ui->version, &QComboBox::currentTextChanged, this, &ModFilterWidget::onVersionFilterTextChanged);
connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged);
if (extended) {
connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged);
connect(ui->serverSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged);
}
connect(ui->hideInstalled, &QCheckBox::stateChanged, this, &ModFilterWidget::onHideInstalledFilterChanged);
setHidden(true);
loadVersionList();
prepareBasicFilter();
}
auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter>
{
m_filter_changed = false;
return m_filter;
}
ModFilterWidget::~ModFilterWidget()
{
delete ui;
}
void ModFilterWidget::loadVersionList()
{
m_version_list = APPLICATION->metadataIndex()->get("net.minecraft");
if (!m_version_list->isLoaded()) {
QEventLoop load_version_list_loop;
QTimer time_limit_for_list_load;
@ -16,10 +142,12 @@ unique_qobject_ptr<ModFilterWidget> ModFilterWidget::create(Version default_vers
time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit);
time_limit_for_list_load.start(4000);
auto task = filter_widget->versionList()->getLoadTask();
auto task = m_version_list->getLoadTask();
connect(task.get(), &Task::failed,
[filter_widget] { filter_widget->disableVersionButton(VersionButtonID::Major, tr("failed to get version index")); });
connect(task.get(), &Task::failed, [this] {
ui->versions->setEnabled(false);
ui->showAllVersions->setEnabled(false);
});
connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit);
if (!task->isRunning())
@ -29,128 +157,132 @@ unique_qobject_ptr<ModFilterWidget> ModFilterWidget::create(Version default_vers
if (time_limit_for_list_load.isActive())
time_limit_for_list_load.stop();
}
return unique_qobject_ptr<ModFilterWidget>(filter_widget);
m_versions_proxy->setSourceModel(m_version_list.get());
}
ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget)
void ModFilterWidget::prepareBasicFilter()
{
ui->setupUi(this);
m_mcVersion_buttons.addButton(ui->strictVersionButton, VersionButtonID::Strict);
ui->strictVersionButton->click();
m_mcVersion_buttons.addButton(ui->majorVersionButton, VersionButtonID::Major);
m_mcVersion_buttons.addButton(ui->allVersionsButton, VersionButtonID::All);
// m_mcVersion_buttons.addButton(ui->betweenVersionsButton, VersionButtonID::Between);
connect(&m_mcVersion_buttons, SIGNAL(idClicked(int)), this, SLOT(onVersionFilterChanged(int)));
m_filter->versions.push_front(def);
m_version_list = APPLICATION->metadataIndex()->get("net.minecraft");
setHidden(true);
m_filter->hideInstalled = false;
m_filter->side = ""; // or "both"
auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value();
ui->neoForge->setChecked(loaders & ModPlatform::NeoForge);
ui->forge->setChecked(loaders & ModPlatform::Forge);
ui->fabric->setChecked(loaders & ModPlatform::Fabric);
ui->quilt->setChecked(loaders & ModPlatform::Quilt);
m_filter->loaders = loaders;
auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft");
m_filter->versions.emplace_front(def);
ui->versions->setCheckedItems({ def });
ui->version->setCurrentIndex(ui->version->findText(def));
}
void ModFilterWidget::setInstance(MinecraftInstance* instance)
void ModFilterWidget::onShowAllVersionsChanged()
{
m_instance = instance;
ui->strictVersionButton->setText(tr("Strict match (= %1)").arg(mcVersionStr()));
// we can't do this for snapshots sadly
if (mcVersionStr().contains('.')) {
auto mcVersionSplit = mcVersionStr().split(".");
ui->majorVersionButton->setText(tr("Major version match (= %1.%2.x)").arg(mcVersionSplit[0], mcVersionSplit[1]));
} else {
ui->majorVersionButton->setText(tr("Major version match (unsupported)"));
disableVersionButton(Major);
}
ui->allVersionsButton->setText(tr("Any version"));
// ui->betweenVersionsButton->setText(
// tr("Between two versions"));
}
auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter>
{
m_last_version_id = m_version_id;
emit filterUnchanged();
return m_filter;
}
void ModFilterWidget::disableVersionButton(VersionButtonID id, QString reason)
{
QAbstractButton* btn = nullptr;
switch (id) {
case (VersionButtonID::Strict):
btn = ui->strictVersionButton;
break;
case (VersionButtonID::Major):
btn = ui->majorVersionButton;
break;
case (VersionButtonID::All):
btn = ui->allVersionsButton;
break;
case (VersionButtonID::Between):
default:
break;
}
if (btn) {
btn->setEnabled(false);
if (!reason.isEmpty())
btn->setText(btn->text() + QString(" (%1)").arg(reason));
}
}
void ModFilterWidget::onVersionFilterChanged(int id)
{
// ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between);
// ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between);
int index = 1;
auto cast_id = (VersionButtonID)id;
if (cast_id != m_version_id) {
m_version_id = cast_id;
} else {
return;
}
m_filter->versions.clear();
switch (cast_id) {
case (VersionButtonID::Strict):
m_filter->versions.push_front(mcVersion());
break;
case (VersionButtonID::Major): {
auto versionSplit = mcVersionStr().split(".");
auto major_version = QString("%1.%2").arg(versionSplit[0], versionSplit[1]);
QString version_str = major_version;
while (m_version_list->hasVersion(version_str)) {
m_filter->versions.emplace_back(version_str);
version_str = QString("%1.%2").arg(major_version, QString::number(index++));
}
break;
}
case (VersionButtonID::All):
// Empty list to avoid enumerating all versions :P
break;
case (VersionButtonID::Between):
// TODO
break;
}
if (changed())
emit filterChanged();
if (ui->showAllVersions->isChecked())
m_versions_proxy->clearFilters();
else
emit filterUnchanged();
m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release"));
}
ModFilterWidget::~ModFilterWidget()
void ModFilterWidget::onVersionFilterChanged(int)
{
delete ui;
auto versions = ui->versions->checkedItems();
versions.sort();
std::list<Version> current_list;
for (const QString& version : versions)
current_list.emplace_back(version);
m_filter_changed = m_filter->versions.size() != current_list.size() ||
!std::equal(m_filter->versions.begin(), m_filter->versions.end(), current_list.begin(), current_list.end());
m_filter->versions = current_list;
if (m_filter_changed)
emit filterChanged();
}
void ModFilterWidget::onLoadersFilterChanged()
{
ModPlatform::ModLoaderTypes loaders;
if (ui->neoForge->isChecked())
loaders |= ModPlatform::NeoForge;
if (ui->forge->isChecked())
loaders |= ModPlatform::Forge;
if (ui->fabric->isChecked())
loaders |= ModPlatform::Fabric;
if (ui->quilt->isChecked())
loaders |= ModPlatform::Quilt;
m_filter_changed = loaders != m_filter->loaders;
m_filter->loaders = loaders;
if (m_filter_changed)
emit filterChanged();
}
void ModFilterWidget::onSideFilterChanged()
{
QString side;
if (ui->clientSide->isChecked() != ui->serverSide->isChecked()) {
if (ui->clientSide->isChecked())
side = "client";
else
side = "server";
} else {
// both are checked or none are checked; in either case no filtering will happen
side = "";
}
m_filter_changed = side != m_filter->side;
m_filter->side = side;
if (m_filter_changed)
emit filterChanged();
}
void ModFilterWidget::onHideInstalledFilterChanged()
{
auto hide = ui->hideInstalled->isChecked();
m_filter_changed = hide != m_filter->hideInstalled;
m_filter->hideInstalled = hide;
if (m_filter_changed)
emit filterChanged();
}
void ModFilterWidget::onVersionFilterTextChanged(const QString& version)
{
m_filter->versions.clear();
m_filter->versions.emplace_back(version);
m_filter_changed = true;
emit filterChanged();
}
void ModFilterWidget::setCategories(const QList<ModPlatform::Category>& categories)
{
m_categories = categories;
delete ui->categoryGroup->layout();
auto layout = new QVBoxLayout(ui->categoryGroup);
for (const auto& category : categories) {
auto name = category.name;
name.replace("-", " ");
name.replace("&", "&&");
auto checkbox = new QCheckBox(name);
auto font = checkbox->font();
font.setCapitalization(QFont::Capitalize);
checkbox->setFont(font);
layout->addWidget(checkbox);
const QString id = category.id;
connect(checkbox, &QCheckBox::toggled, this, [this, id](bool checked) {
if (checked)
m_filter->categoryIds.append(id);
else
m_filter->categoryIds.removeOne(id);
m_filter_changed = true;
emit filterChanged();
});
}
}
#include "ModFilterWidget.moc"

View File

@ -1,15 +1,52 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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.
*/
#pragma once
#include <QButtonGroup>
#include <QList>
#include <QListWidgetItem>
#include <QTabWidget>
#include "Version.h"
#include "meta/Index.h"
#include "VersionProxyModel.h"
#include "meta/VersionList.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/ModIndex.h"
class MinecraftInstance;
@ -20,59 +57,57 @@ class ModFilterWidget;
class ModFilterWidget : public QTabWidget {
Q_OBJECT
public:
enum VersionButtonID { Strict = 0, Major = 1, All = 2, Between = 3 };
struct Filter {
std::list<Version> versions;
std::list<ModPlatform::IndexedVersionType> releases;
ModPlatform::ModLoaderTypes loaders;
QString side;
bool hideInstalled;
QStringList categoryIds;
bool operator==(const Filter& other) const { return versions == other.versions; }
bool operator==(const Filter& other) const
{
return hideInstalled == other.hideInstalled && side == other.side && loaders == other.loaders && versions == other.versions &&
releases == other.releases && categoryIds == other.categoryIds;
}
bool operator!=(const Filter& other) const { return !(*this == other); }
};
std::shared_ptr<Filter> m_filter;
public:
static unique_qobject_ptr<ModFilterWidget> create(Version default_version, QWidget* parent = nullptr);
~ModFilterWidget();
void setInstance(MinecraftInstance* instance);
/// By default all buttons are enabled
void disableVersionButton(VersionButtonID, QString reason = {});
static unique_qobject_ptr<ModFilterWidget> create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr);
virtual ~ModFilterWidget();
auto getFilter() -> std::shared_ptr<Filter>;
auto changed() const -> bool { return m_last_version_id != m_version_id; }
auto changed() const -> bool { return m_filter_changed; }
Meta::VersionList::Ptr versionList() { return m_version_list; }
private:
ModFilterWidget(Version def, QWidget* parent = nullptr);
inline auto mcVersionStr() const -> QString
{
return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : "";
}
inline auto mcVersion() const -> Version { return { mcVersionStr() }; }
private slots:
void onVersionFilterChanged(int id);
public:
signals:
void filterChanged();
void filterUnchanged();
public slots:
void setCategories(const QList<ModPlatform::Category>&);
private:
ModFilterWidget(MinecraftInstance* instance, bool extendedSupport, QWidget* parent = nullptr);
void loadVersionList();
void prepareBasicFilter();
private slots:
void onVersionFilterChanged(int);
void onVersionFilterTextChanged(const QString& version);
void onLoadersFilterChanged();
void onSideFilterChanged();
void onHideInstalledFilterChanged();
void onShowAllVersionsChanged();
private:
Ui::ModFilterWidget* ui;
MinecraftInstance* m_instance = nullptr;
/* Version stuff */
QButtonGroup m_mcVersion_buttons;
std::shared_ptr<Filter> m_filter;
bool m_filter_changed = false;
Meta::VersionList::Ptr m_version_list;
VersionProxyModel* m_versions_proxy = nullptr;
/* Used to tell if the filter was changed since the last getFilter() call */
VersionButtonID m_last_version_id = VersionButtonID::Strict;
VersionButtonID m_version_id = VersionButtonID::Strict;
QList<ModPlatform::Category> m_categories;
};

View File

@ -1,54 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ModFilterWidget</class>
<widget class="QTabWidget" name="ModFilterWidget">
<widget class="QWidget" name="ModFilterWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>310</width>
<height>600</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<widget class="QWidget" name="VersionPage">
<attribute name="title">
<string>Minecraft versions</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<item row="2" column="0">
<widget class="QRadioButton" name="allVersionsButton">
<property name="text">
<string notr="true">allVersions</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="strictVersionButton">
<property name="text">
<string notr="true">strictVersion</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="majorVersionButton">
<property name="text">
<string notr="true">majorVersion</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<property name="minimumSize">
<size>
<width>275</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>310</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="minimumSize">
<size>
<width>275</width>
<height>0</height>
</size>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>308</width>
<height>598</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="categoryGroup">
<property name="title">
<string>Categories</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="loaderGroup">
<property name="title">
<string>Loaders</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="neoForge">
<property name="text">
<string>NeoForge</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="forge">
<property name="text">
<string>Forge</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="fabric">
<property name="text">
<string>Fabric</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="quilt">
<property name="text">
<string>Quilt</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="minecraftVersionGroup">
<property name="title">
<string>Versions</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="showAllVersions">
<property name="text">
<string>Show all versions</string>
</property>
</widget>
</item>
<item>
<widget class="CheckComboBox" name="versions"/>
</item>
<item>
<widget class="QComboBox" name="version"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="environmentGroup">
<property name="title">
<string>Environments</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="clientSide">
<property name="text">
<string>Client</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="serverSide">
<property name="text">
<string>Server</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="hideInstalled">
<property name="text">
<string>Hide installed items</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CheckComboBox</class>
<extends>QComboBox</extends>
<header>ui/widgets/CheckComboBox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -27,6 +27,7 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa
{
ui->setupUi(this);
loadSettings();
ThemeCustomizationWidget::refresh();
connect(ui->iconsComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme);
connect(ui->widgetStyleComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
@ -39,6 +40,8 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa
[] { DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
connect(ui->catPackFolder, &QPushButton::clicked, this,
[] { DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path()); });
connect(ui->refreshButton, &QPushButton::clicked, this, &ThemeCustomizationWidget::refresh);
}
ThemeCustomizationWidget::~ThemeCustomizationWidget()
@ -169,3 +172,22 @@ void ThemeCustomizationWidget::retranslate()
{
ui->retranslateUi(this);
}
void ThemeCustomizationWidget::refresh()
{
applySettings();
disconnect(ui->iconsComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme);
disconnect(ui->widgetStyleComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ThemeCustomizationWidget::applyWidgetTheme);
disconnect(ui->backgroundCatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ThemeCustomizationWidget::applyCatTheme);
APPLICATION->themeManager()->refresh();
ui->iconsComboBox->clear();
ui->widgetStyleComboBox->clear();
ui->backgroundCatComboBox->clear();
loadSettings();
connect(ui->iconsComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme);
connect(ui->widgetStyleComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ThemeCustomizationWidget::applyWidgetTheme);
connect(ui->backgroundCatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme);
};

View File

@ -44,6 +44,7 @@ class ThemeCustomizationWidget : public QWidget {
void applyIconTheme(int index);
void applyWidgetTheme(int index);
void applyCatTheme(int index);
void refresh();
signals:
int currentIconThemeChanged(int index);

View File

@ -13,7 +13,7 @@
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QFormLayout" name="formLayout">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
@ -29,141 +29,179 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="iconsLabel">
<property name="text">
<string>&amp;Icons</string>
</property>
<property name="buddy">
<cstring>iconsComboBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="iconsLayout">
<item>
<widget class="QComboBox" name="iconsComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="iconsFolder">
<property name="toolTip">
<string>View icon themes folder.</string>
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="iconsLabel">
<property name="text">
<string/>
<string>&amp;Icons</string>
</property>
<property name="icon">
<iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
<property name="buddy">
<cstring>iconsComboBox</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="widgetStyleLabel">
<property name="text">
<string>&amp;Widgets</string>
</property>
<property name="buddy">
<cstring>widgetStyleComboBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="widgetStyleLayout">
<item>
<widget class="QComboBox" name="widgetStyleComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
<item row="0" column="1">
<layout class="QHBoxLayout" name="iconsLayout">
<item>
<widget class="QComboBox" name="iconsComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="iconsFolder">
<property name="toolTip">
<string>View icon themes folder.</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="viewfolder"/>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="widgetStyleFolder">
<property name="toolTip">
<string>View widget themes folder.</string>
</property>
<item row="1" column="0">
<widget class="QLabel" name="widgetStyleLabel">
<property name="text">
<string/>
<string>&amp;Widgets</string>
</property>
<property name="icon">
<iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
<property name="buddy">
<cstring>widgetStyleComboBox</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="backgroundCatLabel">
<property name="toolTip">
<string>The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar.</string>
</property>
<property name="text">
<string>C&amp;at</string>
</property>
<property name="buddy">
<cstring>backgroundCatComboBox</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="catLayout">
<item>
<widget class="QComboBox" name="backgroundCatComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<item row="1" column="1">
<layout class="QHBoxLayout" name="widgetStyleLayout">
<item>
<widget class="QComboBox" name="widgetStyleComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="widgetStyleFolder">
<property name="toolTip">
<string>View widget themes folder.</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="viewfolder"/>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="backgroundCatLabel">
<property name="toolTip">
<string>The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar.</string>
</property>
<property name="text">
<string>C&amp;at</string>
</property>
<property name="buddy">
<cstring>backgroundCatComboBox</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="catLayout">
<item>
<widget class="QComboBox" name="backgroundCatComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar.</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="catPackFolder">
<property name="toolTip">
<string>View cat packs folder.</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="viewfolder"/>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="refreshLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refreshButton">
<property name="text">
<string>Refresh all</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="catPackFolder">
<property name="toolTip">
<string>View cat packs folder.</string>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="text">
<string/>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
<property name="icon">
<iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</spacer>
</item>
</layout>
</item>