Merge branch 'develop' into better-launch
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -153,6 +153,8 @@ private slots:
|
||||
inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
|
||||
void on_actionExportInstanceZip_triggered();
|
||||
void on_actionExportInstanceMrPack_triggered();
|
||||
void on_actionExportInstanceFlamePack_triggered();
|
||||
void on_actionExportInstanceToModList_triggered();
|
||||
|
||||
void on_actionRenameInstance_triggered();
|
||||
|
||||
|
@ -465,6 +465,22 @@
|
||||
<string>Modrinth (mrpack)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExportInstanceFlamePack">
|
||||
<property name="icon">
|
||||
<iconset theme="flame"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CurseForge (zip)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExportInstanceToModList">
|
||||
<property name="icon">
|
||||
<iconset theme="new"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Mod List</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCreateInstanceShortcut">
|
||||
<property name="icon">
|
||||
<iconset theme="shortcut">
|
||||
|
@ -3,6 +3,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
|
||||
@ -41,6 +42,9 @@
|
||||
#include <QFileSystemModel>
|
||||
#include <QMessageBox>
|
||||
#include "FileIgnoreProxy.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui_ExportInstanceDialog.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
@ -72,7 +76,7 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent
|
||||
ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root)));
|
||||
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
|
||||
|
||||
connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int)));
|
||||
connect(proxyModel, SIGNAL(rowsInserted(QModelIndex, int, int)), SLOT(rowsInserted(QModelIndex, int, int)));
|
||||
|
||||
model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);
|
||||
model->setRootPath(root);
|
||||
@ -92,32 +96,26 @@ void SaveIcon(InstancePtr m_instance)
|
||||
auto iconKey = m_instance->iconKey();
|
||||
auto iconList = APPLICATION->icons();
|
||||
auto mmcIcon = iconList->icon(iconKey);
|
||||
if(!mmcIcon || mmcIcon->isBuiltIn()) {
|
||||
if (!mmcIcon || mmcIcon->isBuiltIn()) {
|
||||
return;
|
||||
}
|
||||
auto path = mmcIcon->getFilePath();
|
||||
if(!path.isNull()) {
|
||||
QFileInfo inInfo (path);
|
||||
FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) ();
|
||||
if (!path.isNull()) {
|
||||
QFileInfo inInfo(path);
|
||||
FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName()))();
|
||||
return;
|
||||
}
|
||||
auto & image = mmcIcon->m_images[mmcIcon->type()];
|
||||
auto & icon = image.icon;
|
||||
auto& image = mmcIcon->m_images[mmcIcon->type()];
|
||||
auto& icon = image.icon;
|
||||
auto sizes = icon.availableSizes();
|
||||
if(sizes.size() == 0)
|
||||
{
|
||||
if (sizes.size() == 0) {
|
||||
return;
|
||||
}
|
||||
auto areaOf = [](QSize size)
|
||||
{
|
||||
return size.width() * size.height();
|
||||
};
|
||||
auto areaOf = [](QSize size) { return size.width() * size.height(); };
|
||||
QSize largest = sizes[0];
|
||||
// find variant with largest area
|
||||
for(auto size: sizes)
|
||||
{
|
||||
if(areaOf(largest) < areaOf(size))
|
||||
{
|
||||
for (auto size : sizes) {
|
||||
if (areaOf(largest) < areaOf(size)) {
|
||||
largest = size;
|
||||
}
|
||||
}
|
||||
@ -125,16 +123,15 @@ void SaveIcon(InstancePtr m_instance)
|
||||
pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png"));
|
||||
}
|
||||
|
||||
bool ExportInstanceDialog::doExport()
|
||||
void ExportInstanceDialog::doExport()
|
||||
{
|
||||
auto name = FS::RemoveInvalidFilenameChars(m_instance->name());
|
||||
|
||||
const QString output = QFileDialog::getSaveFileName(
|
||||
this, tr("Export %1").arg(m_instance->name()),
|
||||
FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr);
|
||||
if (output.isEmpty())
|
||||
{
|
||||
return false;
|
||||
const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(m_instance->name()),
|
||||
FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr);
|
||||
if (output.isEmpty()) {
|
||||
QDialog::done(QDialog::Rejected);
|
||||
return;
|
||||
}
|
||||
|
||||
SaveIcon(m_instance);
|
||||
@ -143,46 +140,40 @@ bool ExportInstanceDialog::doExport()
|
||||
if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files,
|
||||
std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
|
||||
return false;
|
||||
QDialog::done(QDialog::Rejected);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true);
|
||||
|
||||
connect(task.get(), &Task::failed, this,
|
||||
[this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
connect(task.get(), &Task::finished, this, [task] { task->deleteLater(); });
|
||||
|
||||
ProgressDialog progress(this);
|
||||
progress.setSkipButton(true, tr("Abort"));
|
||||
auto result = progress.execWithTask(task.get());
|
||||
QDialog::done(result);
|
||||
}
|
||||
|
||||
void ExportInstanceDialog::done(int result)
|
||||
{
|
||||
savePackIgnore();
|
||||
if (result == QDialog::Accepted)
|
||||
{
|
||||
if (doExport())
|
||||
{
|
||||
QDialog::done(QDialog::Accepted);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (result == QDialog::Accepted) {
|
||||
doExport();
|
||||
return;
|
||||
}
|
||||
QDialog::done(result);
|
||||
}
|
||||
|
||||
void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom)
|
||||
{
|
||||
//WARNING: possible off-by-one?
|
||||
for(int i = top; i < bottom; i++)
|
||||
{
|
||||
// WARNING: possible off-by-one?
|
||||
for (int i = top; i < bottom; i++) {
|
||||
auto node = proxyModel->index(i, 0, parent);
|
||||
if(proxyModel->shouldExpand(node))
|
||||
{
|
||||
if (proxyModel->shouldExpand(node)) {
|
||||
auto expNode = node.parent();
|
||||
if(!expNode.isValid())
|
||||
{
|
||||
if (!expNode.isValid()) {
|
||||
continue;
|
||||
}
|
||||
ui->treeView->expand(node);
|
||||
@ -199,8 +190,7 @@ void ExportInstanceDialog::loadPackIgnore()
|
||||
{
|
||||
auto filename = ignoreFileName();
|
||||
QFile ignoreFile(filename);
|
||||
if(!ignoreFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
if (!ignoreFile.open(QIODevice::ReadOnly)) {
|
||||
return;
|
||||
}
|
||||
auto data = ignoreFile.readAll();
|
||||
@ -216,12 +206,9 @@ void ExportInstanceDialog::savePackIgnore()
|
||||
{
|
||||
auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8();
|
||||
auto filename = ignoreFileName();
|
||||
try
|
||||
{
|
||||
try {
|
||||
FS::write(filename, data);
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
} catch (const Exception& e) {
|
||||
qWarning() << e.cause();
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* 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
|
||||
@ -38,39 +39,37 @@
|
||||
#include <QDialog>
|
||||
#include <QModelIndex>
|
||||
#include <memory>
|
||||
#include "FileIgnoreProxy.h"
|
||||
#include "FastFileIconProvider.h"
|
||||
#include "FileIgnoreProxy.h"
|
||||
|
||||
class BaseInstance;
|
||||
typedef std::shared_ptr<BaseInstance> InstancePtr;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace Ui {
|
||||
class ExportInstanceDialog;
|
||||
}
|
||||
|
||||
class ExportInstanceDialog : public QDialog
|
||||
{
|
||||
class ExportInstanceDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ExportInstanceDialog(InstancePtr instance, QWidget *parent = 0);
|
||||
public:
|
||||
explicit ExportInstanceDialog(InstancePtr instance, QWidget* parent = 0);
|
||||
~ExportInstanceDialog();
|
||||
|
||||
virtual void done(int result);
|
||||
|
||||
private:
|
||||
bool doExport();
|
||||
private:
|
||||
void doExport();
|
||||
void loadPackIgnore();
|
||||
void savePackIgnore();
|
||||
QString ignoreFileName();
|
||||
|
||||
private:
|
||||
Ui::ExportInstanceDialog *ui;
|
||||
private:
|
||||
Ui::ExportInstanceDialog* ui;
|
||||
InstancePtr m_instance;
|
||||
FileIgnoreProxy * proxyModel;
|
||||
FileIgnoreProxy* proxyModel;
|
||||
FastFileIconProvider icons;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void rowsInserted(QModelIndex parent, int top, int bottom);
|
||||
};
|
||||
|
@ -16,11 +16,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ExportMrPackDialog.h"
|
||||
#include "ExportPackDialog.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlamePackExportTask.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui_ExportMrPackDialog.h"
|
||||
#include "ui_ExportPackDialog.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QFileSystemModel>
|
||||
@ -32,17 +34,24 @@
|
||||
#include "MMCZip.h"
|
||||
#include "modplatform/modrinth/ModrinthPackExportTask.h"
|
||||
|
||||
ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
|
||||
: QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog)
|
||||
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
|
||||
: QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->name->setText(instance->name());
|
||||
ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
|
||||
setWindowTitle("Export Modrinth Pack");
|
||||
} else {
|
||||
setWindowTitle("Export CurseForge Pack");
|
||||
ui->version->setText("");
|
||||
ui->summaryLabel->setText("Author");
|
||||
}
|
||||
|
||||
// ensure a valid pack is generated
|
||||
// the name and version fields mustn't be empty
|
||||
connect(ui->name, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate);
|
||||
connect(ui->version, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate);
|
||||
connect(ui->name, &QLineEdit::textEdited, this, &ExportPackDialog::validate);
|
||||
connect(ui->version, &QLineEdit::textEdited, this, &ExportPackDialog::validate);
|
||||
// the instance name can technically be empty
|
||||
validate();
|
||||
|
||||
@ -66,6 +75,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
|
||||
|
||||
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
|
||||
if (mcInstance) {
|
||||
mcInstance->loaderModList()->update();
|
||||
const QDir index = mcInstance->loaderModList()->indexDir();
|
||||
if (index.exists())
|
||||
proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath()));
|
||||
@ -83,43 +93,54 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent)
|
||||
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
}
|
||||
|
||||
ExportMrPackDialog::~ExportMrPackDialog()
|
||||
ExportPackDialog::~ExportPackDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ExportMrPackDialog::done(int result)
|
||||
void ExportPackDialog::done(int result)
|
||||
{
|
||||
if (result == Accepted) {
|
||||
const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
|
||||
const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
|
||||
FS::PathCombine(QDir::homePath(), filename + ".mrpack"),
|
||||
"Modrinth pack (*.mrpack *.zip)", nullptr);
|
||||
QString output;
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
|
||||
FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)",
|
||||
nullptr);
|
||||
else
|
||||
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
|
||||
FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr);
|
||||
|
||||
if (output.isEmpty())
|
||||
return;
|
||||
Task* task;
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||
task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
|
||||
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
else
|
||||
task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
|
||||
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
|
||||
ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
|
||||
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
|
||||
connect(&task, &Task::failed,
|
||||
connect(task, &Task::failed,
|
||||
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
connect(&task, &Task::aborted, [this] {
|
||||
connect(task, &Task::aborted, [this] {
|
||||
CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)
|
||||
->show();
|
||||
});
|
||||
connect(task, &Task::finished, [task] { task->deleteLater(); });
|
||||
|
||||
ProgressDialog progress(this);
|
||||
progress.setSkipButton(true, tr("Abort"));
|
||||
if (progress.execWithTask(&task) != QDialog::Accepted)
|
||||
if (progress.execWithTask(task) != QDialog::Accepted)
|
||||
return;
|
||||
}
|
||||
|
||||
QDialog::done(result);
|
||||
}
|
||||
|
||||
void ExportMrPackDialog::validate()
|
||||
void ExportPackDialog::validate()
|
||||
{
|
||||
const bool invalid = ui->name->text().isEmpty() || ui->version->text().isEmpty();
|
||||
const bool invalid =
|
||||
ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty());
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
|
||||
}
|
@ -22,24 +22,28 @@
|
||||
#include "BaseInstance.h"
|
||||
#include "FastFileIconProvider.h"
|
||||
#include "FileIgnoreProxy.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
namespace Ui {
|
||||
class ExportMrPackDialog;
|
||||
class ExportPackDialog;
|
||||
}
|
||||
|
||||
class ExportMrPackDialog : public QDialog {
|
||||
class ExportPackDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ExportMrPackDialog(InstancePtr instance, QWidget* parent = nullptr);
|
||||
~ExportMrPackDialog();
|
||||
explicit ExportPackDialog(InstancePtr instance,
|
||||
QWidget* parent = nullptr,
|
||||
ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
~ExportPackDialog();
|
||||
|
||||
void done(int result) override;
|
||||
void validate();
|
||||
|
||||
private:
|
||||
const InstancePtr instance;
|
||||
Ui::ExportMrPackDialog* ui;
|
||||
Ui::ExportPackDialog* ui;
|
||||
FileIgnoreProxy* proxy;
|
||||
FastFileIconProvider icons;
|
||||
const ModPlatform::ResourceProvider m_provider;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ExportMrPackDialog</class>
|
||||
<widget class="QDialog" name="ExportMrPackDialog">
|
||||
<class>ExportPackDialog</class>
|
||||
<widget class="QDialog" name="ExportPackDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Export Modrinth Pack</string>
|
||||
<string>Export Pack</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
@ -24,7 +24,7 @@
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<widget class="QLabel" name="summaryLabel">
|
||||
<property name="text">
|
||||
<string>Summary</string>
|
||||
</property>
|
||||
@ -41,7 +41,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="summaryLabel">
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
@ -57,6 +57,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -103,7 +104,7 @@
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ExportMrPackDialog</receiver>
|
||||
<receiver>ExportPackDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
@ -119,7 +120,7 @@
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ExportMrPackDialog</receiver>
|
||||
<receiver>ExportPackDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
223
launcher/ui/dialogs/ExportToModListDialog.cpp
Normal file
223
launcher/ui/dialogs/ExportToModListDialog.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
// 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 "ExportToModListDialog.h"
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QTextEdit>
|
||||
#include "FileSystem.h"
|
||||
#include "Markdown.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "modplatform/helpers/ExportToModList.h"
|
||||
#include "ui_ExportToModListDialog.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QFileSystemModel>
|
||||
#include <QJsonDocument>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
const QHash<ExportToModList::Formats, QString> ExportToModListDialog::exampleLines = {
|
||||
{ ExportToModList::HTML, "<li><a href=\"{url}\">{name}</a> [{version}] by {authors}</li>" },
|
||||
{ ExportToModList::MARKDOWN, "[{name}]({url}) [{version}] by {authors}" },
|
||||
{ ExportToModList::PLAINTXT, "{name} ({url}) [{version}] by {authors}" },
|
||||
{ ExportToModList::JSON, "{\"name\":\"{name}\",\"url\":\"{url}\",\"version\":\"{version}\",\"authors\":\"{authors}\"}," },
|
||||
{ ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" },
|
||||
};
|
||||
|
||||
ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent)
|
||||
: QDialog(parent), m_template_changed(false), name(instance->name()), ui(new Ui::ExportToModListDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
enableCustom(false);
|
||||
|
||||
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
|
||||
if (mcInstance) {
|
||||
mcInstance->loaderModList()->update();
|
||||
connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() {
|
||||
m_allMods = mcInstance->loaderModList()->allMods();
|
||||
triggerImp();
|
||||
});
|
||||
}
|
||||
|
||||
connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged);
|
||||
connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
|
||||
connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
|
||||
connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
|
||||
connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); });
|
||||
connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); });
|
||||
connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); });
|
||||
connect(ui->templateText, &QTextEdit::textChanged, this, [this] {
|
||||
if (ui->templateText->toPlainText() != exampleLines[format])
|
||||
ui->formatComboBox->setCurrentIndex(5);
|
||||
else
|
||||
triggerImp();
|
||||
});
|
||||
connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) {
|
||||
this->ui->finalText->selectAll();
|
||||
this->ui->finalText->copy();
|
||||
});
|
||||
}
|
||||
|
||||
ExportToModListDialog::~ExportToModListDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ExportToModListDialog::formatChanged(int index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0: {
|
||||
enableCustom(false);
|
||||
ui->resultText->show();
|
||||
format = ExportToModList::HTML;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
enableCustom(false);
|
||||
ui->resultText->show();
|
||||
format = ExportToModList::MARKDOWN;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
enableCustom(false);
|
||||
ui->resultText->hide();
|
||||
format = ExportToModList::PLAINTXT;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
enableCustom(false);
|
||||
ui->resultText->hide();
|
||||
format = ExportToModList::JSON;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
enableCustom(false);
|
||||
ui->resultText->hide();
|
||||
format = ExportToModList::CSV;
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
m_template_changed = true;
|
||||
enableCustom(true);
|
||||
ui->resultText->hide();
|
||||
format = ExportToModList::CUSTOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
triggerImp();
|
||||
}
|
||||
|
||||
void ExportToModListDialog::triggerImp()
|
||||
{
|
||||
if (format == ExportToModList::CUSTOM) {
|
||||
ui->finalText->setPlainText(ExportToModList::exportToModList(m_allMods, ui->templateText->toPlainText()));
|
||||
return;
|
||||
}
|
||||
auto opt = 0;
|
||||
if (ui->authorsCheckBox->isChecked())
|
||||
opt |= ExportToModList::Authors;
|
||||
if (ui->versionCheckBox->isChecked())
|
||||
opt |= ExportToModList::Version;
|
||||
if (ui->urlCheckBox->isChecked())
|
||||
opt |= ExportToModList::Url;
|
||||
auto txt = ExportToModList::exportToModList(m_allMods, format, static_cast<ExportToModList::OptionalData>(opt));
|
||||
ui->finalText->setPlainText(txt);
|
||||
switch (format) {
|
||||
case ExportToModList::CUSTOM:
|
||||
return;
|
||||
case ExportToModList::HTML:
|
||||
ui->resultText->setHtml(txt);
|
||||
break;
|
||||
case ExportToModList::MARKDOWN:
|
||||
ui->resultText->setHtml(markdownToHTML(txt));
|
||||
break;
|
||||
case ExportToModList::PLAINTXT:
|
||||
break;
|
||||
case ExportToModList::JSON:
|
||||
break;
|
||||
case ExportToModList::CSV:
|
||||
break;
|
||||
}
|
||||
auto exampleLine = exampleLines[format];
|
||||
if (!m_template_changed && ui->templateText->toPlainText() != exampleLine)
|
||||
ui->templateText->setPlainText(exampleLine);
|
||||
}
|
||||
|
||||
void ExportToModListDialog::done(int result)
|
||||
{
|
||||
if (result == Accepted) {
|
||||
const QString filename = FS::RemoveInvalidFilenameChars(name);
|
||||
const QString output =
|
||||
QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()),
|
||||
"File (*.txt *.html *.md *.json *.csv)", nullptr);
|
||||
|
||||
if (output.isEmpty())
|
||||
return;
|
||||
FS::write(output, ui->finalText->toPlainText().toUtf8());
|
||||
}
|
||||
|
||||
QDialog::done(result);
|
||||
}
|
||||
|
||||
QString ExportToModListDialog::extension()
|
||||
{
|
||||
switch (format) {
|
||||
case ExportToModList::HTML:
|
||||
return ".html";
|
||||
case ExportToModList::MARKDOWN:
|
||||
return ".md";
|
||||
case ExportToModList::PLAINTXT:
|
||||
return ".txt";
|
||||
case ExportToModList::CUSTOM:
|
||||
return ".txt";
|
||||
case ExportToModList::JSON:
|
||||
return ".json";
|
||||
case ExportToModList::CSV:
|
||||
return ".csv";
|
||||
}
|
||||
return ".txt";
|
||||
}
|
||||
|
||||
void ExportToModListDialog::addExtra(ExportToModList::OptionalData option)
|
||||
{
|
||||
if (format != ExportToModList::CUSTOM)
|
||||
return;
|
||||
switch (option) {
|
||||
case ExportToModList::Authors:
|
||||
ui->templateText->insertPlainText("{authors}");
|
||||
break;
|
||||
case ExportToModList::Url:
|
||||
ui->templateText->insertPlainText("{url}");
|
||||
break;
|
||||
case ExportToModList::Version:
|
||||
ui->templateText->insertPlainText("{version}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
void ExportToModListDialog::enableCustom(bool enabled)
|
||||
{
|
||||
ui->authorsCheckBox->setHidden(enabled);
|
||||
ui->versionCheckBox->setHidden(enabled);
|
||||
ui->urlCheckBox->setHidden(enabled);
|
||||
|
||||
ui->authorsButton->setHidden(!enabled);
|
||||
ui->versionButton->setHidden(!enabled);
|
||||
ui->urlButton->setHidden(!enabled);
|
||||
}
|
55
launcher/ui/dialogs/ExportToModListDialog.h
Normal file
55
launcher/ui/dialogs/ExportToModListDialog.h
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 <QList>
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "modplatform/helpers/ExportToModList.h"
|
||||
|
||||
namespace Ui {
|
||||
class ExportToModListDialog;
|
||||
}
|
||||
|
||||
class ExportToModListDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr);
|
||||
~ExportToModListDialog();
|
||||
|
||||
void done(int result) override;
|
||||
|
||||
protected slots:
|
||||
void formatChanged(int index);
|
||||
void triggerImp();
|
||||
void trigger(int) { triggerImp(); };
|
||||
void addExtra(ExportToModList::OptionalData option);
|
||||
|
||||
private:
|
||||
QString extension();
|
||||
void enableCustom(bool enabled);
|
||||
QList<Mod*> m_allMods;
|
||||
bool m_template_changed;
|
||||
QString name;
|
||||
ExportToModList::Formats format = ExportToModList::Formats::HTML;
|
||||
Ui::ExportToModListDialog* ui;
|
||||
static const QHash<ExportToModList::Formats, QString> exampleLines;
|
||||
};
|
240
launcher/ui/dialogs/ExportToModListDialog.ui
Normal file
240
launcher/ui/dialogs/ExportToModListDialog.ui
Normal file
@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ExportToModListDialog</class>
|
||||
<widget class="QDialog" name="ExportToModListDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>650</width>
|
||||
<height>446</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Export Pack to ModList</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="formatComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>HTML</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Markdown</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Plaintext</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>JSON</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>CSV</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Custom</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGroupBox" name="templateGroup">
|
||||
<property name="title">
|
||||
<string>Template</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="templateText"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QGroupBox" name="optionsGroup">
|
||||
<property name="title">
|
||||
<string>Optional Info</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="versionCheckBox">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="authorsCheckBox">
|
||||
<property name="text">
|
||||
<string>Authors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="urlCheckBox">
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="versionButton">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="authorsButton">
|
||||
<property name="text">
|
||||
<string>Authors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="urlButton">
|
||||
<property name="text">
|
||||
<string>URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Result</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="finalText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>143</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="resultText">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="warningLabel">
|
||||
<property name="text">
|
||||
<string>This depends on the mods' metadata. To ensure it is available, run an update on the instance. Installing the updates isn't necessary.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="copyButton">
|
||||
<property name="text">
|
||||
<string>Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ExportToModListDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>334</x>
|
||||
<y>435</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>324</x>
|
||||
<y>206</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ExportToModListDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>324</x>
|
||||
<y>390</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>324</x>
|
||||
<y>206</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -34,6 +34,7 @@
|
||||
*/
|
||||
|
||||
#include "ProgressDialog.h"
|
||||
#include <QPoint>
|
||||
#include "ui_ProgressDialog.h"
|
||||
|
||||
#include <limits>
|
||||
@ -66,8 +67,9 @@ ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Pr
|
||||
ui->taskProgressScrollArea->setHidden(true);
|
||||
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true);
|
||||
setSkipButton(false);
|
||||
changeProgress(0, 100);
|
||||
updateSize(true);
|
||||
setSkipButton(false);
|
||||
}
|
||||
|
||||
void ProgressDialog::setSkipButton(bool present, QString label)
|
||||
@ -93,24 +95,38 @@ ProgressDialog::~ProgressDialog()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ProgressDialog::updateSize()
|
||||
void ProgressDialog::updateSize(bool recenterParent)
|
||||
{
|
||||
QSize lastSize = this->size();
|
||||
QSize qSize = QSize(480, minimumSizeHint().height());
|
||||
QPoint lastPos = this->pos();
|
||||
int minHeight = ui->globalStatusDetailsLabel->minimumSize().height() + (ui->verticalLayout->spacing() * 2);
|
||||
minHeight += ui->globalProgressBar->minimumSize().height() + ui->verticalLayout->spacing();
|
||||
if (!ui->taskProgressScrollArea->isHidden())
|
||||
minHeight += ui->taskProgressScrollArea->minimumSizeHint().height() + ui->verticalLayout->spacing();
|
||||
if (ui->skipButton->isVisible())
|
||||
minHeight += ui->skipButton->height() + ui->verticalLayout->spacing();
|
||||
minHeight = std::max(minHeight, 60);
|
||||
QSize minSize = QSize(480, minHeight);
|
||||
|
||||
// if the current window is too small
|
||||
if ((lastSize != qSize) && (lastSize.height() < qSize.height()))
|
||||
{
|
||||
resize(qSize);
|
||||
|
||||
// keep the dialog in the center after a resize
|
||||
this->move(
|
||||
this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2,
|
||||
this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2
|
||||
);
|
||||
setMinimumSize(minSize);
|
||||
adjustSize();
|
||||
|
||||
QSize newSize = this->size();
|
||||
// if the current window is a different size
|
||||
auto parent = this->parentWidget();
|
||||
if (recenterParent && parent) {
|
||||
auto newX = std::max(0, parent->x() + ((parent->width() - newSize.width()) / 2));
|
||||
auto newY = std::max(0, parent->y() + ((parent->height() - newSize.height()) / 2));
|
||||
this->move(newX, newY);
|
||||
}
|
||||
else if (lastSize != newSize)
|
||||
{
|
||||
// center on old position after resize
|
||||
QSize sizeDiff = lastSize - newSize; // last size was smaller, the results should be negative
|
||||
auto newX = std::max(0, lastPos.x() + (sizeDiff.width() / 2));
|
||||
auto newY = std::max(0, lastPos.y() + (sizeDiff.height() / 2));
|
||||
this->move(newX, newY);
|
||||
}
|
||||
|
||||
setMinimumSize(qSize);
|
||||
|
||||
}
|
||||
|
||||
@ -201,7 +217,9 @@ void ProgressDialog::onTaskSucceeded()
|
||||
void ProgressDialog::changeStatus(const QString& status)
|
||||
{
|
||||
ui->globalStatusLabel->setText(task->getStatus());
|
||||
ui->globalStatusLabel->adjustSize();
|
||||
ui->globalStatusDetailsLabel->setText(task->getDetails());
|
||||
ui->globalStatusDetailsLabel->adjustSize();
|
||||
|
||||
updateSize();
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public:
|
||||
explicit ProgressDialog(QWidget *parent = 0);
|
||||
~ProgressDialog();
|
||||
|
||||
void updateSize();
|
||||
void updateSize(bool recenterParent = false);
|
||||
|
||||
int execWithTask(Task* task);
|
||||
int execWithTask(std::unique_ptr<Task> &&task);
|
||||
|
@ -43,6 +43,8 @@
|
||||
#include "ui/pages/modplatform/flame/FlameResourcePages.h"
|
||||
#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
|
||||
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
|
||||
namespace ResourceDownload {
|
||||
@ -281,8 +283,11 @@ QList<BasePage*> ModDownloadDialog::getPages()
|
||||
{
|
||||
QList<BasePage*> pages;
|
||||
|
||||
pages.append(ModrinthModPage::create(this, *m_instance));
|
||||
if (APPLICATION->capabilities() & Application::SupportsFlame)
|
||||
auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value();
|
||||
|
||||
if (ModrinthAPI::validateModLoaders(loaders))
|
||||
pages.append(ModrinthModPage::create(this, *m_instance));
|
||||
if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders))
|
||||
pages.append(FlameModPage::create(this, *m_instance));
|
||||
|
||||
m_selectedPage = dynamic_cast<ModPage*>(pages[0]);
|
||||
|
@ -248,8 +248,8 @@ bool AccessibleInstanceView::selectColumn(int column)
|
||||
if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) {
|
||||
return false;
|
||||
}
|
||||
// fallthrough intentional
|
||||
}
|
||||
/* fallthrough */
|
||||
case QAbstractItemView::ContiguousSelection: {
|
||||
if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
|
||||
view()->clearSelection();
|
||||
|
@ -48,7 +48,6 @@
|
||||
#include <QAccessible>
|
||||
|
||||
#include "VisualGroup.h"
|
||||
#include "ui/themes/ThemeManager.h"
|
||||
#include <QDebug>
|
||||
|
||||
#include <Application.h>
|
||||
@ -504,7 +503,7 @@ void InstanceView::setPaintCat(bool visible)
|
||||
{
|
||||
m_catVisible = visible;
|
||||
if (visible)
|
||||
m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage()));
|
||||
m_catPixmap.load(APPLICATION->getCatPack());
|
||||
else
|
||||
m_catPixmap = QPixmap();
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
@ -35,22 +36,18 @@
|
||||
|
||||
#include "VisualGroup.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <utility>
|
||||
|
||||
#include "InstanceView.h"
|
||||
|
||||
VisualGroup::VisualGroup(const QString &text, InstanceView *view) : view(view), text(text), collapsed(false)
|
||||
{
|
||||
}
|
||||
VisualGroup::VisualGroup(QString text, InstanceView* view) : view(view), text(std::move(text)), collapsed(false) {}
|
||||
|
||||
VisualGroup::VisualGroup(const VisualGroup *other)
|
||||
: view(other->view), text(other->text), collapsed(other->collapsed)
|
||||
{
|
||||
}
|
||||
VisualGroup::VisualGroup(const VisualGroup* other) : view(other->view), text(other->text), collapsed(other->collapsed) {}
|
||||
|
||||
void VisualGroup::update()
|
||||
{
|
||||
@ -64,13 +61,11 @@ void VisualGroup::update()
|
||||
int positionInRow = 0;
|
||||
int currentRow = 0;
|
||||
int offsetFromTop = 0;
|
||||
for (auto item: temp_items)
|
||||
{
|
||||
if(positionInRow == itemsPerRow)
|
||||
{
|
||||
for (auto item : temp_items) {
|
||||
if (positionInRow == itemsPerRow) {
|
||||
rows[currentRow].height = maxRowHeight;
|
||||
rows[currentRow].top = offsetFromTop;
|
||||
currentRow ++;
|
||||
currentRow++;
|
||||
offsetFromTop += maxRowHeight + 5;
|
||||
positionInRow = 0;
|
||||
maxRowHeight = 0;
|
||||
@ -83,8 +78,7 @@ void VisualGroup::update()
|
||||
#endif
|
||||
|
||||
auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height();
|
||||
if(itemHeight > maxRowHeight)
|
||||
{
|
||||
if (itemHeight > maxRowHeight) {
|
||||
maxRowHeight = itemHeight;
|
||||
}
|
||||
rows[currentRow].items.append(item);
|
||||
@ -94,16 +88,13 @@ void VisualGroup::update()
|
||||
rows[currentRow].top = offsetFromTop;
|
||||
}
|
||||
|
||||
QPair<int, int> VisualGroup::positionOf(const QModelIndex &index) const
|
||||
QPair<int, int> VisualGroup::positionOf(const QModelIndex& index) const
|
||||
{
|
||||
int y = 0;
|
||||
for (auto & row: rows)
|
||||
{
|
||||
for(auto x = 0; x < row.items.size(); x++)
|
||||
{
|
||||
if(row.items[x] == index)
|
||||
{
|
||||
return qMakePair(x,y);
|
||||
for (auto& row : rows) {
|
||||
for (auto x = 0; x < row.items.size(); x++) {
|
||||
if (row.items[x] == index) {
|
||||
return qMakePair(x, y);
|
||||
}
|
||||
}
|
||||
y++;
|
||||
@ -112,193 +103,109 @@ QPair<int, int> VisualGroup::positionOf(const QModelIndex &index) const
|
||||
return qMakePair(0, 0);
|
||||
}
|
||||
|
||||
int VisualGroup::rowTopOf(const QModelIndex &index) const
|
||||
int VisualGroup::rowTopOf(const QModelIndex& index) const
|
||||
{
|
||||
auto position = positionOf(index);
|
||||
return rows[position.second].top;
|
||||
}
|
||||
|
||||
int VisualGroup::rowHeightOf(const QModelIndex &index) const
|
||||
int VisualGroup::rowHeightOf(const QModelIndex& index) const
|
||||
{
|
||||
auto position = positionOf(index);
|
||||
return rows[position.second].height;
|
||||
}
|
||||
|
||||
VisualGroup::HitResults VisualGroup::hitScan(const QPoint &pos) const
|
||||
VisualGroup::HitResults VisualGroup::hitScan(const QPoint& pos) const
|
||||
{
|
||||
VisualGroup::HitResults results = VisualGroup::NoHit;
|
||||
int y_start = verticalPosition();
|
||||
int body_start = y_start + headerHeight();
|
||||
int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5?
|
||||
int body_end = body_start + contentHeight();
|
||||
int y = pos.y();
|
||||
// int x = pos.x();
|
||||
if (y < y_start)
|
||||
{
|
||||
if (y < y_start) {
|
||||
results = VisualGroup::NoHit;
|
||||
}
|
||||
else if (y < body_start)
|
||||
{
|
||||
} else if (y < body_start) {
|
||||
results = VisualGroup::HeaderHit;
|
||||
int collapseSize = headerHeight() - 4;
|
||||
|
||||
// the icon
|
||||
QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize);
|
||||
if (iconRect.contains(pos))
|
||||
{
|
||||
QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, view->width() - 4, collapseSize);
|
||||
if (iconRect.contains(pos)) {
|
||||
results |= VisualGroup::CheckboxHit;
|
||||
}
|
||||
}
|
||||
else if (y < body_end)
|
||||
{
|
||||
} else if (y < body_end) {
|
||||
results |= VisualGroup::BodyHit;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
void VisualGroup::drawHeader(QPainter *painter, const QStyleOptionViewItem &option)
|
||||
void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& option) const
|
||||
{
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
const QRect optRect = option.rect;
|
||||
QRect optRect = option.rect;
|
||||
optRect.setTop(optRect.top() + 7);
|
||||
QFont font(QApplication::font());
|
||||
font.setBold(true);
|
||||
const QFontMetrics fontMetrics = QFontMetrics(font);
|
||||
painter->setFont(font);
|
||||
|
||||
QColor outlineColor = option.palette.text().color();
|
||||
outlineColor.setAlphaF(0.35);
|
||||
QPen pen;
|
||||
pen.setWidth(2);
|
||||
QColor penColor = option.palette.text().color();
|
||||
penColor.setAlphaF(0.6);
|
||||
pen.setColor(penColor);
|
||||
painter->setPen(pen);
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
//BEGIN: top left corner
|
||||
// sizes and offsets, to keep things consistent below
|
||||
int arrowOffsetLeft = fontMetrics.height() / 2 + 7;
|
||||
int textOffsetLeft = arrowOffsetLeft * 2;
|
||||
int arrowSize = 6;
|
||||
int centerHeight = optRect.top() + fontMetrics.height() / 2;
|
||||
|
||||
// BEGIN: arrow
|
||||
{
|
||||
painter->save();
|
||||
painter->setPen(outlineColor);
|
||||
const QPointF topLeft(optRect.topLeft());
|
||||
QRectF arc(topLeft, QSizeF(4, 4));
|
||||
arc.translate(0.5, 0.5);
|
||||
painter->drawArc(arc, 1440, 1440);
|
||||
painter->restore();
|
||||
}
|
||||
//END: top left corner
|
||||
|
||||
//BEGIN: left vertical line
|
||||
{
|
||||
QPoint start(optRect.topLeft());
|
||||
start.ry() += 3;
|
||||
QPoint verticalGradBottom(optRect.topLeft());
|
||||
verticalGradBottom.ry() += fontMetrics.height() + 5;
|
||||
QLinearGradient gradient(start, verticalGradBottom);
|
||||
gradient.setColorAt(0, outlineColor);
|
||||
gradient.setColorAt(1, Qt::transparent);
|
||||
painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
|
||||
}
|
||||
//END: left vertical line
|
||||
|
||||
//BEGIN: horizontal line
|
||||
{
|
||||
QPoint start(optRect.topLeft());
|
||||
start.rx() += 3;
|
||||
QPoint horizontalGradTop(optRect.topLeft());
|
||||
horizontalGradTop.rx() += optRect.width() - 6;
|
||||
painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor);
|
||||
}
|
||||
//END: horizontal line
|
||||
|
||||
//BEGIN: top right corner
|
||||
{
|
||||
painter->save();
|
||||
painter->setPen(outlineColor);
|
||||
QPointF topRight(optRect.topRight());
|
||||
topRight.rx() -= 4;
|
||||
QRectF arc(topRight, QSizeF(4, 4));
|
||||
arc.translate(0.5, 0.5);
|
||||
painter->drawArc(arc, 0, 1440);
|
||||
painter->restore();
|
||||
}
|
||||
//END: top right corner
|
||||
|
||||
//BEGIN: right vertical line
|
||||
{
|
||||
QPoint start(optRect.topRight());
|
||||
start.ry() += 3;
|
||||
QPoint verticalGradBottom(optRect.topRight());
|
||||
verticalGradBottom.ry() += fontMetrics.height() + 5;
|
||||
QLinearGradient gradient(start, verticalGradBottom);
|
||||
gradient.setColorAt(0, outlineColor);
|
||||
gradient.setColorAt(1, Qt::transparent);
|
||||
painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient);
|
||||
}
|
||||
//END: right vertical line
|
||||
|
||||
//BEGIN: checkboxy thing
|
||||
{
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, false);
|
||||
painter->setFont(font);
|
||||
QColor penColor(option.palette.text().color());
|
||||
penColor.setAlphaF(0.6);
|
||||
painter->setPen(penColor);
|
||||
QRect iconSubRect(option.rect);
|
||||
iconSubRect.setTop(iconSubRect.top() + 7);
|
||||
iconSubRect.setLeft(iconSubRect.left() + 7);
|
||||
|
||||
int sizing = fontMetrics.height();
|
||||
int even = ( (sizing - 1) % 2 );
|
||||
|
||||
iconSubRect.setHeight(sizing - even);
|
||||
iconSubRect.setWidth(sizing - even);
|
||||
painter->drawRect(iconSubRect);
|
||||
|
||||
|
||||
/*
|
||||
if(collapsed)
|
||||
painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+");
|
||||
else
|
||||
painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-");
|
||||
*/
|
||||
painter->setBrush(option.palette.text());
|
||||
painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2,
|
||||
iconSubRect.width(), 2, penColor);
|
||||
if (collapsed)
|
||||
{
|
||||
painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2,
|
||||
iconSubRect.height(), penColor);
|
||||
QPolygon arrowPolygon;
|
||||
if (collapsed) {
|
||||
arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize)
|
||||
<< QPoint(arrowOffsetLeft + arrowSize / 2, centerHeight)
|
||||
<< QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight + arrowSize);
|
||||
painter->drawPolyline(arrowPolygon);
|
||||
} else {
|
||||
arrowPolygon << QPoint(arrowOffsetLeft - arrowSize, centerHeight - arrowSize / 2)
|
||||
<< QPoint(arrowOffsetLeft, centerHeight + arrowSize / 2)
|
||||
<< QPoint(arrowOffsetLeft + arrowSize, centerHeight - arrowSize / 2);
|
||||
painter->drawPolyline(arrowPolygon);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
//END: checkboxy thing
|
||||
// END: arrow
|
||||
|
||||
//BEGIN: text
|
||||
// BEGIN: text
|
||||
{
|
||||
QRect textRect(option.rect);
|
||||
textRect.setTop(textRect.top() + 7);
|
||||
textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 7);
|
||||
QRect textRect(optRect);
|
||||
textRect.setTop(textRect.top());
|
||||
textRect.setLeft(textOffsetLeft);
|
||||
textRect.setHeight(fontMetrics.height());
|
||||
textRect.setRight(textRect.right() - 7);
|
||||
|
||||
painter->save();
|
||||
painter->setFont(font);
|
||||
QColor penColor(option.palette.text().color());
|
||||
penColor.setAlphaF(0.6);
|
||||
painter->setPen(penColor);
|
||||
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text);
|
||||
painter->restore();
|
||||
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped"));
|
||||
}
|
||||
//END: text
|
||||
// END: text
|
||||
}
|
||||
|
||||
int VisualGroup::totalHeight() const
|
||||
{
|
||||
return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'?
|
||||
return headerHeight() + contentHeight();
|
||||
}
|
||||
|
||||
int VisualGroup::headerHeight() const
|
||||
int VisualGroup::headerHeight()
|
||||
{
|
||||
QFont font(QApplication::font());
|
||||
font.setBold(true);
|
||||
QFontMetrics fontMetrics(font);
|
||||
|
||||
const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */
|
||||
+ 11 /* top and bottom separation */;
|
||||
+ 11 /* top and bottom separation */;
|
||||
return height;
|
||||
/*
|
||||
int raw = view->viewport()->fontMetrics().height() + 4;
|
||||
@ -311,8 +218,7 @@ int VisualGroup::headerHeight() const
|
||||
|
||||
int VisualGroup::contentHeight() const
|
||||
{
|
||||
if (collapsed)
|
||||
{
|
||||
if (collapsed) {
|
||||
return 0;
|
||||
}
|
||||
auto last = rows[numRows() - 1];
|
||||
@ -321,7 +227,7 @@ int VisualGroup::contentHeight() const
|
||||
|
||||
int VisualGroup::numRows() const
|
||||
{
|
||||
return rows.size();
|
||||
return (int)rows.size();
|
||||
}
|
||||
|
||||
int VisualGroup::verticalPosition() const
|
||||
@ -332,11 +238,9 @@ int VisualGroup::verticalPosition() const
|
||||
QList<QModelIndex> VisualGroup::items() const
|
||||
{
|
||||
QList<QModelIndex> indices;
|
||||
for (int i = 0; i < view->model()->rowCount(); ++i)
|
||||
{
|
||||
for (int i = 0; i < view->model()->rowCount(); ++i) {
|
||||
const QModelIndex index = view->model()->index(i, 0);
|
||||
if (index.data(InstanceViewRoles::GroupRole).toString() == text)
|
||||
{
|
||||
if (index.data(InstanceViewRoles::GroupRole).toString() == text) {
|
||||
indices.append(index);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,36 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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
|
||||
@ -42,8 +62,8 @@ struct VisualRow
|
||||
struct VisualGroup
|
||||
{
|
||||
/* constructors */
|
||||
VisualGroup(const QString &text, InstanceView *view);
|
||||
VisualGroup(const VisualGroup *other);
|
||||
VisualGroup(QString text, InstanceView *view);
|
||||
explicit VisualGroup(const VisualGroup *other);
|
||||
|
||||
/* data */
|
||||
InstanceView *view = nullptr;
|
||||
@ -58,13 +78,13 @@ struct VisualGroup
|
||||
void update();
|
||||
|
||||
/// draw the header at y-position.
|
||||
void drawHeader(QPainter *painter, const QStyleOptionViewItem &option);
|
||||
void drawHeader(QPainter *painter, const QStyleOptionViewItem &option) const;
|
||||
|
||||
/// height of the group, in total. includes a small bit of padding.
|
||||
int totalHeight() const;
|
||||
|
||||
/// height of the group header, in pixels
|
||||
int headerHeight() const;
|
||||
static int headerHeight() ;
|
||||
|
||||
/// height of the group content, in pixels
|
||||
int contentHeight() const;
|
||||
|
@ -81,6 +81,8 @@ APIPage::APIPage(QWidget *parent) :
|
||||
connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLPlaceholder);
|
||||
// This function needs to be called even when the ComboBox's index is still in its default state.
|
||||
updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());
|
||||
// NOTE: this allows http://, but we replace that with https later anyway
|
||||
ui->metaURL->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->metaURL));
|
||||
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
|
||||
ui->msaClientID->setValidator(new QRegularExpressionValidator(validMSAClientID, ui->msaClientID));
|
||||
ui->flameKey->setValidator(new QRegularExpressionValidator(validFlameKey, ui->flameKey));
|
||||
@ -163,7 +165,7 @@ void APIPage::applySettings()
|
||||
|
||||
QString msaClientID = ui->msaClientID->text();
|
||||
s->set("MSAClientIDOverride", msaClientID);
|
||||
QUrl metaURL = ui->metaURL->text();
|
||||
QUrl metaURL(ui->metaURL->text());
|
||||
// Add required trailing slash
|
||||
if (!metaURL.isEmpty() && !metaURL.path().endsWith('/'))
|
||||
{
|
||||
|
@ -159,19 +159,6 @@ void AccountListPage::on_actionAddMojang_triggered()
|
||||
|
||||
void AccountListPage::on_actionAddMicrosoft_triggered()
|
||||
{
|
||||
if(BuildConfig.BUILD_PLATFORM == "osx64") {
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Microsoft Accounts not available"),
|
||||
//: %1 refers to the launcher itself
|
||||
tr(
|
||||
"Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated %1.\n\n"
|
||||
"Please update both your operating system and %1."
|
||||
).arg(BuildConfig.LAUNCHER_DISPLAYNAME),
|
||||
QMessageBox::Warning
|
||||
)->exec();
|
||||
return;
|
||||
}
|
||||
MinecraftAccountPtr account = MSALoginDialog::newAccount(
|
||||
this,
|
||||
tr("Please enter your Mojang account email and password to add your account.")
|
||||
|
@ -3,7 +3,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (c) 2022 dada513 <dada513@protonmail.com>
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -169,7 +169,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="metadataDisableBtn">
|
||||
<property name="toolTip">
|
||||
<string>Disable using metadata provided by mod providers (like Modrinth or Curseforge) for mods.</string>
|
||||
<string>Disable using metadata provided by mod providers (like Modrinth or CurseForge) for mods.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disable using metadata for mods</string>
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2023 seth <getchoo at tuta dot io>
|
||||
*
|
||||
* 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
|
||||
@ -99,6 +100,9 @@ void MinecraftPage::applySettings()
|
||||
// Miscellaneous
|
||||
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
|
||||
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
|
||||
|
||||
// Mod loader settings
|
||||
s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
|
||||
}
|
||||
|
||||
void MinecraftPage::loadSettings()
|
||||
@ -137,6 +141,8 @@ void MinecraftPage::loadSettings()
|
||||
|
||||
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
|
||||
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
|
||||
|
||||
ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool());
|
||||
}
|
||||
|
||||
void MinecraftPage::retranslate()
|
||||
|
@ -190,6 +190,25 @@
|
||||
<string>Tweaks</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="modLoaderSettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Mod loader settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
|
||||
<property name="text">
|
||||
<string>Disable Quilt Loader Beacon</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Disable Quilt loader's beacon for counting monthly active users</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
|
||||
<property name="title">
|
||||
|
@ -151,9 +151,6 @@ void ExternalResourcesPage::retranslate()
|
||||
|
||||
void ExternalResourcesPage::itemActivated(const QModelIndex&)
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
|
||||
}
|
||||
|
||||
@ -197,9 +194,6 @@ bool ExternalResourcesPage::eventFilter(QObject* obj, QEvent* ev)
|
||||
|
||||
void ExternalResourcesPage::addItem()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
|
||||
auto list = GuiUtil::BrowseForFiles(
|
||||
helpPage(), tr("Select %1", "Select whatever type of files the page contains. Example: 'Loader Mods'").arg(displayName()),
|
||||
m_fileSelectionFilter.arg(displayName()), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
|
||||
@ -213,9 +207,6 @@ void ExternalResourcesPage::addItem()
|
||||
|
||||
void ExternalResourcesPage::removeItem()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
|
||||
|
||||
int count = 0;
|
||||
@ -259,23 +250,37 @@ void ExternalResourcesPage::removeItem()
|
||||
|
||||
void ExternalResourcesPage::removeItems(const QItemSelection& selection)
|
||||
{
|
||||
if (m_instance != nullptr && m_instance->isRunning()) {
|
||||
auto response = CustomMessageBox::selectable(this, "Confirm Delete",
|
||||
"If you remove this resource while the game is running it may crash your game.\n"
|
||||
"Are you sure you want to do this?",
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
m_model->deleteResources(selection.indexes());
|
||||
}
|
||||
|
||||
void ExternalResourcesPage::enableItem()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
|
||||
m_model->setResourceEnabled(selection.indexes(), EnableAction::ENABLE);
|
||||
}
|
||||
|
||||
void ExternalResourcesPage::disableItem()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
if (m_instance != nullptr && m_instance->isRunning()) {
|
||||
auto response = CustomMessageBox::selectable(this, "Confirm disable",
|
||||
"If you disable this resource while the game is running it may crash your game.\n"
|
||||
"Are you sure you want to do this?",
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
|
||||
m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE);
|
||||
}
|
||||
|
@ -73,7 +73,5 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
|
||||
QString m_fileSelectionFilter;
|
||||
QString m_viewFilter;
|
||||
|
||||
bool m_controlsEnabled = true;
|
||||
|
||||
std::shared_ptr<Setting> m_wide_bar_setting = nullptr;
|
||||
};
|
||||
|
@ -157,6 +157,17 @@
|
||||
<string>Try to check or update all selected resources (all resources if none are selected)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionVisitItemPage">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Visit mod's page</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Go to mods home page</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -3,6 +3,7 @@
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 seth <getchoo at tuta dot io>
|
||||
*
|
||||
* 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
|
||||
@ -50,9 +51,9 @@
|
||||
#include "Application.h"
|
||||
#include "minecraft/auth/AccountList.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "java/JavaInstallList.h"
|
||||
#include "java/JavaUtils.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst)
|
||||
@ -60,17 +61,13 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent)
|
||||
m_settings = inst->settings();
|
||||
ui->setupUi(this);
|
||||
|
||||
// As the signal will (probably) not be triggered once we click edit, let's update it manually instead.
|
||||
updateRunningStatus(m_instance->isRunning());
|
||||
|
||||
connect(m_instance, &BaseInstance::runningStatusChanged, this, &InstanceSettingsPage::updateRunningStatus);
|
||||
connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked);
|
||||
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings);
|
||||
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
|
||||
connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &InstanceSettingsPage::changeInstanceAccount);
|
||||
connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&InstanceSettingsPage::changeInstanceAccount);
|
||||
loadSettings();
|
||||
|
||||
|
||||
updateThresholds();
|
||||
}
|
||||
|
||||
@ -284,6 +281,14 @@ void InstanceSettingsPage::applySettings()
|
||||
m_settings->reset("InstanceAccountId");
|
||||
}
|
||||
|
||||
bool overrideModLoaderSettings = ui->modLoaderSettingsGroupBox->isChecked();
|
||||
m_settings->set("OverrideModLoaderSettings", overrideModLoaderSettings);
|
||||
if (overrideModLoaderSettings) {
|
||||
m_settings->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
|
||||
} else {
|
||||
m_settings->reset("DisableQuiltBeacon");
|
||||
}
|
||||
|
||||
// FIXME: This should probably be called by a signal instead
|
||||
m_instance->updateRuntimeContext();
|
||||
}
|
||||
@ -384,6 +389,10 @@ void InstanceSettingsPage::loadSettings()
|
||||
|
||||
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
|
||||
updateAccountsMenu();
|
||||
|
||||
// Mod loader specific settings
|
||||
ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool());
|
||||
ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool());
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::on_javaDetectBtn_clicked()
|
||||
@ -523,8 +532,3 @@ void InstanceSettingsPage::updateThresholds()
|
||||
ui->labelMaxMemIcon->setPixmap(pix);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::updateRunningStatus(bool running)
|
||||
{
|
||||
setEnabled(!running);
|
||||
}
|
||||
|
@ -79,8 +79,7 @@ public:
|
||||
|
||||
void updateThresholds();
|
||||
|
||||
private slots:
|
||||
void updateRunningStatus(bool running);
|
||||
private slots:
|
||||
void on_javaDetectBtn_clicked();
|
||||
void on_javaTestBtn_clicked();
|
||||
void on_javaBrowseBtn_clicked();
|
||||
|
@ -541,6 +541,31 @@
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="modLoaderSettingsGroupBox">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Mod loader settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="VerticalLayout_16">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
|
||||
<property name="text">
|
||||
<string>Disable Quilt Loader Beacon</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Disable Quilt loader's beacon for counting monthly active users</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameTimeGroupBox">
|
||||
<property name="enabled">
|
||||
|
@ -69,7 +69,6 @@ class NoBigComboBoxStyle : public QProxyStyle {
|
||||
|
||||
private:
|
||||
NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
|
||||
|
||||
};
|
||||
|
||||
ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent)
|
||||
@ -91,13 +90,13 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
|
||||
|
||||
// NOTE: GTK2 themes crash with the proxy style.
|
||||
// This seems like an upstream bug, so there's not much else that can be done.
|
||||
if (!QStyleFactory::keys().contains("gtk2")){
|
||||
if (!QStyleFactory::keys().contains("gtk2")) {
|
||||
auto comboStyle = NoBigComboBoxStyle::getInstance(ui->versionsComboBox->style());
|
||||
ui->versionsComboBox->setStyle(comboStyle);
|
||||
}
|
||||
|
||||
ui->reloadButton->setVisible(false);
|
||||
connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool){
|
||||
connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool) {
|
||||
ui->reloadButton->setVisible(false);
|
||||
|
||||
m_loaded = false;
|
||||
@ -226,7 +225,8 @@ void ModrinthManagedPackPage::parseManagedPack()
|
||||
|
||||
QString id = m_inst->getManagedPackID();
|
||||
|
||||
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
m_fetch_job->addNetAction(
|
||||
Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
|
||||
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -267,7 +267,6 @@ void ModrinthManagedPackPage::parseManagedPack()
|
||||
if (version.version == m_inst->getManagedPackVersionName())
|
||||
name = tr("%1 (Current)").arg(name);
|
||||
|
||||
|
||||
ui->versionsComboBox->addItem(name, QVariant(version.id));
|
||||
}
|
||||
|
||||
@ -291,6 +290,10 @@ QString ModrinthManagedPackPage::url() const
|
||||
void ModrinthManagedPackPage::suggestVersion()
|
||||
{
|
||||
auto index = ui->versionsComboBox->currentIndex();
|
||||
if (m_pack.versions.length() == 0) {
|
||||
setFailState();
|
||||
return;
|
||||
}
|
||||
auto version = m_pack.versions.at(index);
|
||||
|
||||
ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8()));
|
||||
@ -301,6 +304,10 @@ void ModrinthManagedPackPage::suggestVersion()
|
||||
void ModrinthManagedPackPage::update()
|
||||
{
|
||||
auto index = ui->versionsComboBox->currentIndex();
|
||||
if (m_pack.versions.length() == 0) {
|
||||
setFailState();
|
||||
return;
|
||||
}
|
||||
auto version = m_pack.versions.at(index);
|
||||
|
||||
QMap<QString, QString> extra_info;
|
||||
@ -429,6 +436,10 @@ QString FlameManagedPackPage::url() const
|
||||
void FlameManagedPackPage::suggestVersion()
|
||||
{
|
||||
auto index = ui->versionsComboBox->currentIndex();
|
||||
if (m_pack.versions.length() == 0) {
|
||||
setFailState();
|
||||
return;
|
||||
}
|
||||
auto version = m_pack.versions.at(index);
|
||||
|
||||
ui->changelogTextBrowser->setHtml(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId));
|
||||
@ -439,6 +450,10 @@ void FlameManagedPackPage::suggestVersion()
|
||||
void FlameManagedPackPage::update()
|
||||
{
|
||||
auto index = ui->versionsComboBox->currentIndex();
|
||||
if (m_pack.versions.length() == 0) {
|
||||
setFailState();
|
||||
return;
|
||||
}
|
||||
auto version = m_pack.versions.at(index);
|
||||
|
||||
QMap<QString, QString> extra_info;
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
@ -60,6 +61,7 @@
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
|
||||
#include "Version.h"
|
||||
@ -86,12 +88,28 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
|
||||
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
|
||||
|
||||
auto check_allow_update = [this] {
|
||||
return (!m_instance || !m_instance->isRunning()) && (ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
|
||||
};
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
|
||||
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
|
||||
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
|
||||
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
[this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
|
||||
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
|
||||
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
|
||||
ui->actionUpdateItem->setEnabled(check_allow_update());
|
||||
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||
auto mods_list = m_model->selectedMods(selection);
|
||||
auto selected = std::count_if(mods_list.cbegin(), mods_list.cend(),
|
||||
[](Mod* v) { return v->metadata() != nullptr || v->homeurl().size() != 0; });
|
||||
if (selected <= 1) {
|
||||
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
|
||||
} else {
|
||||
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
|
||||
}
|
||||
ui->actionVisitItemPage->setEnabled(selected != 0);
|
||||
});
|
||||
|
||||
connect(mods.get(), &ModFolderModel::rowsInserted, this,
|
||||
[this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
|
||||
@ -101,22 +119,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
|
||||
connect(mods.get(), &ModFolderModel::updateFinished, this,
|
||||
[this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
|
||||
|
||||
connect(m_instance, &BaseInstance::runningStatusChanged, this, &ModFolderPage::runningStateChanged);
|
||||
ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning());
|
||||
}
|
||||
}
|
||||
|
||||
void ModFolderPage::runningStateChanged(bool running)
|
||||
{
|
||||
ui->actionDownloadItem->setEnabled(!running);
|
||||
ui->actionUpdateItem->setEnabled(!running);
|
||||
ui->actionAddItem->setEnabled(!running);
|
||||
ui->actionEnableItem->setEnabled(!running);
|
||||
ui->actionDisableItem->setEnabled(!running);
|
||||
ui->actionRemoveItem->setEnabled(!running);
|
||||
}
|
||||
|
||||
bool ModFolderPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
@ -133,15 +138,23 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelI
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModFolderPage::removeItems(const QItemSelection &selection)
|
||||
void ModFolderPage::removeItems(const QItemSelection& selection)
|
||||
{
|
||||
if (m_instance != nullptr && m_instance->isRunning()) {
|
||||
auto response = CustomMessageBox::selectable(this, "Confirm Delete",
|
||||
"If you remove mods while the game is running it may crash your game.\n"
|
||||
"Are you sure you want to do this?",
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
m_model->deleteMods(selection.indexes());
|
||||
}
|
||||
|
||||
void ModFolderPage::installMods()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
if (m_instance->typeName() != "Minecraft")
|
||||
return; // this is a null instance or a legacy instance
|
||||
|
||||
@ -207,8 +220,7 @@ void ModFolderPage::updateMods()
|
||||
message = tr("All selected mods are up-to-date! :)");
|
||||
}
|
||||
}
|
||||
CustomMessageBox::selectable(this, tr("Update checker"), message)
|
||||
->exec();
|
||||
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -275,3 +287,13 @@ bool NilModFolderPage::shouldDisplay() const
|
||||
{
|
||||
return m_model->dir().exists();
|
||||
}
|
||||
|
||||
void ModFolderPage::visitModPages()
|
||||
{
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||
for (auto mod : m_model->selectedMods(selection)) {
|
||||
auto url = mod->metaurl();
|
||||
if (!url.isEmpty())
|
||||
DesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* 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
|
||||
@ -59,11 +60,11 @@ class ModFolderPage : public ExternalResourcesPage {
|
||||
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
|
||||
|
||||
private slots:
|
||||
void runningStateChanged(bool running);
|
||||
void removeItems(const QItemSelection &selection) override;
|
||||
void removeItems(const QItemSelection& selection) override;
|
||||
|
||||
void installMods();
|
||||
void updateMods();
|
||||
void visitModPages();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ModFolderModel> m_model;
|
||||
|
@ -67,8 +67,6 @@ bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, const QMod
|
||||
|
||||
void ResourcePackPage::downloadRPs()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
if (m_instance->typeName() != "Minecraft")
|
||||
return; // this is a null instance or a legacy instance
|
||||
|
||||
|
@ -46,7 +46,6 @@
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/ResourceDownloadDialog.h"
|
||||
|
||||
|
||||
ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget* parent)
|
||||
: ExternalResourcesPage(instance, model, parent)
|
||||
{
|
||||
@ -61,8 +60,6 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<Shad
|
||||
|
||||
void ShaderPackPage::downloadShaders()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
if (m_instance->typeName() != "Minecraft")
|
||||
return; // this is a null instance or a legacy instance
|
||||
|
||||
|
@ -69,8 +69,6 @@ bool TexturePackPage::onSelectionChanged(const QModelIndex& current, const QMode
|
||||
|
||||
void TexturePackPage::downloadTPs()
|
||||
{
|
||||
if (!m_controlsEnabled)
|
||||
return;
|
||||
if (m_instance->typeName() != "Minecraft")
|
||||
return; // this is a null instance or a legacy instance
|
||||
|
||||
|
@ -40,14 +40,13 @@
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
#include <QListView>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
@ -55,49 +54,42 @@
|
||||
#include "ui_VersionPage.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/VersionSelectDialog.h"
|
||||
#include "ui/dialogs/NewComponentDialog.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/VersionSelectDialog.h"
|
||||
|
||||
#include "ui/GuiUtil.h"
|
||||
|
||||
#include "DesktopServices.h"
|
||||
#include "Exception.h"
|
||||
#include "Version.h"
|
||||
#include "icons/IconList.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/auth/AccountList.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "icons/IconList.h"
|
||||
#include "Exception.h"
|
||||
#include "Version.h"
|
||||
#include "DesktopServices.h"
|
||||
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
class IconProxy : public QIdentityProxyModel
|
||||
{
|
||||
class IconProxy : public QIdentityProxyModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget)
|
||||
public:
|
||||
IconProxy(QWidget* parentWidget) : QIdentityProxyModel(parentWidget)
|
||||
{
|
||||
connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone);
|
||||
m_parentWidget = parentWidget;
|
||||
}
|
||||
|
||||
virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override
|
||||
virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const override
|
||||
{
|
||||
QVariant var = QIdentityProxyModel::data(proxyIndex, role);
|
||||
int column = proxyIndex.column();
|
||||
if(column == 0 && role == Qt::DecorationRole && m_parentWidget)
|
||||
{
|
||||
if(!var.isNull())
|
||||
{
|
||||
if (column == 0 && role == Qt::DecorationRole && m_parentWidget) {
|
||||
if (!var.isNull()) {
|
||||
auto string = var.toString();
|
||||
if(string == "warning")
|
||||
{
|
||||
if (string == "warning") {
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
}
|
||||
else if(string == "error")
|
||||
{
|
||||
} else if (string == "error") {
|
||||
return APPLICATION->getThemedIcon("status-bad");
|
||||
}
|
||||
}
|
||||
@ -105,14 +97,11 @@ public:
|
||||
}
|
||||
return var;
|
||||
}
|
||||
private slots:
|
||||
void widgetGone()
|
||||
{
|
||||
m_parentWidget = nullptr;
|
||||
}
|
||||
private slots:
|
||||
void widgetGone() { m_parentWidget = nullptr; }
|
||||
|
||||
private:
|
||||
QWidget *m_parentWidget = nullptr;
|
||||
private:
|
||||
QWidget* m_parentWidget = nullptr;
|
||||
};
|
||||
|
||||
QIcon VersionPage::icon() const
|
||||
@ -144,15 +133,14 @@ void VersionPage::closedImpl()
|
||||
m_wide_bar_setting->set(ui->toolBar->getVisibilityState());
|
||||
}
|
||||
|
||||
QMenu * VersionPage::createPopupMenu()
|
||||
QMenu* VersionPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
|
||||
filteredMenu->removeAction(ui->toolBar->toggleViewAction());
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
|
||||
: QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst)
|
||||
VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
@ -182,10 +170,8 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
|
||||
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
|
||||
|
||||
connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
|
||||
controlsEnabled = !m_inst->isRunning();
|
||||
updateVersionControls();
|
||||
preselect(0);
|
||||
connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus);
|
||||
connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
|
||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
|
||||
}
|
||||
@ -202,18 +188,16 @@ void VersionPage::showContextMenu(const QPoint& pos)
|
||||
delete menu;
|
||||
}
|
||||
|
||||
void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
void VersionPage::packageCurrent(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
if (!current.isValid())
|
||||
{
|
||||
if (!current.isValid()) {
|
||||
ui->frame->clear();
|
||||
return;
|
||||
}
|
||||
int row = current.row();
|
||||
auto patch = m_profile->getComponent(row);
|
||||
auto severity = patch->getProblemSeverity();
|
||||
switch(severity)
|
||||
{
|
||||
switch (severity) {
|
||||
case ProblemSeverity::Warning:
|
||||
ui->frame->setName(tr("%1 possibly has issues.").arg(patch->getName()));
|
||||
break;
|
||||
@ -226,16 +210,12 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &
|
||||
return;
|
||||
}
|
||||
|
||||
auto &problems = patch->getProblems();
|
||||
auto& problems = patch->getProblems();
|
||||
QString problemOut;
|
||||
for (auto &problem: problems)
|
||||
{
|
||||
if(problem.m_severity == ProblemSeverity::Error)
|
||||
{
|
||||
for (auto& problem : problems) {
|
||||
if (problem.m_severity == ProblemSeverity::Error) {
|
||||
problemOut += tr("Error: ");
|
||||
}
|
||||
else if(problem.m_severity == ProblemSeverity::Warning)
|
||||
{
|
||||
} else if (problem.m_severity == ProblemSeverity::Warning) {
|
||||
problemOut += tr("Warning: ");
|
||||
}
|
||||
problemOut += problem.m_description;
|
||||
@ -244,71 +224,47 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &
|
||||
ui->frame->setDescription(problemOut);
|
||||
}
|
||||
|
||||
void VersionPage::updateRunningStatus(bool running)
|
||||
{
|
||||
if(controlsEnabled == running) {
|
||||
controlsEnabled = !running;
|
||||
updateVersionControls();
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::updateVersionControls()
|
||||
{
|
||||
// FIXME: this is a dirty hack
|
||||
auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft"));
|
||||
|
||||
ui->actionInstall_Forge->setEnabled(controlsEnabled);
|
||||
|
||||
bool supportsFabric = minecraftVersion >= Version("1.14");
|
||||
ui->actionInstall_Fabric->setEnabled(controlsEnabled && supportsFabric);
|
||||
ui->actionInstall_Fabric->setEnabled(supportsFabric);
|
||||
|
||||
bool supportsQuilt = minecraftVersion >= Version("1.14");
|
||||
ui->actionInstall_Quilt->setEnabled(controlsEnabled && supportsQuilt);
|
||||
ui->actionInstall_Quilt->setEnabled(supportsQuilt);
|
||||
|
||||
bool supportsLiteLoader = minecraftVersion <= Version("1.12.2");
|
||||
ui->actionInstall_LiteLoader->setEnabled(controlsEnabled && supportsLiteLoader);
|
||||
ui->actionInstall_LiteLoader->setEnabled(supportsLiteLoader);
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void VersionPage::updateButtons(int row)
|
||||
{
|
||||
if(row == -1)
|
||||
if (row == -1)
|
||||
row = currentRow();
|
||||
auto patch = m_profile->getComponent(row);
|
||||
ui->actionRemove->setEnabled(controlsEnabled && patch && patch->isRemovable());
|
||||
ui->actionMove_down->setEnabled(controlsEnabled && patch && patch->isMoveable());
|
||||
ui->actionMove_up->setEnabled(controlsEnabled && patch && patch->isMoveable());
|
||||
ui->actionChange_version->setEnabled(controlsEnabled && patch && patch->isVersionChangeable());
|
||||
ui->actionEdit->setEnabled(controlsEnabled && patch && patch->isCustom());
|
||||
ui->actionCustomize->setEnabled(controlsEnabled && patch && patch->isCustomizable());
|
||||
ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible());
|
||||
ui->actionDownload_All->setEnabled(controlsEnabled);
|
||||
ui->actionAdd_Empty->setEnabled(controlsEnabled);
|
||||
ui->actionImport_Components->setEnabled(controlsEnabled);
|
||||
ui->actionReload->setEnabled(controlsEnabled);
|
||||
ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled);
|
||||
ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled);
|
||||
ui->actionAdd_Agents->setEnabled(controlsEnabled);
|
||||
ui->actionRemove->setEnabled(patch && patch->isRemovable());
|
||||
ui->actionMove_down->setEnabled(patch && patch->isMoveable());
|
||||
ui->actionMove_up->setEnabled(patch && patch->isMoveable());
|
||||
ui->actionChange_version->setEnabled(patch && patch->isVersionChangeable());
|
||||
ui->actionEdit->setEnabled(patch && patch->isCustom());
|
||||
ui->actionCustomize->setEnabled(patch && patch->isCustomizable());
|
||||
ui->actionRevert->setEnabled(patch && patch->isRevertible());
|
||||
}
|
||||
|
||||
bool VersionPage::reloadPackProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
m_profile->reload(Net::Mode::Online);
|
||||
return true;
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
} catch (const Exception& e) {
|
||||
QMessageBox::critical(this, tr("Error"), e.cause());
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Couldn't load the instance profile."));
|
||||
} catch (...) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Couldn't load the instance profile."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -321,14 +277,12 @@ void VersionPage::on_actionReload_triggered()
|
||||
|
||||
void VersionPage::on_actionRemove_triggered()
|
||||
{
|
||||
if (!ui->packageView->currentIndex().isValid())
|
||||
{
|
||||
if (!ui->packageView->currentIndex().isValid()) {
|
||||
return;
|
||||
}
|
||||
int index = ui->packageView->currentIndex().row();
|
||||
auto component = m_profile->getComponent(index);
|
||||
if (component->isCustom())
|
||||
{
|
||||
if (component->isCustom()) {
|
||||
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
|
||||
tr("You are about to remove \"%1\".\n"
|
||||
"This is permanent and will completely remove the custom component.\n\n"
|
||||
@ -341,8 +295,7 @@ void VersionPage::on_actionRemove_triggered()
|
||||
return;
|
||||
}
|
||||
// FIXME: use actual model, not reloading.
|
||||
if (!m_profile->remove(index))
|
||||
{
|
||||
if (!m_profile->remove(index)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
|
||||
}
|
||||
updateButtons();
|
||||
@ -352,17 +305,16 @@ void VersionPage::on_actionRemove_triggered()
|
||||
|
||||
void VersionPage::on_actionInstall_mods_triggered()
|
||||
{
|
||||
if(m_container)
|
||||
{
|
||||
if (m_container) {
|
||||
m_container->selectPage("mods");
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
|
||||
{
|
||||
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
|
||||
if(!list.empty())
|
||||
{
|
||||
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"),
|
||||
APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
|
||||
if (!list.empty()) {
|
||||
m_profile->installJarMods(list);
|
||||
}
|
||||
updateButtons();
|
||||
@ -370,9 +322,9 @@ void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
|
||||
|
||||
void VersionPage::on_actionReplace_Minecraft_jar_triggered()
|
||||
{
|
||||
auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
|
||||
if(!jarPath.isEmpty())
|
||||
{
|
||||
auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"),
|
||||
APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
|
||||
if (!jarPath.isEmpty()) {
|
||||
m_profile->installCustomJar(jarPath);
|
||||
}
|
||||
updateButtons();
|
||||
@ -406,12 +358,9 @@ void VersionPage::on_actionAdd_Agents_triggered()
|
||||
|
||||
void VersionPage::on_actionMove_up_triggered()
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
m_profile->move(currentRow(), PackProfile::MoveUp);
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
} catch (const Exception& e) {
|
||||
QMessageBox::critical(this, tr("Error"), e.cause());
|
||||
}
|
||||
updateButtons();
|
||||
@ -419,12 +368,9 @@ void VersionPage::on_actionMove_up_triggered()
|
||||
|
||||
void VersionPage::on_actionMove_down_triggered()
|
||||
{
|
||||
try
|
||||
{
|
||||
try {
|
||||
m_profile->move(currentRow(), PackProfile::MoveDown);
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
} catch (const Exception& e) {
|
||||
QMessageBox::critical(this, tr("Error"), e.cause());
|
||||
}
|
||||
updateButtons();
|
||||
@ -433,39 +379,32 @@ void VersionPage::on_actionMove_down_triggered()
|
||||
void VersionPage::on_actionChange_version_triggered()
|
||||
{
|
||||
auto versionRow = currentRow();
|
||||
if(versionRow == -1)
|
||||
{
|
||||
if (versionRow == -1) {
|
||||
return;
|
||||
}
|
||||
auto patch = m_profile->getComponent(versionRow);
|
||||
auto name = patch->getName();
|
||||
auto list = patch->getVersionList();
|
||||
if(!list)
|
||||
{
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
auto uid = list->uid();
|
||||
// FIXME: this is a horrible HACK. Get version filtering information from the actual metadata...
|
||||
if(uid == "net.minecraftforge")
|
||||
{
|
||||
if (uid == "net.minecraftforge") {
|
||||
on_actionInstall_Forge_triggered();
|
||||
return;
|
||||
}
|
||||
else if (uid == "com.mumfrey.liteloader")
|
||||
{
|
||||
} else if (uid == "com.mumfrey.liteloader") {
|
||||
on_actionInstall_LiteLoader_triggered();
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
|
||||
if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed")
|
||||
{
|
||||
if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed") {
|
||||
vselect.setEmptyString(tr("No intermediary mappings versions are currently available."));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the intermediary mappings version lists!"));
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
}
|
||||
auto currentVersion = patch->getVersion();
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
if (!currentVersion.isEmpty()) {
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
if (!vselect.exec() || !vselect.selectedVersion())
|
||||
@ -473,8 +412,7 @@ void VersionPage::on_actionChange_version_triggered()
|
||||
|
||||
qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor();
|
||||
bool important = false;
|
||||
if(uid == "net.minecraft")
|
||||
{
|
||||
if (uid == "net.minecraft") {
|
||||
important = true;
|
||||
}
|
||||
m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important);
|
||||
@ -484,19 +422,17 @@ void VersionPage::on_actionChange_version_triggered()
|
||||
|
||||
void VersionPage::on_actionDownload_All_triggered()
|
||||
{
|
||||
if (!APPLICATION->accounts()->anyAccountIsValid())
|
||||
{
|
||||
CustomMessageBox::selectable(
|
||||
this, tr("Error"),
|
||||
tr("Cannot download Minecraft or update instances unless you have at least "
|
||||
"one account added.\nPlease add your Mojang or Minecraft account."),
|
||||
QMessageBox::Warning)->show();
|
||||
if (!APPLICATION->accounts()->anyAccountIsValid()) {
|
||||
CustomMessageBox::selectable(this, tr("Error"),
|
||||
tr("Cannot download Minecraft or update instances unless you have at least "
|
||||
"one account added.\nPlease add your Mojang or Minecraft account."),
|
||||
QMessageBox::Warning)
|
||||
->show();
|
||||
return;
|
||||
}
|
||||
|
||||
auto updateTask = m_inst->createUpdateTask(Net::Mode::Online);
|
||||
if (!updateTask)
|
||||
{
|
||||
if (!updateTask) {
|
||||
return;
|
||||
}
|
||||
ProgressDialog tDialog(this);
|
||||
@ -510,28 +446,26 @@ void VersionPage::on_actionDownload_All_triggered()
|
||||
void VersionPage::on_actionInstall_Forge_triggered()
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge");
|
||||
if(!vlist)
|
||||
{
|
||||
if (!vlist) {
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this);
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") +
|
||||
m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!"));
|
||||
|
||||
auto currentVersion = m_profile->getComponentVersion("net.minecraftforge");
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
if (!currentVersion.isEmpty()) {
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
if (vselect.exec() && vselect.selectedVersion()) {
|
||||
auto vsn = vselect.selectedVersion();
|
||||
m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor());
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
// m_profile->installVersion();
|
||||
preselect(m_profile->rowCount(QModelIndex())-1);
|
||||
preselect(m_profile->rowCount(QModelIndex()) - 1);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
}
|
||||
@ -539,8 +473,7 @@ void VersionPage::on_actionInstall_Forge_triggered()
|
||||
void VersionPage::on_actionInstall_Fabric_triggered()
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("net.fabricmc.fabric-loader");
|
||||
if(!vlist)
|
||||
{
|
||||
if (!vlist) {
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this);
|
||||
@ -548,17 +481,15 @@ void VersionPage::on_actionInstall_Fabric_triggered()
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!"));
|
||||
|
||||
auto currentVersion = m_profile->getComponentVersion("net.fabricmc.fabric-loader");
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
if (!currentVersion.isEmpty()) {
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
if (vselect.exec() && vselect.selectedVersion()) {
|
||||
auto vsn = vselect.selectedVersion();
|
||||
m_profile->setComponentVersion("net.fabricmc.fabric-loader", vsn->descriptor());
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
preselect(m_profile->rowCount(QModelIndex())-1);
|
||||
preselect(m_profile->rowCount(QModelIndex()) - 1);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
}
|
||||
@ -566,8 +497,7 @@ void VersionPage::on_actionInstall_Fabric_triggered()
|
||||
void VersionPage::on_actionInstall_Quilt_triggered()
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("org.quiltmc.quilt-loader");
|
||||
if(!vlist)
|
||||
{
|
||||
if (!vlist) {
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select Quilt Loader version"), this);
|
||||
@ -575,17 +505,15 @@ void VersionPage::on_actionInstall_Quilt_triggered()
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the Quilt Loader version lists!"));
|
||||
|
||||
auto currentVersion = m_profile->getComponentVersion("org.quiltmc.quilt-loader");
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
if (!currentVersion.isEmpty()) {
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
if (vselect.exec() && vselect.selectedVersion()) {
|
||||
auto vsn = vselect.selectedVersion();
|
||||
m_profile->setComponentVersion("org.quiltmc.quilt-loader", vsn->descriptor());
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
preselect(m_profile->rowCount(QModelIndex())-1);
|
||||
preselect(m_profile->rowCount(QModelIndex()) - 1);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
}
|
||||
@ -594,14 +522,12 @@ void VersionPage::on_actionAdd_Empty_triggered()
|
||||
{
|
||||
NewComponentDialog compdialog(QString(), QString(), this);
|
||||
QStringList blacklist;
|
||||
for(int i = 0; i < m_profile->rowCount(); i++)
|
||||
{
|
||||
for (int i = 0; i < m_profile->rowCount(); i++) {
|
||||
auto comp = m_profile->getComponent(i);
|
||||
blacklist.push_back(comp->getID());
|
||||
}
|
||||
compdialog.setBlacklist(blacklist);
|
||||
if (compdialog.exec())
|
||||
{
|
||||
if (compdialog.exec()) {
|
||||
qDebug() << "name:" << compdialog.name();
|
||||
qDebug() << "uid:" << compdialog.uid();
|
||||
m_profile->installEmpty(compdialog.uid(), compdialog.name());
|
||||
@ -611,28 +537,26 @@ void VersionPage::on_actionAdd_Empty_triggered()
|
||||
void VersionPage::on_actionInstall_LiteLoader_triggered()
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("com.mumfrey.liteloader");
|
||||
if(!vlist)
|
||||
{
|
||||
if (!vlist) {
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this);
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") +
|
||||
m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!"));
|
||||
|
||||
auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader");
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
if (!currentVersion.isEmpty()) {
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
if (vselect.exec() && vselect.selectedVersion()) {
|
||||
auto vsn = vselect.selectedVersion();
|
||||
m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
// m_profile->installVersion(vselect.selectedVersion());
|
||||
preselect(m_profile->rowCount(QModelIndex())-1);
|
||||
preselect(m_profile->rowCount(QModelIndex()) - 1);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
}
|
||||
@ -647,7 +571,7 @@ void VersionPage::on_actionMinecraftFolder_triggered()
|
||||
DesktopServices::openDirectory(m_inst->gameRoot(), true);
|
||||
}
|
||||
|
||||
void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
void VersionPage::versionCurrent(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
currentIdx = current.row();
|
||||
updateButtons(currentIdx);
|
||||
@ -655,16 +579,13 @@ void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &
|
||||
|
||||
void VersionPage::preselect(int row)
|
||||
{
|
||||
if(row < 0)
|
||||
{
|
||||
if (row < 0) {
|
||||
row = 0;
|
||||
}
|
||||
if(row >= m_profile->rowCount(QModelIndex()))
|
||||
{
|
||||
if (row >= m_profile->rowCount(QModelIndex())) {
|
||||
row = m_profile->rowCount(QModelIndex()) - 1;
|
||||
}
|
||||
if(row < 0)
|
||||
{
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
auto model_index = m_profile->index(row);
|
||||
@ -680,8 +601,7 @@ void VersionPage::onGameUpdateError(QString error)
|
||||
ComponentPtr VersionPage::current()
|
||||
{
|
||||
auto row = currentRow();
|
||||
if(row < 0)
|
||||
{
|
||||
if (row < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_profile->getComponent(row);
|
||||
@ -689,8 +609,7 @@ ComponentPtr VersionPage::current()
|
||||
|
||||
int VersionPage::currentRow()
|
||||
{
|
||||
if (ui->packageView->selectionModel()->selectedRows().isEmpty())
|
||||
{
|
||||
if (ui->packageView->selectionModel()->selectedRows().isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
return ui->packageView->selectionModel()->selectedRows().first().row();
|
||||
@ -699,18 +618,15 @@ int VersionPage::currentRow()
|
||||
void VersionPage::on_actionCustomize_triggered()
|
||||
{
|
||||
auto version = currentRow();
|
||||
if(version == -1)
|
||||
{
|
||||
if (version == -1) {
|
||||
return;
|
||||
}
|
||||
auto patch = m_profile->getComponent(version);
|
||||
if(!patch->getVersionFile())
|
||||
{
|
||||
if (!patch->getVersionFile()) {
|
||||
// TODO: wait for the update task to finish here...
|
||||
return;
|
||||
}
|
||||
if(!m_profile->customize(version))
|
||||
{
|
||||
if (!m_profile->customize(version)) {
|
||||
// TODO: some error box here
|
||||
}
|
||||
updateButtons();
|
||||
@ -720,13 +636,11 @@ void VersionPage::on_actionCustomize_triggered()
|
||||
void VersionPage::on_actionEdit_triggered()
|
||||
{
|
||||
auto version = current();
|
||||
if(!version)
|
||||
{
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
auto filename = version->getFilename();
|
||||
if(!QFileInfo::exists(filename))
|
||||
{
|
||||
if (!QFileInfo::exists(filename)) {
|
||||
qWarning() << "file" << filename << "can't be opened for editing, doesn't exist!";
|
||||
return;
|
||||
}
|
||||
@ -736,8 +650,7 @@ void VersionPage::on_actionEdit_triggered()
|
||||
void VersionPage::on_actionRevert_triggered()
|
||||
{
|
||||
auto version = currentRow();
|
||||
if(version == -1)
|
||||
{
|
||||
if (version == -1) {
|
||||
return;
|
||||
}
|
||||
auto component = m_profile->getComponent(version);
|
||||
@ -753,8 +666,7 @@ void VersionPage::on_actionRevert_triggered()
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
if(!m_profile->revertToBase(version))
|
||||
{
|
||||
if (!m_profile->revertToBase(version)) {
|
||||
// TODO: some error box here
|
||||
}
|
||||
updateButtons();
|
||||
@ -762,7 +674,7 @@ void VersionPage::on_actionRevert_triggered()
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
void VersionPage::onFilterTextChanged(const QString &newContents)
|
||||
void VersionPage::onFilterTextChanged(const QString& newContents)
|
||||
{
|
||||
m_filterModel->setFilterFixedString(newContents);
|
||||
}
|
||||
|
@ -46,38 +46,27 @@
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace Ui {
|
||||
class VersionPage;
|
||||
}
|
||||
|
||||
class VersionPage : public QMainWindow, public BasePage
|
||||
{
|
||||
class VersionPage : public QMainWindow, public BasePage {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0);
|
||||
public:
|
||||
explicit VersionPage(MinecraftInstance* inst, QWidget* parent = 0);
|
||||
virtual ~VersionPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Version");
|
||||
}
|
||||
virtual QString displayName() const override { return tr("Version"); }
|
||||
virtual QIcon icon() const override;
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "version";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Instance-Version";
|
||||
}
|
||||
virtual QString id() const override { return "version"; }
|
||||
virtual QString helpPage() const override { return "Instance-Version"; }
|
||||
virtual bool shouldDisplay() const override;
|
||||
void retranslate() override;
|
||||
|
||||
void openedImpl() override;
|
||||
void closedImpl() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void on_actionChange_version_triggered();
|
||||
void on_actionInstall_Forge_triggered();
|
||||
void on_actionInstall_Fabric_triggered();
|
||||
@ -103,36 +92,34 @@ private slots:
|
||||
|
||||
void updateVersionControls();
|
||||
|
||||
private:
|
||||
private:
|
||||
ComponentPtr current();
|
||||
int currentRow();
|
||||
void updateButtons(int row = -1);
|
||||
void preselect(int row = 0);
|
||||
int doUpdate();
|
||||
|
||||
protected:
|
||||
QMenu * createPopupMenu() override;
|
||||
protected:
|
||||
QMenu* createPopupMenu() override;
|
||||
|
||||
/// FIXME: this shouldn't be necessary!
|
||||
bool reloadPackProfile();
|
||||
|
||||
private:
|
||||
Ui::VersionPage *ui;
|
||||
QSortFilterProxyModel *m_filterModel;
|
||||
private:
|
||||
Ui::VersionPage* ui;
|
||||
QSortFilterProxyModel* m_filterModel;
|
||||
std::shared_ptr<PackProfile> m_profile;
|
||||
MinecraftInstance *m_inst;
|
||||
MinecraftInstance* m_inst;
|
||||
int currentIdx = 0;
|
||||
bool controlsEnabled = false;
|
||||
|
||||
std::shared_ptr<Setting> m_wide_bar_setting = nullptr;
|
||||
|
||||
public slots:
|
||||
void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
public slots:
|
||||
void versionCurrent(const QModelIndex& current, const QModelIndex& previous);
|
||||
|
||||
private slots:
|
||||
void updateRunningStatus(bool running);
|
||||
private slots:
|
||||
void onGameUpdateError(QString error);
|
||||
void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void showContextMenu(const QPoint &pos);
|
||||
void onFilterTextChanged(const QString & newContents);
|
||||
void packageCurrent(const QModelIndex& current, const QModelIndex& previous);
|
||||
void showContextMenu(const QPoint& pos);
|
||||
void onFilterTextChanged(const QString& newContents);
|
||||
};
|
||||
|
@ -339,6 +339,7 @@ void WorldListPage::mceditState(LoggedProcess::State state)
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
/* fallthrough */
|
||||
case LoggedProcess::Running:
|
||||
case LoggedProcess::Finished:
|
||||
{
|
||||
|
@ -104,6 +104,7 @@ void ResourcePage::openedImpl()
|
||||
|
||||
updateSelectionButton();
|
||||
triggerSearch();
|
||||
m_ui->searchEdit->setFocus();
|
||||
}
|
||||
|
||||
auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool
|
||||
|
@ -69,6 +69,7 @@ bool JavaWizardPage::validatePage()
|
||||
case JavaSettingsWidget::ValidationStatus::AllOK:
|
||||
{
|
||||
settings->set("JavaPath", m_java_widget->javaPath());
|
||||
return true;
|
||||
}
|
||||
case JavaSettingsWidget::ValidationStatus::JavaBad:
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
@ -61,7 +61,7 @@ void ThemeWizardPage::updateIcons()
|
||||
void ThemeWizardPage::updateCat()
|
||||
{
|
||||
qDebug() << "Setting Cat";
|
||||
ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(ThemeManager::getCatImage())));
|
||||
ui->catImagePreviewButton->setIcon(QIcon(QString(R"(%1)").arg(APPLICATION->getCatPack())));
|
||||
}
|
||||
|
||||
void ThemeWizardPage::retranslate()
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
117
launcher/ui/themes/CatPack.cpp
Normal file
117
launcher/ui/themes/CatPack.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
// 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 "ui/themes/CatPack.h"
|
||||
#include <QDate>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
QString BasicCatPack::path()
|
||||
{
|
||||
const auto now = QDate::currentDate();
|
||||
const auto birthday = QDate(now.year(), 11, 30);
|
||||
const auto xmas = QDate(now.year(), 12, 25);
|
||||
const auto halloween = QDate(now.year(), 10, 31);
|
||||
|
||||
QString cat = QString(":/backgrounds/%1").arg(m_id);
|
||||
if (std::abs(now.daysTo(xmas)) <= 4) {
|
||||
cat += "-xmas";
|
||||
} else if (std::abs(now.daysTo(halloween)) <= 4) {
|
||||
cat += "-spooky";
|
||||
} else if (std::abs(now.daysTo(birthday)) <= 12) {
|
||||
cat += "-bday";
|
||||
}
|
||||
return cat;
|
||||
}
|
||||
|
||||
JsonCatPack::PartialDate partialDate(QJsonObject date)
|
||||
{
|
||||
auto month = Json::ensureInteger(date, "month", 1);
|
||||
if (month > 12)
|
||||
month = 12;
|
||||
else if (month <= 0)
|
||||
month = 1;
|
||||
auto day = Json::ensureInteger(date, "day", 1);
|
||||
if (day > 31)
|
||||
day = 31;
|
||||
else if (day <= 0)
|
||||
day = 1;
|
||||
return { month, day };
|
||||
};
|
||||
|
||||
JsonCatPack::JsonCatPack(QFileInfo& manifestInfo) : BasicCatPack(manifestInfo.dir().dirName())
|
||||
{
|
||||
QString path = manifestInfo.path();
|
||||
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file");
|
||||
const auto root = doc.object();
|
||||
m_name = Json::requireString(root, "name", "Catpack name");
|
||||
m_defaultPath = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat"));
|
||||
auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants");
|
||||
for (auto v : variants) {
|
||||
auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant");
|
||||
m_variants << Variant{ FS::PathCombine(path, Json::requireString(variant, "path", "Variant path")),
|
||||
partialDate(Json::requireObject(variant, "startTime", "Variant startTime")),
|
||||
partialDate(Json::requireObject(variant, "endTime", "Variant endTime")) };
|
||||
}
|
||||
}
|
||||
|
||||
QDate ensureDay(int year, int month, int day)
|
||||
{
|
||||
QDate date(year, month, 1);
|
||||
if (day > date.daysInMonth())
|
||||
day = date.daysInMonth();
|
||||
return QDate(year, month, day);
|
||||
}
|
||||
|
||||
QString JsonCatPack::path()
|
||||
{
|
||||
const QDate now = QDate::currentDate();
|
||||
for (auto var : m_variants) {
|
||||
QDate startDate = ensureDay(now.year(), var.startTime.month, var.startTime.day);
|
||||
QDate endDate = ensureDay(now.year(), var.endTime.month, var.endTime.day);
|
||||
if (startDate > endDate) { // it's spans over multiple years
|
||||
if (endDate <= now) // end date is in the past so jump one year into the future for endDate
|
||||
endDate = endDate.addYears(1);
|
||||
else // end date is in the future so jump one year into the past for startDate
|
||||
startDate = startDate.addYears(-1);
|
||||
}
|
||||
|
||||
if (startDate >= now && now >= endDate)
|
||||
return var.path;
|
||||
}
|
||||
return m_defaultPath;
|
||||
}
|
91
launcher/ui/themes/CatPack.h
Normal file
91
launcher/ui/themes/CatPack.h
Normal file
@ -0,0 +1,91 @@
|
||||
// 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 <QDate>
|
||||
#include <QFileInfo>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
class CatPack {
|
||||
public:
|
||||
virtual ~CatPack() {}
|
||||
virtual QString id() = 0;
|
||||
virtual QString name() = 0;
|
||||
virtual QString path() = 0;
|
||||
};
|
||||
|
||||
class BasicCatPack : public CatPack {
|
||||
public:
|
||||
BasicCatPack(QString id, QString name) : m_id(id), m_name(name) {}
|
||||
BasicCatPack(QString id) : BasicCatPack(id, id) {}
|
||||
virtual QString id() { return m_id; };
|
||||
virtual QString name() { return m_name; };
|
||||
virtual QString path();
|
||||
|
||||
protected:
|
||||
QString m_id;
|
||||
QString m_name;
|
||||
};
|
||||
|
||||
class FileCatPack : public BasicCatPack {
|
||||
public:
|
||||
FileCatPack(QString id, QFileInfo& fileInfo) : BasicCatPack(id), m_path(fileInfo.absoluteFilePath()) {}
|
||||
FileCatPack(QFileInfo& fileInfo) : FileCatPack(fileInfo.baseName(), fileInfo) {}
|
||||
virtual QString path() { return m_path; }
|
||||
|
||||
private:
|
||||
QString m_path;
|
||||
};
|
||||
|
||||
class JsonCatPack : public BasicCatPack {
|
||||
public:
|
||||
struct PartialDate {
|
||||
int month;
|
||||
int day;
|
||||
};
|
||||
struct Variant {
|
||||
QString path;
|
||||
PartialDate startTime;
|
||||
PartialDate endTime;
|
||||
};
|
||||
JsonCatPack(QFileInfo& manifestInfo);
|
||||
virtual QString path();
|
||||
|
||||
private:
|
||||
QString m_defaultPath;
|
||||
QList<Variant> m_variants;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
@ -21,7 +21,10 @@
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QIcon>
|
||||
#include <QImageReader>
|
||||
#include "Exception.h"
|
||||
#include "ui/themes/BrightTheme.h"
|
||||
#include "ui/themes/CatPack.h"
|
||||
#include "ui/themes/CustomTheme.h"
|
||||
#include "ui/themes/DarkTheme.h"
|
||||
#include "ui/themes/SystemTheme.h"
|
||||
@ -32,6 +35,7 @@ ThemeManager::ThemeManager(MainWindow* mainWindow)
|
||||
{
|
||||
m_mainWindow = mainWindow;
|
||||
initializeThemes();
|
||||
initializeCatPacks();
|
||||
}
|
||||
|
||||
/// @brief Adds the Theme to the list of themes
|
||||
@ -40,7 +44,10 @@ ThemeManager::ThemeManager(MainWindow* mainWindow)
|
||||
QString ThemeManager::addTheme(std::unique_ptr<ITheme> theme)
|
||||
{
|
||||
QString id = theme->id();
|
||||
m_themes.emplace(id, std::move(theme));
|
||||
if (m_themes.find(id) == m_themes.end())
|
||||
m_themes.emplace(id, std::move(theme));
|
||||
else
|
||||
themeWarningLog() << "Theme(" << id << ") not added to prevent id duplication";
|
||||
return id;
|
||||
}
|
||||
|
||||
@ -77,7 +84,7 @@ void ThemeManager::initializeThemes()
|
||||
QString themeFolder = QDir("./themes/").absoluteFilePath("");
|
||||
themeDebugLog() << "Theme Folder Path: " << themeFolder;
|
||||
|
||||
QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
|
||||
QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
while (directoryIterator.hasNext()) {
|
||||
QDir dir(directoryIterator.next());
|
||||
QFileInfo themeJson(dir.absoluteFilePath("theme.json"));
|
||||
@ -111,6 +118,16 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes()
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<CatPack*> ThemeManager::getValidCatPacks()
|
||||
{
|
||||
QList<CatPack*> ret;
|
||||
ret.reserve(m_catPacks.size());
|
||||
for (auto&& [id, theme] : m_catPacks) {
|
||||
ret.append(theme.get());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ThemeManager::setIconTheme(const QString& name)
|
||||
{
|
||||
QIcon::setThemeName(name);
|
||||
@ -137,19 +154,74 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial)
|
||||
}
|
||||
}
|
||||
|
||||
QString ThemeManager::getCatImage(QString catName)
|
||||
QString ThemeManager::getCatPack(QString catName)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0));
|
||||
QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0));
|
||||
QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0));
|
||||
QString cat = !catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString();
|
||||
if (std::abs(now.daysTo(xmas)) <= 4) {
|
||||
cat += "-xmas";
|
||||
} else if (std::abs(now.daysTo(halloween)) <= 4) {
|
||||
cat += "-spooky";
|
||||
} else if (std::abs(now.daysTo(birthday)) <= 12) {
|
||||
cat += "-bday";
|
||||
auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
|
||||
if (catIter != m_catPacks.end()) {
|
||||
auto& catPack = catIter->second;
|
||||
themeDebugLog() << "applying catpack" << catPack->id();
|
||||
return catPack->path();
|
||||
} else {
|
||||
themeWarningLog() << "Tried to get invalid catPack:" << catName;
|
||||
}
|
||||
|
||||
return m_catPacks.begin()->second->path();
|
||||
}
|
||||
|
||||
QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack)
|
||||
{
|
||||
QString id = catPack->id();
|
||||
if (m_catPacks.find(id) == m_catPacks.end())
|
||||
m_catPacks.emplace(id, std::move(catPack));
|
||||
else
|
||||
themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication";
|
||||
return id;
|
||||
}
|
||||
|
||||
void ThemeManager::initializeCatPacks()
|
||||
{
|
||||
QList<std::pair<QString, QString>> defaultCats{ { "kitteh", QObject::tr("Background Cat (from MultiMC)") },
|
||||
{ "rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)") },
|
||||
{ "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") },
|
||||
{ "teawie", QObject::tr("Teawie (drawn by SympathyTea)") } };
|
||||
for (auto [id, name] : defaultCats) {
|
||||
addCatPack(std::unique_ptr<CatPack>(new BasicCatPack(id, name)));
|
||||
}
|
||||
QDir catpacksDir("catpacks");
|
||||
QString catpacksFolder = catpacksDir.absoluteFilePath("");
|
||||
themeDebugLog() << "CatPacks Folder Path:" << catpacksFolder;
|
||||
|
||||
QStringList supportedImageFormats;
|
||||
for (auto format : QImageReader::supportedImageFormats()) {
|
||||
supportedImageFormats.append("*." + format);
|
||||
}
|
||||
auto loadFiles = [this, supportedImageFormats](QDir dir) {
|
||||
// Load image files directly
|
||||
QDirIterator ImageFileIterator(dir.absoluteFilePath(""), supportedImageFormats, QDir::Files);
|
||||
while (ImageFileIterator.hasNext()) {
|
||||
QFile customCatFile(ImageFileIterator.next());
|
||||
QFileInfo customCatFileInfo(customCatFile);
|
||||
themeDebugLog() << "Loading CatPack from:" << customCatFileInfo.absoluteFilePath();
|
||||
addCatPack(std::unique_ptr<CatPack>(new FileCatPack(customCatFileInfo)));
|
||||
}
|
||||
};
|
||||
|
||||
loadFiles(catpacksDir);
|
||||
|
||||
QDirIterator directoryIterator(catpacksFolder, QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
while (directoryIterator.hasNext()) {
|
||||
QDir dir(directoryIterator.next());
|
||||
QFileInfo manifest(dir.absoluteFilePath("catpack.json"));
|
||||
if (manifest.isFile()) {
|
||||
try {
|
||||
// Load background manifest
|
||||
themeDebugLog() << "Loading background manifest from:" << manifest.absoluteFilePath();
|
||||
addCatPack(std::unique_ptr<CatPack>(new JsonCatPack(manifest)));
|
||||
} catch (const Exception& e) {
|
||||
themeWarningLog() << "Couldn't load catpack json:" << e.cause();
|
||||
}
|
||||
} else {
|
||||
loadFiles(dir);
|
||||
}
|
||||
}
|
||||
return cat;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
@ -20,6 +20,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/themes/CatPack.h"
|
||||
#include "ui/themes/ITheme.h"
|
||||
|
||||
inline auto themeDebugLog()
|
||||
@ -40,18 +41,20 @@ class ThemeManager {
|
||||
void applyCurrentlySelectedTheme(bool initial = false);
|
||||
void setApplicationTheme(const QString& name, bool initial = false);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cat based on selected cat and with events (Birthday, XMas, etc.)
|
||||
/// </summary>
|
||||
/// <param name="catName">Optional, if you need a specific cat.</param>
|
||||
/// <returns></returns>
|
||||
static QString getCatImage(QString catName = "");
|
||||
/// @brief Returns the background based on selected and with events (Birthday, XMas, etc.)
|
||||
/// @param catName Optional, if you need a specific background.
|
||||
/// @return
|
||||
QString getCatPack(QString catName = "");
|
||||
QList<CatPack*> getValidCatPacks();
|
||||
|
||||
private:
|
||||
std::map<QString, std::unique_ptr<ITheme>> m_themes;
|
||||
std::map<QString, std::unique_ptr<CatPack>> m_catPacks;
|
||||
MainWindow* m_mainWindow;
|
||||
|
||||
void initializeThemes();
|
||||
void initializeCatPacks();
|
||||
QString addTheme(std::unique_ptr<ITheme> theme);
|
||||
ITheme* getTheme(QString themeId);
|
||||
QString addCatPack(std::unique_ptr<CatPack> catPack);
|
||||
};
|
||||
|
@ -1,54 +1,70 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@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.
|
||||
*/
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* 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 <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QToolTip>
|
||||
|
||||
#include "InfoFrame.h"
|
||||
#include "ui_InfoFrame.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
InfoFrame::InfoFrame(QWidget *parent) :
|
||||
QFrame(parent),
|
||||
ui(new Ui::InfoFrame)
|
||||
void setupLinkToolTip(QLabel* label)
|
||||
{
|
||||
QObject::connect(label, &QLabel::linkHovered, [label](const QString& link) {
|
||||
if (auto url = QUrl(link); !url.isValid() || (url.scheme() != "http" && url.scheme() != "https"))
|
||||
return;
|
||||
label->setToolTip(link);
|
||||
});
|
||||
}
|
||||
|
||||
InfoFrame::InfoFrame(QWidget* parent) : QFrame(parent), ui(new Ui::InfoFrame)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->descriptionLabel->setHidden(true);
|
||||
ui->nameLabel->setHidden(true);
|
||||
ui->licenseLabel->setHidden(true);
|
||||
ui->issueTrackerLabel->setHidden(true);
|
||||
|
||||
setupLinkToolTip(ui->iconLabel);
|
||||
setupLinkToolTip(ui->descriptionLabel);
|
||||
setupLinkToolTip(ui->nameLabel);
|
||||
setupLinkToolTip(ui->licenseLabel);
|
||||
setupLinkToolTip(ui->issueTrackerLabel);
|
||||
updateHiddenState();
|
||||
}
|
||||
|
||||
@ -59,45 +75,43 @@ InfoFrame::~InfoFrame()
|
||||
|
||||
void InfoFrame::updateWithMod(Mod const& m)
|
||||
{
|
||||
if (m.type() == ResourceType::FOLDER)
|
||||
{
|
||||
if (m.type() == ResourceType::FOLDER) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = "";
|
||||
QString name = "";
|
||||
QString link = m.metaurl();
|
||||
if (m.name().isEmpty())
|
||||
name = m.internal_id();
|
||||
else
|
||||
name = m.name();
|
||||
|
||||
if (m.homeurl().isEmpty())
|
||||
if (link.isEmpty())
|
||||
text = name;
|
||||
else
|
||||
text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>";
|
||||
else {
|
||||
text = "<a href=\"" + link + "\">" + name + "</a>";
|
||||
}
|
||||
if (!m.authors().isEmpty())
|
||||
text += " by " + m.authors().join(", ");
|
||||
|
||||
setName(text);
|
||||
|
||||
if (m.description().isEmpty())
|
||||
{
|
||||
if (m.description().isEmpty()) {
|
||||
setDescription(QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
setDescription(m.description());
|
||||
}
|
||||
|
||||
setImage(m.icon({64,64}));
|
||||
setImage(m.icon({ 64, 64 }));
|
||||
|
||||
auto licenses = m.licenses();
|
||||
QString licenseText = "";
|
||||
if (!licenses.empty()) {
|
||||
for (auto l : licenses) {
|
||||
if (!licenseText.isEmpty()) {
|
||||
licenseText += "\n"; // add newline between licenses
|
||||
licenseText += "\n"; // add newline between licenses
|
||||
}
|
||||
if (!l.name.isEmpty()) {
|
||||
if (l.url.isEmpty()) {
|
||||
@ -109,9 +123,9 @@ void InfoFrame::updateWithMod(Mod const& m)
|
||||
licenseText += "<a href=\"" + l.url + "\">" + l.url + "</a>";
|
||||
}
|
||||
if (!l.description.isEmpty() && l.description != l.name) {
|
||||
licenseText += " " + l.description;
|
||||
licenseText += " " + l.description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!licenseText.isEmpty()) {
|
||||
setLicense(tr("License: %1").arg(licenseText));
|
||||
@ -123,7 +137,7 @@ void InfoFrame::updateWithMod(Mod const& m)
|
||||
if (!m.issueTracker().isEmpty()) {
|
||||
issueTracker += tr("Report issues to: ");
|
||||
issueTracker += "<a href=\"" + m.issueTracker() + "\">" + m.issueTracker() + "</a>";
|
||||
}
|
||||
}
|
||||
setIssueTracker(issueTracker);
|
||||
}
|
||||
|
||||
@ -133,7 +147,8 @@ void InfoFrame::updateWithResource(const Resource& resource)
|
||||
setImage();
|
||||
}
|
||||
|
||||
QString InfoFrame::renderColorCodes(QString input) {
|
||||
QString InfoFrame::renderColorCodes(QString input)
|
||||
{
|
||||
// We have to manually set the colors for use.
|
||||
//
|
||||
// A color is set using §x, with x = a hex number from 0 to f.
|
||||
@ -144,16 +159,12 @@ QString InfoFrame::renderColorCodes(QString input) {
|
||||
// TODO: Wrap links inside <a> tags
|
||||
|
||||
// https://minecraft.fandom.com/wiki/Formatting_codes#Color_codes
|
||||
const QMap<QChar, QString> color_codes_map = {
|
||||
{'0', "#000000"}, {'1', "#0000AA"}, {'2', "#00AA00"}, {'3', "#00AAAA"}, {'4', "#AA0000"},
|
||||
{'5', "#AA00AA"}, {'6', "#FFAA00"}, {'7', "#AAAAAA"}, {'8', "#555555"}, {'9', "#5555FF"},
|
||||
{'a', "#55FF55"}, {'b', "#55FFFF"}, {'c', "#FF5555"}, {'d', "#FF55FF"}, {'e', "#FFFF55"},
|
||||
{'f', "#FFFFFF"}
|
||||
};
|
||||
const QMap<QChar, QString> color_codes_map = { { '0', "#000000" }, { '1', "#0000AA" }, { '2', "#00AA00" }, { '3', "#00AAAA" },
|
||||
{ '4', "#AA0000" }, { '5', "#AA00AA" }, { '6', "#FFAA00" }, { '7', "#AAAAAA" },
|
||||
{ '8', "#555555" }, { '9', "#5555FF" }, { 'a', "#55FF55" }, { 'b', "#55FFFF" },
|
||||
{ 'c', "#FF5555" }, { 'd', "#FF55FF" }, { 'e', "#FFFF55" }, { 'f', "#FFFFFF" } };
|
||||
// https://minecraft.fandom.com/wiki/Formatting_codes#Formatting_codes
|
||||
const QMap<QChar, QString> formatting_codes_map = {
|
||||
{'l', "b"}, {'m', "s"}, {'n', "u"}, {'o', "i"}
|
||||
};
|
||||
const QMap<QChar, QString> formatting_codes_map = { { 'l', "b" }, { 'm', "s" }, { 'n', "u" }, { 'o', "i" } };
|
||||
|
||||
QString html("<html>");
|
||||
QList<QString> tags{};
|
||||
@ -198,14 +209,14 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack)
|
||||
{
|
||||
setName(renderColorCodes(resource_pack.name()));
|
||||
setDescription(renderColorCodes(resource_pack.description()));
|
||||
setImage(resource_pack.image({64, 64}));
|
||||
setImage(resource_pack.image({ 64, 64 }));
|
||||
}
|
||||
|
||||
void InfoFrame::updateWithTexturePack(TexturePack& texture_pack)
|
||||
{
|
||||
setName(renderColorCodes(texture_pack.name()));
|
||||
setDescription(renderColorCodes(texture_pack.description()));
|
||||
setImage(texture_pack.image({64, 64}));
|
||||
setImage(texture_pack.image({ 64, 64 }));
|
||||
}
|
||||
|
||||
void InfoFrame::clear()
|
||||
@ -229,12 +240,9 @@ void InfoFrame::updateHiddenState()
|
||||
|
||||
void InfoFrame::setName(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->nameLabel->setHidden(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->nameLabel->setText(text);
|
||||
ui->nameLabel->setHidden(false);
|
||||
}
|
||||
@ -243,14 +251,11 @@ void InfoFrame::setName(QString text)
|
||||
|
||||
void InfoFrame::setDescription(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->descriptionLabel->setHidden(true);
|
||||
updateHiddenState();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->descriptionLabel->setHidden(false);
|
||||
updateHiddenState();
|
||||
}
|
||||
@ -260,9 +265,8 @@ void InfoFrame::setDescription(QString text)
|
||||
QChar rem('\n');
|
||||
QString finaltext;
|
||||
finaltext.reserve(intermediatetext.size());
|
||||
foreach(const QChar& c, intermediatetext)
|
||||
{
|
||||
if(c == rem && prev){
|
||||
foreach (const QChar& c, intermediatetext) {
|
||||
if (c == rem && prev) {
|
||||
continue;
|
||||
}
|
||||
prev = c == rem;
|
||||
@ -270,17 +274,14 @@ void InfoFrame::setDescription(QString text)
|
||||
}
|
||||
QString labeltext;
|
||||
labeltext.reserve(300);
|
||||
if(finaltext.length() > 290)
|
||||
{
|
||||
if (finaltext.length() > 290) {
|
||||
ui->descriptionLabel->setOpenExternalLinks(false);
|
||||
ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText);
|
||||
m_description = text;
|
||||
// This allows injecting HTML here.
|
||||
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
|
||||
QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
labeltext.append(finaltext);
|
||||
}
|
||||
@ -289,14 +290,11 @@ void InfoFrame::setDescription(QString text)
|
||||
|
||||
void InfoFrame::setLicense(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->licenseLabel->setHidden(true);
|
||||
updateHiddenState();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->licenseLabel->setHidden(false);
|
||||
updateHiddenState();
|
||||
}
|
||||
@ -306,9 +304,8 @@ void InfoFrame::setLicense(QString text)
|
||||
QChar rem('\n');
|
||||
QString finaltext;
|
||||
finaltext.reserve(intermediatetext.size());
|
||||
foreach(const QChar& c, intermediatetext)
|
||||
{
|
||||
if(c == rem && prev){
|
||||
foreach (const QChar& c, intermediatetext) {
|
||||
if (c == rem && prev) {
|
||||
continue;
|
||||
}
|
||||
prev = c == rem;
|
||||
@ -316,17 +313,14 @@ void InfoFrame::setLicense(QString text)
|
||||
}
|
||||
QString labeltext;
|
||||
labeltext.reserve(300);
|
||||
if(finaltext.length() > 290)
|
||||
{
|
||||
if (finaltext.length() > 290) {
|
||||
ui->licenseLabel->setOpenExternalLinks(false);
|
||||
ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
|
||||
m_description = 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
labeltext.append(finaltext);
|
||||
}
|
||||
@ -335,12 +329,9 @@ void InfoFrame::setLicense(QString text)
|
||||
|
||||
void InfoFrame::setIssueTracker(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->issueTrackerLabel->setHidden(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->issueTrackerLabel->setText(text);
|
||||
ui->issueTrackerLabel->setHidden(false);
|
||||
}
|
||||
@ -359,28 +350,22 @@ void InfoFrame::setImage(QPixmap img)
|
||||
|
||||
void InfoFrame::descriptionEllipsisHandler(QString link)
|
||||
{
|
||||
if(!m_current_box)
|
||||
{
|
||||
if (!m_current_box) {
|
||||
m_current_box = CustomMessageBox::selectable(this, "", m_description);
|
||||
connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed);
|
||||
m_current_box->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
m_current_box->setText(m_description);
|
||||
}
|
||||
}
|
||||
|
||||
void InfoFrame::licenseEllipsisHandler(QString link)
|
||||
{
|
||||
if(!m_current_box)
|
||||
{
|
||||
if (!m_current_box) {
|
||||
m_current_box = CustomMessageBox::selectable(this, "", m_license);
|
||||
connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed);
|
||||
m_current_box->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
m_current_box->setText(m_license);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,36 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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
|
||||
@ -21,8 +41,7 @@
|
||||
#include "minecraft/mod/ResourcePack.h"
|
||||
#include "minecraft/mod/TexturePack.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace Ui {
|
||||
class InfoFrame;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
#include "LanguageSelectionWidget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QTreeView>
|
||||
#include <QCheckBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "translations/TranslationsModel.h"
|
||||
#include "settings/Setting.h"
|
||||
#include "translations/TranslationsModel.h"
|
||||
|
||||
LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
LanguageSelectionWidget::LanguageSelectionWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
verticalLayout = new QVBoxLayout(this);
|
||||
verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
|
||||
@ -31,6 +31,13 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
|
||||
helpUsLabel->setWordWrap(true);
|
||||
verticalLayout->addWidget(helpUsLabel);
|
||||
|
||||
formatCheckbox = new QCheckBox(this);
|
||||
formatCheckbox->setObjectName(QStringLiteral("formatCheckbox"));
|
||||
formatCheckbox->setCheckState(APPLICATION->settings()->get("UseSystemLocale").toBool() ? Qt::Checked : Qt::Unchecked);
|
||||
connect(formatCheckbox, &QCheckBox::stateChanged,
|
||||
[this]() { APPLICATION->translations()->setUseSystemLocale(formatCheckbox->isChecked()); });
|
||||
verticalLayout->addWidget(formatCheckbox);
|
||||
|
||||
auto translations = APPLICATION->translations();
|
||||
auto index = translations->selectedIndex();
|
||||
languageView->setModel(translations.get());
|
||||
@ -38,7 +45,7 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
|
||||
languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged);
|
||||
verticalLayout->setContentsMargins(0,0,0,0);
|
||||
verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto language_setting = APPLICATION->settings()->getSetting("Language");
|
||||
connect(language_setting.get(), &Setting::SettingChanged, this, &LanguageSelectionWidget::languageSettingChanged);
|
||||
@ -53,15 +60,14 @@ QString LanguageSelectionWidget::getSelectedLanguageKey() const
|
||||
void LanguageSelectionWidget::retranslate()
|
||||
{
|
||||
QString text = tr("Don't see your language or the quality is poor?<br/><a href=\"%1\">Help us with translations!</a>")
|
||||
.arg(BuildConfig.TRANSLATIONS_URL);
|
||||
.arg(BuildConfig.TRANSLATIONS_URL);
|
||||
helpUsLabel->setText(text);
|
||||
|
||||
formatCheckbox->setText(tr("Use system locales"));
|
||||
}
|
||||
|
||||
void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
if (current == previous)
|
||||
{
|
||||
if (current == previous) {
|
||||
return;
|
||||
}
|
||||
auto translations = APPLICATION->translations();
|
||||
@ -70,7 +76,7 @@ void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, con
|
||||
translations->updateLanguage(key);
|
||||
}
|
||||
|
||||
void LanguageSelectionWidget::languageSettingChanged(const Setting &, const QVariant)
|
||||
void LanguageSelectionWidget::languageSettingChanged(const Setting&, const QVariant)
|
||||
{
|
||||
auto translations = APPLICATION->translations();
|
||||
auto index = translations->selectedIndex();
|
||||
|
@ -21,23 +21,24 @@ class QVBoxLayout;
|
||||
class QTreeView;
|
||||
class QLabel;
|
||||
class Setting;
|
||||
class QCheckBox;
|
||||
|
||||
class LanguageSelectionWidget: public QWidget
|
||||
{
|
||||
class LanguageSelectionWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LanguageSelectionWidget(QWidget *parent = 0);
|
||||
virtual ~LanguageSelectionWidget() { };
|
||||
public:
|
||||
explicit LanguageSelectionWidget(QWidget* parent = 0);
|
||||
virtual ~LanguageSelectionWidget(){};
|
||||
|
||||
QString getSelectedLanguageKey() const;
|
||||
void retranslate();
|
||||
|
||||
protected slots:
|
||||
void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void languageSettingChanged(const Setting &, const QVariant);
|
||||
protected slots:
|
||||
void languageRowChanged(const QModelIndex& current, const QModelIndex& previous);
|
||||
void languageSettingChanged(const Setting&, const QVariant);
|
||||
|
||||
private:
|
||||
QVBoxLayout *verticalLayout = nullptr;
|
||||
QTreeView *languageView = nullptr;
|
||||
QLabel *helpUsLabel = nullptr;
|
||||
private:
|
||||
QVBoxLayout* verticalLayout = nullptr;
|
||||
QTreeView* languageView = nullptr;
|
||||
QLabel* helpUsLabel = nullptr;
|
||||
QCheckBox* formatCheckbox = nullptr;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
@ -95,9 +95,14 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index) {
|
||||
emit currentWidgetThemeChanged(index);
|
||||
}
|
||||
|
||||
void ThemeCustomizationWidget::applyCatTheme(int index) {
|
||||
void ThemeCustomizationWidget::applyCatTheme(int index)
|
||||
{
|
||||
auto settings = APPLICATION->settings();
|
||||
settings->set("BackgroundCat", m_catOptions[index].first);
|
||||
auto originalCat = settings->get("BackgroundCat").toString();
|
||||
auto newCat = ui->backgroundCatComboBox->currentData().toString();
|
||||
if (originalCat != newCat) {
|
||||
settings->set("BackgroundCat", newCat);
|
||||
}
|
||||
|
||||
emit currentCatChanged(index);
|
||||
}
|
||||
@ -135,10 +140,10 @@ void ThemeCustomizationWidget::loadSettings()
|
||||
}
|
||||
|
||||
auto cat = settings->get("BackgroundCat").toString();
|
||||
for (auto& catFromList : m_catOptions) {
|
||||
QIcon catIcon = QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first)));
|
||||
ui->backgroundCatComboBox->addItem(catIcon, catFromList.second);
|
||||
if (cat == catFromList.first) {
|
||||
for (auto& catFromList : APPLICATION->getValidCatPacks()) {
|
||||
QIcon catIcon = QIcon(QString("%1").arg(catFromList->path()));
|
||||
ui->backgroundCatComboBox->addItem(catIcon, catFromList->name(), catFromList->id());
|
||||
if (cat == catFromList->id()) {
|
||||
ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
*
|
||||
* 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
|
||||
@ -53,25 +53,17 @@ class ThemeCustomizationWidget : public QWidget {
|
||||
private:
|
||||
Ui::ThemeCustomizationWidget* ui;
|
||||
|
||||
//TODO finish implementing
|
||||
QList<std::pair<QString, QString>> m_iconThemeOptions{
|
||||
{ "pe_colored", QObject::tr("Simple (Colored Icons)") },
|
||||
{ "pe_light", QObject::tr("Simple (Light Icons)") },
|
||||
{ "pe_dark", QObject::tr("Simple (Dark Icons)") },
|
||||
{ "pe_blue", QObject::tr("Simple (Blue Icons)") },
|
||||
{ "breeze_light", QObject::tr("Breeze Light") },
|
||||
{ "breeze_dark", QObject::tr("Breeze Dark") },
|
||||
{ "OSX", QObject::tr("OSX") },
|
||||
{ "iOS", QObject::tr("iOS") },
|
||||
{ "flat", QObject::tr("Flat") },
|
||||
{ "flat_white", QObject::tr("Flat (White)") },
|
||||
{ "multimc", QObject::tr("Legacy") },
|
||||
{ "custom", QObject::tr("Custom") }
|
||||
};
|
||||
QList<std::pair<QString, QString>> m_catOptions{
|
||||
{ "kitteh", QObject::tr("Background Cat (from MultiMC)") },
|
||||
{ "rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)") },
|
||||
{ "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") },
|
||||
{ "teawie", QObject::tr("Teawie (drawn by SympathyTea)") }
|
||||
};
|
||||
// TODO finish implementing
|
||||
QList<std::pair<QString, QString>> m_iconThemeOptions{ { "pe_colored", QObject::tr("Simple (Colored Icons)") },
|
||||
{ "pe_light", QObject::tr("Simple (Light Icons)") },
|
||||
{ "pe_dark", QObject::tr("Simple (Dark Icons)") },
|
||||
{ "pe_blue", QObject::tr("Simple (Blue Icons)") },
|
||||
{ "breeze_light", QObject::tr("Breeze Light") },
|
||||
{ "breeze_dark", QObject::tr("Breeze Dark") },
|
||||
{ "OSX", QObject::tr("OSX") },
|
||||
{ "iOS", QObject::tr("iOS") },
|
||||
{ "flat", QObject::tr("Flat") },
|
||||
{ "flat_white", QObject::tr("Flat (White)") },
|
||||
{ "multimc", QObject::tr("Legacy") },
|
||||
{ "custom", QObject::tr("Custom") } };
|
||||
};
|
||||
|
@ -116,12 +116,21 @@ void WideBar::insertActionAfter(QAction* after, QAction* action)
|
||||
if (iter == m_entries.end())
|
||||
return;
|
||||
|
||||
iter++;
|
||||
// the action to insert after is present
|
||||
// however, the element after it isn't valid
|
||||
if (iter == m_entries.end()) {
|
||||
// append the action instead of inserting it
|
||||
addAction(action);
|
||||
return;
|
||||
}
|
||||
|
||||
BarEntry entry;
|
||||
entry.bar_action = insertWidget((iter + 1)->bar_action, new ActionButton(action, this, m_use_default_action));
|
||||
entry.bar_action = insertWidget(iter->bar_action, new ActionButton(action, this, m_use_default_action));
|
||||
entry.menu_action = action;
|
||||
entry.type = BarEntry::Type::Action;
|
||||
|
||||
m_entries.insert(iter + 1, entry);
|
||||
m_entries.insert(iter, entry);
|
||||
|
||||
m_menu_state = MenuState::Dirty;
|
||||
}
|
||||
|
Reference in New Issue
Block a user