aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/update-flake.yml2
-rw-r--r--launcher/CMakeLists.txt8
-rw-r--r--launcher/FileSystem.cpp60
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp15
-rw-r--r--launcher/minecraft/PackProfile.cpp3
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp11
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp87
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h14
-rw-r--r--launcher/modplatform/ModIndex.h1
-rw-r--r--launcher/modplatform/flame/FlameAPI.h2
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.cpp473
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.h90
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h6
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.cpp55
-rw-r--r--launcher/ui/MainWindow.cpp73
-rw-r--r--launcher/ui/MainWindow.h1
-rw-r--r--launcher/ui/MainWindow.ui8
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.cpp (renamed from launcher/ui/dialogs/ExportMrPackDialog.cpp)63
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.h (renamed from launcher/ui/dialogs/ExportMrPackDialog.h)14
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.ui (renamed from launcher/ui/dialogs/ExportMrPackDialog.ui)15
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp9
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui2
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp33
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.h2
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp13
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.h3
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp31
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.cpp3
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp306
-rw-r--r--launcher/ui/pages/instance/VersionPage.h55
-rw-r--r--launcher/ui/widgets/WideBar.cpp13
34 files changed, 1038 insertions, 438 deletions
diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml
index c790c9c6..ad22120e 100644
--- a/.github/workflows/update-flake.yml
+++ b/.github/workflows/update-flake.yml
@@ -7,10 +7,12 @@ on:
workflow_dispatch:
permissions:
+ contents: write
pull-requests: write
jobs:
update-flake:
+ if: github.repository == 'PrismLauncher/PrismLauncher'
runs-on: ubuntu-latest
steps:
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 312288a1..af3bc28e 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -514,6 +514,8 @@ set(FLAME_SOURCES
modplatform/flame/FlameCheckUpdate.h
modplatform/flame/FlameInstanceCreationTask.h
modplatform/flame/FlameInstanceCreationTask.cpp
+ modplatform/flame/FlamePackExportTask.h
+ modplatform/flame/FlamePackExportTask.cpp
)
set(MODRINTH_SOURCES
@@ -907,8 +909,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/EditAccountDialog.h
ui/dialogs/ExportInstanceDialog.cpp
ui/dialogs/ExportInstanceDialog.h
- ui/dialogs/ExportMrPackDialog.cpp
- ui/dialogs/ExportMrPackDialog.h
+ ui/dialogs/ExportPackDialog.cpp
+ ui/dialogs/ExportPackDialog.h
ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp
@@ -1055,7 +1057,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ProfileSelectDialog.ui
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
- ui/dialogs/ExportMrPackDialog.ui
+ ui/dialogs/ExportPackDialog.ui
ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 812d45eb..4538702f 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -778,9 +778,43 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
}
#if defined(Q_OS_MACOS)
- destination += ".command";
+ // Create the Application
+ QDir applicationDirectory = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/";
- QFile f(destination);
+ if (!applicationDirectory.mkpath(".")) {
+ qWarning() << "Couldn't create application directory";
+ return false;
+ }
+
+ QDir application = applicationDirectory.path() + "/" + name + ".app/";
+
+ if (application.exists()) {
+ qWarning() << "Application already exists!";
+ return false;
+ }
+
+ if (!application.mkpath(".")) {
+ qWarning() << "Couldn't create application";
+ return false;
+ }
+
+ QDir content = application.path() + "/Contents/";
+ QDir resources = content.path() + "/Resources/";
+ QDir binaryDir = content.path() + "/MacOS/";
+ QFile info = content.path() + "/Info.plist";
+
+ if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) {
+ qWarning() << "Couldn't create directories within application";
+ return false;
+ }
+ info.open(QIODevice::WriteOnly | QIODevice::Text);
+
+ QFile(icon).rename(resources.path() + "/Icon.icns");
+
+ // Create the Command file
+ QString exec = binaryDir.path() + "/Run.command";
+
+ QFile f(exec);
f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f);
@@ -797,6 +831,28 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
+ // Generate the Info.plist
+ QTextStream infoStream(&info);
+ infoStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
+ "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
+ "<plist version=\"1.0\">\n"
+ "<dict>\n"
+ " <key>CFBundleExecutable</key>\n"
+ " <string>Run.command</string>\n" // The path to the executable
+ " <key>CFBundleIconFile</key>\n"
+ " <string>Icon.icns</string>\n"
+ " <key>CFBundleName</key>\n"
+ " <string>" << name << "</string>\n" // Name of the application
+ " <key>CFBundlePackageType</key>\n"
+ " <string>APPL</string>\n"
+ " <key>CFBundleShortVersionString</key>\n"
+ " <string>1.0</string>\n"
+ " <key>CFBundleVersion</key>\n"
+ " <string>1.0</string>\n"
+ "</dict>\n"
+ "</plist>";
+
return true;
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index aab930de..4867cc7a 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -1112,36 +1112,27 @@ JavaVersion MinecraftInstance::getJavaVersion()
std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
{
- if (!m_loader_mod_list)
- {
+ if (!m_loader_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
- m_loader_mod_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
}
return m_loader_mod_list;
}
std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
{
- if (!m_core_mod_list)
- {
+ if (!m_core_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
- m_core_mod_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
}
return m_core_mod_list;
}
std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
{
- if (!m_nil_mod_list)
- {
+ if (!m_nil_mod_list) {
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false));
- m_nil_mod_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction);
}
return m_nil_mod_list;
}
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index aff05dbc..e8fd2157 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -65,7 +65,8 @@
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{
{"net.minecraftforge", ResourceAPI::Forge},
{"net.fabricmc.fabric-loader", ResourceAPI::Fabric},
- {"org.quiltmc.quilt-loader", ResourceAPI::Quilt}
+ {"org.quiltmc.quilt-loader", ResourceAPI::Quilt},
+ {"com.mumfrey.liteloader", ResourceAPI::LiteLoader}
};
PackProfile::PackProfile(MinecraftInstance * instance)
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 50f96b72..51383edf 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -215,16 +215,11 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
{
- if(!m_can_interact) {
- return false;
- }
-
- if(indexes.isEmpty())
+ if (indexes.isEmpty())
return true;
- for (auto i: indexes)
- {
- if(i.column() != 0) {
+ for (auto i : indexes) {
+ if (i.column() != 0) {
continue;
}
auto m = at(i.row());
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index 79169ae8..39a61067 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -1,14 +1,15 @@
#include "ResourceFolderModel.h"
+#include <QMessageBox>
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QIcon>
+#include <QMenu>
#include <QMimeData>
#include <QStyle>
#include <QThreadPool>
#include <QUrl>
-#include <QMenu>
#include "Application.h"
#include "FileSystem.h"
@@ -18,6 +19,7 @@
#include "settings/Setting.h"
#include "tasks/Task.h"
+#include "ui/dialogs/CustomMessageBox.h"
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
@@ -77,10 +79,6 @@ bool ResourceFolderModel::stopWatching(const QStringList paths)
bool ResourceFolderModel::installResource(QString original_path)
{
- if (!m_can_interact) {
- return false;
- }
-
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
original_path = FS::NormalizePath(original_path);
QFileInfo file_info(original_path);
@@ -171,9 +169,6 @@ bool ResourceFolderModel::uninstallResource(QString file_name)
bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
{
- if (!m_can_interact)
- return false;
-
if (indexes.isEmpty())
return true;
@@ -192,11 +187,8 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
return true;
}
-bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action)
+bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
{
- if (!m_can_interact)
- return false;
-
if (indexes.isEmpty())
return true;
@@ -249,15 +241,18 @@ bool ResourceFolderModel::update()
connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded,
Qt::ConnectionType::QueuedConnection);
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
- connect(m_current_update_task.get(), &Task::finished, this, [=] {
- m_current_update_task.reset();
- if (m_scheduled_update) {
- m_scheduled_update = false;
- update();
- } else {
- emit updateFinished();
- }
- }, Qt::ConnectionType::QueuedConnection);
+ connect(
+ m_current_update_task.get(), &Task::finished, this,
+ [=] {
+ m_current_update_task.reset();
+ if (m_scheduled_update) {
+ m_scheduled_update = false;
+ update();
+ } else {
+ emit updateFinished();
+ }
+ },
+ Qt::ConnectionType::QueuedConnection);
QThreadPool::globalInstance()->start(m_current_update_task.get());
@@ -347,15 +342,9 @@ Qt::DropActions ResourceFolderModel::supportedDropActions() const
Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- auto flags = defaultFlags;
- if (!m_can_interact) {
- flags &= ~Qt::ItemIsDropEnabled;
- } else {
- flags |= Qt::ItemIsDropEnabled;
- if (index.isValid()) {
- flags |= Qt::ItemIsUserCheckable;
- }
- }
+ auto flags = defaultFlags | Qt::ItemIsDropEnabled;
+ if (index.isValid())
+ flags |= Qt::ItemIsUserCheckable;
return flags;
}
@@ -428,16 +417,17 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
if (column == NAME_COLUMN) {
if (at(row).isSymLinkUnder(instDirPath())) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
- "\nCanonical Path: %1")
- .arg(at(row).fileinfo().canonicalFilePath());;
+ tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
+ "\nCanonical Path: %1")
+ .arg(at(row).fileinfo().canonicalFilePath());
+ ;
}
if (at(row).isMoreThanOneHardLink()) {
return m_resources[row]->internal_id() +
- tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
+ tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
}
}
-
+
return m_resources[row]->internal_id();
case Qt::DecorationRole: {
if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
@@ -463,8 +453,20 @@ bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& valu
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
return false;
- if (role == Qt::CheckStateRole)
+ if (role == Qt::CheckStateRole) {
+ if (m_instance != nullptr && m_instance->isRunning()) {
+ auto response =
+ CustomMessageBox::selectable(nullptr, "Confirm toggle",
+ "If you enable/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 false;
+ }
return setResourceEnabled({ index }, EnableAction::TOGGLE);
+ }
return false;
}
@@ -583,16 +585,6 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const
return m_column_sort_keys.at(column);
}
-void ResourceFolderModel::enableInteraction(bool enabled)
-{
- if (m_can_interact == enabled)
- return;
-
- m_can_interact = enabled;
- if (size())
- emit dataChanged(index(0), index(size() - 1));
-}
-
/* Standard Proxy Model for createFilterProxyModel */
[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
@@ -628,6 +620,7 @@ void ResourceFolderModel::enableInteraction(bool enabled)
return (compare_result.first > 0);
}
-QString ResourceFolderModel::instDirPath() const {
+QString ResourceFolderModel::instDirPath() const
+{
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
}
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index eb1d7c4f..454b84c3 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -14,8 +14,8 @@
#include "BaseInstance.h"
-#include "tasks/Task.h"
#include "tasks/ConcurrentTask.h"
+#include "tasks/Task.h"
class QSortFilterProxyModel;
@@ -141,10 +141,6 @@ class ResourceFolderModel : public QAbstractListModel {
QString instDirPath() const;
- public slots:
- void enableInteraction(bool enabled);
- void disableInteraction(bool disabled) { enableInteraction(!disabled); }
-
signals:
void updateFinished();
@@ -193,7 +189,11 @@ class ResourceFolderModel : public QAbstractListModel {
* if the resource is complex and has more stuff to parse.
*/
virtual void onParseSucceeded(int ticket, QString resource_id);
- virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
+ virtual void onParseFailed(int ticket, QString resource_id)
+ {
+ Q_UNUSED(ticket);
+ Q_UNUSED(resource_id);
+ }
protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
@@ -203,8 +203,6 @@ class ResourceFolderModel : public QAbstractListModel {
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
- bool m_can_interact = true;
-
QDir m_dir;
BaseInstance* m_instance;
QFileSystemWatcher m_watcher;
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index 3f51e700..2aa91602 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -128,6 +128,7 @@ struct IndexedPack {
return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
}
};
+QString getMetaURL(ResourceProvider provider, QVariant projectID);
struct OverrideDep {
QString quilt;
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 0a6dc78f..49bc316f 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -23,6 +23,8 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
+ static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt); }
+
private:
static int getClassId(ModPlatform::ResourceType type)
{
diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
new file mode 100644
index 00000000..ac0da214
--- /dev/null
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * 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
+ * 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 "FlamePackExportTask.h"
+#include <QJsonArray>
+#include <QJsonObject>
+
+#include <QCryptographicHash>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QtConcurrentRun>
+#include <algorithm>
+#include <memory>
+#include "Json.h"
+#include "MMCZip.h"
+#include "minecraft/PackProfile.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameModIndex.h"
+#include "modplatform/helpers/HashUtils.h"
+#include "tasks/Task.h"
+
+const QString FlamePackExportTask::TEMPLATE = "<li><a href=\"{url}\">{name}{authors}</a></li>\n";
+const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
+
+FlamePackExportTask::FlamePackExportTask(const QString& name,
+ const QString& version,
+ const QString& author,
+ InstancePtr instance,
+ const QString& output,
+ MMCZip::FilterFunction filter)
+ : name(name)
+ , version(version)
+ , author(author)
+ , instance(instance)
+ , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
+ , gameRoot(instance->gameRoot())
+ , output(output)
+ , filter(filter)
+{}
+
+void FlamePackExportTask::executeTask()
+{
+ setStatus(tr("Searching for files..."));
+ setProgress(0, 5);
+ collectFiles();
+}
+
+bool FlamePackExportTask::abort()
+{
+ if (task != nullptr) {
+ task->abort();
+ task = nullptr;
+ emitAborted();
+ return true;
+ }
+
+ if (buildZipFuture.isRunning()) {
+ buildZipFuture.cancel();
+ // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
+ // immediately.
+ return true;
+ }
+
+ return false;
+}
+
+void FlamePackExportTask::collectFiles()
+{
+ setAbortable(false);
+ QCoreApplication::processEvents();
+
+ files.clear();
+ if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) {
+ emitFailed(tr("Could not search for files"));
+ return;
+ }
+
+ pendingHashes.clear();
+ resolvedFiles.clear();
+
+ if (mcInstance != nullptr) {
+ mcInstance->loaderModList()->update();
+ connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes);
+ } else
+ collectHashes();
+}
+
+void FlamePackExportTask::collectHashes()
+{
+ setAbortable(true);
+ setStatus(tr("Finding file hashes..."));
+ setProgress(1, 5);
+ auto allMods = mcInstance->loaderModList()->allMods();
+ ConcurrentTask::Ptr hashingTask(new ConcurrentTask(this, "MakeHashesTask", 10));
+ task.reset(hashingTask);
+ for (const QFileInfo& file : files) {
+ const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
+ // require sensible file types
+ if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) {
+ return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled");
+ }))
+ continue;
+
+ if (relative.startsWith("resourcepacks/") &&
+ (relative.endsWith(".zip") || relative.endsWith(".zip.disabled"))) { // is resourcepack
+ auto hashTask = Hashing::createFlameHasher(file.absoluteFilePath());
+ connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, relative, file](QString hash) {
+ if (m_state == Task::State::Running) {
+ pendingHashes.insert(hash, { relative, file.absoluteFilePath(), relative.endsWith(".zip") });
+ }
+ });
+ connect(hashTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
+ hashingTask->addTask(hashTask);
+ continue;
+ }
+
+ if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; });
+ modIter != allMods.end()) {
+ const Mod* mod = *modIter;
+ if (!mod || mod->type() == ResourceType::FOLDER) {
+ continue;
+ }
+ if (mod->metadata() && mod->metadata()->provider == ModPlatform::ResourceProvider::FLAME) {
+ resolvedFiles.insert(mod->fileinfo().absoluteFilePath(),
+ { mod->metadata()->project_id.toInt(), mod->metadata()->file_id.toInt(), mod->enabled(), true,
+ mod->metadata()->name, mod->metadata()->slug, mod->authors().join(", ") });
+ continue;
+ }
+
+ auto hashTask = Hashing::createFlameHasher(mod->fileinfo().absoluteFilePath());
+ connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) {
+ if (m_state == Task::State::Running) {
+ pendingHashes.insert(hash, { mod->name(), mod->fileinfo().absoluteFilePath(), mod->enabled(), true });
+ }
+ });
+ connect(hashTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
+ hashingTask->addTask(hashTask);
+ }
+ }
+ auto progressStep = std::make_shared<TaskStepProgress>();
+ connect(hashingTask.get(), &Task::finished, this, [this, progressStep] {
+ progressStep->state = TaskStepState::Succeeded;
+ stepProgress(*progressStep);
+ });
+
+ connect(hashingTask.get(), &Task::succeeded, this, &FlamePackExportTask::makeApiRequest);
+ connect(hashingTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ emitFailed(reason);
+ });
+ connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propogateStepProgress);
+
+ connect(hashingTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
+ progressStep->update(current, total);
+ stepProgress(*progressStep);
+ });
+ connect(hashingTask.get(), &Task::status, this, [this, progressStep](QString status) {
+ progressStep->status = status;
+ stepProgress(*progressStep);
+ });
+ hashingTask->start();
+}
+
+void FlamePackExportTask::makeApiRequest()
+{
+ if (pendingHashes.isEmpty()) {
+ buildZip();
+ return;
+ }
+
+ setStatus(tr("Finding versions for hashes..."));
+ setProgress(2, 5);
+ auto response = std::make_shared<QByteArray>();
+
+ QList<uint> fingerprints;
+ for (auto& murmur : pendingHashes.keys()) {
+ fingerprints.push_back(murmur.toUInt());
+ }
+
+ task.reset(api.matchFingerprints(fingerprints, response));
+
+ connect(task.get(), &Task::succeeded, this, [this, response] {
+ QJsonParseError parseError{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError);
+ if (parseError.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from CurseForge::CurrentVersions at " << parseError.offset
+ << " reason: " << parseError.errorString();
+ qWarning() << *response;
+
+ failed(parseError.errorString());
+ return;
+ }
+
+ try {
+ auto docObj = Json::requireObject(doc);
+ auto dataObj = Json::requireObject(docObj, "data");
+ auto dataArr = Json::requireArray(dataObj, "exactMatches");
+
+ if (dataArr.isEmpty()) {
+ qWarning() << "No matches found for fingerprint search!";
+
+ return;
+ }
+ for (auto match : dataArr) {
+ auto matchObj = Json::ensureObject(match, {});
+ auto fileObj = Json::ensureObject(matchObj, "file", {});
+
+ if (matchObj.isEmpty() || fileObj.isEmpty()) {
+ qWarning() << "Fingerprint match is empty!";
+
+ return;
+ }
+
+ auto fingerprint = QString::number(Json::ensureVariant(fileObj, "fileFingerprint").toUInt());
+ auto mod = pendingHashes.find(fingerprint);
+ if (mod == pendingHashes.end()) {
+ qWarning() << "Invalid fingerprint from the API response.";
+ continue;
+ }
+
+ setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name));
+ if (Json::ensureBoolean(fileObj, "isAvailable", false, "isAvailable"))
+ resolvedFiles.insert(mod->path, { Json::requireInteger(fileObj, "modId"), Json::requireInteger(fileObj, "id"),
+ mod->enabled, mod->isMod });
+ }
+
+ } catch (Json::JsonException& e) {
+ qDebug() << e.cause();
+ qDebug() << doc;
+ }
+ pendingHashes.clear();
+ });
+ connect(task.get(), &Task::finished, this, &FlamePackExportTask::getProjectsInfo);
+ connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::emitFailed);
+ task->start();
+}
+
+void FlamePackExportTask::getProjectsInfo()
+{
+ setStatus(tr("Finding project info from CurseForge..."));
+ setProgress(3, 5);
+ QStringList addonIds;
+ for (const auto& resolved : resolvedFiles) {
+ if (resolved.slug.isEmpty()) {
+ addonIds << QString::number(resolved.addonId);
+ }
+ }
+
+ auto response = std::make_shared<QByteArray>();
+ Task::Ptr projTask;
+
+ if (addonIds.isEmpty()) {
+ buildZip();
+ return;
+ } else if (addonIds.size() == 1) {
+ projTask = api.getProject(*addonIds.begin(), response);
+ } else {
+ projTask = api.getProjects(addonIds, response);
+ }
+
+ connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds] {
+ QJsonParseError parseError{};
+ auto doc = QJsonDocument::fromJson(*response, &parseError);
+ if (parseError.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from CurseForge projects task at " << parseError.offset
+ << " reason: " << parseError.errorString();
+ qWarning() << *response;
+ failed(parseError.errorString());
+ return;
+ }
+
+ try {
+ QJsonArray entries;
+ if (addonIds.size() == 1)
+ entries = { Json::requireObject(Json::requireObject(doc), "data") };
+ else
+ entries = Json::requireArray(Json::requireObject(doc), "data");
+
+ for (auto entry : entries) {
+ auto entryObj = Json::requireObject(entry);
+
+ try {
+ setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(Json::requireString(entryObj, "name")));
+
+ ModPlatform::IndexedPack pack;
+ FlameMod::loadIndexedPack(pack, entryObj);
+ for (auto key : resolvedFiles.keys()) {
+ auto val = resolvedFiles.value(key);
+ if (val.addonId == pack.addonId) {
+ val.name = pack.name;
+ val.slug = pack.slug;
+ QStringList authors;
+ for (auto author : pack.authors)
+ authors << author.name;
+
+ val.authors = authors.join(", ");
+ resolvedFiles[key] = val;
+ }
+ }
+
+ } catch (Json::JsonException& e) {
+ qDebug() << e.cause();
+ qDebug() << entries;
+ }
+ }
+ } catch (Json::JsonException& e) {
+ qDebug() << e.cause();
+ qDebug() << doc;
+ }
+ buildZip();
+ });
+ task.reset(projTask);
+ task->start();
+}
+
+void FlamePackExportTask::buildZip()
+{
+ setStatus(tr("Adding files..."));
+ setProgress(4, 5);
+
+ buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
+ QuaZip zip(output);
+ if (!zip.open(QuaZip::mdCreate)) {
+ QFile::remove(output);
+ return BuildZipResult(tr("Could not create file"));
+ }
+
+ if (buildZipFuture.isCanceled())
+ return BuildZipResult();
+
+ QuaZipFile indexFile(&zip);
+ if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("manifest.json"))) {
+ QFile::remove(output);
+ return BuildZipResult(tr("Could not create index"));
+ }
+ indexFile.write(generateIndex());
+
+ QuaZipFile modlist(&zip);
+ if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) {
+ QFile::remove(output);
+ return BuildZipResult(tr("Could not create index"));
+ }
+ QString content = "";
+ for (auto mod : resolvedFiles) {
+ if (mod.isMod) {
+ content += QString(TEMPLATE)
+ .replace("{name}", mod.name.toHtmlEscaped())
+ .replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped())
+ .replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : "");
+ }
+ }
+ content = "<ul>" + content + "</ul>";
+ modlist.write(content.toUtf8());
+
+ auto progressStep = std::make_shared<TaskStepProgress>();
+
+ size_t progress = 0;
+ for (const QFileInfo& file : files) {
+ if (buildZipFuture.isCanceled()) {
+ QFile::remove(output);
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ return BuildZipResult();
+ }
+ progressStep->update(progress, files.length());
+ stepProgress(*progressStep);
+
+ const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
+ if (!resolvedFiles.contains(file.absoluteFilePath()) &&
+ !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
+ QFile::remove(output);
+ return BuildZipResult(tr("Could not read and compress %1").arg(relative));
+ }
+ progress++;
+ }
+
+ zip.close();
+
+ if (zip.getZipError() != 0) {
+ QFile::remove(output);
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ return BuildZipResult(tr("A zip error occurred"));
+ }
+ progressStep->state = TaskStepState::Succeeded;
+ stepProgress(*progressStep);
+ return BuildZipResult();
+ });
+ connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish);
+ buildZipWatcher.setFuture(buildZipFuture);
+}
+
+void FlamePackExportTask::finish()
+{
+ if (buildZipFuture.isCanceled())
+ emitAborted();
+ else {
+ const BuildZipResult result = buildZipFuture.result();
+ if (result.has_value())
+ emitFailed(result.value());
+ else
+ emitSucceeded();
+ }
+}
+
+QByteArray FlamePackExportTask::generateIndex()
+{
+ QJsonObject obj;
+ obj["manifestType"] = "minecraftModpack";
+ obj["manifestVersion"] = 1;
+ obj["name"] = name;
+ obj["version"] = version;
+ obj["author"] = author;
+ obj["overrides"] = "overrides";
+ if (mcInstance) {
+ QJsonObject version;
+ auto profile = mcInstance->getPackProfile();
+ // collect all supported components
+ const ComponentPtr minecraft = profile->getComponent("net.minecraft");
+ const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
+ const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
+ const ComponentPtr forge = profile->getComponent("net.minecraftforge");
+
+ // convert all available components to mrpack dependencies
+ if (minecraft != nullptr)
+ version["version"] = minecraft->m_version;
+ QString id;
+ if (quilt != nullptr)
+ id = "quilt-" + quilt->getVersion();
+ else if (fabric != nullptr)
+ id = "fabric-" + fabric->getVersion();
+ else if (forge != nullptr)
+ id = "forge-" + forge->getVersion();
+ version["modLoaders"] = QJsonArray();
+ if (!id.isEmpty()) {
+ QJsonObject loader;
+ loader["id"] = id;
+ loader["primary"] = true;
+ version["modLoaders"] = QJsonArray({ loader });
+ }
+ obj["minecraft"] = version;
+ }
+
+ QJsonArray files;
+ for (auto mod : resolvedFiles) {
+ QJsonObject file;
+ file["projectID"] = mod.addonId;
+ file["fileID"] = mod.version;
+ file["required"] = mod.enabled;
+ files << file;
+ }
+ obj["files"] = files;
+
+ return QJsonDocument(obj).toJson(QJsonDocument::Compact);
+}
diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h
new file mode 100644
index 00000000..3dee0a7e
--- /dev/null
+++ b/launcher/modplatform/flame/FlamePackExportTask.h
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * 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
+ * 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 <QFuture>
+#include <QFutureWatcher>
+#include "BaseInstance.h"
+#include "MMCZip.h"
+#include "minecraft/MinecraftInstance.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "tasks/Task.h"
+
+class FlamePackExportTask : public Task {
+ public:
+ FlamePackExportTask(const QString& name,
+ const QString& version,
+ const QString& author,
+ InstancePtr instance,
+ const QString& output,
+ MMCZip::FilterFunction filter);
+
+ protected:
+ void executeTask() override;
+ bool abort() override;
+
+ private:
+ static const QString TEMPLATE;
+ static const QStringList FILE_EXTENSIONS;
+
+ // inputs
+ const QString name, version, author;
+ const InstancePtr instance;
+ MinecraftInstance* mcInstance;
+ const QDir gameRoot;
+ const QString output;
+ const MMCZip::FilterFunction filter;
+
+ typedef std::optional<QString> BuildZipResult;
+ struct ResolvedFile {
+ int addonId;
+ int version;
+ bool enabled;
+ bool isMod;
+
+ QString name;
+ QString slug;
+ QString authors;
+ };
+ struct HashInfo {
+ QString name;
+ QString path;
+ bool enabled;
+ bool isMod;
+ };
+
+ FlameAPI api;
+
+ QFileInfoList files;
+ QMap<QString, HashInfo> pendingHashes{};
+ QMap<QString, ResolvedFile> resolvedFiles{};
+ Task::Ptr task;
+ QFuture<BuildZipResult> buildZipFuture;
+ QFutureWatcher<BuildZipResult> buildZipWatcher;
+
+ void collectFiles();
+ void collectHashes();
+ void makeApiRequest();
+ void getProjectsInfo();
+ void buildZip();
+ void finish();
+
+ QByteArray generateIndex();
+};
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index e83ed2bf..58af14cc 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -38,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
- for (auto loader : { Forge, Fabric, Quilt }) {
+ for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) {
if (types & loader) {
l << getModLoaderString(loader);
}
@@ -92,7 +92,7 @@ class ModrinthAPI : public NetworkResourceAPI {
{
if (args.loaders.has_value()) {
if (!validateModLoaders(args.loaders.value())) {
- qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
+ qWarning() << "Modrinth - or our interface - does not support any the provided mod loaders!";
return {};
}
}
@@ -141,7 +141,7 @@ class ModrinthAPI : public NetworkResourceAPI {
return s.isEmpty() ? QString() : s;
}
- inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); }
+ static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt | LiteLoader); }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
index 4cd88aa6..30fe566d 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
@@ -64,7 +64,8 @@ bool ModrinthPackExportTask::abort()
if (buildZipFuture.isRunning()) {
buildZipFuture.cancel();
- // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately.
+ // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
+ // immediately.
return true;
}
@@ -94,6 +95,7 @@ void ModrinthPackExportTask::collectFiles()
void ModrinthPackExportTask::collectHashes()
{
+ setStatus(tr("Finding file hashes..."));
for (const QFileInfo& file : files) {
QCoreApplication::processEvents();
@@ -157,6 +159,7 @@ void ModrinthPackExportTask::makeApiRequest()
if (pendingHashes.isEmpty())
buildZip();
else {
+ setStatus(tr("Finding versions for hashes..."));
auto response = std::make_shared<QByteArray>();
task = api.currentVersions(pendingHashes.values(), "sha512", response);
connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); });
@@ -263,13 +266,13 @@ void ModrinthPackExportTask::finish()
QByteArray ModrinthPackExportTask::generateIndex()
{
- QJsonObject obj;
- obj["formatVersion"] = 1;
- obj["game"] = "minecraft";
- obj["name"] = name;
- obj["versionId"] = version;
+ QJsonObject out;
+ out["formatVersion"] = 1;
+ out["game"] = "minecraft";
+ out["name"] = name;
+ out["versionId"] = version;
if (!summary.isEmpty())
- obj["summary"] = summary;
+ out["summary"] = summary;
if (mcInstance) {
auto profile = mcInstance->getPackProfile();
@@ -290,30 +293,40 @@ QByteArray ModrinthPackExportTask::generateIndex()
if (forge != nullptr)
dependencies["forge"] = forge->m_version;
- obj["dependencies"] = dependencies;
+ out["dependencies"] = dependencies;
}
- QJsonArray files;
- QMapIterator<QString, ResolvedFile> iterator(resolvedFiles);
- while (iterator.hasNext()) {
- iterator.next();
+ QJsonArray filesOut;
+ for (auto iterator = resolvedFiles.constBegin(); iterator != resolvedFiles.constEnd(); iterator++) {
+ QJsonObject fileOut;
+ QString path = iterator.key();
const ResolvedFile& value = iterator.value();
- QJsonObject file;
- file["path"] = iterator.key();
- file["downloads"] = QJsonArray({ iterator.value().url });
+ // detect disabled mod
+ const QFileInfo pathInfo(path);
+ if (pathInfo.suffix() == "disabled") {
+ // rename it
+ path = pathInfo.dir().filePath(pathInfo.completeBaseName());
+ // ...and make it optional
+ QJsonObject env;
+ env["client"] = "optional";
+ env["server"] = "optional";
+ fileOut["env"] = env;
+ }
+
+ fileOut["path"] = path;
+ fileOut["downloads"] = QJsonArray{ iterator.value().url };
QJsonObject hashes;
hashes["sha1"] = value.sha1;
hashes["sha512"] = value.sha512;
+ fileOut["hashes"] = hashes;
- file["hashes"] = hashes;
- file["fileSize"] = value.size;
-
- files << file;
+ fileOut["fileSize"] = value.size;
+ filesOut << fileOut;
}
- obj["files"] = files;
+ out["files"] = filesOut;
- return QJsonDocument(obj).toJson(QJsonDocument::Compact);
+ return QJsonDocument(out).toJson(QJsonDocument::Compact);
}
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 515abf07..fe364937 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -107,7 +107,7 @@
#include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"
-#include "ui/dialogs/ExportMrPackDialog.h"
+#include "ui/dialogs/ExportPackDialog.h"
#include "ui/dialogs/ImportResourceDialog.h"
#include "ui/themes/ITheme.h"
#include "ui/themes/ThemeManager.h"
@@ -205,6 +205,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
auto exportInstanceMenu = new QMenu(this);
exportInstanceMenu->addAction(ui->actionExportInstanceZip);
exportInstanceMenu->addAction(ui->actionExportInstanceMrPack);
+ exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack);
ui->actionExportInstance->setMenu(exportInstanceMenu);
}
@@ -1405,11 +1406,35 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
{
if (m_selectedInstance)
{
- ExportMrPackDialog dlg(m_selectedInstance, this);
+ ExportPackDialog dlg(m_selectedInstance, this);
dlg.exec();
}
}
+void MainWindow::on_actionExportInstanceFlamePack_triggered()
+{
+ if (m_selectedInstance) {
+ auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
+ if (instance) {
+ QString errorMsg;
+ if (instance->getPackProfile()->getComponent("org.quiltmc.quilt-loader")) {
+ errorMsg = tr("Quilt is currently not supported by CurseForge modpacks.");
+ } else if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
+ cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
+ errorMsg = tr("Snapshots are currently not supported by CurseForge modpacks.");
+ }
+ if (!errorMsg.isEmpty()) {
+ QMessageBox msgBox;
+ msgBox.setText(errorMsg);
+ msgBox.exec();
+ return;
+ }
+ ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME);
+ dlg.exec();
+ }
+ }
+}
+
void MainWindow::on_actionRenameInstance_triggered()
{
if (m_selectedInstance)
@@ -1511,11 +1536,39 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
QString iconPath;
QStringList args;
#if defined(Q_OS_MACOS)
- if (appPath.startsWith("/private/var/")) {
- QMessageBox::critical(this, tr("Create instance shortcut"),
- tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
- return;
- }
+ appPath = QApplication::applicationFilePath();
+ if (appPath.startsWith("/private/var/")) {
+ QMessageBox::critical(this, tr("Create instance shortcut"),
+ tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts."));
+ return;
+ }
+
+ auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey());
+ if (pIcon == nullptr)
+ {
+ pIcon = APPLICATION->icons()->icon("grass");
+ }
+
+ iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns");
+
+ QFile iconFile(iconPath);
+ if (!iconFile.open(QFile::WriteOnly))
+ {
+ QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
+ return;
+ }
+
+ QIcon icon = pIcon->icon();
+
+ bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS");
+ iconFile.close();
+
+ if (!success)
+ {
+ iconFile.remove();
+ QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application."));
+ return;
+ }
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
if (appPath.startsWith("/tmp/.mount_")) {
// AppImage!
@@ -1598,7 +1651,11 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
#endif
args.append({ "--launch", m_selectedInstance->id() });
if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
- QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
+#if not defined(Q_OS_MACOS)
+ QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!"));
+#else
+ QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!"));
+#endif
} else {
#if not defined(Q_OS_MACOS)
iconFile.remove();
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 3bb20c4a..5f74b501 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -157,6 +157,7 @@ private slots:
inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered();
+ void on_actionExportInstanceFlamePack_triggered();
void on_actionRenameInstance_triggered();
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index 113dfc1e..190219b9 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -479,6 +479,14 @@
<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="actionCreateInstanceShortcut">
<property name="icon">
<iconset theme="shortcut">
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp
index b302f7ba..2abe2805 100644
--- a/launcher/ui/dialogs/ExportMrPackDialog.cpp
+++ b/launcher/ui/dialogs/ExportPackDialog.cpp
@@ -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;
-
- 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,
+ 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));
+
+ 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);
}
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h
index 1c70c4ae..830c24d2 100644
--- a/launcher/ui/dialogs/ExportMrPackDialog.h
+++ b/launcher/ui/dialogs/ExportPackDialog.h
@@ -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;
};
diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui
index 9a789737..3976e28f 100644
--- a/launcher/ui/dialogs/ExportMrPackDialog.ui
+++ b/launcher/ui/dialogs/ExportPackDialog.ui
@@ -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">
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
index 4f59f560..b17eced3 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
@@ -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]);
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index d9116bfc..26408f44 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -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>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index 173bcb66..12038f88 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -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);
}
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h
index 6c0a12cb..97d922d8 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.h
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.h
@@ -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;
};
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index 2a7c5b27..943ff17f 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -60,17 +60,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();
}
@@ -523,8 +519,3 @@ void InstanceSettingsPage::updateThresholds()
ui->labelMaxMemIcon->setPixmap(pix);
}
}
-
-void InstanceSettingsPage::updateRunningStatus(bool running)
-{
- setEnabled(!running);
-}
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h
index 0438fe3b..036b4181 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.h
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.h
@@ -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();
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 041d1bfd..cef292bd 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -89,12 +89,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
- ui->actionsToolbar->insertActionAfter(ui->actionViewFolder, ui->actionVisitItemPage);
+ ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
- auto check_allow_update = [this] {
- return (!m_instance || !m_instance->isRunning()) && (ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
- };
+ 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());
@@ -121,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;
@@ -155,13 +140,21 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelI
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
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index 2c1a9c77..a23dcae1 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -60,7 +60,6 @@ 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 installMods();
diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp
index 24bfb38d..12b371df 100644
--- a/launcher/ui/pages/instance/ResourcePackPage.cpp
+++ b/launcher/ui/pages/instance/ResourcePackPage.cpp
@@ -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
diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp
index 2d0c10aa..dc8b0a05 100644
--- a/launcher/ui/pages/instance/ShaderPackPage.cpp
+++ b/launcher/ui/pages/instance/ShaderPackPage.cpp
@@ -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
diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp
index 427aba11..e477ceda 100644
--- a/launcher/ui/pages/instance/TexturePackPage.cpp
+++ b/launcher/ui/pages/instance/TexturePackPage.cpp
@@ -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
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index 59107c53..a180c804 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -40,14 +40,13 @@
#include "Application.h"
-#include <QMessageBox>
-#include <QLabel>
+#include <QAbstractItemModel>
#include <QEvent>
#include <QKeyEvent>
+#include <QLabel>
+#include <QListView>
#include <QMenu>
-#include <QAbstractItemModel>
#include <QMessageBox>
-#include <QListView>
#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 &current, 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 &current, 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 &current, 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 &current, 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 &current, 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);
}
diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h
index d0087714..45d383f4 100644
--- a/launcher/ui/pages/instance/VersionPage.h
+++ b/launcher/ui/pages/instance/VersionPage.h
@@ -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 &current, 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 &current, 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);
};
diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp
index ac34e3aa..a77c45fe 100644
--- a/launcher/ui/widgets/WideBar.cpp
+++ b/launcher/ui/widgets/WideBar.cpp
@@ -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;
}