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:
@@ -77,7 +77,6 @@
|
||||
#include <DesktopServices.h>
|
||||
#include <InstanceList.h>
|
||||
#include <MMCZip.h>
|
||||
#include <SkinUtils.h>
|
||||
#include <icons/IconList.h>
|
||||
#include <java/JavaInstallList.h>
|
||||
#include <java/JavaUtils.h>
|
||||
@@ -869,30 +868,6 @@ void MainWindow::on_actionCopyInstance_triggered()
|
||||
runModalTask(task.get());
|
||||
}
|
||||
|
||||
void MainWindow::finalizeInstance(InstancePtr inst)
|
||||
{
|
||||
view->updateGeometries();
|
||||
setSelectedInstanceById(inst->id());
|
||||
if (APPLICATION->accounts()->anyAccountIsValid()) {
|
||||
ProgressDialog loadDialog(this);
|
||||
auto update = inst->createUpdateTask(Net::Mode::Online);
|
||||
connect(update.get(), &Task::failed, [this](QString reason) {
|
||||
QString error = QString("Instance load failed: %1").arg(reason);
|
||||
CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show();
|
||||
});
|
||||
if (update) {
|
||||
loadDialog.setSkipButton(true, tr("Abort"));
|
||||
loadDialog.execWithTask(update.get());
|
||||
}
|
||||
} else {
|
||||
CustomMessageBox::selectable(this, tr("Error"),
|
||||
tr("The launcher cannot download Minecraft or update instances unless you have at least "
|
||||
"one account added.\nPlease add a Microsoft account."),
|
||||
QMessageBox::Warning)
|
||||
->show();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info)
|
||||
{
|
||||
QString groupName;
|
||||
@@ -1210,6 +1185,11 @@ void MainWindow::on_actionViewCentralModsFolder_triggered()
|
||||
DesktopServices::openPath(APPLICATION->settings()->get("CentralModsDir").toString(), true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewSkinsFolder_triggered()
|
||||
{
|
||||
DesktopServices::openPath(APPLICATION->settings()->get("SkinsDir").toString(), true);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionViewIconThemeFolder_triggered()
|
||||
{
|
||||
DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
|
||||
|
||||
@@ -120,6 +120,8 @@ class MainWindow : public QMainWindow {
|
||||
void on_actionViewIconsFolder_triggered();
|
||||
void on_actionViewLogsFolder_triggered();
|
||||
|
||||
void on_actionViewSkinsFolder_triggered();
|
||||
|
||||
void on_actionViewSelectedInstFolder_triggered();
|
||||
|
||||
void refreshInstances();
|
||||
@@ -227,7 +229,6 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
void runModalTask(Task* task);
|
||||
void instanceFromInstanceTask(InstanceTask* task);
|
||||
void finalizeInstance(InstancePtr inst);
|
||||
|
||||
private:
|
||||
Ui::MainWindow* ui;
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>20</height>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="fileMenu">
|
||||
@@ -191,6 +191,7 @@
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionViewInstanceFolder"/>
|
||||
<addaction name="actionViewCentralModsFolder"/>
|
||||
<addaction name="actionViewSkinsFolder"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionViewIconThemeFolder"/>
|
||||
<addaction name="actionViewWidgetThemeFolder"/>
|
||||
@@ -578,6 +579,18 @@
|
||||
<string>Open the central mods folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewSkinsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Skins</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the skins folder in a file browser.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewIconsFolder">
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "Markdown.h"
|
||||
#include "StringUtils.h"
|
||||
#include "ui_AboutDialog.h"
|
||||
|
||||
#include <net/NetJob.h>
|
||||
@@ -139,10 +140,10 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
|
||||
setWindowTitle(tr("About %1").arg(launcherName));
|
||||
|
||||
QString chtml = getCreditsHtml();
|
||||
ui->creditsText->setHtml(chtml);
|
||||
ui->creditsText->setHtml(StringUtils::htmlListPatch(chtml));
|
||||
|
||||
QString lhtml = getLicenseHtml();
|
||||
ui->licenseText->setHtml(lhtml);
|
||||
ui->licenseText->setHtml(StringUtils::htmlListPatch(lhtml));
|
||||
|
||||
ui->urlLabel->setOpenExternalLinks(true);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <QTextEdit>
|
||||
#include "FileSystem.h"
|
||||
#include "Markdown.h"
|
||||
#include "StringUtils.h"
|
||||
#include "modplatform/helpers/ExportToModList.h"
|
||||
#include "ui_ExportToModListDialog.h"
|
||||
|
||||
@@ -136,10 +137,10 @@ void ExportToModListDialog::triggerImp()
|
||||
case ExportToModList::CUSTOM:
|
||||
return;
|
||||
case ExportToModList::HTML:
|
||||
ui->resultText->setHtml(txt);
|
||||
ui->resultText->setHtml(StringUtils::htmlListPatch(txt));
|
||||
break;
|
||||
case ExportToModList::MARKDOWN:
|
||||
ui->resultText->setHtml(markdownToHTML(txt));
|
||||
ui->resultText->setHtml(StringUtils::htmlListPatch(markdownToHTML(txt)));
|
||||
break;
|
||||
case ExportToModList::PLAINTXT:
|
||||
break;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "CustomMessageBox.h"
|
||||
#include "ProgressDialog.h"
|
||||
#include "ScrollMessageBox.h"
|
||||
#include "StringUtils.h"
|
||||
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
@@ -473,7 +474,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
|
||||
break;
|
||||
}
|
||||
|
||||
changelog_area->setHtml(text);
|
||||
changelog_area->setHtml(StringUtils::htmlListPatch(text));
|
||||
changelog_area->setOpenExternalLinks(true);
|
||||
changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
|
||||
changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include <QFileDialog>
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
#include <QScreen>
|
||||
#include <QValidator>
|
||||
#include <utility>
|
||||
|
||||
@@ -63,6 +64,7 @@
|
||||
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
|
||||
#include "ui/pages/modplatform/technic/TechnicPage.h"
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
|
||||
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
|
||||
const QString& url,
|
||||
const QMap<QString, QString>& extra_info,
|
||||
@@ -127,7 +129,17 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
|
||||
|
||||
updateDialogState();
|
||||
|
||||
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray()));
|
||||
if (APPLICATION->settings()->get("NewInstanceGeometry").isValid()) {
|
||||
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("NewInstanceGeometry").toByteArray()));
|
||||
} else {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
auto screen = parent->screen();
|
||||
#else
|
||||
auto screen = QGuiApplication::primaryScreen();
|
||||
#endif
|
||||
auto geometry = screen->availableSize();
|
||||
resize(width(), qMin(geometry.height() - 50, 710));
|
||||
}
|
||||
}
|
||||
|
||||
void NewInstanceDialog::reject()
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <QItemSelectionModel>
|
||||
|
||||
#include "Application.h"
|
||||
#include "SkinUtils.h"
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "Application.h"
|
||||
#include "ResourceDownloadTask.h"
|
||||
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourcePackFolderModel.h"
|
||||
#include "minecraft/mod/ShaderPackFolderModel.h"
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - 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 <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QPainter>
|
||||
|
||||
#include <FileSystem.h>
|
||||
|
||||
#include <minecraft/services/CapeChange.h>
|
||||
#include <minecraft/services/SkinUpload.h>
|
||||
#include <tasks/SequentialTask.h>
|
||||
|
||||
#include "CustomMessageBox.h"
|
||||
#include "ProgressDialog.h"
|
||||
#include "SkinUploadDialog.h"
|
||||
#include "ui_SkinUploadDialog.h"
|
||||
|
||||
void SkinUploadDialog::on_buttonBox_rejected()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void SkinUploadDialog::on_buttonBox_accepted()
|
||||
{
|
||||
QString fileName;
|
||||
QString input = ui->skinPathTextBox->text();
|
||||
ProgressDialog prog(this);
|
||||
SequentialTask skinUpload;
|
||||
|
||||
if (!input.isEmpty()) {
|
||||
QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$"));
|
||||
bool isLocalFile = false;
|
||||
// it has an URL prefix -> it is an URL
|
||||
if (urlPrefixMatcher.match(input).hasMatch()) {
|
||||
QUrl fileURL = input;
|
||||
if (fileURL.isValid()) {
|
||||
// local?
|
||||
if (fileURL.isLocalFile()) {
|
||||
isLocalFile = true;
|
||||
fileName = fileURL.toLocalFile();
|
||||
} else {
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Using remote URLs for setting skins is not implemented yet."),
|
||||
QMessageBox::Warning)
|
||||
->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("You cannot use an invalid URL for uploading skins."),
|
||||
QMessageBox::Warning)
|
||||
->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// just assume it's a path then
|
||||
isLocalFile = true;
|
||||
fileName = ui->skinPathTextBox->text();
|
||||
}
|
||||
if (isLocalFile && !QFile::exists(fileName)) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
SkinUpload::Model model = SkinUpload::STEVE;
|
||||
if (ui->steveBtn->isChecked()) {
|
||||
model = SkinUpload::STEVE;
|
||||
} else if (ui->alexBtn->isChecked()) {
|
||||
model = SkinUpload::ALEX;
|
||||
}
|
||||
skinUpload.addTask(shared_qobject_ptr<SkinUpload>(new SkinUpload(this, m_acct->accessToken(), FS::read(fileName), model)));
|
||||
}
|
||||
|
||||
auto selectedCape = ui->capeCombo->currentData().toString();
|
||||
if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
||||
skinUpload.addTask(shared_qobject_ptr<CapeChange>(new CapeChange(this, m_acct->accessToken(), selectedCape)));
|
||||
}
|
||||
if (prog.execWithTask(&skinUpload) != QDialog::Accepted) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec();
|
||||
close();
|
||||
}
|
||||
|
||||
void SkinUploadDialog::on_skinBrowseBtn_clicked()
|
||||
{
|
||||
auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString();
|
||||
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter);
|
||||
if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) {
|
||||
return;
|
||||
}
|
||||
QString cooked_path = FS::NormalizePath(raw_path);
|
||||
ui->skinPathTextBox->setText(cooked_path);
|
||||
}
|
||||
|
||||
SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent) : QDialog(parent), m_acct(acct), ui(new Ui::SkinUploadDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// FIXME: add a model for this, download/refresh the capes on demand
|
||||
auto& accountData = *acct->accountData();
|
||||
int index = 0;
|
||||
ui->capeCombo->addItem(tr("No Cape"), QVariant());
|
||||
auto currentCape = accountData.minecraftProfile.currentCape;
|
||||
if (currentCape.isEmpty()) {
|
||||
ui->capeCombo->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
for (auto& cape : accountData.minecraftProfile.capes) {
|
||||
index++;
|
||||
if (cape.data.size()) {
|
||||
QPixmap capeImage;
|
||||
if (capeImage.loadFromData(cape.data, "PNG")) {
|
||||
QPixmap preview = QPixmap(10, 16);
|
||||
QPainter painter(&preview);
|
||||
painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16));
|
||||
ui->capeCombo->addItem(capeImage, cape.alias, cape.id);
|
||||
if (currentCape == cape.id) {
|
||||
ui->capeCombo->setCurrentIndex(index);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ui->capeCombo->addItem(cape.alias, cape.id);
|
||||
if (currentCape == cape.id) {
|
||||
ui->capeCombo->setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <minecraft/auth/MinecraftAccount.h>
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class SkinUploadDialog;
|
||||
}
|
||||
|
||||
class SkinUploadDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent = 0);
|
||||
virtual ~SkinUploadDialog(){};
|
||||
|
||||
public slots:
|
||||
void on_buttonBox_accepted();
|
||||
|
||||
void on_buttonBox_rejected();
|
||||
|
||||
void on_skinBrowseBtn_clicked();
|
||||
|
||||
protected:
|
||||
MinecraftAccountPtr m_acct;
|
||||
|
||||
private:
|
||||
Ui::SkinUploadDialog* ui;
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SkinUploadDialog</class>
|
||||
<widget class="QDialog" name="SkinUploadDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>394</width>
|
||||
<height>360</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Skin Upload</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="fileBox">
|
||||
<property name="title">
|
||||
<string>Skin File</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="skinPathTextBox">
|
||||
<property name="placeholderText">
|
||||
<string>Leave empty to keep current skin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="skinBrowseBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="modelBox">
|
||||
<property name="title">
|
||||
<string>Player Model</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="steveBtn">
|
||||
<property name="text">
|
||||
<string>Steve Model</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="alexBtn">
|
||||
<property name="text">
|
||||
<string>Alex Model</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="capeBox">
|
||||
<property name="title">
|
||||
<string>Cape</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QComboBox" name="capeCombo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "Markdown.h"
|
||||
#include "StringUtils.h"
|
||||
#include "ui_UpdateAvailableDialog.h"
|
||||
|
||||
UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion,
|
||||
@@ -43,7 +44,7 @@ UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion,
|
||||
ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64));
|
||||
|
||||
auto releaseNotesHtml = markdownToHTML(releaseNotes);
|
||||
ui->releaseNotes->setHtml(releaseNotesHtml);
|
||||
ui->releaseNotes->setHtml(StringUtils::htmlListPatch(releaseNotesHtml));
|
||||
ui->releaseNotes->setOpenExternalLinks(true);
|
||||
|
||||
connect(ui->skipButton, &QPushButton::clicked, this, [this]() {
|
||||
|
||||
500
launcher/ui/dialogs/skins/SkinManageDialog.cpp
Normal file
500
launcher/ui/dialogs/skins/SkinManageDialog.cpp
Normal file
@@ -0,0 +1,500 @@
|
||||
// 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 "SkinManageDialog.h"
|
||||
#include "ui_SkinManageDialog.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <QAction>
|
||||
#include <QDialog>
|
||||
#include <QEventLoop>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QKeyEvent>
|
||||
#include <QListView>
|
||||
#include <QMimeDatabase>
|
||||
#include <QPainter>
|
||||
#include <QUrl>
|
||||
|
||||
#include "Application.h"
|
||||
#include "DesktopServices.h"
|
||||
#include "Json.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "minecraft/skins/CapeChange.h"
|
||||
#include "minecraft/skins/SkinDelete.h"
|
||||
#include "minecraft/skins/SkinList.h"
|
||||
#include "minecraft/skins/SkinModel.h"
|
||||
#include "minecraft/skins/SkinUpload.h"
|
||||
|
||||
#include "net/Download.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/instanceview/InstanceDelegate.h"
|
||||
|
||||
SkinManageDialog::SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct)
|
||||
: QDialog(parent), m_acct(acct), ui(new Ui::SkinManageDialog), m_list(this, APPLICATION->settings()->get("SkinsDir").toString(), acct)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
setWindowModality(Qt::WindowModal);
|
||||
|
||||
auto contentsWidget = ui->listView;
|
||||
contentsWidget->setViewMode(QListView::IconMode);
|
||||
contentsWidget->setFlow(QListView::LeftToRight);
|
||||
contentsWidget->setIconSize(QSize(48, 48));
|
||||
contentsWidget->setMovement(QListView::Static);
|
||||
contentsWidget->setResizeMode(QListView::Adjust);
|
||||
contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
contentsWidget->setSpacing(5);
|
||||
contentsWidget->setWordWrap(false);
|
||||
contentsWidget->setWrapping(true);
|
||||
contentsWidget->setUniformItemSizes(true);
|
||||
contentsWidget->setTextElideMode(Qt::ElideRight);
|
||||
contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
contentsWidget->installEventFilter(this);
|
||||
contentsWidget->setItemDelegate(new ListViewDelegate(this));
|
||||
|
||||
contentsWidget->setAcceptDrops(true);
|
||||
contentsWidget->setDropIndicatorShown(true);
|
||||
contentsWidget->viewport()->setAcceptDrops(true);
|
||||
contentsWidget->setDragDropMode(QAbstractItemView::DropOnly);
|
||||
contentsWidget->setDefaultDropAction(Qt::CopyAction);
|
||||
|
||||
contentsWidget->installEventFilter(this);
|
||||
contentsWidget->setModel(&m_list);
|
||||
|
||||
connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex)));
|
||||
|
||||
connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
|
||||
SLOT(selectionChanged(QItemSelection, QItemSelection)));
|
||||
connect(ui->listView, &QListView::customContextMenuRequested, this, &SkinManageDialog::show_context_menu);
|
||||
|
||||
setupCapes();
|
||||
|
||||
ui->listView->setCurrentIndex(m_list.index(m_list.getSelectedAccountSkin()));
|
||||
}
|
||||
|
||||
SkinManageDialog::~SkinManageDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void SkinManageDialog::activated(QModelIndex index)
|
||||
{
|
||||
m_selected_skin = index.data(Qt::UserRole).toString();
|
||||
accept();
|
||||
}
|
||||
|
||||
void SkinManageDialog::selectionChanged(QItemSelection selected, QItemSelection deselected)
|
||||
{
|
||||
if (selected.empty())
|
||||
return;
|
||||
|
||||
QString key = selected.first().indexes().first().data(Qt::UserRole).toString();
|
||||
if (key.isEmpty())
|
||||
return;
|
||||
m_selected_skin = key;
|
||||
auto skin = m_list.skin(key);
|
||||
if (!skin)
|
||||
return;
|
||||
ui->selectedModel->setPixmap(skin->getTexture().scaled(128, 128, Qt::KeepAspectRatio, Qt::FastTransformation));
|
||||
ui->capeCombo->setCurrentIndex(m_capes_idx.value(skin->getCapeId()));
|
||||
ui->steveBtn->setChecked(skin->getModel() == SkinModel::CLASSIC);
|
||||
ui->alexBtn->setChecked(skin->getModel() == SkinModel::SLIM);
|
||||
}
|
||||
|
||||
void SkinManageDialog::delayed_scroll(QModelIndex model_index)
|
||||
{
|
||||
auto contentsWidget = ui->listView;
|
||||
contentsWidget->scrollTo(model_index);
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_openDirBtn_clicked()
|
||||
{
|
||||
DesktopServices::openPath(m_list.getDir(), true);
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_fileBtn_clicked()
|
||||
{
|
||||
auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString();
|
||||
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter);
|
||||
auto message = m_list.installSkin(raw_path, {});
|
||||
if (!message.isEmpty()) {
|
||||
CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap previewCape(QPixmap capeImage)
|
||||
{
|
||||
QPixmap preview = QPixmap(10, 16);
|
||||
QPainter painter(&preview);
|
||||
painter.drawPixmap(0, 0, capeImage.copy(1, 1, 10, 16));
|
||||
return preview.scaled(80, 128, Qt::IgnoreAspectRatio, Qt::FastTransformation);
|
||||
}
|
||||
|
||||
void SkinManageDialog::setupCapes()
|
||||
{
|
||||
// FIXME: add a model for this, download/refresh the capes on demand
|
||||
auto& accountData = *m_acct->accountData();
|
||||
int index = 0;
|
||||
ui->capeCombo->addItem(tr("No Cape"), QVariant());
|
||||
auto currentCape = accountData.minecraftProfile.currentCape;
|
||||
if (currentCape.isEmpty()) {
|
||||
ui->capeCombo->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
auto capesDir = FS::PathCombine(m_list.getDir(), "capes");
|
||||
NetJob::Ptr job{ new NetJob(tr("Download capes"), APPLICATION->network()) };
|
||||
bool needsToDownload = false;
|
||||
for (auto& cape : accountData.minecraftProfile.capes) {
|
||||
auto path = FS::PathCombine(capesDir, cape.id + ".png");
|
||||
if (cape.data.size()) {
|
||||
QPixmap capeImage;
|
||||
if (capeImage.loadFromData(cape.data, "PNG") && capeImage.save(path)) {
|
||||
m_capes[cape.id] = previewCape(capeImage);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (QFileInfo(path).exists()) {
|
||||
continue;
|
||||
}
|
||||
if (!cape.url.isEmpty()) {
|
||||
needsToDownload = true;
|
||||
job->addNetAction(Net::Download::makeFile(cape.url, path));
|
||||
}
|
||||
}
|
||||
if (needsToDownload) {
|
||||
ProgressDialog dlg(this);
|
||||
dlg.execWithTask(job.get());
|
||||
}
|
||||
for (auto& cape : accountData.minecraftProfile.capes) {
|
||||
index++;
|
||||
QPixmap capeImage;
|
||||
if (!m_capes.contains(cape.id)) {
|
||||
auto path = FS::PathCombine(capesDir, cape.id + ".png");
|
||||
if (QFileInfo(path).exists() && capeImage.load(path)) {
|
||||
capeImage = previewCape(capeImage);
|
||||
m_capes[cape.id] = capeImage;
|
||||
}
|
||||
}
|
||||
if (!capeImage.isNull()) {
|
||||
ui->capeCombo->addItem(capeImage, cape.alias, cape.id);
|
||||
} else {
|
||||
ui->capeCombo->addItem(cape.alias, cape.id);
|
||||
}
|
||||
|
||||
m_capes_idx[cape.id] = index;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_capeCombo_currentIndexChanged(int index)
|
||||
{
|
||||
auto id = ui->capeCombo->currentData();
|
||||
ui->capeImage->setPixmap(m_capes.value(id.toString(), {}));
|
||||
if (auto skin = m_list.skin(m_selected_skin); skin) {
|
||||
skin->setCapeId(id.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_steveBtn_toggled(bool checked)
|
||||
{
|
||||
if (auto skin = m_list.skin(m_selected_skin); skin) {
|
||||
skin->setModel(checked ? SkinModel::CLASSIC : SkinModel::SLIM);
|
||||
}
|
||||
}
|
||||
|
||||
void SkinManageDialog::accept()
|
||||
{
|
||||
auto skin = m_list.skin(m_selected_skin);
|
||||
if (!skin) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
auto path = skin->getPath();
|
||||
|
||||
ProgressDialog prog(this);
|
||||
NetJob::Ptr skinUpload{ new NetJob(tr("Change skin"), APPLICATION->network(), 1) };
|
||||
|
||||
if (!QFile::exists(path)) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
skinUpload->addNetAction(SkinUpload::make(m_acct->accessToken(), skin->getPath(), skin->getModelString()));
|
||||
|
||||
auto selectedCape = skin->getCapeId();
|
||||
if (selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
||||
skinUpload->addNetAction(CapeChange::make(m_acct->accessToken(), selectedCape));
|
||||
}
|
||||
|
||||
skinUpload->addTask(m_acct->refresh().staticCast<Task>());
|
||||
if (prog.execWithTask(skinUpload.get()) != QDialog::Accepted) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
skin->setURL(m_acct->accountData()->minecraftProfile.skin.url);
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_resetBtn_clicked()
|
||||
{
|
||||
ProgressDialog prog(this);
|
||||
NetJob::Ptr skinReset{ new NetJob(tr("Reset skin"), APPLICATION->network(), 1) };
|
||||
skinReset->addNetAction(SkinDelete::make(m_acct->accessToken()));
|
||||
skinReset->addTask(m_acct->refresh().staticCast<Task>());
|
||||
if (prog.execWithTask(skinReset.get()) != QDialog::Accepted) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec();
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void SkinManageDialog::show_context_menu(const QPoint& pos)
|
||||
{
|
||||
QMenu myMenu(tr("Context menu"), this);
|
||||
myMenu.addAction(ui->action_Rename_Skin);
|
||||
myMenu.addAction(ui->action_Delete_Skin);
|
||||
|
||||
myMenu.exec(ui->listView->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
bool SkinManageDialog::eventFilter(QObject* obj, QEvent* ev)
|
||||
{
|
||||
if (obj == ui->listView) {
|
||||
if (ev->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
|
||||
switch (keyEvent->key()) {
|
||||
case Qt::Key_Delete:
|
||||
on_action_Delete_Skin_triggered(false);
|
||||
return true;
|
||||
case Qt::Key_F2:
|
||||
on_action_Rename_Skin_triggered(false);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QDialog::eventFilter(obj, ev);
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_action_Rename_Skin_triggered(bool checked)
|
||||
{
|
||||
if (!m_selected_skin.isEmpty()) {
|
||||
ui->listView->edit(ui->listView->currentIndex());
|
||||
}
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_action_Delete_Skin_triggered(bool checked)
|
||||
{
|
||||
if (m_selected_skin.isEmpty())
|
||||
return;
|
||||
|
||||
if (m_list.getSkinIndex(m_selected_skin) == m_list.getSelectedAccountSkin()) {
|
||||
CustomMessageBox::selectable(this, tr("Delete error"), tr("Can not delete skin that is in use."), QMessageBox::Warning)->exec();
|
||||
return;
|
||||
}
|
||||
|
||||
auto skin = m_list.skin(m_selected_skin);
|
||||
if (!skin)
|
||||
return;
|
||||
|
||||
auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
|
||||
tr("You are about to delete \"%1\".\n"
|
||||
"Are you sure?")
|
||||
.arg(skin->name()),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response == QMessageBox::Yes) {
|
||||
if (!m_list.deleteSkin(m_selected_skin, true)) {
|
||||
m_list.deleteSkin(m_selected_skin, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkinManageDialog::on_urlBtn_clicked()
|
||||
{
|
||||
auto url = QUrl(ui->urlLine->text());
|
||||
if (!url.isValid()) {
|
||||
CustomMessageBox::selectable(this, tr("Invalid url"), tr("Invalid url"), QMessageBox::Critical)->show();
|
||||
return;
|
||||
}
|
||||
|
||||
NetJob::Ptr job{ new NetJob(tr("Download skin"), APPLICATION->network()) };
|
||||
job->setAskRetry(false);
|
||||
|
||||
auto path = FS::PathCombine(m_list.getDir(), url.fileName());
|
||||
job->addNetAction(Net::Download::makeFile(url, path));
|
||||
ProgressDialog dlg(this);
|
||||
dlg.execWithTask(job.get());
|
||||
SkinModel s(path);
|
||||
if (!s.isValid()) {
|
||||
CustomMessageBox::selectable(this, tr("URL is not a valid skin"),
|
||||
QFileInfo::exists(path) ? tr("Skin images must be 64x64 or 64x32 pixel PNG files.")
|
||||
: tr("Unable to download the skin: '%1'.").arg(ui->urlLine->text()),
|
||||
QMessageBox::Critical)
|
||||
->show();
|
||||
QFile::remove(path);
|
||||
return;
|
||||
}
|
||||
ui->urlLine->setText("");
|
||||
if (QFileInfo(path).suffix().isEmpty()) {
|
||||
QFile::rename(path, path + ".png");
|
||||
}
|
||||
}
|
||||
|
||||
class WaitTask : public Task {
|
||||
public:
|
||||
WaitTask() : m_loop(), m_done(false){};
|
||||
virtual ~WaitTask() = default;
|
||||
|
||||
public slots:
|
||||
void quit()
|
||||
{
|
||||
m_done = true;
|
||||
m_loop.quit();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void executeTask()
|
||||
{
|
||||
if (!m_done)
|
||||
m_loop.exec();
|
||||
emitSucceeded();
|
||||
};
|
||||
|
||||
private:
|
||||
QEventLoop m_loop;
|
||||
bool m_done;
|
||||
};
|
||||
|
||||
void SkinManageDialog::on_userBtn_clicked()
|
||||
{
|
||||
auto user = ui->urlLine->text();
|
||||
if (user.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
MinecraftProfile mcProfile;
|
||||
auto path = FS::PathCombine(m_list.getDir(), user + ".png");
|
||||
|
||||
NetJob::Ptr job{ new NetJob(tr("Download user skin"), APPLICATION->network(), 1) };
|
||||
job->setAskRetry(false);
|
||||
|
||||
auto uuidOut = std::make_shared<QByteArray>();
|
||||
auto profileOut = std::make_shared<QByteArray>();
|
||||
|
||||
auto uuidLoop = makeShared<WaitTask>();
|
||||
auto profileLoop = makeShared<WaitTask>();
|
||||
|
||||
auto getUUID = Net::Download::makeByteArray("https://api.mojang.com/users/profiles/minecraft/" + user, uuidOut);
|
||||
auto getProfile = Net::Download::makeByteArray(QUrl(), profileOut);
|
||||
auto downloadSkin = Net::Download::makeFile(QUrl(), path);
|
||||
|
||||
QString failReason;
|
||||
|
||||
connect(getUUID.get(), &Task::aborted, uuidLoop.get(), &WaitTask::quit);
|
||||
connect(getUUID.get(), &Task::failed, this, [&failReason](QString reason) {
|
||||
qCritical() << "Couldn't get user UUID:" << reason;
|
||||
failReason = tr("failed to get user UUID");
|
||||
});
|
||||
connect(getUUID.get(), &Task::failed, uuidLoop.get(), &WaitTask::quit);
|
||||
connect(getProfile.get(), &Task::aborted, profileLoop.get(), &WaitTask::quit);
|
||||
connect(getProfile.get(), &Task::failed, profileLoop.get(), &WaitTask::quit);
|
||||
connect(getProfile.get(), &Task::failed, this, [&failReason](QString reason) {
|
||||
qCritical() << "Couldn't get user profile:" << reason;
|
||||
failReason = tr("failed to get user profile");
|
||||
});
|
||||
connect(downloadSkin.get(), &Task::failed, this, [&failReason](QString reason) {
|
||||
qCritical() << "Couldn't download skin:" << reason;
|
||||
failReason = tr("failed to download skin");
|
||||
});
|
||||
|
||||
connect(getUUID.get(), &Task::succeeded, this, [uuidLoop, uuidOut, job, getProfile, &failReason] {
|
||||
try {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*uuidOut, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from Minecraft skin service at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
failReason = tr("failed to parse get user UUID response");
|
||||
uuidLoop->quit();
|
||||
return;
|
||||
}
|
||||
const auto root = doc.object();
|
||||
auto id = Json::ensureString(root, "id");
|
||||
if (!id.isEmpty()) {
|
||||
getProfile->setUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + id);
|
||||
} else {
|
||||
failReason = tr("user id is empty");
|
||||
job->abort();
|
||||
}
|
||||
} catch (const Exception& e) {
|
||||
qCritical() << "Couldn't load skin json:" << e.cause();
|
||||
failReason = tr("failed to parse get user UUID response");
|
||||
}
|
||||
uuidLoop->quit();
|
||||
});
|
||||
|
||||
connect(getProfile.get(), &Task::succeeded, this, [profileLoop, profileOut, job, getProfile, &mcProfile, downloadSkin, &failReason] {
|
||||
if (Parsers::parseMinecraftProfileMojang(*profileOut, mcProfile)) {
|
||||
downloadSkin->setUrl(mcProfile.skin.url);
|
||||
} else {
|
||||
failReason = tr("failed to parse get user profile response");
|
||||
job->abort();
|
||||
}
|
||||
profileLoop->quit();
|
||||
});
|
||||
|
||||
job->addNetAction(getUUID);
|
||||
job->addTask(uuidLoop);
|
||||
job->addNetAction(getProfile);
|
||||
job->addTask(profileLoop);
|
||||
job->addNetAction(downloadSkin);
|
||||
ProgressDialog dlg(this);
|
||||
dlg.execWithTask(job.get());
|
||||
|
||||
SkinModel s(path);
|
||||
if (!s.isValid()) {
|
||||
if (failReason.isEmpty()) {
|
||||
failReason = tr("the skin is invalid");
|
||||
}
|
||||
CustomMessageBox::selectable(this, tr("Username not found"),
|
||||
tr("Unable to find the skin for '%1'\n because: %2.").arg(user, failReason), QMessageBox::Critical)
|
||||
->show();
|
||||
QFile::remove(path);
|
||||
return;
|
||||
}
|
||||
ui->urlLine->setText("");
|
||||
s.setModel(mcProfile.skin.variant.toUpper() == "SLIM" ? SkinModel::SLIM : SkinModel::CLASSIC);
|
||||
s.setURL(mcProfile.skin.url);
|
||||
if (m_capes.contains(mcProfile.currentCape)) {
|
||||
s.setCapeId(mcProfile.currentCape);
|
||||
}
|
||||
m_list.updateSkin(&s);
|
||||
}
|
||||
64
launcher/ui/dialogs/skins/SkinManageDialog.h
Normal file
64
launcher/ui/dialogs/skins/SkinManageDialog.h
Normal 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 <QDialog>
|
||||
#include <QItemSelection>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "minecraft/auth/MinecraftAccount.h"
|
||||
#include "minecraft/skins/SkinList.h"
|
||||
|
||||
namespace Ui {
|
||||
class SkinManageDialog;
|
||||
}
|
||||
|
||||
class SkinManageDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SkinManageDialog(QWidget* parent, MinecraftAccountPtr acct);
|
||||
virtual ~SkinManageDialog();
|
||||
|
||||
public slots:
|
||||
void selectionChanged(QItemSelection, QItemSelection);
|
||||
void activated(QModelIndex);
|
||||
void delayed_scroll(QModelIndex);
|
||||
void on_openDirBtn_clicked();
|
||||
void on_fileBtn_clicked();
|
||||
void on_urlBtn_clicked();
|
||||
void on_userBtn_clicked();
|
||||
void accept() override;
|
||||
void on_capeCombo_currentIndexChanged(int index);
|
||||
void on_steveBtn_toggled(bool checked);
|
||||
void on_resetBtn_clicked();
|
||||
void show_context_menu(const QPoint& pos);
|
||||
bool eventFilter(QObject* obj, QEvent* ev) override;
|
||||
void on_action_Rename_Skin_triggered(bool checked);
|
||||
void on_action_Delete_Skin_triggered(bool checked);
|
||||
|
||||
private:
|
||||
void setupCapes();
|
||||
|
||||
MinecraftAccountPtr m_acct;
|
||||
Ui::SkinManageDialog* ui;
|
||||
SkinList m_list;
|
||||
QString m_selected_skin;
|
||||
QHash<QString, QPixmap> m_capes;
|
||||
QHash<QString, int> m_capes_idx;
|
||||
};
|
||||
220
launcher/ui/dialogs/skins/SkinManageDialog.ui
Normal file
220
launcher/ui/dialogs/skins/SkinManageDialog.ui
Normal file
@@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SkinManageDialog</class>
|
||||
<widget class="QDialog" name="SkinManageDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>968</width>
|
||||
<height>757</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Skin Upload</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="mainHlLayout" stretch="3,8">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="selectedVLayout" stretch="2,1,3">
|
||||
<item>
|
||||
<widget class="QLabel" name="selectedModel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="modelBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Model</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="steveBtn">
|
||||
<property name="text">
|
||||
<string>Classic</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="alexBtn">
|
||||
<property name="text">
|
||||
<string>Slim</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="capeBox">
|
||||
<property name="title">
|
||||
<string>Cape</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QComboBox" name="capeCombo"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="capeImage">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="listView">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="modelColumn">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="buttonsHLayout" stretch="0,0,3,0,0,0,1">
|
||||
<item>
|
||||
<widget class="QPushButton" name="openDirBtn">
|
||||
<property name="text">
|
||||
<string>Open Folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="resetBtn">
|
||||
<property name="text">
|
||||
<string>Reset Skin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="urlLine">
|
||||
<property name="placeholderText">
|
||||
<string extracomment="URL or username"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="urlBtn">
|
||||
<property name="text">
|
||||
<string>Import URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="userBtn">
|
||||
<property name="text">
|
||||
<string>Import user</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="fileBtn">
|
||||
<property name="text">
|
||||
<string>Import File</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="action_Delete_Skin">
|
||||
<property name="text">
|
||||
<string>&Delete Skin</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Deletes selected skin</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Del</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Rename_Skin">
|
||||
<property name="text">
|
||||
<string>&Rename Skin</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Rename selected skin</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>F2</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SkinManageDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>617</x>
|
||||
<y>736</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>483</x>
|
||||
<y>378</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SkinManageDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>617</x>
|
||||
<y>736</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>483</x>
|
||||
<y>378</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -66,6 +66,9 @@ void VisualGroup::update()
|
||||
rows[currentRow].height = maxRowHeight;
|
||||
rows[currentRow].top = offsetFromTop;
|
||||
currentRow++;
|
||||
if (currentRow >= rows.size()) {
|
||||
currentRow = rows.size() - 1;
|
||||
}
|
||||
offsetFromTop += maxRowHeight + 5;
|
||||
positionInRow = 0;
|
||||
maxRowHeight = 0;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "AccountListPage.h"
|
||||
#include "minecraft/auth/AccountData.h"
|
||||
#include "ui/dialogs/skins/SkinManageDialog.h"
|
||||
#include "ui_AccountListPage.h"
|
||||
|
||||
#include <QItemSelectionModel>
|
||||
@@ -47,11 +47,6 @@
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/MSALoginDialog.h"
|
||||
#include "ui/dialogs/OfflineLoginDialog.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/SkinUploadDialog.h"
|
||||
|
||||
#include "minecraft/services/SkinDelete.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
@@ -233,8 +228,7 @@ void AccountListPage::updateButtonStates()
|
||||
}
|
||||
ui->actionRemove->setEnabled(accountIsReady);
|
||||
ui->actionSetDefault->setEnabled(accountIsReady);
|
||||
ui->actionUploadSkin->setEnabled(accountIsReady && accountIsOnline);
|
||||
ui->actionDeleteSkin->setEnabled(accountIsReady && accountIsOnline);
|
||||
ui->actionManageSkins->setEnabled(accountIsReady && accountIsOnline);
|
||||
ui->actionRefresh->setEnabled(accountIsReady && accountIsOnline);
|
||||
|
||||
if (m_accounts->defaultAccount().get() == nullptr) {
|
||||
@@ -247,29 +241,13 @@ void AccountListPage::updateButtonStates()
|
||||
ui->listView->resizeColumnToContents(3);
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionUploadSkin_triggered()
|
||||
void AccountListPage::on_actionManageSkins_triggered()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0) {
|
||||
QModelIndex selected = selection.first();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
SkinUploadDialog dialog(account, this);
|
||||
SkinManageDialog dialog(this, account);
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionDeleteSkin_triggered()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() <= 0)
|
||||
return;
|
||||
|
||||
QModelIndex selected = selection.first();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
ProgressDialog prog(this);
|
||||
auto deleteSkinTask = std::make_shared<SkinDelete>(this, account->accessToken());
|
||||
if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ class AccountListPage : public QMainWindow, public BasePage {
|
||||
void on_actionRefresh_triggered();
|
||||
void on_actionSetDefault_triggered();
|
||||
void on_actionNoDefault_triggered();
|
||||
void on_actionUploadSkin_triggered();
|
||||
void on_actionDeleteSkin_triggered();
|
||||
void on_actionManageSkins_triggered();
|
||||
|
||||
void listChanged();
|
||||
|
||||
|
||||
@@ -59,14 +59,8 @@
|
||||
<addaction name="actionSetDefault"/>
|
||||
<addaction name="actionNoDefault"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionUploadSkin"/>
|
||||
<addaction name="actionDeleteSkin"/>
|
||||
<addaction name="actionManageSkins"/>
|
||||
</widget>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remo&ve</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSetDefault">
|
||||
<property name="text">
|
||||
<string>&Set Default</string>
|
||||
@@ -80,17 +74,12 @@
|
||||
<string>&No Default</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUploadSkin">
|
||||
<action name="actionManageSkins">
|
||||
<property name="text">
|
||||
<string>&Upload Skin</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDeleteSkin">
|
||||
<property name="text">
|
||||
<string>&Delete Skin</string>
|
||||
<string>&Manage Skins</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete the currently active skin and go back to the default one</string>
|
||||
<string>Manage Skins</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAddMicrosoft">
|
||||
@@ -111,6 +100,11 @@
|
||||
<string>Refresh the account tokens</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remo&ve</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -173,6 +173,17 @@ void LauncherPage::on_downloadsDirBrowseBtn_clicked()
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherPage::on_skinsDirBrowseBtn_clicked()
|
||||
{
|
||||
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text());
|
||||
|
||||
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
|
||||
if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) {
|
||||
QString cooked_dir = FS::NormalizePath(raw_dir);
|
||||
ui->skinsDirTextBox->setText(cooked_dir);
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherPage::on_metadataDisableBtn_clicked()
|
||||
{
|
||||
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
|
||||
@@ -185,6 +196,7 @@ void LauncherPage::applySettings()
|
||||
// Updates
|
||||
if (APPLICATION->updater()) {
|
||||
APPLICATION->updater()->setAutomaticallyChecksForUpdates(ui->autoUpdateCheckBox->isChecked());
|
||||
APPLICATION->updater()->setUpdateCheckInterval(ui->updateIntervalSpinBox->value() * 3600);
|
||||
}
|
||||
|
||||
s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked());
|
||||
@@ -208,6 +220,7 @@ void LauncherPage::applySettings()
|
||||
s->set("CentralModsDir", ui->modsDirTextBox->text());
|
||||
s->set("IconsDir", ui->iconsDirTextBox->text());
|
||||
s->set("DownloadsDir", ui->downloadsDirTextBox->text());
|
||||
s->set("SkinsDir", ui->skinsDirTextBox->text());
|
||||
s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked());
|
||||
|
||||
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
|
||||
@@ -234,6 +247,7 @@ void LauncherPage::loadSettings()
|
||||
// Updates
|
||||
if (APPLICATION->updater()) {
|
||||
ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates());
|
||||
ui->updateIntervalSpinBox->setValue(APPLICATION->updater()->getUpdateCheckInterval() / 3600);
|
||||
}
|
||||
|
||||
// Toolbar/menu bar settings (not applicable if native menu bar is present)
|
||||
@@ -269,6 +283,7 @@ void LauncherPage::loadSettings()
|
||||
ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
|
||||
ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
|
||||
ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString());
|
||||
ui->skinsDirTextBox->setText(s->get("SkinsDir").toString());
|
||||
ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool());
|
||||
|
||||
QString sortMode = s->get("InstSortMode").toString();
|
||||
|
||||
@@ -74,6 +74,7 @@ class LauncherPage : public QWidget, public BasePage {
|
||||
void on_modsDirBrowseBtn_clicked();
|
||||
void on_iconsDirBrowseBtn_clicked();
|
||||
void on_downloadsDirBrowseBtn_clicked();
|
||||
void on_skinsDirBrowseBtn_clicked();
|
||||
void on_metadataDisableBtn_clicked();
|
||||
|
||||
/*!
|
||||
|
||||
@@ -58,6 +58,33 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="updateSetingsLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="updateIntervalLabel">
|
||||
<property name="text">
|
||||
<string>Update interval</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="updateIntervalSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>Set it to 0 to only check on launch</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>h</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -67,7 +94,7 @@
|
||||
<string>Folders</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="foldersBoxLayout">
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="labelDownloadsDir">
|
||||
<property name="text">
|
||||
<string>&Downloads:</string>
|
||||
@@ -90,13 +117,16 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="instDirTextBox"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="downloadsDirTextBox"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="iconsDirTextBox"/>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="skinsDirTextBox"/>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QToolButton" name="downloadsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
@@ -147,7 +177,24 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="2">
|
||||
<item row="3" column="2">
|
||||
<widget class="QToolButton" name="skinsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelSkinsDir">
|
||||
<property name="text">
|
||||
<string>&Skins:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>skinsDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="downloadsDirWatchRecursiveCheckBox">
|
||||
<property name="toolTip">
|
||||
<string>When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge).</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "InstanceTask.h"
|
||||
#include "Json.h"
|
||||
#include "Markdown.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||
|
||||
@@ -332,7 +333,7 @@ void ModrinthManagedPackPage::suggestVersion()
|
||||
}
|
||||
auto version = m_pack.versions.at(index);
|
||||
|
||||
ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8()));
|
||||
ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(markdownToHTML(version.changelog.toUtf8())));
|
||||
|
||||
ManagedPackPage::suggestVersion();
|
||||
}
|
||||
@@ -420,7 +421,7 @@ void FlameManagedPackPage::parseManagedPack()
|
||||
"Don't worry though, it will ask you to update this instance instead, so you'll not lose this instance!"
|
||||
"</h4>");
|
||||
|
||||
ui->changelogTextBrowser->setHtml(message);
|
||||
ui->changelogTextBrowser->setHtml(StringUtils::htmlListPatch(message));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -502,7 +503,8 @@ void FlameManagedPackPage::suggestVersion()
|
||||
}
|
||||
auto version = m_pack.versions.at(index);
|
||||
|
||||
ui->changelogTextBrowser->setHtml(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId));
|
||||
ui->changelogTextBrowser->setHtml(
|
||||
StringUtils::htmlListPatch(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId)));
|
||||
|
||||
ManagedPackPage::suggestVersion();
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
CustomPage::CustomPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog(dialog), ui(new Ui::CustomPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedVersion);
|
||||
filterChanged();
|
||||
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
|
||||
@@ -24,29 +24,21 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string notr="true"/>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="0">
|
||||
<widget class="Line" name="line">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QWidget" name="content">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>813</width>
|
||||
<height>605</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="minecraftLayout">
|
||||
<item>
|
||||
<widget class="VersionSelectWidget" name="versionList" native="true">
|
||||
@@ -147,7 +139,20 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="loaderLayout">
|
||||
<item>
|
||||
<widget class="VersionSelectWidget" name="loaderVersionList" native="true">
|
||||
@@ -273,7 +278,6 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>releaseFilter</tabstop>
|
||||
<tabstop>snapshotFilter</tabstop>
|
||||
<tabstop>betaFilter</tabstop>
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "ui_ImportPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMimeDatabase>
|
||||
#include <QValidator>
|
||||
#include <utility>
|
||||
|
||||
@@ -51,6 +52,7 @@
|
||||
#include "Json.h"
|
||||
|
||||
#include "InstanceImportTask.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
class UrlValidator : public QValidator {
|
||||
public:
|
||||
|
||||
@@ -25,15 +25,21 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
|
||||
Q_ASSERT(m_filter);
|
||||
|
||||
std::optional<std::list<Version>> versions{};
|
||||
std::optional<QStringList> categories{};
|
||||
auto loaders = profile->getSupportedModLoaders();
|
||||
|
||||
{ // Version filter
|
||||
if (!m_filter->versions.empty())
|
||||
versions = m_filter->versions;
|
||||
}
|
||||
// Version filter
|
||||
if (!m_filter->versions.empty())
|
||||
versions = m_filter->versions;
|
||||
if (m_filter->loaders)
|
||||
loaders = m_filter->loaders;
|
||||
if (!m_filter->categoryIds.empty())
|
||||
categories = m_filter->categoryIds;
|
||||
auto side = m_filter->side;
|
||||
|
||||
auto sort = getCurrentSortingMethodByIndex();
|
||||
|
||||
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };
|
||||
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, loaders, versions, side, categories };
|
||||
}
|
||||
|
||||
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
|
||||
@@ -45,10 +51,13 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
|
||||
Q_ASSERT(m_filter);
|
||||
|
||||
std::optional<std::list<Version>> versions{};
|
||||
auto loaders = profile->getSupportedModLoaders();
|
||||
if (!m_filter->versions.empty())
|
||||
versions = m_filter->versions;
|
||||
if (m_filter->loaders)
|
||||
loaders = m_filter->loaders;
|
||||
|
||||
return { pack, versions, profile->getSupportedModLoaders() };
|
||||
return { pack, versions, loaders };
|
||||
}
|
||||
|
||||
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
|
||||
@@ -89,4 +98,44 @@ QVariant ModModel::getInstalledPackVersion(ModPlatform::IndexedPack::Ptr pack) c
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool checkSide(QString filter, QString value)
|
||||
{
|
||||
return filter.isEmpty() || value.isEmpty() || filter == "both" || value == "both" || filter == value;
|
||||
}
|
||||
|
||||
bool checkMcVersions(std::list<Version> filter, QStringList value)
|
||||
{
|
||||
bool valid = false;
|
||||
for (auto mcVersion : filter) {
|
||||
if (value.contains(mcVersion.toString())) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return filter.empty() || valid;
|
||||
}
|
||||
|
||||
bool ModModel::checkFilters(ModPlatform::IndexedPack::Ptr pack)
|
||||
{
|
||||
if (!m_filter)
|
||||
return true;
|
||||
return !(m_filter->hideInstalled && isPackInstalled(pack)) && checkSide(m_filter->side, pack->side);
|
||||
}
|
||||
|
||||
bool ModModel::checkVersionFilters(const ModPlatform::IndexedVersion& v)
|
||||
{
|
||||
if (!m_filter)
|
||||
return true;
|
||||
auto loaders = static_cast<MinecraftInstance&>(m_base_instance).getPackProfile()->getSupportedModLoaders();
|
||||
if (m_filter->loaders)
|
||||
loaders = m_filter->loaders;
|
||||
return (!optedOut(v) && // is opted out(aka curseforge download link)
|
||||
(!loaders.has_value() || !v.loaders || loaders.value() & v.loaders) && // loaders
|
||||
checkSide(m_filter->side, v.side) && // side
|
||||
(m_filter->releases.empty() || // releases
|
||||
std::find(m_filter->releases.cbegin(), m_filter->releases.cend(), v.version_type) != m_filter->releases.cend()) &&
|
||||
checkMcVersions(m_filter->versions, v.mcVersion)); // mcVersions
|
||||
}
|
||||
|
||||
} // namespace ResourceDownload
|
||||
|
||||
@@ -46,6 +46,9 @@ class ModModel : public ResourceModel {
|
||||
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0;
|
||||
virtual bool isPackInstalled(ModPlatform::IndexedPack::Ptr) const override;
|
||||
|
||||
virtual bool checkFilters(ModPlatform::IndexedPack::Ptr) override;
|
||||
virtual bool checkVersionFilters(const ModPlatform::IndexedVersion&) override;
|
||||
|
||||
protected:
|
||||
BaseInstance& m_base_instance;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* 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
|
||||
@@ -57,7 +58,6 @@ namespace ResourceDownload {
|
||||
|
||||
ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance)
|
||||
{
|
||||
connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch);
|
||||
connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
|
||||
connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected);
|
||||
}
|
||||
@@ -67,17 +67,18 @@ void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& widget)
|
||||
if (m_filter_widget)
|
||||
disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr);
|
||||
|
||||
auto old = m_ui->splitter->replaceWidget(0, widget.get());
|
||||
// because we replaced the widget we also need to delete it
|
||||
if (old) {
|
||||
delete old;
|
||||
}
|
||||
|
||||
m_filter_widget.swap(widget);
|
||||
|
||||
m_ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, m_ui->gridLayout_3->columnCount());
|
||||
|
||||
m_filter_widget->setInstance(&static_cast<MinecraftInstance&>(m_base_instance));
|
||||
m_filter = m_filter_widget->getFilter();
|
||||
|
||||
connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this,
|
||||
[&] { m_ui->searchButton->setStyleSheet("text-decoration: underline"); });
|
||||
connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this,
|
||||
[&] { m_ui->searchButton->setStyleSheet("text-decoration: none"); });
|
||||
connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, &ModPage::triggerSearch);
|
||||
prepareProviderCategories();
|
||||
}
|
||||
|
||||
/******** Callbacks to events in the UI (set up in the derived classes) ********/
|
||||
@@ -89,6 +90,7 @@ void ModPage::filterMods()
|
||||
|
||||
void ModPage::triggerSearch()
|
||||
{
|
||||
auto changed = m_filter_widget->changed();
|
||||
m_filter = m_filter_widget->getFilter();
|
||||
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||
m_ui->packView->clearSelection();
|
||||
@@ -96,7 +98,7 @@ void ModPage::triggerSearch()
|
||||
m_ui->versionSelectionBox->clear();
|
||||
updateSelectionButton();
|
||||
|
||||
static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), m_filter_widget->changed());
|
||||
static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed);
|
||||
m_fetch_progress.watch(m_model->activeSearchJob().get());
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,7 @@ class ModPage : public ResourcePage {
|
||||
auto page = new T(dialog, instance);
|
||||
auto model = static_cast<ModModel*>(page->getModel());
|
||||
|
||||
auto filter_widget =
|
||||
ModFilterWidget::create(static_cast<MinecraftInstance&>(instance).getPackProfile()->getComponentVersion("net.minecraft"), page);
|
||||
auto filter_widget = page->createFilterWidget();
|
||||
page->setFilterWidget(filter_widget);
|
||||
model->setFilter(page->getFilter());
|
||||
|
||||
@@ -51,20 +50,17 @@ class ModPage : public ResourcePage {
|
||||
|
||||
void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, std::shared_ptr<ResourceFolderModel>) override;
|
||||
|
||||
virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0;
|
||||
virtual unique_qobject_ptr<ModFilterWidget> createFilterWidget() = 0;
|
||||
|
||||
[[nodiscard]] bool supportsFiltering() const override { return true; };
|
||||
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
|
||||
void setFilterWidget(unique_qobject_ptr<ModFilterWidget>&);
|
||||
|
||||
public slots:
|
||||
void updateVersionList() override;
|
||||
|
||||
protected:
|
||||
ModPage(ModDownloadDialog* dialog, BaseInstance& instance);
|
||||
|
||||
virtual void prepareProviderCategories(){};
|
||||
|
||||
protected slots:
|
||||
virtual void filterMods();
|
||||
void triggerSearch() override;
|
||||
|
||||
@@ -410,12 +410,17 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
|
||||
m_search_state = SearchState::CanFetchMore;
|
||||
}
|
||||
|
||||
QList<ModPlatform::IndexedPack::Ptr> filteredNewList;
|
||||
for (auto p : newList)
|
||||
if (checkFilters(p))
|
||||
filteredNewList << p;
|
||||
|
||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||
if (newList.size() == 0)
|
||||
if (filteredNewList.size() == 0)
|
||||
return;
|
||||
|
||||
beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1);
|
||||
m_packs.append(newList);
|
||||
beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + filteredNewList.size() - 1);
|
||||
m_packs.append(filteredNewList);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
@@ -558,4 +563,8 @@ void ResourceModel::removePack(const QString& rem)
|
||||
ver.is_currently_selected = false;
|
||||
}
|
||||
|
||||
bool ResourceModel::checkVersionFilters(const ModPlatform::IndexedVersion& v)
|
||||
{
|
||||
return (!optedOut(v));
|
||||
}
|
||||
} // namespace ResourceDownload
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "ResourceDownloadTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
@@ -56,6 +57,15 @@ class ResourceModel : public QAbstractListModel {
|
||||
[[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); }
|
||||
|
||||
virtual QVariant getInstalledPackVersion(ModPlatform::IndexedPack::Ptr) const { return {}; }
|
||||
/** Whether the version is opted out or not. Currently only makes sense in CF. */
|
||||
virtual bool optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
Q_UNUSED(ver);
|
||||
return false;
|
||||
};
|
||||
|
||||
virtual bool checkFilters(ModPlatform::IndexedPack::Ptr) { return true; }
|
||||
virtual bool checkVersionFilters(const ModPlatform::IndexedVersion&);
|
||||
|
||||
public slots:
|
||||
void fetchMore(const QModelIndex& parent) override;
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace ResourceDownload {
|
||||
|
||||
ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance)
|
||||
{
|
||||
connect(m_ui->searchButton, &QPushButton::clicked, this, &ResourcePackResourcePage::triggerSearch);
|
||||
connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePackResourcePage::onResourceSelected);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
* 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
|
||||
@@ -45,6 +46,7 @@
|
||||
|
||||
#include "Markdown.h"
|
||||
|
||||
#include "StringUtils.h"
|
||||
#include "ui/dialogs/ResourceDownloadDialog.h"
|
||||
#include "ui/pages/modplatform/ResourceModel.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
@@ -66,11 +68,13 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in
|
||||
|
||||
connect(&m_search_timer, &QTimer::timeout, this, &ResourcePage::triggerSearch);
|
||||
|
||||
// hide progress bar to prevent weird artifact
|
||||
m_fetch_progress.hide();
|
||||
m_fetch_progress.hideIfInactive(true);
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
m_ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, m_ui->gridLayout_3->columnCount());
|
||||
m_ui->verticalLayout->insertWidget(1, &m_fetch_progress);
|
||||
|
||||
m_ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
||||
m_ui->packView->installEventFilter(this);
|
||||
@@ -92,8 +96,10 @@ void ResourcePage::retranslate()
|
||||
|
||||
void ResourcePage::openedImpl()
|
||||
{
|
||||
if (!supportsFiltering())
|
||||
if (!supportsFiltering()) {
|
||||
m_ui->resourceFilterButton->setVisible(false);
|
||||
m_ui->filterWidget->hide();
|
||||
}
|
||||
|
||||
//: String in the search bar of the mod downloading dialog
|
||||
m_ui->searchEdit->setPlaceholderText(tr("Search for %1...").arg(resourcesString()));
|
||||
@@ -234,8 +240,8 @@ void ResourcePage::updateUi()
|
||||
|
||||
text += "<hr>";
|
||||
|
||||
m_ui->packDescription->setHtml(
|
||||
text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body)));
|
||||
m_ui->packDescription->setHtml(StringUtils::htmlListPatch(
|
||||
text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body))));
|
||||
m_ui->packDescription->flush();
|
||||
}
|
||||
|
||||
@@ -270,7 +276,7 @@ void ResourcePage::updateVersionList()
|
||||
|
||||
for (int i = 0; i < current_pack->versions.size(); i++) {
|
||||
auto& version = current_pack->versions[i];
|
||||
if (optedOut(version))
|
||||
if (!m_model->checkVersionFilters(version))
|
||||
continue;
|
||||
|
||||
auto release_type = current_pack->versions[i].version_type.isValid()
|
||||
|
||||
@@ -86,7 +86,7 @@ class ResourcePage : public QWidget, public BasePage {
|
||||
virtual void openProject(QVariant projectID);
|
||||
|
||||
protected slots:
|
||||
virtual void triggerSearch() {}
|
||||
virtual void triggerSearch() = 0;
|
||||
|
||||
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
void onVersionSelectionChanged(QString data);
|
||||
@@ -98,13 +98,6 @@ class ResourcePage : public QWidget, public BasePage {
|
||||
virtual QMap<QString, QString> urlHandlers() const = 0;
|
||||
virtual void openUrl(const QUrl&);
|
||||
|
||||
/** Whether the version is opted out or not. Currently only makes sense in CF. */
|
||||
virtual bool optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
Q_UNUSED(ver);
|
||||
return false;
|
||||
};
|
||||
|
||||
public:
|
||||
BaseInstance& m_base_instance;
|
||||
|
||||
|
||||
@@ -10,48 +10,55 @@
|
||||
<height>685</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="4">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="2">
|
||||
<widget class="ProjectDescriptionPage" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>false</bool>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="resourceFilterButton">
|
||||
<property name="text">
|
||||
<string>Filter options</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QListView" name="packView">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="filterWidget" native="true"/>
|
||||
<widget class="QListView" name="packView">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="ProjectDescriptionPage" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="searchEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="4">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
@@ -74,20 +81,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="resourceFilterButton">
|
||||
<property name="text">
|
||||
<string>Filter options</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
@@ -98,8 +91,6 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>searchEdit</tabstop>
|
||||
<tabstop>searchButton</tabstop>
|
||||
<tabstop>packView</tabstop>
|
||||
<tabstop>packDescription</tabstop>
|
||||
<tabstop>sortByBox</tabstop>
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace ResourceDownload {
|
||||
|
||||
ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance)
|
||||
{
|
||||
connect(m_ui->searchButton, &QPushButton::clicked, this, &ShaderPackResourcePage::triggerSearch);
|
||||
connect(m_ui->packView, &QListView::doubleClicked, this, &ShaderPackResourcePage::onResourceSelected);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ class TexturePackResourcePage : public ResourcePackResourcePage {
|
||||
protected:
|
||||
TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance) : ResourcePackResourcePage(dialog, instance)
|
||||
{
|
||||
connect(m_ui->searchButton, &QPushButton::clicked, this, &TexturePackResourcePage::triggerSearch);
|
||||
connect(m_ui->packView, &QListView::doubleClicked, this, &TexturePackResourcePage::onResourceSelected);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "ui_AtlPage.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "AtlUserInteractionSupportImpl.h"
|
||||
#include "modplatform/atlauncher/ATLPackInstallTask.h"
|
||||
@@ -144,7 +145,7 @@ void AtlPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex
|
||||
|
||||
selected = filterModel->data(first, Qt::UserRole).value<ATLauncher::IndexedPack>();
|
||||
|
||||
ui->packDescription->setHtml(selected.description.replace("\n", "<br>"));
|
||||
ui->packDescription->setHtml(StringUtils::htmlListPatch(selected.description.replace("\n", "<br>")));
|
||||
|
||||
for (const auto& version : selected.versions) {
|
||||
ui->versionSelectionBox->addItem(version.version);
|
||||
|
||||
@@ -10,72 +10,8 @@
|
||||
<height>685</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
@@ -93,6 +29,63 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "FlameModel.h"
|
||||
#include "InstanceImportTask.h"
|
||||
#include "Json.h"
|
||||
#include "StringUtils.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
@@ -55,7 +56,6 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
|
||||
ui->searchEdit->installEventFilter(this);
|
||||
listModel = new Flame::ListModel(this);
|
||||
ui->packView->setModel(listModel);
|
||||
@@ -72,7 +72,7 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
|
||||
ui->verticalLayout->insertWidget(2, &m_fetch_progress);
|
||||
|
||||
// index is used to set the sorting with the curseforge api
|
||||
ui->sortByBox->addItem(tr("Sort by Featured"));
|
||||
@@ -178,7 +178,11 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
|
||||
|
||||
for (auto version : current.versions) {
|
||||
auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
|
||||
ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(version.downloadUrl));
|
||||
auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion)
|
||||
? QString(" for %1").arg(version.mcVersion)
|
||||
: "";
|
||||
ui->versionSelectionBox->addItem(QString("%1%2%3").arg(version.version, mcVersion, release_type),
|
||||
QVariant(version.downloadUrl));
|
||||
}
|
||||
|
||||
QVariant current_updated;
|
||||
@@ -292,6 +296,6 @@ void FlamePage::updateUi()
|
||||
text += "<hr>";
|
||||
text += api.getModDescription(current.addonId).toUtf8();
|
||||
|
||||
ui->packDescription->setHtml(text + current.description);
|
||||
ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description));
|
||||
ui->packDescription->flush();
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
@@ -29,25 +29,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="packView">
|
||||
@@ -77,7 +66,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
|
||||
@@ -6,11 +6,17 @@
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
|
||||
namespace ResourceDownload {
|
||||
|
||||
static bool isOptedOut(const ModPlatform::IndexedVersion& ver)
|
||||
{
|
||||
return ver.downloadUrl.isEmpty();
|
||||
}
|
||||
|
||||
FlameModModel::FlameModModel(BaseInstance& base) : ModModel(base, new FlameAPI) {}
|
||||
|
||||
void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
|
||||
@@ -34,6 +40,11 @@ auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJs
|
||||
return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
bool FlameModModel::optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
{
|
||||
return Json::ensureArray(obj.object(), "data");
|
||||
@@ -57,6 +68,11 @@ void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m
|
||||
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
}
|
||||
|
||||
bool FlameResourcePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
{
|
||||
return Json::ensureArray(obj.object(), "data");
|
||||
@@ -116,6 +132,11 @@ ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(QM
|
||||
return args;
|
||||
}
|
||||
|
||||
bool FlameTexturePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
{
|
||||
return Json::ensureArray(obj.object(), "data");
|
||||
@@ -139,6 +160,11 @@ void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m,
|
||||
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
}
|
||||
|
||||
bool FlameShaderPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
{
|
||||
return Json::ensureArray(obj.object(), "data");
|
||||
|
||||
@@ -17,6 +17,8 @@ class FlameModModel : public ModModel {
|
||||
FlameModModel(BaseInstance&);
|
||||
~FlameModModel() override = default;
|
||||
|
||||
bool optedOut(const ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
|
||||
[[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
|
||||
@@ -36,6 +38,8 @@ class FlameResourcePackModel : public ResourcePackResourceModel {
|
||||
FlameResourcePackModel(const BaseInstance&);
|
||||
~FlameResourcePackModel() override = default;
|
||||
|
||||
bool optedOut(const ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
|
||||
[[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
|
||||
@@ -54,6 +58,8 @@ class FlameTexturePackModel : public TexturePackResourceModel {
|
||||
FlameTexturePackModel(const BaseInstance&);
|
||||
~FlameTexturePackModel() override = default;
|
||||
|
||||
bool optedOut(const ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
|
||||
[[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
|
||||
@@ -75,6 +81,8 @@ class FlameShaderPackModel : public ShaderPackResourceModel {
|
||||
FlameShaderPackModel(const BaseInstance&);
|
||||
~FlameShaderPackModel() override = default;
|
||||
|
||||
bool optedOut(const ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
private:
|
||||
[[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
|
||||
[[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* 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
|
||||
@@ -37,6 +38,10 @@
|
||||
*/
|
||||
|
||||
#include "FlameResourcePages.h"
|
||||
#include <QList>
|
||||
#include <memory>
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "ui_ResourcePage.h"
|
||||
|
||||
#include "FlameResourceModels.h"
|
||||
@@ -44,11 +49,6 @@
|
||||
|
||||
namespace ResourceDownload {
|
||||
|
||||
static bool isOptedOut(ModPlatform::IndexedVersion const& ver)
|
||||
{
|
||||
return ver.downloadUrl.isEmpty();
|
||||
}
|
||||
|
||||
FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ModPage(dialog, instance)
|
||||
{
|
||||
m_model = new FlameModModel(instance);
|
||||
@@ -66,19 +66,6 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
|
||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||
}
|
||||
|
||||
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
|
||||
{
|
||||
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() &&
|
||||
(!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
|
||||
}
|
||||
|
||||
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
void FlameModPage::openUrl(const QUrl& url)
|
||||
{
|
||||
if (url.scheme().isEmpty()) {
|
||||
@@ -113,11 +100,6 @@ FlameResourcePackPage::FlameResourcePackPage(ResourcePackDownloadDialog* dialog,
|
||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||
}
|
||||
|
||||
bool FlameResourcePackPage::optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
void FlameResourcePackPage::openUrl(const QUrl& url)
|
||||
{
|
||||
if (url.scheme().isEmpty()) {
|
||||
@@ -152,11 +134,6 @@ FlameTexturePackPage::FlameTexturePackPage(TexturePackDownloadDialog* dialog, Ba
|
||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||
}
|
||||
|
||||
bool FlameTexturePackPage::optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
void FlameTexturePackPage::openUrl(const QUrl& url)
|
||||
{
|
||||
if (url.scheme().isEmpty()) {
|
||||
@@ -191,11 +168,6 @@ FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseI
|
||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||
}
|
||||
|
||||
bool FlameShaderPackPage::optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
void FlameShaderPackPage::openUrl(const QUrl& url)
|
||||
{
|
||||
if (url.scheme().isEmpty()) {
|
||||
@@ -232,4 +204,19 @@ auto FlameShaderPackPage::shouldDisplay() const -> bool
|
||||
return true;
|
||||
}
|
||||
|
||||
unique_qobject_ptr<ModFilterWidget> FlameModPage::createFilterWidget()
|
||||
{
|
||||
return ModFilterWidget::create(&static_cast<MinecraftInstance&>(m_base_instance), false, this);
|
||||
}
|
||||
|
||||
void FlameModPage::prepareProviderCategories()
|
||||
{
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto task = FlameAPI::getModCategories(response);
|
||||
QObject::connect(task.get(), &Task::succeeded, [this, response]() {
|
||||
auto categories = FlameAPI::loadModCategories(response);
|
||||
m_filter_widget->setCategories(categories);
|
||||
});
|
||||
task->start();
|
||||
};
|
||||
} // namespace ResourceDownload
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* 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
|
||||
@@ -94,12 +95,11 @@ class FlameModPage : public ModPage {
|
||||
|
||||
[[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
|
||||
|
||||
bool validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;
|
||||
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
void openUrl(const QUrl& url) override;
|
||||
unique_qobject_ptr<ModFilterWidget> createFilterWidget() override;
|
||||
|
||||
protected:
|
||||
virtual void prepareProviderCategories() override;
|
||||
};
|
||||
|
||||
class FlameResourcePackPage : public ResourcePackResourcePage {
|
||||
@@ -124,8 +124,6 @@ class FlameResourcePackPage : public ResourcePackResourcePage {
|
||||
|
||||
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
||||
|
||||
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
void openUrl(const QUrl& url) override;
|
||||
};
|
||||
|
||||
@@ -151,8 +149,6 @@ class FlameTexturePackPage : public TexturePackResourcePage {
|
||||
|
||||
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
||||
|
||||
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
void openUrl(const QUrl& url) override;
|
||||
};
|
||||
|
||||
@@ -178,8 +174,6 @@ class FlameShaderPackPage : public ShaderPackResourcePage {
|
||||
|
||||
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
||||
|
||||
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
void openUrl(const QUrl& url) override;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "ui_ImportFTBPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QWidget>
|
||||
#include "FileSystem.h"
|
||||
#include "ListModel.h"
|
||||
@@ -58,8 +59,8 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
|
||||
connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
|
||||
|
||||
connect(ui->browseButton, &QPushButton::clicked, this, [this] {
|
||||
auto path = listModel->getPath();
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly);
|
||||
QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), listModel->getUserPath(),
|
||||
QFileDialog::ShowDirsOnly);
|
||||
if (!dir.isEmpty())
|
||||
listModel->setPath(dir);
|
||||
});
|
||||
|
||||
@@ -10,8 +10,49 @@
|
||||
<height>1011</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Note: If your FTB instances are not in the default location, select it using the button next to search.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browseButton">
|
||||
<property name="toolTip">
|
||||
<string>Select FTBApp instances directory</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="modpackList">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
@@ -21,7 +62,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox">
|
||||
@@ -48,54 +89,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="browseButton">
|
||||
<property name="toolTip">
|
||||
<string>Select FTBApp instances directory</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="viewfolder">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Note: If your FTB instances are not in the default location, select it using the button next to search.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
||||
@@ -24,45 +24,76 @@
|
||||
#include <QIcon>
|
||||
#include <QProcessEnvironment>
|
||||
#include "Application.h"
|
||||
#include "Exception.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "StringUtils.h"
|
||||
#include "modplatform/import_ftb/PackHelpers.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
namespace FTBImportAPP {
|
||||
|
||||
QString getStaticPath()
|
||||
QString getFTBRoot()
|
||||
{
|
||||
QString partialPath;
|
||||
QString partialPath = QDir::homePath();
|
||||
#if defined(Q_OS_OSX)
|
||||
partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
|
||||
#elif defined(Q_OS_WIN32)
|
||||
partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
|
||||
#else
|
||||
partialPath = QDir::homePath();
|
||||
partialPath = FS::PathCombine(partialPath, "Library/Application Support");
|
||||
#endif
|
||||
return FS::PathCombine(partialPath, ".ftba");
|
||||
}
|
||||
|
||||
static const QString FTB_APP_PATH = FS::PathCombine(getStaticPath(), "instances");
|
||||
QString getDynamicPath()
|
||||
{
|
||||
auto settingsPath = FS::PathCombine(getFTBRoot(), "storage", "settings.json");
|
||||
if (!QFileInfo::exists(settingsPath))
|
||||
settingsPath = FS::PathCombine(getFTBRoot(), "bin", "settings.json");
|
||||
if (!QFileInfo::exists(settingsPath)) {
|
||||
qWarning() << "The ftb app setings doesn't exist.";
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
auto doc = Json::requireDocument(FS::read(settingsPath));
|
||||
return Json::requireString(Json::requireObject(doc), "instanceLocation");
|
||||
} catch (const Exception& e) {
|
||||
qCritical() << "Could not read ftb settings file: " << e.cause();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent), m_instances_path(getDynamicPath()) {}
|
||||
|
||||
void ListModel::update()
|
||||
{
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
m_modpacks.clear();
|
||||
|
||||
QString instancesPath = getPath();
|
||||
if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) {
|
||||
QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden,
|
||||
auto wasPathAdded = [this](QString path) {
|
||||
for (auto pack : m_modpacks) {
|
||||
if (pack.path == path)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
auto scanPath = [this, wasPathAdded](QString path) {
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
if (auto instancesInfo = QFileInfo(path); !instancesInfo.exists() || !instancesInfo.isDir())
|
||||
return;
|
||||
QDirIterator directoryIterator(path, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden,
|
||||
QDirIterator::FollowSymlinks);
|
||||
while (directoryIterator.hasNext()) {
|
||||
auto modpack = parseDirectory(directoryIterator.next());
|
||||
if (!modpack.path.isEmpty())
|
||||
modpacks.append(modpack);
|
||||
auto currentPath = directoryIterator.next();
|
||||
if (!wasPathAdded(currentPath)) {
|
||||
auto modpack = parseDirectory(currentPath);
|
||||
if (!modpack.path.isEmpty())
|
||||
m_modpacks.append(modpack);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Couldn't find ftb instances folder: " << instancesPath;
|
||||
}
|
||||
};
|
||||
|
||||
scanPath(APPLICATION->settings()->get("FTBAppInstancesPath").toString());
|
||||
scanPath(m_instances_path);
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
@@ -70,11 +101,11 @@ void ListModel::update()
|
||||
QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
int pos = index.row();
|
||||
if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
|
||||
if (pos >= m_modpacks.size() || pos < 0 || !index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
auto pack = modpacks.at(pos);
|
||||
auto pack = m_modpacks.at(pos);
|
||||
if (role == Qt::ToolTipRole) {
|
||||
}
|
||||
|
||||
@@ -110,9 +141,9 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
|
||||
FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
currentSorting = Sorting::ByGameVersion;
|
||||
sortings.insert(tr("Sort by Name"), Sorting::ByName);
|
||||
sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
|
||||
m_currentSorting = Sorting::ByGameVersion;
|
||||
m_sortings.insert(tr("Sort by Name"), Sorting::ByName);
|
||||
m_sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
|
||||
}
|
||||
|
||||
bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
|
||||
@@ -120,12 +151,12 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
|
||||
Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
|
||||
Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
|
||||
|
||||
if (currentSorting == Sorting::ByGameVersion) {
|
||||
if (m_currentSorting == Sorting::ByGameVersion) {
|
||||
Version lv(leftPack.mcVersion);
|
||||
Version rv(rightPack.mcVersion);
|
||||
return lv < rv;
|
||||
|
||||
} else if (currentSorting == Sorting::ByName) {
|
||||
} else if (m_currentSorting == Sorting::ByName) {
|
||||
return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
|
||||
}
|
||||
|
||||
@@ -136,39 +167,39 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
|
||||
|
||||
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
|
||||
{
|
||||
if (searchTerm.isEmpty()) {
|
||||
if (m_searchTerm.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
|
||||
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
|
||||
return pack.name.contains(m_searchTerm, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
void FilterModel::setSearchTerm(const QString term)
|
||||
{
|
||||
searchTerm = term.trimmed();
|
||||
m_searchTerm = term.trimmed();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
|
||||
{
|
||||
return sortings;
|
||||
return m_sortings;
|
||||
}
|
||||
|
||||
QString FilterModel::translateCurrentSorting()
|
||||
{
|
||||
return sortings.key(currentSorting);
|
||||
return m_sortings.key(m_currentSorting);
|
||||
}
|
||||
|
||||
void FilterModel::setSorting(Sorting s)
|
||||
{
|
||||
currentSorting = s;
|
||||
m_currentSorting = s;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
FilterModel::Sorting FilterModel::getCurrentSorting()
|
||||
{
|
||||
return currentSorting;
|
||||
return m_currentSorting;
|
||||
}
|
||||
void ListModel::setPath(QString path)
|
||||
{
|
||||
@@ -176,11 +207,11 @@ void ListModel::setPath(QString path)
|
||||
update();
|
||||
}
|
||||
|
||||
QString ListModel::getPath()
|
||||
QString ListModel::getUserPath()
|
||||
{
|
||||
auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString();
|
||||
if (path.isEmpty() || !QFileInfo(path).exists())
|
||||
path = FTB_APP_PATH;
|
||||
if (path.isEmpty())
|
||||
path = m_instances_path;
|
||||
return path;
|
||||
}
|
||||
} // namespace FTBImportAPP
|
||||
@@ -42,28 +42,29 @@ class FilterModel : public QSortFilterProxyModel {
|
||||
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
|
||||
|
||||
private:
|
||||
QMap<QString, Sorting> sortings;
|
||||
Sorting currentSorting;
|
||||
QString searchTerm;
|
||||
QMap<QString, Sorting> m_sortings;
|
||||
Sorting m_currentSorting;
|
||||
QString m_searchTerm;
|
||||
};
|
||||
|
||||
class ListModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
ListModel(QObject* parent);
|
||||
virtual ~ListModel() = default;
|
||||
|
||||
int rowCount(const QModelIndex& parent) const { return modpacks.size(); }
|
||||
int rowCount(const QModelIndex& parent) const { return m_modpacks.size(); }
|
||||
int columnCount(const QModelIndex& parent) const { return 1; }
|
||||
QVariant data(const QModelIndex& index, int role) const;
|
||||
|
||||
void update();
|
||||
|
||||
QString getPath();
|
||||
QString getUserPath();
|
||||
void setPath(QString path);
|
||||
|
||||
private:
|
||||
ModpackList modpacks;
|
||||
ModpackList m_modpacks;
|
||||
const QString m_instances_path;
|
||||
};
|
||||
} // namespace FTBImportAPP
|
||||
@@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "Page.h"
|
||||
#include "StringUtils.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
#include "ui_Page.h"
|
||||
|
||||
@@ -260,8 +261,9 @@ void Page::onPackSelectionChanged(Modpack* pack)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
if (pack) {
|
||||
currentModpackInfo->setHtml("Pack by <b>" + pack->author + "</b>" + "<br>Minecraft " + pack->mcVersion + "<br>" + "<br>" +
|
||||
pack->description + "<ul><li>" + pack->mods.replace(";", "</li><li>") + "</li></ul>");
|
||||
currentModpackInfo->setHtml(StringUtils::htmlListPatch("Pack by <b>" + pack->author + "</b>" + "<br>Minecraft " + pack->mcVersion +
|
||||
"<br>" + "<br>" + pack->description + "<ul><li>" +
|
||||
pack->mods.replace(";", "</li><li>") + "</li></ul>"));
|
||||
bool currentAdded = false;
|
||||
|
||||
for (int i = 0; i < pack->oldVersions.size(); i++) {
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<height>602</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
@@ -23,16 +23,9 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
@@ -134,22 +127,9 @@
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
@@ -159,6 +139,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "InstanceImportTask.h"
|
||||
#include "Json.h"
|
||||
#include "Markdown.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
@@ -58,7 +59,6 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch);
|
||||
ui->searchEdit->installEventFilter(this);
|
||||
m_model = new Modrinth::ModpackListModel(this);
|
||||
ui->packView->setModel(m_model);
|
||||
@@ -75,7 +75,7 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
|
||||
ui->verticalLayout->insertWidget(1, &m_fetch_progress);
|
||||
|
||||
ui->sortByBox->addItem(tr("Sort by Relevance"));
|
||||
ui->sortByBox->addItem(tr("Sort by Total Downloads"));
|
||||
@@ -223,11 +223,12 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
|
||||
}
|
||||
for (auto version : current.versions) {
|
||||
auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
|
||||
if (!version.name.contains(version.version))
|
||||
ui->versionSelectionBox->addItem(QString("%1 — %2%3").arg(version.name, version.version, release_type),
|
||||
QVariant(version.id));
|
||||
else
|
||||
ui->versionSelectionBox->addItem(QString("%1%2").arg(version.name, release_type), QVariant(version.id));
|
||||
auto mcVersion = !version.gameVersion.isEmpty() && !version.name.contains(version.gameVersion)
|
||||
? QString(" for %1").arg(version.gameVersion)
|
||||
: "";
|
||||
auto versionStr = !version.name.contains(version.version) ? version.version : "";
|
||||
ui->versionSelectionBox->addItem(QString("%1%2 — %3%4").arg(version.name, mcVersion, versionStr, release_type),
|
||||
QVariant(version.id));
|
||||
}
|
||||
|
||||
QVariant current_updated;
|
||||
@@ -304,7 +305,7 @@ void ModrinthPage::updateUI()
|
||||
|
||||
text += markdownToHTML(current.extra.body.toUtf8());
|
||||
|
||||
ui->packDescription->setHtml(text + current.description);
|
||||
ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description));
|
||||
ui->packDescription->flush();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,15 @@
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="packView">
|
||||
@@ -41,7 +48,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
@@ -61,24 +68,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
@@ -89,8 +78,6 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>searchEdit</tabstop>
|
||||
<tabstop>searchButton</tabstop>
|
||||
<tabstop>packView</tabstop>
|
||||
<tabstop>packDescription</tabstop>
|
||||
<tabstop>sortByBox</tabstop>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* 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
|
||||
@@ -63,13 +64,6 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
|
||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||
}
|
||||
|
||||
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
|
||||
{
|
||||
return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
|
||||
}
|
||||
|
||||
ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
|
||||
: ResourcePackResourcePage(dialog, instance)
|
||||
{
|
||||
@@ -144,4 +138,19 @@ auto ModrinthShaderPackPage::shouldDisplay() const -> bool
|
||||
return true;
|
||||
}
|
||||
|
||||
unique_qobject_ptr<ModFilterWidget> ModrinthModPage::createFilterWidget()
|
||||
{
|
||||
return ModFilterWidget::create(&static_cast<MinecraftInstance&>(m_base_instance), true, this);
|
||||
}
|
||||
|
||||
void ModrinthModPage::prepareProviderCategories()
|
||||
{
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto task = ModrinthAPI::getModCategories(response);
|
||||
QObject::connect(task.get(), &Task::succeeded, [this, response]() {
|
||||
auto categories = ModrinthAPI::loadModCategories(response);
|
||||
m_filter_widget->setCategories(categories);
|
||||
});
|
||||
task->start();
|
||||
};
|
||||
} // namespace ResourceDownload
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* 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
|
||||
@@ -93,8 +94,10 @@ class ModrinthModPage : public ModPage {
|
||||
|
||||
[[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
|
||||
|
||||
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const
|
||||
-> bool override;
|
||||
unique_qobject_ptr<ModFilterWidget> createFilterWidget() override;
|
||||
|
||||
protected:
|
||||
virtual void prepareProviderCategories() override;
|
||||
};
|
||||
|
||||
class ModrinthResourcePackPage : public ResourcePackResourcePage {
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
#include "StringUtils.h"
|
||||
#include "TechnicModel.h"
|
||||
#include "modplatform/technic/SingleZipPackInstallTask.h"
|
||||
#include "modplatform/technic/SolderPackInstallTask.h"
|
||||
@@ -57,7 +58,6 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
|
||||
ui->searchEdit->installEventFilter(this);
|
||||
model = new Technic::ListModel(this);
|
||||
ui->packView->setModel(model);
|
||||
@@ -71,7 +71,7 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
|
||||
ui->verticalLayout->insertWidget(1, &m_fetch_progress);
|
||||
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
|
||||
@@ -233,7 +233,7 @@ void TechnicPage::metadataLoaded()
|
||||
|
||||
text += "<br><br>";
|
||||
|
||||
ui->packDescription->setHtml(text + current.description);
|
||||
ui->packDescription->setHtml(StringUtils::htmlListPatch(text + current.description));
|
||||
|
||||
// Strip trailing forward-slashes from Solder URL's
|
||||
if (current.isSolder) {
|
||||
|
||||
@@ -10,23 +10,41 @@
|
||||
<height>405</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@@ -42,46 +60,21 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
||||
@@ -313,3 +313,13 @@ void ThemeManager::initializeCatPacks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThemeManager::refresh()
|
||||
{
|
||||
m_themes.clear();
|
||||
m_icons.clear();
|
||||
m_cat_packs.clear();
|
||||
|
||||
initializeThemes();
|
||||
initializeCatPacks();
|
||||
};
|
||||
@@ -55,6 +55,8 @@ class ThemeManager {
|
||||
QString getCatPack(QString catName = "");
|
||||
QList<CatPack*> getValidCatPacks();
|
||||
|
||||
void refresh();
|
||||
|
||||
private:
|
||||
std::map<QString, std::unique_ptr<ITheme>> m_themes;
|
||||
std::map<QString, IconTheme> m_icons;
|
||||
|
||||
206
launcher/ui/widgets/CheckComboBox.cpp
Normal file
206
launcher/ui/widgets/CheckComboBox.cpp
Normal 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"
|
||||
64
launcher/ui/widgets/CheckComboBox.h
Normal file
64
launcher/ui/widgets/CheckComboBox.h
Normal 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;
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -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>&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>&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>&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>&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&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&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>
|
||||
|
||||
Reference in New Issue
Block a user