aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp14
-rw-r--r--launcher/CMakeLists.txt6
-rw-r--r--launcher/FileSystem.cpp60
-rw-r--r--launcher/minecraft/PackProfile.cpp3
-rw-r--r--launcher/minecraft/mod/Mod.cpp4
-rw-r--r--launcher/minecraft/mod/Mod.h2
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp6
-rw-r--r--launcher/minecraft/mod/Resource.cpp8
-rw-r--r--launcher/minecraft/mod/Resource.h2
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourceParse.cpp9
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp2
-rw-r--r--launcher/modplatform/EnsureMetadataTask.cpp6
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp23
-rw-r--r--launcher/modplatform/flame/FlameAPI.h2
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp8
-rw-r--r--launcher/modplatform/flame/PackManifest.cpp9
-rw-r--r--launcher/modplatform/helpers/ExportToModList.cpp200
-rw-r--r--launcher/modplatform/helpers/ExportToModList.h33
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.cpp86
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.h27
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h6
-rw-r--r--launcher/translations/TranslationsModel.cpp30
-rw-r--r--launcher/translations/TranslationsModel.h29
-rw-r--r--launcher/ui/MainWindow.cpp51
-rw-r--r--launcher/ui/MainWindow.h1
-rw-r--r--launcher/ui/MainWindow.ui10
-rw-r--r--launcher/ui/dialogs/ExportToModListDialog.cpp223
-rw-r--r--launcher/ui/dialogs/ExportToModListDialog.h55
-rw-r--r--launcher/ui/dialogs/ExportToModListDialog.ui240
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp9
-rw-r--r--launcher/ui/pages/instance/ManagedPackPage.cpp25
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp1
-rw-r--r--launcher/ui/widgets/LanguageSelectionWidget.cpp28
-rw-r--r--launcher/ui/widgets/LanguageSelectionWidget.h25
35 files changed, 1056 insertions, 189 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index a80af39e..814741dd 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -433,7 +433,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
// seach root path
if(!foundLoggingRules) {
+#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
+ logRulesPath = FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME, logRulesFile);
+#else
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
+#endif
qDebug() << "Testing" << logRulesPath << "...";
foundLoggingRules = QFile::exists(logRulesPath);
}
@@ -568,6 +572,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Language
m_settings->registerSetting("Language", QString());
+ m_settings->registerSetting("UseSystemLocale", false);
// Console
m_settings->registerSetting("ShowConsole", false);
@@ -918,12 +923,7 @@ bool Application::createSetupWizard()
}
return false;
}();
- bool languageRequired = [&]()
- {
- if (settings()->get("Language").toString().isEmpty())
- return true;
- return false;
- }();
+ bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
@@ -1577,7 +1577,7 @@ QString Application::getJarPath(QString jarFile)
{
QStringList potentialPaths = {
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
- FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME),
+ FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME),
#endif
FS::PathCombine(m_rootPath, "jars"),
FS::PathCombine(applicationDirPath(), "jars"),
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 203ab986..2d06dbf4 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -487,6 +487,9 @@ set(API_SOURCES
modplatform/helpers/HashUtils.cpp
modplatform/helpers/OverrideUtils.h
modplatform/helpers/OverrideUtils.cpp
+
+ modplatform/helpers/ExportToModList.h
+ modplatform/helpers/ExportToModList.cpp
)
set(FTB_SOURCES
@@ -913,6 +916,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ExportInstanceDialog.h
ui/dialogs/ExportPackDialog.cpp
ui/dialogs/ExportPackDialog.h
+ ui/dialogs/ExportToModListDialog.cpp
+ ui/dialogs/ExportToModListDialog.h
ui/dialogs/IconPickerDialog.cpp
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp
@@ -1060,6 +1065,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/ExportPackDialog.ui
+ ui/dialogs/ExportToModListDialog.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/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/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index fa1a0253..880dacb1 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -126,7 +126,7 @@ bool Mod::applyFilter(QRegularExpression filter) const
return Resource::applyFilter(filter);
}
-auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
+auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
@@ -139,7 +139,7 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
}
}
- return Resource::destroy();
+ return Resource::destroy(attempt_trash);
}
auto Mod::details() const -> const ModDetails&
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index d6272f4d..b67bd465 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -93,7 +93,7 @@ public:
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
// Delete all the files of this mod
- auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool;
+ auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
void finishResolvingWithDetails(ModDetails&& details);
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index d4ff29e6..51383edf 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -199,10 +199,10 @@ Task* ModFolderModel::createParseTask(Resource& resource)
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
{
- for(auto mod : allMods()){
- if(mod->fileinfo().fileName() == filename){
+ for(auto mod : allMods()) {
+ if(mod->fileinfo().fileName() == filename) {
auto index_dir = indexDir();
- mod->destroy(index_dir, preserve_metadata);
+ mod->destroy(index_dir, preserve_metadata, false);
update();
diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp
index e5077260..098a617f 100644
--- a/launcher/minecraft/mod/Resource.cpp
+++ b/launcher/minecraft/mod/Resource.cpp
@@ -148,14 +148,10 @@ bool Resource::enable(EnableAction action)
return true;
}
-bool Resource::destroy()
+bool Resource::destroy(bool attemptTrash)
{
m_type = ResourceType::UNKNOWN;
-
- if (FS::trash(m_file_info.filePath()))
- return true;
-
- return FS::deletePath(m_file_info.filePath());
+ return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
}
bool Resource::isSymLinkUnder(const QString& instPath) const
diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h
index a5e9ae91..94f3160c 100644
--- a/launcher/minecraft/mod/Resource.h
+++ b/launcher/minecraft/mod/Resource.h
@@ -92,7 +92,7 @@ class Resource : public QObject {
}
// Delete all files of this resource.
- bool destroy();
+ bool destroy(bool attemptTrash = true);
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index c2c0d178..39a61067 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -157,7 +157,7 @@ bool ResourceFolderModel::uninstallResource(QString file_name)
{
for (auto& resource : m_resources) {
if (resource->fileinfo().fileName() == file_name) {
- auto res = resource->destroy();
+ auto res = resource->destroy(false);
update();
diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp
index 4d760df2..0894049c 100644
--- a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp
@@ -44,7 +44,11 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = {
namespace ResourceUtils {
PackedResourceType identify(QFileInfo file){
if (file.exists() && file.isFile()) {
- if (ResourcePackUtils::validate(file)) {
+ if (ModUtils::validate(file)) {
+ // mods can contain resource and data packs so they must be tested first
+ qDebug() << file.fileName() << "is a mod";
+ return PackedResourceType::Mod;
+ } else if (ResourcePackUtils::validate(file)) {
qDebug() << file.fileName() << "is a resource pack";
return PackedResourceType::ResourcePack;
} else if (TexturePackUtils::validate(file)) {
@@ -53,9 +57,6 @@ PackedResourceType identify(QFileInfo file){
} else if (DataPackUtils::validate(file)) {
qDebug() << file.fileName() << "is a data pack";
return PackedResourceType::DataPack;
- } else if (ModUtils::validate(file)) {
- qDebug() << file.fileName() << "is a mod";
- return PackedResourceType::Mod;
} else if (WorldSaveUtils::validate(file)) {
qDebug() << file.fileName() << "is a world save";
return PackedResourceType::WorldSave;
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index 3677a1dc..ef353c70 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -103,7 +103,7 @@ void ModFolderLoadTask::executeTask()
while (iter.hasNext()) {
auto mod = iter.next().value();
if (mod->status() == ModStatus::NotInstalled) {
- mod->destroy(m_index_dir, false);
+ mod->destroy(m_index_dir, false, false);
iter.remove();
}
}
diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp
index 93b5ce76..c3eadd06 100644
--- a/launcher/modplatform/EnsureMetadataTask.cpp
+++ b/launcher/modplatform/EnsureMetadataTask.cpp
@@ -145,7 +145,8 @@ void EnsureMetadataTask::executeTask()
connect(project_task.get(), &Task::finished, this, [=] {
invalidade_leftover();
project_task->deleteLater();
- m_current_task = nullptr;
+ if (m_current_task)
+ m_current_task.reset();
});
m_current_task = project_task;
@@ -154,7 +155,8 @@ void EnsureMetadataTask::executeTask()
connect(version_task.get(), &Task::finished, [=] {
version_task->deleteLater();
- m_current_task = nullptr;
+ if (m_current_task)
+ m_current_task.reset();
});
if (m_mods.size() > 1)
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index ce7a6055..34bd401d 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -21,6 +21,10 @@ bool Flame::FileResolvingTask::abort()
void Flame::FileResolvingTask::executeTask()
{
+ if (m_toProcess.files.isEmpty()) { // no file to resolve so leave it empty and emit success immediately
+ emitSucceeded();
+ return;
+ }
setStatus(tr("Resolving mod IDs..."));
setProgress(0, 3);
m_dljob.reset(new NetJob("Mod id resolver", m_network));
@@ -128,12 +132,13 @@ void Flame::FileResolvingTask::netJobFinished()
m_checkJob->start();
}
-void Flame::FileResolvingTask::modrinthCheckFinished() {
+void Flame::FileResolvingTask::modrinthCheckFinished()
+{
setProgress(2, 3);
qDebug() << "Finished with blocked mods : " << blockedProjects.size();
for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) {
- auto &out = *it;
+ auto& out = *it;
auto bytes = blockedProjects[out];
if (!out->resolved) {
continue;
@@ -153,15 +158,13 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
out->resolved = false;
}
}
- //copy to an output list and filter out projects found on modrinth
+ // copy to an output list and filter out projects found on modrinth
auto block = std::make_shared<QList<File*>>();
auto it = blockedProjects.keys();
- std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
- return !f->resolved;
- });
- //Display not found mods early
+ std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File* f) { return !f->resolved; });
+ // Display not found mods early
if (!block->empty()) {
- //blocked mods found, we need the slug for displaying.... we need another job :D !
+ // blocked mods found, we need the slug for displaying.... we need another job :D !
m_slugJob.reset(new NetJob("Slug Job", m_network));
int index = 0;
for (auto mod : *block) {
@@ -173,8 +176,8 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
auto json = QJsonDocument::fromJson(*output);
- auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
- "websiteUrl");
+ auto base =
+ Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json), "data"), "links"), "websiteUrl");
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
mod->websiteUrl = link;
});
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/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
index e7641d64..b57db288 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
@@ -563,6 +563,8 @@ void FlameCreationTask::validateZIPResouces()
if (FS::move(localPath, destPath)) {
return destPath;
}
+ } else {
+ qDebug() << "Target folder of" << fileName << "is correct at" << targetFolder;
}
return localPath;
};
@@ -584,6 +586,9 @@ void FlameCreationTask::validateZIPResouces()
QString worldPath;
switch (type) {
+ case PackedResourceType::Mod :
+ validatePath(fileName, targetFolder, "mods");
+ break;
case PackedResourceType::ResourcePack :
validatePath(fileName, targetFolder, "resourcepacks");
break;
@@ -593,9 +598,6 @@ void FlameCreationTask::validateZIPResouces()
case PackedResourceType::DataPack :
validatePath(fileName, targetFolder, "datapacks");
break;
- case PackedResourceType::Mod :
- validatePath(fileName, targetFolder, "mods");
- break;
case PackedResourceType::ShaderPack :
// in theroy flame API can't do this but who knows, that *may* change ?
// better to handle it if it *does* occure in the future
diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp
index 22008297..ee4d0766 100644
--- a/launcher/modplatform/flame/PackManifest.cpp
+++ b/launcher/modplatform/flame/PackManifest.cpp
@@ -76,13 +76,8 @@ bool Flame::File::parseFromObject(const QJsonObject& obj, bool throw_on_blocked
// It is also optional
type = File::Type::SingleFile;
- if (fileName.endsWith(".zip")) {
- // this is probably a resource pack
- targetFolder = "resourcepacks";
- } else {
- // this is probably a mod, dunno what else could modpacks download
- targetFolder = "mods";
- }
+ targetFolder = "mods";
+
// get the hash
hash = QString();
auto hashes = Json::ensureArray(obj, "hashes");
diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp
new file mode 100644
index 00000000..1f01c4a8
--- /dev/null
+++ b/launcher/modplatform/helpers/ExportToModList.cpp
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#include "ExportToModList.h"
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+
+namespace ExportToModList {
+QString toHTML(QList<Mod*> mods, OptionalData extraData)
+{
+ QStringList lines;
+ for (auto mod : mods) {
+ auto meta = mod->metadata();
+ auto modName = mod->name().toHtmlEscaped();
+ if (extraData & Url) {
+ auto url = mod->metaurl().toHtmlEscaped();
+ if (!url.isEmpty())
+ modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
+ }
+ auto line = modName;
+ if (extraData & Version) {
+ auto ver = mod->version();
+ if (ver.isEmpty() && meta != nullptr)
+ ver = meta->version().toString();
+ if (!ver.isEmpty())
+ line += QString(" [%1]").arg(ver.toHtmlEscaped());
+ }
+ if (extraData & Authors && !mod->authors().isEmpty())
+ line += " by " + mod->authors().join(", ").toHtmlEscaped();
+ lines.append(QString("<li>%1</li>").arg(line));
+ }
+ return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t"));
+}
+
+QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
+{
+ QStringList lines;
+ for (auto mod : mods) {
+ auto meta = mod->metadata();
+ auto modName = mod->name();
+ if (extraData & Url) {
+ auto url = mod->metaurl();
+ if (!url.isEmpty())
+ modName = QString("[%1](%2)").arg(modName, url);
+ }
+ auto line = modName;
+ if (extraData & Version) {
+ auto ver = mod->version();
+ if (ver.isEmpty() && meta != nullptr)
+ ver = meta->version().toString();
+ if (!ver.isEmpty())
+ line += QString(" [%1]").arg(ver);
+ }
+ if (extraData & Authors && !mod->authors().isEmpty())
+ line += " by " + mod->authors().join(", ");
+ lines << "- " + line;
+ }
+ return lines.join("\n");
+}
+
+QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
+{
+ QStringList lines;
+ for (auto mod : mods) {
+ auto meta = mod->metadata();
+ auto modName = mod->name();
+
+ auto line = modName;
+ if (extraData & Url) {
+ auto url = mod->metaurl();
+ if (!url.isEmpty())
+ line += QString(" (%1)").arg(url);
+ }
+ if (extraData & Version) {
+ auto ver = mod->version();
+ if (ver.isEmpty() && meta != nullptr)
+ ver = meta->version().toString();
+ if (!ver.isEmpty())
+ line += QString(" [%1]").arg(ver);
+ }
+ if (extraData & Authors && !mod->authors().isEmpty())
+ line += " by " + mod->authors().join(", ");
+ lines << line;
+ }
+ return lines.join("\n");
+}
+
+QString toJSON(QList<Mod*> mods, OptionalData extraData)
+{
+ QJsonArray lines;
+ for (auto mod : mods) {
+ auto meta = mod->metadata();
+ auto modName = mod->name();
+ QJsonObject line;
+ line["name"] = modName;
+ if (extraData & Url) {
+ auto url = mod->metaurl();
+ if (!url.isEmpty())
+ line["url"] = url;
+ }
+ if (extraData & Version) {
+ auto ver = mod->version();
+ if (ver.isEmpty() && meta != nullptr)
+ ver = meta->version().toString();
+ if (!ver.isEmpty())
+ line["version"] = ver;
+ }
+ if (extraData & Authors && !mod->authors().isEmpty())
+ line["authors"] = QJsonArray::fromStringList(mod->authors());
+ lines << line;
+ }
+ QJsonDocument doc;
+ doc.setArray(lines);
+ return doc.toJson();
+}
+
+QString toCSV(QList<Mod*> mods, OptionalData extraData)
+{
+ QStringList lines;
+ for (auto mod : mods) {
+ QStringList data;
+ auto meta = mod->metadata();
+ auto modName = mod->name();
+
+ data << modName;
+ if (extraData & Url)
+ data << mod->metaurl();
+ if (extraData & Version) {
+ auto ver = mod->version();
+ if (ver.isEmpty() && meta != nullptr)
+ ver = meta->version().toString();
+ data << ver;
+ }
+ if (extraData & Authors) {
+ QString authors;
+ if (mod->authors().length() == 1)
+ authors = mod->authors().back();
+ else if (mod->authors().length() > 1)
+ authors = QString("\"%1\"").arg(mod->authors().join(","));
+ data << authors;
+ }
+ lines << data.join(",");
+ }
+ return lines.join("\n");
+}
+
+QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData)
+{
+ switch (format) {
+ case HTML:
+ return toHTML(mods, extraData);
+ case MARKDOWN:
+ return toMarkdown(mods, extraData);
+ case PLAINTXT:
+ return toPlainTXT(mods, extraData);
+ case JSON:
+ return toJSON(mods, extraData);
+ case CSV:
+ return toCSV(mods, extraData);
+ default: {
+ return QString("unknown format:%1").arg(format);
+ }
+ }
+}
+
+QString exportToModList(QList<Mod*> mods, QString lineTemplate)
+{
+ QStringList lines;
+ for (auto mod : mods) {
+ auto meta = mod->metadata();
+ auto modName = mod->name();
+ auto url = mod->metaurl();
+ auto ver = mod->version();
+ if (ver.isEmpty() && meta != nullptr)
+ ver = meta->version().toString();
+ auto authors = mod->authors().join(", ");
+ lines << QString(lineTemplate)
+ .replace("{name}", modName)
+ .replace("{url}", url)
+ .replace("{version}", ver)
+ .replace("{authors}", authors);
+ }
+ return lines.join("\n");
+}
+} // namespace ExportToModList \ No newline at end of file
diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h
new file mode 100644
index 00000000..7ea4ba9c
--- /dev/null
+++ b/launcher/modplatform/helpers/ExportToModList.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#pragma once
+#include <QList>
+#include <QString>
+#include "minecraft/mod/Mod.h"
+
+namespace ExportToModList {
+
+enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM };
+enum OptionalData {
+ Authors = 1 << 0,
+ Url = 1 << 1,
+ Version = 1 << 2,
+};
+QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData);
+QString exportToModList(QList<Mod*> mods, QString lineTemplate);
+} // namespace ExportToModList
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
index 36c142ac..a4c78397 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -37,16 +37,16 @@
#include <QtConcurrent>
-#include "MMCZip.h"
#include "BaseInstance.h"
#include "FileSystem.h"
-#include "settings/INISettingsObject.h"
+#include "MMCZip.h"
+#include "minecraft/GradleSpecifier.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
-#include "minecraft/GradleSpecifier.h"
+#include "settings/INISettingsObject.h"
-#include "BuildConfig.h"
#include "Application.h"
+#include "BuildConfig.h"
namespace LegacyFTB {
@@ -65,6 +65,7 @@ void PackInstallTask::executeTask()
void PackInstallTask::downloadPack()
{
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
+ setProgress(1, 4);
setAbortable(false);
archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file);
@@ -78,11 +79,10 @@ void PackInstallTask::downloadPack()
}
netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath));
- connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
- connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
- connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
+ connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
+ connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
- connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
+ connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted);
netJobContainer->start();
@@ -90,27 +90,6 @@ void PackInstallTask::downloadPack()
progress(1, 4);
}
-void PackInstallTask::onDownloadSucceeded()
-{
- unzip();
-}
-
-void PackInstallTask::onDownloadFailed(QString reason)
-{
- emitFailed(reason);
-}
-
-void PackInstallTask::onDownloadProgress(qint64 current, qint64 total)
-{
- progress(current, total * 4);
- setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10));
-}
-
-void PackInstallTask::onDownloadAborted()
-{
- emitAborted();
-}
-
void PackInstallTask::unzip()
{
setStatus(tr("Extracting modpack"));
@@ -120,16 +99,17 @@ void PackInstallTask::unzip()
QDir extractDir(m_stagingPath);
m_packZip.reset(new QuaZip(archivePath));
- if(!m_packZip->open(QuaZip::mdUnzip))
- {
+ if (!m_packZip->open(QuaZip::mdUnzip)) {
emitFailed(tr("Failed to open modpack file %1!").arg(archivePath));
return;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip");
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath,
+ extractDir.absolutePath() + "/unzip");
#else
- m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
+ m_extractFuture =
+ QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
#endif
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
@@ -151,11 +131,9 @@ void PackInstallTask::install()
setStatus(tr("Installing modpack"));
progress(3, 4);
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
- if(unzipMcDir.exists())
- {
- //ok, found minecraft dir, move contents to instance dir
- if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft"))
- {
+ if (unzipMcDir.exists()) {
+ // ok, found minecraft dir, move contents to instance dir
+ if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) {
emitFailed(tr("Failed to move unzipped Minecraft!"));
return;
}
@@ -172,23 +150,20 @@ void PackInstallTask::install()
bool fallback = true;
- //handle different versions
+ // handle different versions
QFile packJson(m_stagingPath + "/.minecraft/pack.json");
QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods");
- if(packJson.exists())
- {
+ if (packJson.exists()) {
packJson.open(QIODevice::ReadOnly | QIODevice::Text);
QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll());
packJson.close();
- //we only care about the libs
+ // we only care about the libs
QJsonArray libs = doc.object().value("libraries").toArray();
- foreach (const QJsonValue &value, libs)
- {
+ foreach (const QJsonValue& value, libs) {
QString nameValue = value.toObject().value("name").toString();
- if(!nameValue.startsWith("net.minecraftforge"))
- {
+ if (!nameValue.startsWith("net.minecraftforge")) {
continue;
}
@@ -199,16 +174,13 @@ void PackInstallTask::install()
fallback = false;
break;
}
-
}
- if(jarmodDir.exists())
- {
+ if (jarmodDir.exists()) {
qDebug() << "Found jarmods, installing...";
QStringList jarmods;
- for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
- {
+ for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
qDebug() << "Jarmod:" << info.fileName();
jarmods.push_back(info.absoluteFilePath());
}
@@ -217,12 +189,11 @@ void PackInstallTask::install()
fallback = false;
}
- //just nuke unzip directory, it s not needed anymore
+ // just nuke unzip directory, it s not needed anymore
FS::deletePath(m_stagingPath + "/unzip");
- if(fallback)
- {
- //TODO: Some fallback mechanism... or just keep failing!
+ if (fallback) {
+ // TODO: Some fallback mechanism... or just keep failing!
emitFailed(tr("No installation method found!"));
return;
}
@@ -232,8 +203,7 @@ void PackInstallTask::install()
progress(4, 4);
instance.setName(name());
- if(m_instIcon == "default")
- {
+ if (m_instIcon == "default") {
m_instIcon = "ftb_logo";
}
instance.setIconKey(m_instIcon);
@@ -252,4 +222,4 @@ bool PackInstallTask::abort()
return InstanceTask::abort();
}
-}
+} // namespace LegacyFTB
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h
index da791e06..30ff4859 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.h
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h
@@ -1,12 +1,12 @@
#pragma once
-#include "InstanceTask.h"
-#include "net/NetJob.h"
#include <quazip/quazip.h>
#include <quazip/quazipdir.h>
+#include "InstanceTask.h"
+#include "PackHelpers.h"
#include "meta/Index.h"
#include "meta/Version.h"
#include "meta/VersionList.h"
-#include "PackHelpers.h"
+#include "net/NetJob.h"
#include "net/NetJob.h"
@@ -14,36 +14,31 @@
namespace LegacyFTB {
-class PackInstallTask : public InstanceTask
-{
+class PackInstallTask : public InstanceTask {
Q_OBJECT
-public:
+ public:
explicit PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version);
- virtual ~PackInstallTask(){}
+ virtual ~PackInstallTask() {}
bool canAbort() const override { return true; }
bool abort() override;
-protected:
+ protected:
//! Entry point for tasks.
virtual void executeTask() override;
-private:
+ private:
void downloadPack();
void unzip();
void install();
-private slots:
- void onDownloadSucceeded();
- void onDownloadFailed(QString reason);
- void onDownloadProgress(qint64 current, qint64 total);
- void onDownloadAborted();
+ private slots:
void onUnzipFinished();
void onUnzipCanceled();
-private: /* data */
+ private: /* data */
shared_qobject_ptr<QNetworkAccessManager> m_network;
bool abortable = false;
std::unique_ptr<QuaZip> m_packZip;
@@ -56,4 +51,4 @@ private: /* data */
QString m_version;
};
-}
+} // namespace LegacyFTB
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/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp
index 489dff86..2763cca2 100644
--- a/launcher/translations/TranslationsModel.cpp
+++ b/launcher/translations/TranslationsModel.cpp
@@ -42,6 +42,7 @@
#include <QDir>
#include <QLibraryInfo>
#include <QDebug>
+#include <locale>
#include "FileSystem.h"
#include "net/NetJob.h"
@@ -527,34 +528,34 @@ Language * TranslationsModel::findLanguage(const QString& key)
}
}
+void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
+{
+ APPLICATION->settings()->set("UseSystemLocale", useSystemLocale);
+ QLocale::setDefault(QLocale(useSystemLocale ? QString::fromStdString(std::locale().name()) : defaultLangCode));
+}
+
bool TranslationsModel::selectLanguage(QString key)
{
- QString &langCode = key;
+ QString& langCode = key;
auto langPtr = findLanguage(key);
- if (langCode.isEmpty())
- {
+ if (langCode.isEmpty()) {
d->no_language_set = true;
}
- if(!langPtr)
- {
+ if (!langPtr) {
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
langCode = defaultLangCode;
- }
- else
- {
+ } else {
langCode = langPtr->key;
}
// uninstall existing translators if there are any
- if (d->m_app_translator)
- {
+ if (d->m_app_translator) {
QCoreApplication::removeTranslator(d->m_app_translator.get());
d->m_app_translator.reset();
}
- if (d->m_qt_translator)
- {
+ if (d->m_qt_translator) {
QCoreApplication::removeTranslator(d->m_qt_translator.get());
d->m_qt_translator.reset();
}
@@ -564,8 +565,9 @@ bool TranslationsModel::selectLanguage(QString key)
* In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created.
* This function is not reentrant.
*/
- QLocale locale = QLocale(langCode);
- QLocale::setDefault(locale);
+ QLocale::setDefault(
+ QLocale(APPLICATION->settings()->get("UseSystemLocale").toBool() ? QString::fromStdString(std::locale().name()) : langCode));
+
// if it's the default UI language, finish
if(langCode == defaultLangCode)
diff --git a/launcher/translations/TranslationsModel.h b/launcher/translations/TranslationsModel.h
index 3abf84e6..cff23ce7 100644
--- a/launcher/translations/TranslationsModel.h
+++ b/launcher/translations/TranslationsModel.h
@@ -20,17 +20,16 @@
struct Language;
-class TranslationsModel : public QAbstractListModel
-{
+class TranslationsModel : public QAbstractListModel {
Q_OBJECT
-public:
- explicit TranslationsModel(QString path, QObject *parent = 0);
+ public:
+ explicit TranslationsModel(QString path, QObject* parent = 0);
virtual ~TranslationsModel();
- QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
- int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- int columnCount(const QModelIndex & parent) const override;
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex& parent) const override;
bool selectLanguage(QString key);
void updateLanguage(QString key);
@@ -38,27 +37,27 @@ public:
QString selectedLanguage();
void downloadIndex();
+ void setUseSystemLocale(bool useSystemLocale);
-private:
- Language *findLanguage(const QString & key);
+ private:
+ Language* findLanguage(const QString& key);
void reloadLocalFiles();
void downloadTranslation(QString key);
void downloadNext();
// hide copy constructor
- TranslationsModel(const TranslationsModel &) = delete;
+ TranslationsModel(const TranslationsModel&) = delete;
// hide assign op
- TranslationsModel &operator=(const TranslationsModel &) = delete;
+ TranslationsModel& operator=(const TranslationsModel&) = delete;
-private slots:
+ private slots:
void indexReceived();
void indexFailed(QString reason);
void dlFailed(QString reason);
void dlGood();
- void translationDirChanged(const QString &path);
+ void translationDirChanged(const QString& path);
-
-private: /* data */
+ private: /* data */
struct Private;
std::unique_ptr<Private> d;
};
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 13155e08..da572fc3 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -43,6 +43,7 @@
#include "FileSystem.h"
#include "MainWindow.h"
+#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h"
#include <QDir>
@@ -202,6 +203,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
exportInstanceMenu->addAction(ui->actionExportInstanceZip);
exportInstanceMenu->addAction(ui->actionExportInstanceMrPack);
exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack);
+ exportInstanceMenu->addAction(ui->actionExportInstanceToModList);
ui->actionExportInstance->setMenu(exportInstanceMenu);
}
@@ -1186,7 +1188,17 @@ void MainWindow::globalSettingsClosed()
void MainWindow::on_actionEditInstance_triggered()
{
- APPLICATION->showInstanceWindow(m_selectedInstance);
+ if (!m_selectedInstance)
+ return;
+
+ if (m_selectedInstance->canEdit()) {
+ APPLICATION->showInstanceWindow(m_selectedInstance);
+ } else {
+ CustomMessageBox::selectable(this, tr("Instance not editable"),
+ tr("This instance is not editable. It may be broken, invalid, or too old. Check logs for details."),
+ QMessageBox::Critical)
+ ->show();
+ }
}
void MainWindow::on_actionManageAccounts_triggered()
@@ -1317,6 +1329,14 @@ void MainWindow::on_actionExportInstanceMrPack_triggered()
}
}
+void MainWindow::on_actionExportInstanceToModList_triggered()
+{
+ if (m_selectedInstance) {
+ ExportToModListDialog dlg(m_selectedInstance, this);
+ dlg.exec();
+ }
+}
+
void MainWindow::on_actionExportInstanceFlamePack_triggered()
{
if (m_selectedInstance) {
@@ -1435,11 +1455,36 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
QString iconPath;
QStringList args;
#if defined(Q_OS_MACOS)
+ 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!
@@ -1522,7 +1567,11 @@ void MainWindow::on_actionCreateInstanceShortcut_triggered()
#endif
args.append({ "--launch", m_selectedInstance->id() });
if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) {
+#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 5f74b501..27c2756f 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -158,6 +158,7 @@ private slots:
void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered();
+ void on_actionExportInstanceToModList_triggered();
void on_actionRenameInstance_triggered();
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index 190219b9..e4421d40 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -484,7 +484,15 @@
<iconset theme="flame"/>
</property>
<property name="text">
- <string>CurseForge (zip)</string>
+ <string>CurseForge (zip)</string>
+ </property>
+ </action>
+ <action name="actionExportInstanceToModList">
+ <property name="icon">
+ <iconset theme="new"/>
+ </property>
+ <property name="text">
+ <string>Mod List</string>
</property>
</action>
<action name="actionCreateInstanceShortcut">
diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp
new file mode 100644
index 00000000..c811bfe6
--- /dev/null
+++ b/launcher/ui/dialogs/ExportToModListDialog.cpp
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ExportToModListDialog.h"
+#include <QCheckBox>
+#include <QComboBox>
+#include <QTextEdit>
+#include "FileSystem.h"
+#include "Markdown.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/helpers/ExportToModList.h"
+#include "ui_ExportToModListDialog.h"
+
+#include <QFileDialog>
+#include <QFileSystemModel>
+#include <QJsonDocument>
+#include <QMessageBox>
+#include <QPushButton>
+
+const QHash<ExportToModList::Formats, QString> ExportToModListDialog::exampleLines = {
+ { ExportToModList::HTML, "<li><a href=\"{url}\">{name}</a> [{version}] by {authors}</li>" },
+ { ExportToModList::MARKDOWN, "[{name}]({url}) [{version}] by {authors}" },
+ { ExportToModList::PLAINTXT, "{name} ({url}) [{version}] by {authors}" },
+ { ExportToModList::JSON, "{\"name\":\"{name}\",\"url\":\"{url}\",\"version\":\"{version}\",\"authors\":\"{authors}\"}," },
+ { ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" },
+};
+
+ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent)
+ : QDialog(parent), m_template_changed(false), name(instance->name()), ui(new Ui::ExportToModListDialog)
+{
+ ui->setupUi(this);
+ enableCustom(false);
+
+ MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
+ if (mcInstance) {
+ mcInstance->loaderModList()->update();
+ connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() {
+ m_allMods = mcInstance->loaderModList()->allMods();
+ triggerImp();
+ });
+ }
+
+ connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged);
+ connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
+ connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
+ connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger);
+ connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); });
+ connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); });
+ connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); });
+ connect(ui->templateText, &QTextEdit::textChanged, this, [this] {
+ if (ui->templateText->toPlainText() != exampleLines[format])
+ ui->formatComboBox->setCurrentIndex(5);
+ else
+ triggerImp();
+ });
+ connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) {
+ this->ui->finalText->selectAll();
+ this->ui->finalText->copy();
+ });
+}
+
+ExportToModListDialog::~ExportToModListDialog()
+{
+ delete ui;
+}
+
+void ExportToModListDialog::formatChanged(int index)
+{
+ switch (index) {
+ case 0: {
+ enableCustom(false);
+ ui->resultText->show();
+ format = ExportToModList::HTML;
+ break;
+ }
+ case 1: {
+ enableCustom(false);
+ ui->resultText->show();
+ format = ExportToModList::MARKDOWN;
+ break;
+ }
+ case 2: {
+ enableCustom(false);
+ ui->resultText->hide();
+ format = ExportToModList::PLAINTXT;
+ break;
+ }
+ case 3: {
+ enableCustom(false);
+ ui->resultText->hide();
+ format = ExportToModList::JSON;
+ break;
+ }
+ case 4: {
+ enableCustom(false);
+ ui->resultText->hide();
+ format = ExportToModList::CSV;
+ break;
+ }
+ case 5: {
+ m_template_changed = true;
+ enableCustom(true);
+ ui->resultText->hide();
+ format = ExportToModList::CUSTOM;
+ break;
+ }
+ }
+ triggerImp();
+}
+
+void ExportToModListDialog::triggerImp()
+{
+ if (format == ExportToModList::CUSTOM) {
+ ui->finalText->setPlainText(ExportToModList::exportToModList(m_allMods, ui->templateText->toPlainText()));
+ return;
+ }
+ auto opt = 0;
+ if (ui->authorsCheckBox->isChecked())
+ opt |= ExportToModList::Authors;
+ if (ui->versionCheckBox->isChecked())
+ opt |= ExportToModList::Version;
+ if (ui->urlCheckBox->isChecked())
+ opt |= ExportToModList::Url;
+ auto txt = ExportToModList::exportToModList(m_allMods, format, static_cast<ExportToModList::OptionalData>(opt));
+ ui->finalText->setPlainText(txt);
+ switch (format) {
+ case ExportToModList::CUSTOM:
+ return;
+ case ExportToModList::HTML:
+ ui->resultText->setHtml(txt);
+ break;
+ case ExportToModList::MARKDOWN:
+ ui->resultText->setHtml(markdownToHTML(txt));
+ break;
+ case ExportToModList::PLAINTXT:
+ break;
+ case ExportToModList::JSON:
+ break;
+ case ExportToModList::CSV:
+ break;
+ }
+ auto exampleLine = exampleLines[format];
+ if (!m_template_changed && ui->templateText->toPlainText() != exampleLine)
+ ui->templateText->setPlainText(exampleLine);
+}
+
+void ExportToModListDialog::done(int result)
+{
+ if (result == Accepted) {
+ const QString filename = FS::RemoveInvalidFilenameChars(name);
+ const QString output =
+ QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()),
+ "File (*.txt *.html *.md *.json *.csv)", nullptr);
+
+ if (output.isEmpty())
+ return;
+ FS::write(output, ui->finalText->toPlainText().toUtf8());
+ }
+
+ QDialog::done(result);
+}
+
+QString ExportToModListDialog::extension()
+{
+ switch (format) {
+ case ExportToModList::HTML:
+ return ".html";
+ case ExportToModList::MARKDOWN:
+ return ".md";
+ case ExportToModList::PLAINTXT:
+ return ".txt";
+ case ExportToModList::CUSTOM:
+ return ".txt";
+ case ExportToModList::JSON:
+ return ".json";
+ case ExportToModList::CSV:
+ return ".csv";
+ }
+ return ".txt";
+}
+
+void ExportToModListDialog::addExtra(ExportToModList::OptionalData option)
+{
+ if (format != ExportToModList::CUSTOM)
+ return;
+ switch (option) {
+ case ExportToModList::Authors:
+ ui->templateText->insertPlainText("{authors}");
+ break;
+ case ExportToModList::Url:
+ ui->templateText->insertPlainText("{url}");
+ break;
+ case ExportToModList::Version:
+ ui->templateText->insertPlainText("{version}");
+ break;
+ }
+}
+void ExportToModListDialog::enableCustom(bool enabled)
+{
+ ui->authorsCheckBox->setHidden(enabled);
+ ui->versionCheckBox->setHidden(enabled);
+ ui->urlCheckBox->setHidden(enabled);
+
+ ui->authorsButton->setHidden(!enabled);
+ ui->versionButton->setHidden(!enabled);
+ ui->urlButton->setHidden(!enabled);
+}
diff --git a/launcher/ui/dialogs/ExportToModListDialog.h b/launcher/ui/dialogs/ExportToModListDialog.h
new file mode 100644
index 00000000..9886ae5a
--- /dev/null
+++ b/launcher/ui/dialogs/ExportToModListDialog.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <QDialog>
+#include <QList>
+#include "BaseInstance.h"
+#include "minecraft/mod/Mod.h"
+#include "modplatform/helpers/ExportToModList.h"
+
+namespace Ui {
+class ExportToModListDialog;
+}
+
+class ExportToModListDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr);
+ ~ExportToModListDialog();
+
+ void done(int result) override;
+
+ protected slots:
+ void formatChanged(int index);
+ void triggerImp();
+ void trigger(int) { triggerImp(); };
+ void addExtra(ExportToModList::OptionalData option);
+
+ private:
+ QString extension();
+ void enableCustom(bool enabled);
+ QList<Mod*> m_allMods;
+ bool m_template_changed;
+ QString name;
+ ExportToModList::Formats format = ExportToModList::Formats::HTML;
+ Ui::ExportToModListDialog* ui;
+ static const QHash<ExportToModList::Formats, QString> exampleLines;
+};
diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui
new file mode 100644
index 00000000..25eb4342
--- /dev/null
+++ b/launcher/ui/dialogs/ExportToModListDialog.ui
@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportToModListDialog</class>
+ <widget class="QDialog" name="ExportToModListDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>650</width>
+ <height>446</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Export Pack to ModList</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="formatComboBox">
+ <item>
+ <property name="text">
+ <string>HTML</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Markdown</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Plaintext</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>JSON</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>CSV</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Custom</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="templateGroup">
+ <property name="title">
+ <string>Template</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QTextEdit" name="templateText"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QGroupBox" name="optionsGroup">
+ <property name="title">
+ <string>Optional Info</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QCheckBox" name="versionCheckBox">
+ <property name="text">
+ <string>Version</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="authorsCheckBox">
+ <property name="text">
+ <string>Authors</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="urlCheckBox">
+ <property name="text">
+ <string>URL</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="versionButton">
+ <property name="text">
+ <string>Version</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="authorsButton">
+ <property name="text">
+ <string>Authors</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="urlButton">
+ <property name="text">
+ <string>URL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="text">
+ <string>Format</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="title">
+ <string>Result</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPlainTextEdit" name="finalText">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>143</height>
+ </size>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="resultText">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="warningLabel">
+ <property name="text">
+ <string>This depends on the mods' metadata. To ensure it is available, run an update on the instance. Installing the updates isn't necessary.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="copyButton">
+ <property name="text">
+ <string>Copy</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ExportToModListDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>334</x>
+ <y>435</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>324</x>
+ <y>206</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ExportToModListDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>324</x>
+ <y>390</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>324</x>
+ <y>206</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
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/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp
index d89c5bfc..0fc0c986 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.cpp
+++ b/launcher/ui/pages/instance/ManagedPackPage.cpp
@@ -69,7 +69,6 @@ class NoBigComboBoxStyle : public QProxyStyle {
private:
NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
-
};
ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent)
@@ -91,13 +90,13 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
// NOTE: GTK2 themes crash with the proxy style.
// This seems like an upstream bug, so there's not much else that can be done.
- if (!QStyleFactory::keys().contains("gtk2")){
+ if (!QStyleFactory::keys().contains("gtk2")) {
auto comboStyle = NoBigComboBoxStyle::getInstance(ui->versionsComboBox->style());
ui->versionsComboBox->setStyle(comboStyle);
}
ui->reloadButton->setVisible(false);
- connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool){
+ connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool) {
ui->reloadButton->setVisible(false);
m_loaded = false;
@@ -226,7 +225,8 @@ void ModrinthManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID();
- m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
+ m_fetch_job->addNetAction(
+ Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{};
@@ -267,7 +267,6 @@ void ModrinthManagedPackPage::parseManagedPack()
if (version.version == m_inst->getManagedPackVersionName())
name = tr("%1 (Current)").arg(name);
-
ui->versionsComboBox->addItem(name, QVariant(version.id));
}
@@ -291,6 +290,10 @@ QString ModrinthManagedPackPage::url() const
void ModrinthManagedPackPage::suggestVersion()
{
auto index = ui->versionsComboBox->currentIndex();
+ if (m_pack.versions.length() == 0) {
+ setFailState();
+ return;
+ }
auto version = m_pack.versions.at(index);
ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8()));
@@ -301,6 +304,10 @@ void ModrinthManagedPackPage::suggestVersion()
void ModrinthManagedPackPage::update()
{
auto index = ui->versionsComboBox->currentIndex();
+ if (m_pack.versions.length() == 0) {
+ setFailState();
+ return;
+ }
auto version = m_pack.versions.at(index);
QMap<QString, QString> extra_info;
@@ -429,6 +436,10 @@ QString FlameManagedPackPage::url() const
void FlameManagedPackPage::suggestVersion()
{
auto index = ui->versionsComboBox->currentIndex();
+ if (m_pack.versions.length() == 0) {
+ setFailState();
+ return;
+ }
auto version = m_pack.versions.at(index);
ui->changelogTextBrowser->setHtml(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId));
@@ -439,6 +450,10 @@ void FlameManagedPackPage::suggestVersion()
void FlameManagedPackPage::update()
{
auto index = ui->versionsComboBox->currentIndex();
+ if (m_pack.versions.length() == 0) {
+ setFailState();
+ return;
+ }
auto version = m_pack.versions.at(index);
QMap<QString, QString> extra_info;
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index aab2ee89..48afbd90 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -104,6 +104,7 @@ void ResourcePage::openedImpl()
updateSelectionButton();
triggerSearch();
+ m_ui->searchEdit->setFocus();
}
auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool
diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp
index 256b09da..37d05347 100644
--- a/launcher/ui/widgets/LanguageSelectionWidget.cpp
+++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp
@@ -1,16 +1,16 @@
#include "LanguageSelectionWidget.h"
-#include <QVBoxLayout>
-#include <QTreeView>
+#include <QCheckBox>
#include <QHeaderView>
#include <QLabel>
+#include <QTreeView>
+#include <QVBoxLayout>
#include "Application.h"
#include "BuildConfig.h"
-#include "translations/TranslationsModel.h"
#include "settings/Setting.h"
+#include "translations/TranslationsModel.h"
-LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
- QWidget(parent)
+LanguageSelectionWidget::LanguageSelectionWidget(QWidget* parent) : QWidget(parent)
{
verticalLayout = new QVBoxLayout(this);
verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
@@ -31,6 +31,13 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
helpUsLabel->setWordWrap(true);
verticalLayout->addWidget(helpUsLabel);
+ formatCheckbox = new QCheckBox(this);
+ formatCheckbox->setObjectName(QStringLiteral("formatCheckbox"));
+ formatCheckbox->setCheckState(APPLICATION->settings()->get("UseSystemLocale").toBool() ? Qt::Checked : Qt::Unchecked);
+ connect(formatCheckbox, &QCheckBox::stateChanged,
+ [this]() { APPLICATION->translations()->setUseSystemLocale(formatCheckbox->isChecked()); });
+ verticalLayout->addWidget(formatCheckbox);
+
auto translations = APPLICATION->translations();
auto index = translations->selectedIndex();
languageView->setModel(translations.get());
@@ -38,7 +45,7 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged);
- verticalLayout->setContentsMargins(0,0,0,0);
+ verticalLayout->setContentsMargins(0, 0, 0, 0);
auto language_setting = APPLICATION->settings()->getSetting("Language");
connect(language_setting.get(), &Setting::SettingChanged, this, &LanguageSelectionWidget::languageSettingChanged);
@@ -53,15 +60,14 @@ QString LanguageSelectionWidget::getSelectedLanguageKey() const
void LanguageSelectionWidget::retranslate()
{
QString text = tr("Don't see your language or the quality is poor?<br/><a href=\"%1\">Help us with translations!</a>")
- .arg(BuildConfig.TRANSLATIONS_URL);
+ .arg(BuildConfig.TRANSLATIONS_URL);
helpUsLabel->setText(text);
-
+ formatCheckbox->setText(tr("Use system locales"));
}
void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous)
{
- if (current == previous)
- {
+ if (current == previous) {
return;
}
auto translations = APPLICATION->translations();
@@ -70,7 +76,7 @@ void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, con
translations->updateLanguage(key);
}
-void LanguageSelectionWidget::languageSettingChanged(const Setting &, const QVariant)
+void LanguageSelectionWidget::languageSettingChanged(const Setting&, const QVariant)
{
auto translations = APPLICATION->translations();
auto index = translations->selectedIndex();
diff --git a/launcher/ui/widgets/LanguageSelectionWidget.h b/launcher/ui/widgets/LanguageSelectionWidget.h
index 4a88924c..5e86a288 100644
--- a/launcher/ui/widgets/LanguageSelectionWidget.h
+++ b/launcher/ui/widgets/LanguageSelectionWidget.h
@@ -21,23 +21,24 @@ class QVBoxLayout;
class QTreeView;
class QLabel;
class Setting;
+class QCheckBox;
-class LanguageSelectionWidget: public QWidget
-{
+class LanguageSelectionWidget : public QWidget {
Q_OBJECT
-public:
- explicit LanguageSelectionWidget(QWidget *parent = 0);
- virtual ~LanguageSelectionWidget() { };
+ public:
+ explicit LanguageSelectionWidget(QWidget* parent = 0);
+ virtual ~LanguageSelectionWidget(){};
QString getSelectedLanguageKey() const;
void retranslate();
-protected slots:
- void languageRowChanged(const QModelIndex &current, const QModelIndex &previous);
- void languageSettingChanged(const Setting &, const QVariant);
+ protected slots:
+ void languageRowChanged(const QModelIndex& current, const QModelIndex& previous);
+ void languageSettingChanged(const Setting&, const QVariant);
-private:
- QVBoxLayout *verticalLayout = nullptr;
- QTreeView *languageView = nullptr;
- QLabel *helpUsLabel = nullptr;
+ private:
+ QVBoxLayout* verticalLayout = nullptr;
+ QTreeView* languageView = nullptr;
+ QLabel* helpUsLabel = nullptr;
+ QCheckBox* formatCheckbox = nullptr;
};