Merge pull request #1246 from Trial97/export

This commit is contained in:
Sefa Eyeoglu 2023-07-16 12:24:42 +02:00 committed by GitHub
commit fcdd9ea986
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1034 additions and 373 deletions

View File

@ -487,6 +487,9 @@ set(API_SOURCES
modplatform/helpers/HashUtils.cpp modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h modplatform/helpers/OverrideUtils.h
modplatform/helpers/OverrideUtils.cpp modplatform/helpers/OverrideUtils.cpp
modplatform/helpers/ExportToModList.h
modplatform/helpers/ExportToModList.cpp
) )
set(FTB_SOURCES set(FTB_SOURCES
@ -911,6 +914,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h ui/dialogs/ExportInstanceDialog.h
ui/dialogs/ExportPackDialog.cpp ui/dialogs/ExportPackDialog.cpp
ui/dialogs/ExportPackDialog.h ui/dialogs/ExportPackDialog.h
ui/dialogs/ExportToModListDialog.cpp
ui/dialogs/ExportToModListDialog.h
ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp ui/dialogs/ImportResourceDialog.cpp
@ -1058,6 +1063,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/SkinUploadDialog.ui ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/ExportPackDialog.ui ui/dialogs/ExportPackDialog.ui
ui/dialogs/ExportToModListDialog.ui
ui/dialogs/IconPickerDialog.ui ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourceDialog.ui ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui ui/dialogs/MSALoginDialog.ui

View File

@ -0,0 +1,200 @@
// 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 "ExportToModList.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
namespace ExportToModList {
QString toHTML(QList<Mod*> mods, OptionalData extraData)
{
QStringList lines;
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name().toHtmlEscaped();
if (extraData & Url) {
auto url = mod->metaurl().toHtmlEscaped();
if (!url.isEmpty())
modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
}
auto line = modName;
if (extraData & Version) {
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();
if (!ver.isEmpty())
line += QString(" [%1]").arg(ver.toHtmlEscaped());
}
if (extraData & Authors && !mod->authors().isEmpty())
line += " by " + mod->authors().join(", ").toHtmlEscaped();
lines.append(QString("<li>%1</li>").arg(line));
}
return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t"));
}
QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
{
QStringList lines;
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name();
if (extraData & Url) {
auto url = mod->metaurl();
if (!url.isEmpty())
modName = QString("[%1](%2)").arg(modName, url);
}
auto line = modName;
if (extraData & Version) {
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();
if (!ver.isEmpty())
line += QString(" [%1]").arg(ver);
}
if (extraData & Authors && !mod->authors().isEmpty())
line += " by " + mod->authors().join(", ");
lines << "- " + line;
}
return lines.join("\n");
}
QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
{
QStringList lines;
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name();
auto line = modName;
if (extraData & Url) {
auto url = mod->metaurl();
if (!url.isEmpty())
line += QString(" (%1)").arg(url);
}
if (extraData & Version) {
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();
if (!ver.isEmpty())
line += QString(" [%1]").arg(ver);
}
if (extraData & Authors && !mod->authors().isEmpty())
line += " by " + mod->authors().join(", ");
lines << line;
}
return lines.join("\n");
}
QString toJSON(QList<Mod*> mods, OptionalData extraData)
{
QJsonArray lines;
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name();
QJsonObject line;
line["name"] = modName;
if (extraData & Url) {
auto url = mod->metaurl();
if (!url.isEmpty())
line["url"] = url;
}
if (extraData & Version) {
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();
if (!ver.isEmpty())
line["version"] = ver;
}
if (extraData & Authors && !mod->authors().isEmpty())
line["authors"] = QJsonArray::fromStringList(mod->authors());
lines << line;
}
QJsonDocument doc;
doc.setArray(lines);
return doc.toJson();
}
QString toCSV(QList<Mod*> mods, OptionalData extraData)
{
QStringList lines;
for (auto mod : mods) {
QStringList data;
auto meta = mod->metadata();
auto modName = mod->name();
data << modName;
if (extraData & Url)
data << mod->metaurl();
if (extraData & Version) {
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();
data << ver;
}
if (extraData & Authors) {
QString authors;
if (mod->authors().length() == 1)
authors = mod->authors().back();
else if (mod->authors().length() > 1)
authors = QString("\"%1\"").arg(mod->authors().join(","));
data << authors;
}
lines << data.join(",");
}
return lines.join("\n");
}
QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData)
{
switch (format) {
case HTML:
return toHTML(mods, extraData);
case MARKDOWN:
return toMarkdown(mods, extraData);
case PLAINTXT:
return toPlainTXT(mods, extraData);
case JSON:
return toJSON(mods, extraData);
case CSV:
return toCSV(mods, extraData);
default: {
return QString("unknown format:%1").arg(format);
}
}
}
QString exportToModList(QList<Mod*> mods, QString lineTemplate)
{
QStringList lines;
for (auto mod : mods) {
auto meta = mod->metadata();
auto modName = mod->name();
auto url = mod->metaurl();
auto ver = mod->version();
if (ver.isEmpty() && meta != nullptr)
ver = meta->version().toString();
auto authors = mod->authors().join(", ");
lines << QString(lineTemplate)
.replace("{name}", modName)
.replace("{url}", url)
.replace("{version}", ver)
.replace("{authors}", authors);
}
return lines.join("\n");
}
} // namespace ExportToModList

View File

@ -0,0 +1,33 @@
// 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 <QList>
#include <QString>
#include "minecraft/mod/Mod.h"
namespace ExportToModList {
enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM };
enum OptionalData {
Authors = 1 << 0,
Url = 1 << 1,
Version = 1 << 2,
};
QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData);
QString exportToModList(QList<Mod*> mods, QString lineTemplate);
} // namespace ExportToModList

View File

@ -43,100 +43,98 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include <QVariant>
#include <QUrl>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QUrl>
#include <QVariant>
#include <QKeyEvent>
#include <QAction> #include <QAction>
#include <QActionGroup> #include <QActionGroup>
#include <QApplication> #include <QApplication>
#include <QButtonGroup> #include <QButtonGroup>
#include <QFileDialog>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QHeaderView> #include <QHeaderView>
#include <QInputDialog>
#include <QKeyEvent>
#include <QLabel>
#include <QMainWindow> #include <QMainWindow>
#include <QStatusBar>
#include <QToolBar>
#include <QWidget>
#include <QMenu> #include <QMenu>
#include <QMenuBar> #include <QMenuBar>
#include <QMessageBox> #include <QMessageBox>
#include <QFileDialog>
#include <QInputDialog>
#include <QLabel>
#include <QToolButton>
#include <QWidgetAction>
#include <QProgressDialog> #include <QProgressDialog>
#include <QShortcut> #include <QShortcut>
#include <QStatusBar>
#include <QToolBar>
#include <QToolButton>
#include <QWidget>
#include <QWidgetAction>
#include <BaseInstance.h> #include <BaseInstance.h>
#include <InstanceList.h>
#include <minecraft/MinecraftInstance.h>
#include <MMCZip.h>
#include <icons/IconList.h>
#include <java/JavaUtils.h>
#include <java/JavaInstallList.h>
#include <launch/LaunchTask.h>
#include <minecraft/auth/AccountList.h>
#include <SkinUtils.h>
#include <BuildConfig.h> #include <BuildConfig.h>
#include <net/NetJob.h> #include <DesktopServices.h>
#include <InstanceList.h>
#include <MMCZip.h>
#include <SkinUtils.h>
#include <icons/IconList.h>
#include <java/JavaInstallList.h>
#include <java/JavaUtils.h>
#include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h>
#include <minecraft/auth/AccountList.h>
#include <net/Download.h> #include <net/Download.h>
#include <net/NetJob.h>
#include <news/NewsChecker.h> #include <news/NewsChecker.h>
#include <tools/BaseProfiler.h> #include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h> #include <updater/ExternalUpdater.h>
#include <DesktopServices.h>
#include "InstanceWindow.h"
#include "InstancePageProvider.h" #include "InstancePageProvider.h"
#include "InstanceWindow.h"
#include "JavaCommon.h" #include "JavaCommon.h"
#include "LaunchController.h" #include "LaunchController.h"
#include "ui/instanceview/InstanceProxyModel.h"
#include "ui/instanceview/InstanceView.h"
#include "ui/instanceview/InstanceDelegate.h"
#include "ui/widgets/LabeledToolButton.h"
#include "ui/dialogs/NewInstanceDialog.h"
#include "ui/dialogs/NewsDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/AboutDialog.h" #include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h" #include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h" #include "ui/dialogs/ExportInstanceDialog.h"
#include "ui/dialogs/ExportPackDialog.h" #include "ui/dialogs/ExportPackDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/ImportResourceDialog.h" #include "ui/dialogs/ImportResourceDialog.h"
#include "ui/dialogs/NewInstanceDialog.h"
#include "ui/dialogs/NewsDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/instanceview/InstanceDelegate.h"
#include "ui/instanceview/InstanceProxyModel.h"
#include "ui/instanceview/InstanceView.h"
#include "ui/themes/ITheme.h" #include "ui/themes/ITheme.h"
#include "ui/themes/ThemeManager.h" #include "ui/themes/ThemeManager.h"
#include "ui/widgets/LabeledToolButton.h"
#include "minecraft/mod/tasks/LocalResourceParse.h" #include "minecraft/WorldList.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/WorldList.h" #include "minecraft/mod/tasks/LocalResourceParse.h"
#include "KonamiCode.h" #include "KonamiCode.h"
#include "InstanceImportTask.h"
#include "InstanceCopyTask.h" #include "InstanceCopyTask.h"
#include "InstanceImportTask.h"
#include "MMCTime.h" #include "MMCTime.h"
namespace { namespace {
QString profileInUseFilter(const QString& profile, bool used) QString profileInUseFilter(const QString& profile, bool used)
{ {
if(used) if (used) {
{
return QObject::tr("%1 (in use)").arg(profile); return QObject::tr("%1 (in use)").arg(profile);
} } else {
else
{
return profile; return profile;
} }
} }
} } // namespace
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{ {
@ -184,7 +182,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars); ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars);
} }
// set the menu for the folders help, accounts, and export tool buttons // set the menu for the folders help, accounts, and export tool buttons
@ -206,6 +203,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
exportInstanceMenu->addAction(ui->actionExportInstanceZip); exportInstanceMenu->addAction(ui->actionExportInstanceZip);
exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); exportInstanceMenu->addAction(ui->actionExportInstanceMrPack);
exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack);
exportInstanceMenu->addAction(ui->actionExportInstanceToModList);
ui->actionExportInstance->setMenu(exportInstanceMenu); ui->actionExportInstance->setMenu(exportInstanceMenu);
} }
@ -231,7 +229,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") { if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
ui->mainToolBar->addAction(ui->actionCloseWindow); ui->mainToolBar->addAction(ui->actionCloseWindow);
} }
} }
// add the toolbar toggles to the view menu // add the toolbar toggles to the view menu
@ -301,9 +298,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged);
view->setModel(proxymodel); view->setModel(proxymodel);
view->setSourceOfGroupCollapseStatus([](const QString & groupName)->bool { view->setSourceOfGroupCollapseStatus(
return APPLICATION->instances()->isGroupCollapsed(groupName); [](const QString& groupName) -> bool { return APPLICATION->instances()->isGroupCollapsed(groupName); });
});
connect(view, &InstanceView::groupStateChanged, APPLICATION->instances().get(), &InstanceList::on_GroupStateChanged); connect(view, &InstanceView::groupStateChanged, APPLICATION->instances().get(), &InstanceList::on_GroupStateChanged);
ui->horizontalLayout->addWidget(view); ui->horizontalLayout->addWidget(view);
} }
@ -361,21 +357,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// Update the menu when the active account changes. // Update the menu when the active account changes.
// Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
// Template hell sucks... // Template hell sucks...
connect( connect(APPLICATION->accounts().get(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); });
APPLICATION->accounts().get(), connect(APPLICATION->accounts().get(), &AccountList::listChanged, [this] { repopulateAccountsMenu(); });
&AccountList::defaultAccountChanged,
[this] {
defaultAccountChanged();
}
);
connect(
APPLICATION->accounts().get(),
&AccountList::listChanged,
[this]
{
repopulateAccountsMenu();
}
);
// Show initial account // Show initial account
defaultAccountChanged(); defaultAccountChanged();
@ -427,7 +410,6 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event)
void MainWindow::retranslateUi() void MainWindow::retranslateUi()
{ {
if (m_selectedInstance) { if (m_selectedInstance) {
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
} else { } else {
@ -457,9 +439,7 @@ void MainWindow::retranslateUi()
} }
} }
MainWindow::~MainWindow() MainWindow::~MainWindow() {}
{
}
QMenu* MainWindow::createPopupMenu() QMenu* MainWindow::createPopupMenu()
{ {
@ -478,10 +458,11 @@ void MainWindow::lockToolbars(bool state)
APPLICATION->settings()->set("ToolbarsLocked", state); APPLICATION->settings()->set("ToolbarsLocked", state);
} }
void MainWindow::konamiTriggered() void MainWindow::konamiTriggered()
{ {
QString gradient = " stop:0 rgba(125, 0, 0, 255), stop:0.166 rgba(125, 125, 0, 255), stop:0.333 rgba(0, 125, 0, 255), stop:0.5 rgba(0, 125, 125, 255), stop:0.666 rgba(0, 0, 125, 255), stop:0.833 rgba(125, 0, 125, 255), stop:1 rgba(125, 0, 0, 255));"; QString gradient =
" stop:0 rgba(125, 0, 0, 255), stop:0.166 rgba(125, 125, 0, 255), stop:0.333 rgba(0, 125, 0, 255), stop:0.5 rgba(0, 125, 125, "
"255), stop:0.666 rgba(0, 0, 125, 255), stop:0.833 rgba(125, 0, 125, 255), stop:1 rgba(125, 0, 0, 255));";
QString stylesheet = "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + gradient; QString stylesheet = "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + gradient;
if (ui->mainToolBar->styleSheet() == stylesheet) { if (ui->mainToolBar->styleSheet() == stylesheet) {
ui->mainToolBar->setStyleSheet(""); ui->mainToolBar->setStyleSheet("");
@ -508,8 +489,7 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
actionSep->setSeparator(true); actionSep->setSeparator(true);
bool onInstance = view->indexAt(pos).isValid(); bool onInstance = view->indexAt(pos).isValid();
if (onInstance) if (onInstance) {
{
// reuse the file menu actions // reuse the file menu actions
actions = ui->fileMenu->actions(); actions = ui->fileMenu->actions();
@ -526,9 +506,7 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
QAction* actionVoid = new QAction(m_selectedInstance->name(), this); QAction* actionVoid = new QAction(m_selectedInstance->name(), this);
actionVoid->setEnabled(false); actionVoid->setEnabled(false);
actions.prepend(actionVoid); actions.prepend(actionVoid);
} } else {
else
{
auto group = view->groupNameAt(pos); auto group = view->groupNameAt(pos);
QAction* actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this); QAction* actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this);
@ -536,8 +514,7 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
QAction* actionCreateInstance = new QAction(tr("Create instance"), this); QAction* actionCreateInstance = new QAction(tr("Create instance"), this);
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
if(!group.isNull()) if (!group.isNull()) {
{
QVariantMap data; QVariantMap data;
data["group"] = group; data["group"] = group;
actionCreateInstance->setData(data); actionCreateInstance->setData(data);
@ -548,8 +525,7 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
actions.prepend(actionSep); actions.prepend(actionSep);
actions.prepend(actionVoid); actions.prepend(actionVoid);
actions.append(actionCreateInstance); actions.append(actionCreateInstance);
if(!group.isNull()) if (!group.isNull()) {
{
QAction* actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this); QAction* actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this);
QVariantMap data; QVariantMap data;
data["group"] = group; data["group"] = group;
@ -582,12 +558,9 @@ void MainWindow::updateToolsMenu()
ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning);
QMenu* launchMenu = ui->actionLaunchInstance->menu(); QMenu* launchMenu = ui->actionLaunchInstance->menu();
if (launchMenu) if (launchMenu) {
{
launchMenu->clear(); launchMenu->clear();
} } else {
else
{
launchMenu = new QMenu(this); launchMenu = new QMenu(this);
} }
QAction* normalLaunch = launchMenu->addAction(tr("Launch")); QAction* normalLaunch = launchMenu->addAction(tr("Launch"));
@ -596,24 +569,15 @@ void MainWindow::updateToolsMenu()
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo")); QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo"));
normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
if (m_selectedInstance) if (m_selectedInstance) {
{
normalLaunch->setEnabled(m_selectedInstance->canLaunch()); normalLaunch->setEnabled(m_selectedInstance->canLaunch());
normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch()); normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch());
normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch()); normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch());
connect(normalLaunch, &QAction::triggered, [this]() { connect(normalLaunch, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, true, false); });
APPLICATION->launch(m_selectedInstance, true, false); connect(normalLaunchOffline, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, false); });
}); connect(normalLaunchDemo, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, true); });
connect(normalLaunchOffline, &QAction::triggered, [this]() { } else {
APPLICATION->launch(m_selectedInstance, false, false);
});
connect(normalLaunchDemo, &QAction::triggered, [this]() {
APPLICATION->launch(m_selectedInstance, false, true);
});
}
else
{
normalLaunch->setDisabled(true); normalLaunch->setDisabled(true);
normalLaunchOffline->setDisabled(true); normalLaunchOffline->setDisabled(true);
normalLaunchDemo->setDisabled(true); normalLaunchDemo->setDisabled(true);
@ -627,35 +591,25 @@ void MainWindow::updateToolsMenu()
QString profilersTitle = tr("Profilers"); QString profilersTitle = tr("Profilers");
launchMenu->addSeparator()->setText(profilersTitle); launchMenu->addSeparator()->setText(profilersTitle);
for (auto profiler : APPLICATION->profilers().values()) for (auto profiler : APPLICATION->profilers().values()) {
{
QAction* profilerAction = launchMenu->addAction(profiler->name()); QAction* profilerAction = launchMenu->addAction(profiler->name());
QAction* profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name())); QAction* profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name()));
QString error; QString error;
if (!profiler->check(&error)) if (!profiler->check(&error)) {
{
profilerAction->setDisabled(true); profilerAction->setDisabled(true);
profilerOfflineAction->setDisabled(true); profilerOfflineAction->setDisabled(true);
QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\".");
profilerAction->setToolTip(profilerToolTip); profilerAction->setToolTip(profilerToolTip);
profilerOfflineAction->setToolTip(profilerToolTip); profilerOfflineAction->setToolTip(profilerToolTip);
} } else if (m_selectedInstance) {
else if (m_selectedInstance)
{
profilerAction->setEnabled(m_selectedInstance->canLaunch()); profilerAction->setEnabled(m_selectedInstance->canLaunch());
profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch());
connect(profilerAction, &QAction::triggered, [this, profiler]() connect(profilerAction, &QAction::triggered,
{ [this, profiler]() { APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); });
APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); connect(profilerOfflineAction, &QAction::triggered,
}); [this, profiler]() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); });
connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() } else {
{
APPLICATION->launch(m_selectedInstance, false, false, profiler.get());
});
}
else
{
profilerAction->setDisabled(true); profilerAction->setDisabled(true);
profilerOfflineAction->setDisabled(true); profilerOfflineAction->setDisabled(true);
} }
@ -712,11 +666,9 @@ void MainWindow::repopulateAccountsMenu()
MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
QString active_profileId = ""; QString active_profileId = "";
if (defaultAccount) if (defaultAccount) {
{
// this can be called before accountMenuButton exists // this can be called before accountMenuButton exists
if (ui->actionAccountsButton) if (ui->actionAccountsButton) {
{
auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse());
ui->actionAccountsButton->setText(profileLabel); ui->actionAccountsButton->setText(profileLabel);
} }
@ -724,38 +676,31 @@ void MainWindow::repopulateAccountsMenu()
QActionGroup* accountsGroup = new QActionGroup(this); QActionGroup* accountsGroup = new QActionGroup(this);
if (accounts->count() <= 0) if (accounts->count() <= 0) {
{
ui->actionNoAccountsAdded->setEnabled(false); ui->actionNoAccountsAdded->setEnabled(false);
ui->accountsMenu->addAction(ui->actionNoAccountsAdded); ui->accountsMenu->addAction(ui->actionNoAccountsAdded);
} } else {
else
{
// TODO: Nicer way to iterate? // TODO: Nicer way to iterate?
for (int i = 0; i < accounts->count(); i++) for (int i = 0; i < accounts->count(); i++) {
{
MinecraftAccountPtr account = accounts->at(i); MinecraftAccountPtr account = accounts->at(i);
auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse());
QAction* action = new QAction(profileLabel, this); QAction* action = new QAction(profileLabel, this);
action->setData(i); action->setData(i);
action->setCheckable(true); action->setCheckable(true);
action->setActionGroup(accountsGroup); action->setActionGroup(accountsGroup);
if (defaultAccount == account) if (defaultAccount == account) {
{
action->setChecked(true); action->setChecked(true);
} }
auto face = account->getFace(); auto face = account->getFace();
if (!face.isNull()) { if (!face.isNull()) {
action->setIcon(face); action->setIcon(face);
} } else {
else {
action->setIcon(APPLICATION->getThemedIcon("noaccount")); action->setIcon(APPLICATION->getThemedIcon("noaccount"));
} }
const int highestNumberKey = 9; const int highestNumberKey = 9;
if(i<highestNumberKey) if (i < highestNumberKey) {
{
action->setShortcut(QKeySequence(tr("Ctrl+%1").arg(i + 1))); action->setShortcut(QKeySequence(tr("Ctrl+%1").arg(i + 1)));
} }
@ -782,8 +727,7 @@ void MainWindow::repopulateAccountsMenu()
void MainWindow::updatesAllowedChanged(bool allowed) void MainWindow::updatesAllowedChanged(bool allowed)
{ {
if(!BuildConfig.UPDATER_ENABLED) if (!BuildConfig.UPDATER_ENABLED) {
{
return; return;
} }
ui->actionCheckUpdate->setEnabled(allowed); ui->actionCheckUpdate->setEnabled(allowed);
@ -818,15 +762,13 @@ void MainWindow::defaultAccountChanged()
MinecraftAccountPtr account = APPLICATION->accounts()->defaultAccount(); MinecraftAccountPtr account = APPLICATION->accounts()->defaultAccount();
// FIXME: this needs adjustment for MSA // FIXME: this needs adjustment for MSA
if (account && account->profileName() != "") if (account && account->profileName() != "") {
{
auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse());
ui->actionAccountsButton->setText(profileLabel); ui->actionAccountsButton->setText(profileLabel);
auto face = account->getFace(); auto face = account->getFace();
if (face.isNull()) { if (face.isNull()) {
ui->actionAccountsButton->setIcon(APPLICATION->getThemedIcon("noaccount")); ui->actionAccountsButton->setIcon(APPLICATION->getThemedIcon("noaccount"));
} } else {
else {
ui->actionAccountsButton->setIcon(face); ui->actionAccountsButton->setIcon(face);
} }
return; return;
@ -839,14 +781,11 @@ void MainWindow::defaultAccountChanged()
bool MainWindow::eventFilter(QObject* obj, QEvent* ev) bool MainWindow::eventFilter(QObject* obj, QEvent* ev)
{ {
if (obj == view) if (obj == view) {
{ if (ev->type() == QEvent::KeyPress) {
if (ev->type() == QEvent::KeyPress)
{
secretEventFilter->input(ev); secretEventFilter->input(ev);
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev); QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
switch (keyEvent->key()) switch (keyEvent->key()) {
{
/* /*
case Qt::Key_Enter: case Qt::Key_Enter:
case Qt::Key_Return: case Qt::Key_Return:
@ -872,23 +811,17 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
void MainWindow::updateNewsLabel() void MainWindow::updateNewsLabel()
{ {
if (m_newsChecker->isLoadingNews()) if (m_newsChecker->isLoadingNews()) {
{
newsLabel->setText(tr("Loading news...")); newsLabel->setText(tr("Loading news..."));
newsLabel->setEnabled(false); newsLabel->setEnabled(false);
ui->actionMoreNews->setVisible(false); ui->actionMoreNews->setVisible(false);
} } else {
else
{
QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries(); QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries();
if (entries.length() > 0) if (entries.length() > 0) {
{
newsLabel->setText(entries[0]->title); newsLabel->setText(entries[0]->title);
newsLabel->setEnabled(true); newsLabel->setEnabled(true);
ui->actionMoreNews->setVisible(true); ui->actionMoreNews->setVisible(true);
} } else {
else
{
newsLabel->setText(tr("No news available.")); newsLabel->setText(tr("No news available."));
newsLabel->setEnabled(false); newsLabel->setEnabled(false);
ui->actionMoreNews->setVisible(false); ui->actionMoreNews->setVisible(false);
@ -904,8 +837,7 @@ QList<int> stringToIntList(const QString &string)
QStringList split = string.split(',', QString::SkipEmptyParts); QStringList split = string.split(',', QString::SkipEmptyParts);
#endif #endif
QList<int> out; QList<int> out;
for (int i = 0; i < split.size(); ++i) for (int i = 0; i < split.size(); ++i) {
{
out.append(split.at(i).toInt()); out.append(split.at(i).toInt());
} }
return out; return out;
@ -913,8 +845,7 @@ QList<int> stringToIntList(const QString &string)
QString intListToString(const QList<int>& list) QString intListToString(const QList<int>& list)
{ {
QStringList slist; QStringList slist;
for (int i = 0; i < list.size(); ++i) for (int i = 0; i < list.size(); ++i) {
{
slist.append(QString::number(list.at(i))); slist.append(QString::number(list.at(i)));
} }
return slist.join(','); return slist.join(',');
@ -934,21 +865,17 @@ void MainWindow::setCatBackground(bool enabled)
void MainWindow::runModalTask(Task* task) void MainWindow::runModalTask(Task* task)
{ {
connect(task, &Task::failed, [this](QString reason) connect(task, &Task::failed,
{ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); connect(task, &Task::succeeded, [this, task]() {
});
connect(task, &Task::succeeded, [this, task]()
{
QStringList warnings = task->warnings(); QStringList warnings = task->warnings();
if(warnings.count()) if (warnings.count()) {
{
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->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)
CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); ->show();
}); });
ProgressDialog loadDialog(this); ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort")); loadDialog.setSkipButton(true, tr("Abort"));
@ -982,38 +909,30 @@ void MainWindow::finalizeInstance(InstancePtr inst)
{ {
view->updateGeometries(); view->updateGeometries();
setSelectedInstanceById(inst->id()); setSelectedInstanceById(inst->id());
if (APPLICATION->accounts()->anyAccountIsValid()) if (APPLICATION->accounts()->anyAccountIsValid()) {
{
ProgressDialog loadDialog(this); ProgressDialog loadDialog(this);
auto update = inst->createUpdateTask(Net::Mode::Online); auto update = inst->createUpdateTask(Net::Mode::Online);
connect(update.get(), &Task::failed, [this](QString reason) connect(update.get(), &Task::failed, [this](QString reason) {
{
QString error = QString("Instance load failed: %1").arg(reason); QString error = QString("Instance load failed: %1").arg(reason);
CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show();
}); });
if(update) if (update) {
{
loadDialog.setSkipButton(true, tr("Abort")); loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(update.get()); loadDialog.execWithTask(update.get());
} }
} } else {
else CustomMessageBox::selectable(this, tr("Error"),
{
CustomMessageBox::selectable(
this,
tr("Error"),
tr("The launcher cannot download Minecraft or update instances unless you have at least " tr("The launcher cannot download Minecraft or update instances unless you have at least "
"one account added.\nPlease add your Mojang or Minecraft account."), "one account added.\nPlease add your Mojang or Minecraft account."),
QMessageBox::Warning QMessageBox::Warning)
)->show(); ->show();
} }
} }
void MainWindow::addInstance(QString url) void MainWindow::addInstance(QString url)
{ {
QString groupName; QString groupName;
do do {
{
QObject* obj = sender(); QObject* obj = sender();
if (!obj) if (!obj)
break; break;
@ -1026,8 +945,7 @@ void MainWindow::addInstance(QString url)
groupName = map["group"].toString(); groupName = map["group"].toString();
} while (0); } while (0);
if(groupName.isEmpty()) if (groupName.isEmpty()) {
{
groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString(); groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString();
} }
@ -1038,8 +956,7 @@ void MainWindow::addInstance(QString url)
APPLICATION->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); APPLICATION->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup());
InstanceTask* creationTask = newInstDlg.extractTask(); InstanceTask* creationTask = newInstDlg.extractTask();
if(creationTask) if (creationTask) {
{
instanceFromInstanceTask(creationTask); instanceFromInstanceTask(creationTask);
} }
} }
@ -1133,8 +1050,7 @@ void MainWindow::on_actionChangeInstIcon_triggered()
IconPickerDialog dlg(this); IconPickerDialog dlg(this);
dlg.execWithSelection(m_selectedInstance->iconKey()); dlg.execWithSelection(m_selectedInstance->iconKey());
if (dlg.result() == QDialog::Accepted) if (dlg.result() == QDialog::Accepted) {
{
m_selectedInstance->setIconKey(dlg.selectedIconKey); m_selectedInstance->setIconKey(dlg.selectedIconKey);
auto icon = APPLICATION->icons()->getIcon(dlg.selectedIconKey); auto icon = APPLICATION->icons()->getIcon(dlg.selectedIconKey);
ui->actionChangeInstIcon->setIcon(icon); ui->actionChangeInstIcon->setIcon(icon);
@ -1144,8 +1060,7 @@ void MainWindow::on_actionChangeInstIcon_triggered()
void MainWindow::iconUpdated(QString icon) void MainWindow::iconUpdated(QString icon)
{ {
if (icon == m_currentInstIcon) if (icon == m_currentInstIcon) {
{
auto icon = APPLICATION->icons()->getIcon(m_currentInstIcon); auto icon = APPLICATION->icons()->getIcon(m_currentInstIcon);
ui->actionChangeInstIcon->setIcon(icon); ui->actionChangeInstIcon->setIcon(icon);
changeIconButton->setIcon(icon); changeIconButton->setIcon(icon);
@ -1165,8 +1080,7 @@ void MainWindow::setSelectedInstanceById(const QString &id)
if (id.isNull()) if (id.isNull())
return; return;
const QModelIndex index = APPLICATION->instances()->getInstanceIndexById(id); const QModelIndex index = APPLICATION->instances()->getInstanceIndexById(id);
if (index.isValid()) if (index.isValid()) {
{
QModelIndex selectionIndex = proxymodel->mapFromSource(index); QModelIndex selectionIndex = proxymodel->mapFromSource(index);
view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect);
updateStatusCenter(); updateStatusCenter();
@ -1188,8 +1102,7 @@ void MainWindow::on_actionChangeInstGroup_triggered()
name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok); name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok);
name = name.simplified(); name = name.simplified();
if (ok) if (ok) {
{
APPLICATION->instances()->setInstanceGroup(instId, name); APPLICATION->instances()->setInstanceGroup(instId, name);
} }
} }
@ -1206,12 +1119,10 @@ void MainWindow::deleteGroup()
if (!map.contains("group")) if (!map.contains("group"))
return; return;
QString groupName = map["group"].toString(); QString groupName = map["group"].toString();
if(!groupName.isEmpty()) if (!groupName.isEmpty()) {
{ auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1?").arg(groupName),
auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1?") QMessageBox::Yes | QMessageBox::No);
.arg(groupName), QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) {
if(reply == QMessageBox::Yes)
{
APPLICATION->instances()->deleteGroup(groupName); APPLICATION->instances()->deleteGroup(groupName);
} }
} }
@ -1247,12 +1158,9 @@ void MainWindow::on_actionViewCentralModsFolder_triggered()
void MainWindow::checkForUpdates() void MainWindow::checkForUpdates()
{ {
if(BuildConfig.UPDATER_ENABLED) if (BuildConfig.UPDATER_ENABLED) {
{
APPLICATION->triggerUpdateCheck(); APPLICATION->triggerUpdateCheck();
} } else {
else
{
qWarning() << "Updater not set up. Cannot check for updates."; qWarning() << "Updater not set up. Cannot check for updates.";
} }
} }
@ -1280,7 +1188,6 @@ void MainWindow::globalSettingsClosed()
void MainWindow::on_actionEditInstance_triggered() void MainWindow::on_actionEditInstance_triggered()
{ {
if (!m_selectedInstance) if (!m_selectedInstance)
return; return;
@ -1289,7 +1196,8 @@ void MainWindow::on_actionEditInstance_triggered()
} else { } else {
CustomMessageBox::selectable(this, tr("Instance not editable"), CustomMessageBox::selectable(this, tr("Instance not editable"),
tr("This instance is not editable. It may be broken, invalid, or too old. Check logs for details."), tr("This instance is not editable. It may be broken, invalid, or too old. Check logs for details."),
QMessageBox::Critical)->show(); QMessageBox::Critical)
->show();
} }
} }
@ -1352,7 +1260,8 @@ void MainWindow::newsButtonClicked()
news_dialog.exec(); news_dialog.exec();
} }
void MainWindow::onCatChanged(int) { void MainWindow::onCatChanged(int)
{
setCatBackground(APPLICATION->settings()->get("TheCat").toBool()); setCatBackground(APPLICATION->settings()->get("TheCat").toBool());
} }
@ -1383,14 +1292,15 @@ void MainWindow::on_actionDeleteInstance_triggered()
auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
if (!linkedInstances.empty()) { if (!linkedInstances.empty()) {
response = CustomMessageBox::selectable( response = CustomMessageBox::selectable(this, tr("There are linked instances"),
this, tr("There are linked instances"),
tr("The following instance(s) might reference files in this instance:\n\n" tr("The following instance(s) might reference files in this instance:\n\n"
"%1\n\n" "%1\n\n"
"Deleting it could break the other instance(s), \n\n" "Deleting it could break the other instance(s), \n\n"
"Do you wish to proceed?", nullptr, linkedInstances.count()).arg(linkedInstances.join("\n")), "Do you wish to proceed?",
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No nullptr, linkedInstances.count())
)->exec(); .arg(linkedInstances.join("\n")),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes) if (response != QMessageBox::Yes)
return; return;
} }
@ -1405,8 +1315,7 @@ void MainWindow::on_actionDeleteInstance_triggered()
void MainWindow::on_actionExportInstanceZip_triggered() void MainWindow::on_actionExportInstanceZip_triggered()
{ {
if (m_selectedInstance) if (m_selectedInstance) {
{
ExportInstanceDialog dlg(m_selectedInstance, this); ExportInstanceDialog dlg(m_selectedInstance, this);
dlg.exec(); dlg.exec();
} }
@ -1414,13 +1323,20 @@ void MainWindow::on_actionExportInstanceZip_triggered()
void MainWindow::on_actionExportInstanceMrPack_triggered() void MainWindow::on_actionExportInstanceMrPack_triggered()
{ {
if (m_selectedInstance) if (m_selectedInstance) {
{
ExportPackDialog dlg(m_selectedInstance, this); ExportPackDialog dlg(m_selectedInstance, this);
dlg.exec(); dlg.exec();
} }
} }
void MainWindow::on_actionExportInstanceToModList_triggered()
{
if (m_selectedInstance) {
ExportToModListDialog dlg(m_selectedInstance, this);
dlg.exec();
}
}
void MainWindow::on_actionExportInstanceFlamePack_triggered() void MainWindow::on_actionExportInstanceFlamePack_triggered()
{ {
if (m_selectedInstance) { if (m_selectedInstance) {
@ -1447,16 +1363,14 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
void MainWindow::on_actionRenameInstance_triggered() void MainWindow::on_actionRenameInstance_triggered()
{ {
if (m_selectedInstance) if (m_selectedInstance) {
{
view->edit(view->currentIndex()); view->edit(view->currentIndex());
} }
} }
void MainWindow::on_actionViewSelectedInstFolder_triggered() void MainWindow::on_actionViewSelectedInstFolder_triggered()
{ {
if (m_selectedInstance) if (m_selectedInstance) {
{
QString str = m_selectedInstance->instanceRoot(); QString str = m_selectedInstance->instanceRoot();
DesktopServices::openDirectory(QDir(str).absolutePath()); DesktopServices::openDirectory(QDir(str).absolutePath());
} }
@ -1474,8 +1388,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
void MainWindow::changeEvent(QEvent* event) void MainWindow::changeEvent(QEvent* event)
{ {
if (event->type() == QEvent::LanguageChange) if (event->type() == QEvent::LanguageChange) {
{
retranslateUi(); retranslateUi();
} }
QMainWindow::changeEvent(event); QMainWindow::changeEvent(event);
@ -1495,8 +1408,7 @@ void MainWindow::instanceActivated(QModelIndex index)
void MainWindow::on_actionLaunchInstance_triggered() void MainWindow::on_actionLaunchInstance_triggered()
{ {
if(m_selectedInstance && !m_selectedInstance->isRunning()) if (m_selectedInstance && !m_selectedInstance->isRunning()) {
{
APPLICATION->launch(m_selectedInstance); APPLICATION->launch(m_selectedInstance);
} }
} }
@ -1508,24 +1420,21 @@ void MainWindow::activateInstance(InstancePtr instance)
void MainWindow::on_actionLaunchInstanceOffline_triggered() void MainWindow::on_actionLaunchInstanceOffline_triggered()
{ {
if (m_selectedInstance) if (m_selectedInstance) {
{
APPLICATION->launch(m_selectedInstance, false); APPLICATION->launch(m_selectedInstance, false);
} }
} }
void MainWindow::on_actionLaunchInstanceDemo_triggered() void MainWindow::on_actionLaunchInstanceDemo_triggered()
{ {
if (m_selectedInstance) if (m_selectedInstance) {
{
APPLICATION->launch(m_selectedInstance, false, true); APPLICATION->launch(m_selectedInstance, false, true);
} }
} }
void MainWindow::on_actionKillInstance_triggered() void MainWindow::on_actionKillInstance_triggered()
{ {
if(m_selectedInstance && m_selectedInstance->isRunning()) if (m_selectedInstance && m_selectedInstance->isRunning()) {
{
APPLICATION->kill(m_selectedInstance); APPLICATION->kill(m_selectedInstance);
} }
} }
@ -1554,16 +1463,14 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
} }
auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
if (pIcon == nullptr) if (pIcon == nullptr) {
{
pIcon = APPLICATION->icons()->icon("grass"); pIcon = APPLICATION->icons()->icon("grass");
} }
iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns"); iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns");
QFile iconFile(iconPath); QFile iconFile(iconPath);
if (!iconFile.open(QFile::WriteOnly)) if (!iconFile.open(QFile::WriteOnly)) {
{
QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
return; return;
} }
@ -1573,8 +1480,7 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS");
iconFile.close(); iconFile.close();
if (!success) if (!success) {
{
iconFile.remove(); iconFile.remove();
QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
return; return;
@ -1692,8 +1598,7 @@ void MainWindow::startTask(Task *task)
void MainWindow::instanceChanged(const QModelIndex& current, const QModelIndex& previous) void MainWindow::instanceChanged(const QModelIndex& current, const QModelIndex& previous)
{ {
if (!current.isValid()) if (!current.isValid()) {
{
APPLICATION->settings()->set("SelectedInstance", QString()); APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad(); selectionBad();
return; return;
@ -1703,8 +1608,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
} }
QString id = current.data(InstanceList::InstanceIDRole).toString(); QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id); m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
if (m_selectedInstance) if (m_selectedInstance) {
{
ui->instanceToolBar->setEnabled(true); ui->instanceToolBar->setEnabled(true);
setInstanceActionsEnabled(true); setInstanceActionsEnabled(true);
ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
@ -1729,9 +1633,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
} } else {
else
{
ui->instanceToolBar->setEnabled(false); ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false); setInstanceActionsEnabled(false);
ui->actionLaunchInstance->setEnabled(false); ui->actionLaunchInstance->setEnabled(false);
@ -1753,8 +1655,7 @@ void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelInd
{ {
auto current = view->selectionModel()->currentIndex(); auto current = view->selectionModel()->currentIndex();
QItemSelection test(topLeft, bottomRight); QItemSelection test(topLeft, bottomRight);
if (test.contains(current)) if (test.contains(current)) {
{
instanceChanged(current, current); instanceChanged(current, current);
} }
} }
@ -1778,34 +1679,28 @@ void MainWindow::selectionBad()
void MainWindow::checkInstancePathForProblems() void MainWindow::checkInstancePathForProblems()
{ {
QString instanceFolder = APPLICATION->settings()->get("InstanceDir").toString(); QString instanceFolder = APPLICATION->settings()->get("InstanceDir").toString();
if (FS::checkProblemticPathJava(QDir(instanceFolder))) if (FS::checkProblemticPathJava(QDir(instanceFolder))) {
{
QMessageBox warning(this); QMessageBox warning(this);
warning.setText(tr("Your instance folder contains \'!\' and this is known to cause Java problems!")); warning.setText(tr("Your instance folder contains \'!\' and this is known to cause Java problems!"));
warning.setInformativeText( warning.setInformativeText(tr("You have now two options: <br/>"
tr(
"You have now two options: <br/>"
" - change the instance folder in the settings <br/>" " - change the instance folder in the settings <br/>"
" - move this installation of %1 to a different folder" " - move this installation of %1 to a different folder")
).arg(BuildConfig.LAUNCHER_DISPLAYNAME) .arg(BuildConfig.LAUNCHER_DISPLAYNAME));
);
warning.setDefaultButton(QMessageBox::Ok); warning.setDefaultButton(QMessageBox::Ok);
warning.exec(); warning.exec();
} }
auto tempFolderText = tr("This is a problem: <br/>" auto tempFolderText =
tr("This is a problem: <br/>"
" - The launcher will likely be deleted without warning by the operating system <br/>" " - The launcher will likely be deleted without warning by the operating system <br/>"
" - close the launcher now and extract it to a real location, not a temporary folder"); " - close the launcher now and extract it to a real location, not a temporary folder");
QString pathfoldername = QDir(instanceFolder).absolutePath(); QString pathfoldername = QDir(instanceFolder).absolutePath();
if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) {
{
QMessageBox warning(this); QMessageBox warning(this);
warning.setText(tr("Your instance folder contains \'Rar$\' - that means you haven't extracted the launcher archive!")); warning.setText(tr("Your instance folder contains \'Rar$\' - that means you haven't extracted the launcher archive!"));
warning.setInformativeText(tempFolderText); warning.setInformativeText(tempFolderText);
warning.setDefaultButton(QMessageBox::Ok); warning.setDefaultButton(QMessageBox::Ok);
warning.exec(); warning.exec();
} } else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/")) {
else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/"))
{
QMessageBox warning(this); QMessageBox warning(this);
warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath())); warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath()));
warning.setInformativeText(tempFolderText); warning.setInformativeText(tempFolderText);

View File

@ -158,6 +158,7 @@ private slots:
void on_actionExportInstanceZip_triggered(); void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered(); void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered(); void on_actionExportInstanceFlamePack_triggered();
void on_actionExportInstanceToModList_triggered();
void on_actionRenameInstance_triggered(); void on_actionRenameInstance_triggered();

View File

@ -487,6 +487,14 @@
<string>CurseForge (zip)</string> <string>CurseForge (zip)</string>
</property> </property>
</action> </action>
<action name="actionExportInstanceToModList">
<property name="icon">
<iconset theme="new"/>
</property>
<property name="text">
<string>Mod List</string>
</property>
</action>
<action name="actionCreateInstanceShortcut"> <action name="actionCreateInstanceShortcut">
<property name="icon"> <property name="icon">
<iconset theme="shortcut"> <iconset theme="shortcut">

View 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);
}

View 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;
};

View 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>