aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
authorTrial97 <alexandru.tripon97@gmail.com>2023-09-28 22:50:12 +0300
committerTrial97 <alexandru.tripon97@gmail.com>2023-09-28 22:50:12 +0300
commit9acbf98f940204cd141203a6eccbc9a7351e5a78 (patch)
tree6ac8fe4b0e51ee58c67e02783fe97b00de707167 /launcher
parent254444470f020b086648ac496ebfffb7d3e9ce05 (diff)
parent59e565ef96b85be9a25fa5d4f1723ee87fd5e75e (diff)
downloadPrismLauncher-9acbf98f940204cd141203a6eccbc9a7351e5a78.tar.gz
PrismLauncher-9acbf98f940204cd141203a6eccbc9a7351e5a78.tar.bz2
PrismLauncher-9acbf98f940204cd141203a6eccbc9a7351e5a78.zip
Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feat/acknowledge_release_type
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp143
-rw-r--r--launcher/Application.h22
-rw-r--r--launcher/BaseInstance.cpp3
-rw-r--r--launcher/BaseInstance.h12
-rw-r--r--launcher/BaseVersion.h5
-rw-r--r--launcher/CMakeLists.txt36
-rw-r--r--launcher/DesktopServices.cpp4
-rw-r--r--launcher/FileIgnoreProxy.cpp5
-rw-r--r--launcher/FileSystem.h12
-rw-r--r--launcher/Filter.cpp6
-rw-r--r--launcher/Filter.h10
-rw-r--r--launcher/InstanceImportTask.cpp37
-rw-r--r--launcher/InstanceImportTask.h1
-rw-r--r--launcher/InstanceList.cpp20
-rw-r--r--launcher/LaunchController.cpp8
-rw-r--r--launcher/LoggedProcess.cpp11
-rw-r--r--launcher/MMCTime.cpp7
-rw-r--r--launcher/MMCTime.h2
-rw-r--r--launcher/MangoHud.cpp43
-rw-r--r--launcher/MangoHud.h4
-rw-r--r--launcher/NullInstance.h4
-rw-r--r--launcher/ProblemProvider.h2
-rw-r--r--launcher/RecursiveFileSystemWatcher.cpp2
-rw-r--r--launcher/ResourceDownloadTask.cpp4
-rw-r--r--launcher/SkinUtils.cpp5
-rw-r--r--launcher/Usable.h2
-rw-r--r--launcher/Version.h22
-rw-r--r--launcher/VersionProxyModel.cpp16
-rw-r--r--launcher/WindowsConsole.cpp128
-rw-r--r--launcher/WindowsConsole.h25
-rw-r--r--launcher/filelink/FileLink.cpp27
-rw-r--r--launcher/icons/IconList.cpp97
-rw-r--r--launcher/icons/IconList.h41
-rw-r--r--launcher/icons/IconUtils.cpp74
-rw-r--r--launcher/icons/IconUtils.h36
-rw-r--r--launcher/icons/MMCIcon.cpp5
-rw-r--r--launcher/icons/MMCIcon.h41
-rw-r--r--launcher/java/JavaVersion.h2
-rw-r--r--launcher/launch/LogModel.h2
-rw-r--r--launcher/meta/BaseEntity.cpp10
-rw-r--r--launcher/minecraft/AssetsUtils.cpp4
-rw-r--r--launcher/minecraft/Component.h3
-rw-r--r--launcher/minecraft/ComponentUpdateTask.cpp4
-rw-r--r--launcher/minecraft/LaunchProfile.h2
-rw-r--r--launcher/minecraft/Library.cpp6
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp103
-rw-r--r--launcher/minecraft/MinecraftInstance.h4
-rw-r--r--launcher/minecraft/MojangDownloadInfo.h12
-rw-r--r--launcher/minecraft/OneSixVersionFormat.cpp2
-rw-r--r--launcher/minecraft/PackProfile.cpp53
-rw-r--r--launcher/minecraft/PackProfile.h13
-rw-r--r--launcher/minecraft/ProfileUtils.cpp2
-rw-r--r--launcher/minecraft/Rule.h2
-rw-r--r--launcher/minecraft/World.cpp12
-rw-r--r--launcher/minecraft/WorldList.cpp11
-rw-r--r--launcher/minecraft/auth/AccountData.cpp4
-rw-r--r--launcher/minecraft/auth/AccountList.cpp7
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp6
-rw-r--r--launcher/minecraft/auth/Parsers.cpp8
-rw-r--r--launcher/minecraft/auth/steps/EntitlementsStep.cpp4
-rw-r--r--launcher/minecraft/gameoptions/GameOptions.cpp3
-rw-r--r--launcher/minecraft/launch/ExtractNatives.cpp12
-rw-r--r--launcher/minecraft/launch/LauncherPartLaunch.cpp2
-rw-r--r--launcher/minecraft/mod/DataPack.cpp2
-rw-r--r--launcher/minecraft/mod/DataPack.h2
-rw-r--r--launcher/minecraft/mod/Mod.cpp23
-rw-r--r--launcher/minecraft/mod/Mod.h4
-rw-r--r--launcher/minecraft/mod/ModDetails.h20
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp71
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h3
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp10
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h19
-rw-r--r--launcher/minecraft/mod/ResourcePack.cpp7
-rw-r--r--launcher/minecraft/mod/ResourcePack.h2
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp4
-rw-r--r--launcher/minecraft/mod/TexturePack.cpp5
-rw-r--r--launcher/minecraft/mod/TexturePackFolderModel.cpp3
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp29
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.h2
-rw-r--r--launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp2
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp33
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp2
-rw-r--r--launcher/minecraft/services/CapeChange.cpp2
-rw-r--r--launcher/minecraft/update/AssetUpdateTask.cpp4
-rw-r--r--launcher/minecraft/update/FMLLibrariesTask.cpp4
-rw-r--r--launcher/modplatform/CheckUpdateTask.h4
-rw-r--r--launcher/modplatform/ModIndex.cpp21
-rw-r--r--launcher/modplatform/ModIndex.h18
-rw-r--r--launcher/modplatform/ResourceAPI.h48
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp33
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp17
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp41
-rw-r--r--launcher/modplatform/flame/FlameAPI.h93
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.cpp8
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.h2
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp11
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp35
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h2
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.cpp7
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.h2
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.cpp15
-rw-r--r--launcher/modplatform/import_ftb/PackHelpers.cpp12
-rw-r--r--launcher/modplatform/import_ftb/PackHelpers.h2
-rw-r--r--launcher/modplatform/import_ftb/PackInstallTask.cpp14
-rw-r--r--launcher/modplatform/legacy_ftb/PackFetchTask.cpp6
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.cpp17
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h18
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp7
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp9
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.cpp33
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp38
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.h7
-rw-r--r--launcher/modplatform/packwiz/Packwiz.cpp12
-rw-r--r--launcher/modplatform/technic/SingleZipPackInstallTask.cpp4
-rw-r--r--launcher/modplatform/technic/SolderPackInstallTask.cpp5
-rw-r--r--launcher/modplatform/technic/TechnicPackProcessor.cpp10
-rw-r--r--launcher/net/ApiDownload.cpp66
-rw-r--r--launcher/net/ApiDownload.h38
-rw-r--r--launcher/net/ApiHeaderProxy.h49
-rw-r--r--launcher/net/ApiUpload.cpp43
-rw-r--r--launcher/net/ApiUpload.h36
-rw-r--r--launcher/net/ByteArraySink.h2
-rw-r--r--launcher/net/Download.cpp266
-rw-r--r--launcher/net/Download.h47
-rw-r--r--launcher/net/HeaderProxy.h49
-rw-r--r--launcher/net/HttpMetaCache.cpp19
-rw-r--r--launcher/net/HttpMetaCache.h2
-rw-r--r--launcher/net/NetAction.h12
-rw-r--r--launcher/net/NetRequest.cpp331
-rw-r--r--launcher/net/NetRequest.h99
-rw-r--r--launcher/net/RawHeaderProxy.h44
-rw-r--r--launcher/net/Upload.cpp209
-rw-r--r--launcher/net/Upload.h25
-rw-r--r--launcher/net/Validator.h4
-rw-r--r--launcher/news/NewsEntry.cpp6
-rw-r--r--launcher/pathmatcher/IPathMatcher.h2
-rw-r--r--launcher/pathmatcher/RegexpMatcher.h2
-rw-r--r--launcher/resources/multimc/128x128/instances/forge.pngbin0 -> 3229 bytes
-rw-r--r--launcher/resources/multimc/128x128/instances/liteloader.pngbin0 -> 7619 bytes
-rw-r--r--launcher/resources/multimc/index.theme2
-rw-r--r--launcher/resources/multimc/multimc.qrc6
-rw-r--r--launcher/resources/multimc/scalable/instances/fabricmc.svg71
-rw-r--r--launcher/resources/multimc/scalable/instances/neoforged.svg3
-rw-r--r--launcher/resources/multimc/scalable/instances/quiltmc.svg98
-rw-r--r--launcher/resources/pe_blue/index.theme2
-rw-r--r--launcher/resources/pe_colored/index.theme2
-rw-r--r--launcher/resources/pe_dark/index.theme2
-rw-r--r--launcher/resources/pe_light/index.theme2
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.cpp2
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.h2
-rw-r--r--launcher/screenshots/ImgurUpload.cpp2
-rw-r--r--launcher/screenshots/ImgurUpload.h1
-rw-r--r--launcher/tasks/ConcurrentTask.cpp4
-rw-r--r--launcher/tasks/ConcurrentTask.h4
-rw-r--r--launcher/tasks/Task.h12
-rw-r--r--launcher/tools/JProfiler.cpp2
-rw-r--r--launcher/tools/JVisualVM.cpp2
-rw-r--r--launcher/translations/POTranslator.cpp2
-rw-r--r--launcher/translations/TranslationsModel.cpp10
-rw-r--r--launcher/ui/InstanceWindow.cpp115
-rw-r--r--launcher/ui/InstanceWindow.h15
-rw-r--r--launcher/ui/MainWindow.cpp296
-rw-r--r--launcher/ui/MainWindow.h24
-rw-r--r--launcher/ui/MainWindow.ui73
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.cpp2
-rw-r--r--launcher/ui/dialogs/ExportInstanceDialog.cpp8
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.cpp81
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.ui86
-rw-r--r--launcher/ui/dialogs/InstallLoaderDialog.cpp166
-rw-r--r--launcher/ui/dialogs/InstallLoaderDialog.h45
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.cpp10
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp11
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.h5
-rw-r--r--launcher/ui/dialogs/ProfileSetupDialog.cpp16
-rw-r--r--launcher/ui/dialogs/ProgressDialog.cpp20
-rw-r--r--launcher/ui/dialogs/ProgressDialog.h2
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp34
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.h5
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp2
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.cpp6
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.ui8
-rw-r--r--launcher/ui/dialogs/VersionSelectDialog.cpp13
-rw-r--r--launcher/ui/dialogs/VersionSelectDialog.h1
-rw-r--r--launcher/ui/instanceview/InstanceDelegate.cpp22
-rw-r--r--launcher/ui/instanceview/InstanceView.cpp86
-rw-r--r--launcher/ui/pages/BasePageContainer.h1
-rw-r--r--launcher/ui/pages/global/AccountListPage.cpp7
-rw-r--r--launcher/ui/pages/global/ExternalToolsPage.ui8
-rw-r--r--launcher/ui/pages/global/JavaPage.cpp6
-rw-r--r--launcher/ui/pages/global/JavaPage.ui150
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui8
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp43
-rw-r--r--launcher/ui/pages/global/MinecraftPage.h3
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui66
-rw-r--r--launcher/ui/pages/global/ProxyPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp3
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui11
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp54
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.h3
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui131
-rw-r--r--launcher/ui/pages/instance/ManagedPackPage.cpp6
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp32
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/instance/OtherLogsPage.cpp22
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp28
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp143
-rw-r--r--launcher/ui/pages/instance/VersionPage.h5
-rw-r--r--launcher/ui/pages/instance/VersionPage.ui62
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp2
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp74
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.h3
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.ui2
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h2
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp66
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h6
-rw-r--r--launcher/ui/pages/modplatform/ResourcePackModel.cpp3
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp13
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.h6
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackModel.cpp3
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp67
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp20
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h2
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp16
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.ui89
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp65
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.h6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp33
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.h8
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.ui4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp27
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.h17
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp49
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.h30
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp29
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h3
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui52
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.cpp105
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.h22
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp103
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.h2
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.cpp31
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.h5
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.ui49
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp75
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h6
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp32
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h7
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp12
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp16
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h2
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp82
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.h3
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.cpp39
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.h8
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.ui4
-rw-r--r--launcher/ui/setupwizard/ThemeWizardPage.cpp2
-rw-r--r--launcher/ui/themes/CatPack.cpp10
-rw-r--r--launcher/ui/themes/CatPack.h9
-rw-r--r--launcher/ui/themes/CustomTheme.cpp2
-rw-r--r--launcher/ui/themes/IconTheme.cpp53
-rw-r--r--launcher/ui/themes/IconTheme.h37
-rw-r--r--launcher/ui/themes/ThemeManager.cpp192
-rw-r--r--launcher/ui/themes/ThemeManager.h23
-rw-r--r--launcher/ui/widgets/InfoFrame.cpp10
-rw-r--r--launcher/ui/widgets/JavaSettingsWidget.cpp30
-rw-r--r--launcher/ui/widgets/PageContainer.cpp8
-rw-r--r--launcher/ui/widgets/PageContainer.h6
-rw-r--r--launcher/ui/widgets/PageContainer_p.h2
-rw-r--r--launcher/ui/widgets/ProjectItem.cpp2
-rw-r--r--launcher/ui/widgets/SubTaskProgressBar.cpp2
-rw-r--r--launcher/ui/widgets/ThemeCustomizationWidget.cpp40
-rw-r--r--launcher/ui/widgets/ThemeCustomizationWidget.h16
-rw-r--r--launcher/ui/widgets/ThemeCustomizationWidget.ui98
-rw-r--r--launcher/ui/widgets/VariableSizedImageObject.cpp3
-rw-r--r--launcher/ui/widgets/VersionSelectWidget.cpp26
-rw-r--r--launcher/ui/widgets/VersionSelectWidget.h6
-rw-r--r--launcher/ui/widgets/WideBar.cpp4
290 files changed, 4904 insertions, 2575 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 7c7fc746..66044d9a 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -9,7 +9,6 @@
* Copyright (C) 2022 Tayou <git@tayou.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -136,11 +135,7 @@
#endif
#if defined Q_OS_WIN32
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-#include <stdio.h>
-#include <windows.h>
+#include "WindowsConsole.h"
#endif
#define STRINGIFY(x) #x
@@ -172,22 +167,8 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
- // attach the parent console
- if (AttachConsole(ATTACH_PARENT_PROCESS)) {
- // if attach succeeds, reopen and sync all the i/o
- if (freopen("CON", "w", stdout)) {
- std::cout.sync_with_stdio();
- }
- if (freopen("CON", "w", stderr)) {
- std::cerr.sync_with_stdio();
- }
- if (freopen("CON", "r", stdin)) {
- std::cin.sync_with_stdio();
- }
- auto out = GetStdHandle(STD_OUTPUT_HANDLE);
- DWORD written;
- const char* endline = "\n";
- WriteConsole(out, endline, strlen(endline), &written, NULL);
+ // attach the parent console if stdout not already captured
+ if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@@ -212,8 +193,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
- { { "I", "import" }, "Import instance from specified zip (local path or URL)", "file" },
+ { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
+ // Has to be positional for some OS to handle that properly
+ parser.addPositionalArgument("URL", "Import the resource(s) at the given URL(s) (same as -I / --import)", "[URL...]");
+
parser.addHelpOption();
parser.addVersionOption();
@@ -226,13 +210,13 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToShowWindowOf = parser.value("show");
- for (auto zip_path : parser.values("import")) {
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.values("import")) {
+ m_urlsToImport.append(normalizeImportUrl(url));
}
// treat unspecified positional arguments as import urls
- for (auto zip_path : parser.positionalArguments()) {
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.positionalArguments()) {
+ m_urlsToImport.append(normalizeImportUrl(url));
}
// error if --launch is missing with --server or --profile
@@ -331,11 +315,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout);
- if (!m_zipsToImport.isEmpty()) {
- for (auto zip_url : m_zipsToImport) {
+ if (!m_urlsToImport.isEmpty()) {
+ for (auto url : m_urlsToImport) {
ApplicationMessage import;
import.command = "import";
- import.args.insert("path", zip_url.toString());
+ import.args.insert("url", url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
@@ -510,7 +494,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
// Theming
- m_settings->registerSetting("IconTheme", QString("pe_colored"));
+ m_settings->registerSetting("IconTheme", QString());
m_settings->registerSetting("ApplicationTheme", QString());
m_settings->registerSetting("BackgroundCat", QString("kitteh"));
@@ -595,12 +579,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);
- // Mod loader settings
- m_settings->registerSetting("DisableQuiltBeacon", false);
-
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false);
+ m_settings->registerSetting("CustomOpenALPath", "");
m_settings->registerSetting("UseNativeGLFW", false);
+ m_settings->registerSetting("CustomGLFWPath", "");
// Peformance related options
m_settings->registerSetting("EnableFeralGamemode", false);
@@ -611,6 +594,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true);
+ m_settings->registerSetting("ShowGameTimeWithoutDays", false);
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
@@ -772,7 +756,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
// Themes
- m_themeManager = std::make_unique<ThemeManager>(m_mainWindow);
+ m_themeManager = std::make_unique<ThemeManager>();
// initialize and load all instances
{
@@ -858,14 +842,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
});
- applyCurrentlySelectedTheme(true);
-
updateCapabilities();
+ detectLibraries();
+
if (createSetupWizard()) {
return;
}
+ m_themeManager->applyCurrentlySelectedTheme(true);
performMainStartupAction();
}
@@ -891,10 +876,20 @@ bool Application::createSetupWizard()
}();
bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
- bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
+ bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
+ bool validIcons = m_themeManager->isValidIconTheme(settings()->get("IconTheme").toString());
+ bool themeInterventionRequired = !validWidgets || !validIcons;
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
if (wizardRequired) {
+ // set default theme after going into theme wizard
+ if (!validIcons)
+ settings()->set("IconTheme", QString("pe_colored"));
+ if (!validWidgets)
+ settings()->set("ApplicationTheme", QString("system"));
+
+ m_themeManager->applyCurrentlySelectedTheme(true);
+
m_setupWizard = new SetupWizard(nullptr);
if (languageRequired) {
m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard));
@@ -909,9 +904,9 @@ bool Application::createSetupWizard()
}
if (themeInterventionRequired) {
- settings()->set("ApplicationTheme", QString("system")); // set default theme after going into theme wizard
m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard));
}
+
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show();
return true;
@@ -970,7 +965,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
- launch(inst, true, false, nullptr, serverToJoin, accountToUse);
+ launch(inst, true, false, serverToJoin, accountToUse);
return;
}
}
@@ -987,9 +982,9 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
- if (!m_zipsToImport.isEmpty()) {
- qDebug() << "<> Importing from zip:" << m_zipsToImport;
- m_mainWindow->processURLs(m_zipsToImport);
+ if (!m_urlsToImport.isEmpty()) {
+ qDebug() << "<> Importing from url:" << m_urlsToImport;
+ m_mainWindow->processURLs(m_urlsToImport);
}
}
@@ -1031,12 +1026,12 @@ void Application::messageReceived(const QByteArray& message)
if (command == "activate") {
showMainWindow();
} else if (command == "import") {
- QString path = received.args["path"];
- if (path.isEmpty()) {
+ QString url = received.args["url"];
+ if (url.isEmpty()) {
qWarning() << "Received" << command << "message without a zip path/URL.";
return;
}
- m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
+ m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
@@ -1069,7 +1064,7 @@ void Application::messageReceived(const QByteArray& message)
}
}
- launch(instance, true, false, nullptr, serverObject, accountObject);
+ launch(instance, true, false, serverObject, accountObject);
} else {
qWarning() << "Received invalid message" << message;
}
@@ -1088,26 +1083,6 @@ std::shared_ptr<JavaInstallList> Application::javalist()
return m_javalist;
}
-QList<ITheme*> Application::getValidApplicationThemes()
-{
- return m_themeManager->getValidApplicationThemes();
-}
-
-void Application::applyCurrentlySelectedTheme(bool initial)
-{
- m_themeManager->applyCurrentlySelectedTheme(initial);
-}
-
-void Application::setApplicationTheme(const QString& name)
-{
- m_themeManager->setApplicationTheme(name);
-}
-
-void Application::setIconTheme(const QString& name)
-{
- m_themeManager->setIconTheme(name);
-}
-
QIcon Application::getThemedIcon(const QString& name)
{
if (name == "logo") {
@@ -1116,16 +1091,6 @@ QIcon Application::getThemedIcon(const QString& name)
return QIcon::fromTheme(name);
}
-QList<CatPack*> Application::getValidCatPacks()
-{
- return m_themeManager->getValidCatPacks();
-}
-
-QString Application::getCatPack(QString catName)
-{
- return m_themeManager->getCatPack(catName);
-}
-
bool Application::openJsonEditor(const QString& filename)
{
const QString file = QDir::current().absoluteFilePath(filename);
@@ -1140,7 +1105,6 @@ bool Application::openJsonEditor(const QString& filename)
bool Application::launch(InstancePtr instance,
bool online,
bool demo,
- BaseProfilerFactory* profiler,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse)
{
@@ -1148,7 +1112,7 @@ bool Application::launch(InstancePtr instance,
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) {
auto& extras = m_instanceExtras[instance->id()];
- auto& window = extras.window;
+ auto window = extras.window;
if (window) {
if (!window->saveAll()) {
return false;
@@ -1159,7 +1123,7 @@ bool Application::launch(InstancePtr instance,
controller->setInstance(instance);
controller->setOnline(online);
controller->setDemo(demo);
- controller->setProfiler(profiler);
+ controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin);
controller->setAccountToUse(accountToUse);
if (window) {
@@ -1451,6 +1415,15 @@ void Application::updateCapabilities()
#endif
}
+void Application::detectLibraries()
+{
+#ifdef Q_OS_LINUX
+ m_detectedGLFWPath = MangoHud::findLibrary(BuildConfig.GLFW_LIBRARY_NAME);
+ m_detectedOpenALPath = MangoHud::findLibrary(BuildConfig.OPENAL_LIBRARY_NAME);
+ qDebug() << "Detected native libraries:" << m_detectedGLFWPath << m_detectedOpenALPath;
+#endif
+}
+
QString Application::getJarPath(QString jarFile)
{
QStringList potentialPaths = {
@@ -1629,3 +1602,13 @@ void Application::triggerUpdateCheck()
qDebug() << "Updater not available.";
}
}
+
+QUrl Application::normalizeImportUrl(QString const& url)
+{
+ auto local_file = QFileInfo(url);
+ if (local_file.exists()) {
+ return QUrl::fromLocalFile(local_file.absoluteFilePath());
+ } else {
+ return QUrl::fromUserInput(url);
+ }
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index cf7967a5..b227bb81 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -71,6 +71,7 @@ class TranslationsModel;
class ITheme;
class MCEditTool;
class ThemeManager;
+class IconTheme;
namespace Meta {
class Index;
@@ -109,17 +110,7 @@ class Application : public QApplication {
QIcon getThemedIcon(const QString& name);
- void setIconTheme(const QString& name);
-
- void applyCurrentlySelectedTheme(bool initial = false);
-
- QList<ITheme*> getValidApplicationThemes();
-
- void setApplicationTheme(const QString& name);
-
- QList<CatPack*> getValidCatPacks();
-
- QString getCatPack(QString catName = "");
+ ThemeManager* themeManager() { return m_themeManager.get(); }
shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; }
@@ -151,6 +142,8 @@ class Application : public QApplication {
void updateCapabilities();
+ void detectLibraries();
+
/*!
* Finds and returns the full path to a jar file.
* Returns a null-string if it could not be found.
@@ -186,6 +179,8 @@ class Application : public QApplication {
int suitableMaxMem();
+ QUrl normalizeImportUrl(QString const& url);
+
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
@@ -200,7 +195,6 @@ class Application : public QApplication {
bool launch(InstancePtr instance,
bool online = true,
bool demo = false,
- BaseProfilerFactory* profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance);
@@ -284,11 +278,13 @@ class Application : public QApplication {
SetupWizard* m_setupWizard = nullptr;
public:
+ QString m_detectedGLFWPath;
+ QString m_detectedOpenALPath;
QString m_instanceIdToLaunch;
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
- QList<QUrl> m_zipsToImport;
+ QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 70c6da6b..72503639 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -100,6 +101,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
+
+ m_settings->registerSetting("Profiler", "");
}
QString BaseInstance::getPreLaunchCommand()
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 4357125f..47fff595 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,6 +39,7 @@
#include <cassert>
#include <QDateTime>
+#include <QMenu>
#include <QObject>
#include <QProcess>
#include <QSet>
@@ -86,7 +88,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
public:
/// virtual destructor to make sure the destruction is COMPLETE
- virtual ~BaseInstance(){};
+ virtual ~BaseInstance() {}
virtual void saveNow() = 0;
@@ -146,7 +148,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log
- virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString& line, MessageLevel::Enum level) { return level; };
+ virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString& line, MessageLevel::Enum level) { return level; }
virtual QStringList extraArguments();
@@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
+ virtual void populateLaunchMenu(QMenu* menu) = 0;
+
bool reloadSettings();
/**
@@ -267,7 +271,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
protected:
void changeStatus(Status newStatus);
- SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); };
+ SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); }
bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; }
void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; }
@@ -282,6 +286,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void runningStatusChanged(bool running);
+ void profilerChanged();
+
void statusChanged(Status from, Status to);
protected slots:
diff --git a/launcher/BaseVersion.h b/launcher/BaseVersion.h
index c7cedbe1..2837ff3a 100644
--- a/launcher/BaseVersion.h
+++ b/launcher/BaseVersion.h
@@ -43,9 +43,8 @@ class BaseVersion {
* the kind of version this is (Stable, Beta, Snapshot, whatever)
*/
virtual QString typeString() const = 0;
-
- virtual bool operator<(BaseVersion& a) { return name() < a.name(); };
- virtual bool operator>(BaseVersion& a) { return name() > a.name(); };
+ virtual bool operator<(BaseVersion& a) { return name() < a.name(); }
+ virtual bool operator>(BaseVersion& a) { return name() > a.name(); }
};
Q_DECLARE_METATYPE(BaseVersion::Ptr)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 824cf80e..18e0acab 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -136,6 +136,15 @@ set(NET_SOURCES
net/Validator.h
net/Upload.cpp
net/Upload.h
+ net/HeaderProxy.h
+ net/RawHeaderProxy.h
+ net/ApiHeaderProxy.h
+ net/ApiDownload.h
+ net/ApiDownload.cpp
+ net/ApiUpload.cpp
+ net/ApiUpload.h
+ net/NetRequest.cpp
+ net/NetRequest.h
)
# Game launch logic
@@ -566,6 +575,9 @@ set(ATLAUNCHER_SOURCES
)
set(LINKEXE_SOURCES
+ WindowsConsole.cpp
+ WindowsConsole.h
+
filelink/FileLink.h
filelink/FileLink.cpp
FileSystem.h
@@ -762,6 +774,8 @@ SET(LAUNCHER_SOURCES
ui/themes/ITheme.h
ui/themes/SystemTheme.cpp
ui/themes/SystemTheme.h
+ ui/themes/IconTheme.cpp
+ ui/themes/IconTheme.h
ui/themes/ThemeManager.cpp
ui/themes/ThemeManager.h
ui/themes/CatPack.cpp
@@ -962,6 +976,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h
+ ui/dialogs/InstallLoaderDialog.cpp
+ ui/dialogs/InstallLoaderDialog.h
# GUI - widgets
ui/widgets/Common.cpp
@@ -1026,6 +1042,14 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
+if(WIN32)
+ set(LAUNCHER_SOURCES
+ WindowsConsole.cpp
+ WindowsConsole.h
+ ${LAUNCHER_SOURCES}
+ )
+endif()
+
qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui
@@ -1109,8 +1133,15 @@ if(WIN32)
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
endif()
+include(CompilerWarnings)
+
# Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
+set_project_warnings(Launcher_logic
+ "${Launcher_MSVC_WARNINGS}"
+ "${Launcher_CLANG_WARNINGS}"
+ "${Launcher_GCC_WARNINGS}")
+target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Launcher_logic
systeminfo
@@ -1195,6 +1226,11 @@ install(TARGETS ${Launcher_Name}
if(WIN32)
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
+ set_project_warnings(filelink_logic
+ "${Launcher_MSVC_WARNINGS}"
+ "${Launcher_CLANG_WARNINGS}"
+ "${Launcher_GCC_WARNINGS}")
+
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
systeminfo
diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp
index 71048734..004e5e08 100644
--- a/launcher/DesktopServices.cpp
+++ b/launcher/DesktopServices.cpp
@@ -96,12 +96,12 @@ bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
#endif
namespace DesktopServices {
-bool openDirectory(const QString& path, bool ensureExists)
+bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
{
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
- if (!dir.exists()) {
+ if (ensureExists && !dir.exists()) {
parentPath.mkpath(dir.absolutePath());
}
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };
diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp
index 4c8c64c7..df06c3c7 100644
--- a/launcher/FileIgnoreProxy.cpp
+++ b/launcher/FileIgnoreProxy.cpp
@@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{
- auto fileName = fileInfo.fileName();
- auto path = relPath(fileInfo.absoluteFilePath());
- return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
- m_ignoreFilePaths.covers(path);
+ return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 85e2cbe0..bfed576c 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -112,8 +112,8 @@ class copy : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
- int totalCopied() { return m_copied; }
- int totalFailed() { return m_failedPaths.length(); }
+ qsizetype totalCopied() { return m_copied; }
+ qsizetype totalFailed() { return m_failedPaths.length(); }
QStringList failed() { return m_failedPaths; }
signals:
@@ -130,7 +130,7 @@ class copy : public QObject {
bool m_whitelist = false;
QDir m_src;
QDir m_dst;
- int m_copied;
+ qsizetype m_copied;
QStringList m_failedPaths;
};
@@ -474,8 +474,8 @@ class clone : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
- int totalCloned() { return m_cloned; }
- int totalFailed() { return m_failedClones.length(); }
+ qsizetype totalCloned() { return m_cloned; }
+ qsizetype totalFailed() { return m_failedClones.length(); }
QList<QPair<QString, QString>> failed() { return m_failedClones; }
@@ -491,7 +491,7 @@ class clone : public QObject {
bool m_whitelist = false;
QDir m_src;
QDir m_dst;
- int m_cloned;
+ qsizetype m_cloned;
QList<QPair<QString, QString>> m_failedClones;
};
diff --git a/launcher/Filter.cpp b/launcher/Filter.cpp
index 9d904473..fc1c4234 100644
--- a/launcher/Filter.cpp
+++ b/launcher/Filter.cpp
@@ -16,6 +16,12 @@ bool ExactFilter::accepts(const QString& value)
return value == pattern;
}
+ExactIfPresentFilter::ExactIfPresentFilter(const QString& pattern) : pattern(pattern) {}
+bool ExactIfPresentFilter::accepts(const QString& value)
+{
+ return value.isEmpty() || value == pattern;
+}
+
RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
{
pattern.setPattern(regexp);
diff --git a/launcher/Filter.h b/launcher/Filter.h
index f58521cf..089c844d 100644
--- a/launcher/Filter.h
+++ b/launcher/Filter.h
@@ -29,6 +29,16 @@ class ExactFilter : public Filter {
QString pattern;
};
+class ExactIfPresentFilter : public Filter {
+ public:
+ ExactIfPresentFilter(const QString& pattern);
+ ~ExactIfPresentFilter() override = default;
+ bool accepts(const QString& value) override;
+
+ private:
+ QString pattern;
+};
+
class RegexpFilter : public Filter {
public:
RegexpFilter(const QString& regexp, bool invert);
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 97e106ba..bc139e4f 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -50,6 +50,9 @@
#include "modplatform/technic/TechnicPackProcessor.h"
#include "settings/INISettingsObject.h"
+#include "tasks/Task.h"
+
+#include "net/ApiDownload.h"
#include <QtConcurrentRun>
#include <algorithm>
@@ -88,25 +91,27 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
- const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
-
- auto entry = APPLICATION->metacache()->resolveEntry("general", path);
- entry->setStale(true);
- m_archivePath = entry->getFullPath();
-
- m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
- m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
-
- connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
- connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
- connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
- connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
- connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
-
- m_filesNetJob->start();
+ downloadFromUrl();
}
}
+void InstanceImportTask::downloadFromUrl()
+{
+ const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
+ auto entry = APPLICATION->metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
+ m_archivePath = entry->getFullPath();
+
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
+ connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
+ connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
+ connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
+ connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
+ m_filesNetJob->start();
+}
+
void InstanceImportTask::downloadSucceeded()
{
processZipPack();
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index 4459e440..ca3d30ad 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -101,4 +101,5 @@ class InstanceImportTask : public InstanceTask {
// FIXME: nuke
QWidget* m_parent;
+ void downloadFromUrl();
};
diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp
index 02938409..856eee81 100644
--- a/launcher/InstanceList.cpp
+++ b/launcher/InstanceList.cpp
@@ -96,7 +96,11 @@ Qt::DropActions InstanceList::supportedDropActions() const
return Qt::MoveAction;
}
-bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
+bool InstanceList::canDropMimeData(const QMimeData* data,
+ [[maybe_unused]] Qt::DropAction action,
+ [[maybe_unused]] int row,
+ [[maybe_unused]] int column,
+ [[maybe_unused]] const QModelIndex& parent) const
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@@ -104,7 +108,11 @@ bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action,
return false;
}
-bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
+bool InstanceList::dropMimeData(const QMimeData* data,
+ [[maybe_unused]] Qt::DropAction action,
+ [[maybe_unused]] int row,
+ [[maybe_unused]] int column,
+ [[maybe_unused]] const QModelIndex& parent)
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@@ -751,7 +759,7 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
emit instancesChanged();
}
-void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
+void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, QVariant value)
{
QString newInstDir = QDir(value.toString()).canonicalPath();
if (newInstDir != m_instDir) {
@@ -789,7 +797,7 @@ class InstanceStaging : public Task {
, m_groupName(std::move(groupName))
{
m_child.reset(child);
- connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
+ connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded);
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
@@ -797,7 +805,7 @@ class InstanceStaging : public Task {
connect(child, &Task::details, this, &InstanceStaging::setDetails);
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress);
- connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
+ connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
}
virtual ~InstanceStaging(){};
@@ -819,7 +827,7 @@ class InstanceStaging : public Task {
QStringList warnings() const override { return m_child->warnings(); }
private slots:
- void childSucceded()
+ void childSucceeded()
{
unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get())) {
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index 380489e0..b21eb281 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -361,22 +362,21 @@ void LaunchController::readyForLaunch()
QString error;
if (!m_profiler->check(&error)) {
m_launcher->abort();
- QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
emitFailed("Profiler startup failed!");
+ QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error));
return;
}
BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) {
- QMessageBox msg;
+ QMessageBox msg(m_parentWidget);
msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1")
.arg(message));
msg.setWindowTitle(tr("Waiting."));
msg.setIcon(QMessageBox::Information);
- msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
- msg.setModal(true);
+ msg.addButton(tr("&Launch"), QMessageBox::AcceptRole);
msg.exec();
m_launcher->proceed();
});
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index 6978777e..fadd64e6 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -65,14 +65,9 @@ QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decod
m_leftover_line = "";
}
-#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
- auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
-#else
- auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
-#endif
-
- if (!str.endsWith(QChar::LineFeed))
- m_leftover_line = lines.takeLast();
+ auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed);
+
+ m_leftover_line = lines.takeLast();
return lines;
}
diff --git a/launcher/MMCTime.cpp b/launcher/MMCTime.cpp
index 3972dbd5..1765fd84 100644
--- a/launcher/MMCTime.cpp
+++ b/launcher/MMCTime.cpp
@@ -16,19 +16,20 @@
*/
#include <MMCTime.h>
+#include <qobject.h>
#include <QDateTime>
#include <QObject>
#include <QTextStream>
-QString Time::prettifyDuration(int64_t duration)
+QString Time::prettifyDuration(int64_t duration, bool noDays)
{
int seconds = (int)(duration % 60);
duration /= 60;
int minutes = (int)(duration % 60);
duration /= 60;
- int hours = (int)(duration % 24);
- int days = (int)(duration / 24);
+ int hours = (int)(noDays ? duration : (duration % 24));
+ int days = (int)(noDays ? 0 : (duration / 24));
if ((hours == 0) && (days == 0)) {
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
}
diff --git a/launcher/MMCTime.h b/launcher/MMCTime.h
index b7d34b5d..ea6d37e7 100644
--- a/launcher/MMCTime.h
+++ b/launcher/MMCTime.h
@@ -20,7 +20,7 @@
namespace Time {
-QString prettifyDuration(int64_t duration);
+QString prettifyDuration(int64_t duration, bool noDays = false);
/**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp
index 5758da3a..ab79f418 100644
--- a/launcher/MangoHud.cpp
+++ b/launcher/MangoHud.cpp
@@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+#include <QDebug>
#include <QDir>
#include <QString>
#include <QStringList>
@@ -26,6 +27,15 @@
#include "Json.h"
#include "MangoHud.h"
+#ifdef __GLIBC__
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#define UNDEF_GNU_SOURCE
+#endif
+#include <dlfcn.h>
+#include <linux/limits.h>
+#endif
+
namespace MangoHud {
QString getLibraryString()
@@ -106,4 +116,37 @@ QString getLibraryString()
return QString();
}
+
+QString findLibrary(QString libName)
+{
+#ifdef __GLIBC__
+ const char* library = libName.toLocal8Bit().constData();
+
+ void* handle = dlopen(library, RTLD_NOW);
+ if (!handle) {
+ qCritical() << "dlopen() failed:" << dlerror();
+ return {};
+ }
+
+ char path[PATH_MAX];
+ if (dlinfo(handle, RTLD_DI_ORIGIN, path) == -1) {
+ qCritical() << "dlinfo() failed:" << dlerror();
+ dlclose(handle);
+ return {};
+ }
+
+ auto fullPath = FS::PathCombine(QString(path), libName);
+
+ dlclose(handle);
+ return fullPath;
+#else
+ qWarning() << "MangoHud::findLibrary is not implemented on this platform";
+ return {};
+#endif
+}
} // namespace MangoHud
+
+#ifdef UNDEF_GNU_SOURCE
+#undef _GNU_SOURCE
+#undef UNDEF_GNU_SOURCE
+#endif
diff --git a/launcher/MangoHud.h b/launcher/MangoHud.h
index 7b7c2849..5361999b 100644
--- a/launcher/MangoHud.h
+++ b/launcher/MangoHud.h
@@ -24,4 +24,6 @@
namespace MangoHud {
QString getLibraryString();
-}
+
+QString findLibrary(QString libName);
+} // namespace MangoHud
diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h
index a332d4c4..c79600e7 100644
--- a/launcher/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -52,7 +53,7 @@ class NullInstance : public BaseInstance {
QSet<QString> traits() const override { return {}; };
QString instanceConfigFolder() const override { return instanceRoot(); };
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override { return nullptr; }
- shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override { return nullptr; }
+ shared_qobject_ptr<Task> createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; }
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
QMap<QString, QString> getVariables() override { return QMap<QString, QString>(); }
@@ -62,6 +63,7 @@ class NullInstance : public BaseInstance {
bool canExport() const override { return false; }
bool canEdit() const override { return false; }
bool canLaunch() const override { return false; }
+ void populateLaunchMenu(QMenu* menu) override {}
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
{
QStringList out;
diff --git a/launcher/ProblemProvider.h b/launcher/ProblemProvider.h
index 246a8b5c..9d1b0004 100644
--- a/launcher/ProblemProvider.h
+++ b/launcher/ProblemProvider.h
@@ -12,7 +12,7 @@ struct PatchProblem {
class ProblemProvider {
public:
- virtual ~ProblemProvider(){};
+ virtual ~ProblemProvider() {}
virtual const QList<PatchProblem> getProblems() const = 0;
virtual ProblemSeverity getProblemSeverity() const = 0;
};
diff --git a/launcher/RecursiveFileSystemWatcher.cpp b/launcher/RecursiveFileSystemWatcher.cpp
index f3be1bd8..8b28a03f 100644
--- a/launcher/RecursiveFileSystemWatcher.cpp
+++ b/launcher/RecursiveFileSystemWatcher.cpp
@@ -90,7 +90,7 @@ void RecursiveFileSystemWatcher::fileChange(const QString& path)
{
emit fileChanged(path);
}
-void RecursiveFileSystemWatcher::directoryChange(const QString& path)
+void RecursiveFileSystemWatcher::directoryChange([[maybe_unused]] const QString& path)
{
setFiles(scanRecursive(m_root));
}
diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp
index 8bb9b64e..a02151ca 100644
--- a/launcher/ResourceDownloadTask.cpp
+++ b/launcher/ResourceDownloadTask.cpp
@@ -24,6 +24,8 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
+#include "net/ApiDownload.h"
+
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
const std::shared_ptr<ResourceFolderModel> packs,
@@ -51,7 +53,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
}
}
- m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);
diff --git a/launcher/SkinUtils.cpp b/launcher/SkinUtils.cpp
index 20d3b52e..989114ad 100644
--- a/launcher/SkinUtils.cpp
+++ b/launcher/SkinUtils.cpp
@@ -35,6 +35,11 @@ QPixmap getFaceFromCache(QString username, int height, int width)
QPixmap skinTexture(fskin.fileName());
if (!skinTexture.isNull()) {
QPixmap skin = QPixmap(8, 8);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ skin.fill(QColorConstants::Transparent);
+#else
+ skin.fill(QColor(0, 0, 0, 0));
+#endif
QPainter painter(&skin);
painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8));
painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8));
diff --git a/launcher/Usable.h b/launcher/Usable.h
index a75415aa..b0ecd401 100644
--- a/launcher/Usable.h
+++ b/launcher/Usable.h
@@ -16,6 +16,8 @@ class Usable {
friend class UseLock;
public:
+ virtual ~Usable() {}
+
std::size_t useCount() const { return m_useCount; }
bool isInUse() const { return m_useCount > 0; }
diff --git a/launcher/Version.h b/launcher/Version.h
index 101a907c..9c043b01 100644
--- a/launcher/Version.h
+++ b/launcher/Version.h
@@ -63,7 +63,7 @@ class Version {
struct Section {
explicit Section(QString fullString) : m_fullString(std::move(fullString))
{
- int cutoff = m_fullString.size();
+ qsizetype cutoff = m_fullString.size();
for (int i = 0; i < m_fullString.size(); i++) {
if (!m_fullString[i].isDigit()) {
cutoff = i;
@@ -103,14 +103,8 @@ class Version {
QString m_fullString;
- [[nodiscard]] inline bool isAppendix() const
- {
- return m_stringPart.startsWith('+');
- }
- [[nodiscard]] inline bool isPreRelease() const
- {
- return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
- }
+ [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
+ [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
inline bool operator==(const Section& other) const
{
@@ -156,14 +150,8 @@ class Version {
return m_fullString < other.m_fullString;
}
- inline bool operator!=(const Section& other) const
- {
- return !(*this == other);
- }
- inline bool operator>(const Section& other) const
- {
- return !(*this < other || *this == other);
- }
+ inline bool operator!=(const Section& other) const { return !(*this == other); }
+ inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
};
private:
diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
index 6e0333e4..0ab9ae2c 100644
--- a/launcher/VersionProxyModel.cpp
+++ b/launcher/VersionProxyModel.cpp
@@ -194,12 +194,12 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
switch (column) {
case Name: {
if (hasRecommended) {
- auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
- if (value.toBool()) {
+ auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
+ if (recommenced.toBool()) {
return APPLICATION->getThemedIcon("star");
} else if (hasLatest) {
- auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
- if (value.toBool()) {
+ auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
+ if (latest.toBool()) {
return APPLICATION->getThemedIcon("bug");
}
}
@@ -228,7 +228,7 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
}
}
-QModelIndex VersionProxyModel::parent(const QModelIndex& child) const
+QModelIndex VersionProxyModel::parent([[maybe_unused]] const QModelIndex& child) const
{
return QModelIndex();
}
@@ -408,7 +408,9 @@ void VersionProxyModel::sourceRowsAboutToBeInserted(const QModelIndex& parent, i
beginInsertRows(parent, first, last);
}
-void VersionProxyModel::sourceRowsInserted(const QModelIndex& parent, int first, int last)
+void VersionProxyModel::sourceRowsInserted([[maybe_unused]] const QModelIndex& parent,
+ [[maybe_unused]] int first,
+ [[maybe_unused]] int last)
{
endInsertRows();
}
@@ -418,7 +420,7 @@ void VersionProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex& parent, in
beginRemoveRows(parent, first, last);
}
-void VersionProxyModel::sourceRowsRemoved(const QModelIndex& parent, int first, int last)
+void VersionProxyModel::sourceRowsRemoved([[maybe_unused]] const QModelIndex& parent, [[maybe_unused]] int first, [[maybe_unused]] int last)
{
endRemoveRows();
}
diff --git a/launcher/WindowsConsole.cpp b/launcher/WindowsConsole.cpp
new file mode 100644
index 00000000..83cad5af
--- /dev/null
+++ b/launcher/WindowsConsole.cpp
@@ -0,0 +1,128 @@
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.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/>.
+ *
+ */
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <fcntl.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+#include <iostream>
+
+void RedirectHandle(DWORD handle, FILE* stream, const char* mode)
+{
+ HANDLE stdHandle = GetStdHandle(handle);
+ if (stdHandle != INVALID_HANDLE_VALUE) {
+ int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
+ if (fileDescriptor != -1) {
+ FILE* file = _fdopen(fileDescriptor, mode);
+ if (file != NULL) {
+ int dup2Result = _dup2(_fileno(file), _fileno(stream));
+ if (dup2Result == 0) {
+ setvbuf(stream, NULL, _IONBF, 0);
+ }
+ }
+ }
+ }
+}
+
+// taken from https://stackoverflow.com/a/25927081
+// getting a proper output to console with redirection support on windows is apparently hell
+void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
+{
+ // Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
+ // observed that the file number of our standard handle file objects can be assigned internally to a value of -2
+ // when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
+ // call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
+ // before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
+ // use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
+ // using the "_dup2" function.
+ if (bindStdIn) {
+ FILE* dummyFile;
+ freopen_s(&dummyFile, "nul", "r", stdin);
+ }
+ if (bindStdOut) {
+ FILE* dummyFile;
+ freopen_s(&dummyFile, "nul", "w", stdout);
+ }
+ if (bindStdErr) {
+ FILE* dummyFile;
+ freopen_s(&dummyFile, "nul", "w", stderr);
+ }
+
+ // Redirect unbuffered stdin from the current standard input handle
+ if (bindStdIn) {
+ RedirectHandle(STD_INPUT_HANDLE, stdin, "r");
+ }
+
+ // Redirect unbuffered stdout to the current standard output handle
+ if (bindStdOut) {
+ RedirectHandle(STD_OUTPUT_HANDLE, stdout, "w");
+ }
+
+ // Redirect unbuffered stderr to the current standard error handle
+ if (bindStdErr) {
+ RedirectHandle(STD_ERROR_HANDLE, stderr, "w");
+ }
+
+ // Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
+ // standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
+ // versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
+ // has been read from or written to the targets or not.
+ if (bindStdIn) {
+ std::wcin.clear();
+ std::cin.clear();
+ }
+ if (bindStdOut) {
+ std::wcout.clear();
+ std::cout.clear();
+ }
+ if (bindStdErr) {
+ std::wcerr.clear();
+ std::cerr.clear();
+ }
+}
+
+bool AttachWindowsConsole()
+{
+ auto stdinType = GetFileType(GetStdHandle(STD_INPUT_HANDLE));
+ auto stdoutType = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE));
+ auto stderrType = GetFileType(GetStdHandle(STD_ERROR_HANDLE));
+
+ bool bindStdIn = false;
+ bool bindStdOut = false;
+ bool bindStdErr = false;
+
+ if (stdinType == FILE_TYPE_CHAR || stdinType == FILE_TYPE_UNKNOWN) {
+ bindStdIn = true;
+ }
+ if (stdoutType == FILE_TYPE_CHAR || stdoutType == FILE_TYPE_UNKNOWN) {
+ bindStdOut = true;
+ }
+ if (stderrType == FILE_TYPE_CHAR || stderrType == FILE_TYPE_UNKNOWN) {
+ bindStdErr = true;
+ }
+
+ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
+ BindCrtHandlesToStdHandles(bindStdIn, bindStdOut, bindStdErr);
+ return true;
+ }
+
+ return false;
+}
diff --git a/launcher/WindowsConsole.h b/launcher/WindowsConsole.h
new file mode 100644
index 00000000..ab53864b
--- /dev/null
+++ b/launcher/WindowsConsole.h
@@ -0,0 +1,25 @@
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.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
+
+void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr);
+bool AttachWindowsConsole();
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index c9599b82..1ea0e382 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -37,11 +37,7 @@
#include <sys.h>
#if defined Q_OS_WIN32
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-#include <stdio.h>
-#include <windows.h>
+#include "WindowsConsole.h"
#endif
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
@@ -67,21 +63,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
{
#if defined Q_OS_WIN32
// attach the parent console
- if (AttachConsole(ATTACH_PARENT_PROCESS)) {
- // if attach succeeds, reopen and sync all the i/o
- if (freopen("CON", "w", stdout)) {
- std::cout.sync_with_stdio();
- }
- if (freopen("CON", "w", stderr)) {
- std::cerr.sync_with_stdio();
- }
- if (freopen("CON", "r", stdin)) {
- std::cin.sync_with_stdio();
- }
- auto out = GetStdHandle(STD_OUTPUT_HANDLE);
- DWORD written;
- const char* endline = "\n";
- WriteConsole(out, endline, strlen(endline), &written, NULL);
+ if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@@ -188,7 +170,7 @@ void FileLinkApp::runLink()
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
m_path_results.append(result);
} else {
- FS::LinkResult result = { src_path, dst_path };
+ FS::LinkResult result = { src_path, dst_path, "", 0 };
m_path_results.append(result);
}
}
@@ -248,7 +230,7 @@ void FileLinkApp::readPathPairs()
in >> numLinks;
qDebug() << "numLinks" << numLinks;
- for (int i = 0; i < numLinks; i++) {
+ for (quint32 i = 0; i < numLinks; i++) {
FS::LinkPair pair;
in >> pair.src;
in >> pair.dst;
@@ -271,7 +253,6 @@ FileLinkApp::~FileLinkApp()
fclose(stdout);
fclose(stdin);
fclose(stderr);
- FreeConsole();
}
#endif
}
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index 30c51caf..5576b974 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,6 +43,7 @@
#include <QMimeData>
#include <QSet>
#include <QUrl>
+#include "icons/IconUtils.h"
#define MAX_SIZE 1024
@@ -128,7 +130,7 @@ void IconList::directoryChanged(const QString& path)
QString suffix = rmfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
+ if (!IconUtils::isIconSuffix(suffix))
key = rmfile.fileName();
int idx = getIconIndex(key);
@@ -155,7 +157,7 @@ void IconList::directoryChanged(const QString& path)
QString suffix = addfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
+ if (!IconUtils::isIconSuffix(suffix))
key = addfile.fileName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
@@ -255,10 +257,7 @@ bool IconList::dropMimeData(const QMimeData* data,
Qt::ItemFlags IconList::flags(const QModelIndex& index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- if (index.isValid())
- return Qt::ItemIsDropEnabled | defaultFlags;
- else
- return Qt::ItemIsDropEnabled | defaultFlags;
+ return Qt::ItemIsDropEnabled | defaultFlags;
}
QVariant IconList::data(const QModelIndex& index, int role) const
@@ -290,19 +289,8 @@ int IconList::rowCount(const QModelIndex& parent) const
void IconList::installIcons(const QStringList& iconFiles)
{
- for (QString file : iconFiles) {
- QFileInfo fileinfo(file);
- if (!fileinfo.isReadable() || !fileinfo.isFile())
- continue;
- QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
-
- QString suffix = fileinfo.suffix();
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
- continue;
-
- if (!QFile::copy(file, target))
- continue;
- }
+ for (QString file : iconFiles)
+ installIcon(file, {});
}
void IconList::installIcon(const QString& file, const QString& name)
@@ -311,18 +299,17 @@ void IconList::installIcon(const QString& file, const QString& name)
if (!fileinfo.isReadable() || !fileinfo.isFile())
return;
- QString target = FS::PathCombine(getDirectory(), name);
+ if (!IconUtils::isIconSuffix(fileinfo.suffix()))
+ return;
+ QString target = FS::PathCombine(getDirectory(), name.isEmpty() ? fileinfo.fileName() : name);
QFile::copy(file, target);
}
bool IconList::iconFileExists(const QString& key) const
{
auto iconEntry = icon(key);
- if (!iconEntry) {
- return false;
- }
- return iconEntry->has(IconType::FileBased);
+ return iconEntry && iconEntry->has(IconType::FileBased);
}
const MMCIcon* IconList::icon(const QString& key) const
@@ -335,18 +322,12 @@ const MMCIcon* IconList::icon(const QString& key) const
bool IconList::deleteIcon(const QString& key)
{
- if (!iconFileExists(key))
- return false;
-
- return QFile::remove(icon(key)->getFilePath());
+ return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
}
bool IconList::trashIcon(const QString& key)
{
- if (!iconFileExists(key))
- return false;
-
- return FS::trash(icon(key)->getFilePath(), nullptr);
+ return iconFileExists(key) && FS::trash(icon(key)->getFilePath(), nullptr);
}
bool IconList::addThemeIcon(const QString& key)
@@ -357,20 +338,19 @@ bool IconList::addThemeIcon(const QString& key)
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
- } else {
- // add a new icon
- beginInsertRows(QModelIndex(), icons.size(), icons.size());
- {
- MMCIcon mmc_icon;
- mmc_icon.m_name = key;
- mmc_icon.m_key = key;
- mmc_icon.replace(Builtin, key);
- icons.push_back(mmc_icon);
- name_index[key] = icons.size() - 1;
- }
- endInsertRows();
- return true;
}
+ // add a new icon
+ beginInsertRows(QModelIndex(), icons.size(), icons.size());
+ {
+ MMCIcon mmc_icon;
+ mmc_icon.m_name = key;
+ mmc_icon.m_key = key;
+ mmc_icon.replace(Builtin, key);
+ icons.push_back(mmc_icon);
+ name_index[key] = icons.size() - 1;
+ }
+ endInsertRows();
+ return true;
}
bool IconList::addIcon(const QString& key, const QString& name, const QString& path, const IconType type)
@@ -385,20 +365,19 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
- } else {
- // add a new icon
- beginInsertRows(QModelIndex(), icons.size(), icons.size());
- {
- MMCIcon mmc_icon;
- mmc_icon.m_name = name;
- mmc_icon.m_key = key;
- mmc_icon.replace(type, icon, path);
- icons.push_back(mmc_icon);
- name_index[key] = icons.size() - 1;
- }
- endInsertRows();
- return true;
}
+ // add a new icon
+ beginInsertRows(QModelIndex(), icons.size(), icons.size());
+ {
+ MMCIcon mmc_icon;
+ mmc_icon.m_name = name;
+ mmc_icon.m_key = key;
+ mmc_icon.replace(type, icon, path);
+ icons.push_back(mmc_icon);
+ name_index[key] = icons.size() - 1;
+ }
+ endInsertRows();
+ return true;
}
void IconList::saveIcon(const QString& key, const QString& path, const char* format) const
@@ -446,5 +425,3 @@ QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
-
-//#include "IconList.moc"
diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h
index bc1dd3b9..8afd0557 100644
--- a/launcher/icons/IconList.h
+++ b/launcher/icons/IconList.h
@@ -1,18 +1,37 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-
#pragma once
#include <QAbstractListModel>
diff --git a/launcher/icons/IconUtils.cpp b/launcher/icons/IconUtils.cpp
index 0b06639e..99c38f47 100644
--- a/launcher/icons/IconUtils.cpp
+++ b/launcher/icons/IconUtils.cpp
@@ -1,19 +1,51 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#include "IconUtils.h"
#include <QDirIterator>
#include "FileSystem.h"
-#include <array>
-
namespace {
-std::array<const char*, 6> validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
+static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
}
namespace IconUtils {
QString findBestIconIn(const QString& folder, const QString& iconKey)
{
- int best_found = validIconExtensions.size();
QString best_filename;
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
@@ -21,36 +53,20 @@ QString findBestIconIn(const QString& folder, const QString& iconKey)
it.next();
auto fileInfo = it.fileInfo();
- if (fileInfo.completeBaseName() != iconKey)
- continue;
-
- auto extension = fileInfo.suffix();
-
- for (int i = 0; i < best_found; i++) {
- if (extension == validIconExtensions[i]) {
- best_found = i;
- qDebug() << i << " : " << fileInfo.fileName();
- best_filename = fileInfo.fileName();
- }
- }
+ if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
+ return fileInfo.absoluteFilePath();
}
- return FS::PathCombine(folder, best_filename);
+ return {};
}
QString getIconFilter()
{
- QString out;
- QTextStream stream(&out);
- stream << '(';
- for (size_t i = 0; i < validIconExtensions.size() - 1; i++) {
- if (i > 0) {
- stream << " ";
- }
- stream << "*." << validIconExtensions[i];
- }
- stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
- stream << ')';
- return out;
+ return "(*." + validIconExtensions.join(" *.") + ")";
+}
+
+bool isIconSuffix(QString suffix)
+{
+ return validIconExtensions.contains(suffix);
}
} // namespace IconUtils
diff --git a/launcher/icons/IconUtils.h b/launcher/icons/IconUtils.h
index 8c6f677d..90cdfe5a 100644
--- a/launcher/icons/IconUtils.h
+++ b/launcher/icons/IconUtils.h
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#pragma once
#include <QString>
@@ -10,4 +45,5 @@ QString findBestIconIn(const QString& folder, const QString& iconKey);
// Get icon file type filter for file browser dialogs
QString getIconFilter();
+bool isIconSuffix(QString suffix);
} // namespace IconUtils
diff --git a/launcher/icons/MMCIcon.cpp b/launcher/icons/MMCIcon.cpp
index 8301b1ba..991b470c 100644
--- a/launcher/icons/MMCIcon.cpp
+++ b/launcher/icons/MMCIcon.cpp
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -50,8 +51,8 @@ IconType operator--(IconType& t, int)
case IconType::FileBased:
t = IconType::Transient;
break;
- default: {
- }
+ default:
+ break;
}
return temp;
}
diff --git a/launcher/icons/MMCIcon.h b/launcher/icons/MMCIcon.h
index c968f306..a6e30566 100644
--- a/launcher/icons/MMCIcon.h
+++ b/launcher/icons/MMCIcon.h
@@ -1,18 +1,37 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-
#pragma once
#include <QDateTime>
#include <QIcon>
diff --git a/launcher/java/JavaVersion.h b/launcher/java/JavaVersion.h
index 69473203..7e66269c 100644
--- a/launcher/java/JavaVersion.h
+++ b/launcher/java/JavaVersion.h
@@ -14,7 +14,7 @@ class JavaVersion {
friend class JavaVersionTest;
public:
- JavaVersion(){};
+ JavaVersion() {}
JavaVersion(const QString& rhs);
JavaVersion& operator=(const QString& rhs);
diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h
index ba2e4054..18e51d7e 100644
--- a/launcher/launch/LogModel.h
+++ b/launcher/launch/LogModel.h
@@ -30,7 +30,7 @@ class LogModel : public QAbstractListModel {
enum Roles { LevelRole = Qt::UserRole };
- private /* types */:
+ private /* types */:
struct entry {
MessageLevel::Enum level;
QString line;
diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp
index d8003b55..5f9804e4 100644
--- a/launcher/meta/BaseEntity.cpp
+++ b/launcher/meta/BaseEntity.cpp
@@ -16,7 +16,7 @@
#include "BaseEntity.h"
#include "Json.h"
-#include "net/Download.h"
+#include "net/ApiDownload.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
@@ -32,7 +32,7 @@ class ParsingValidator : public Net::Validator {
bool init(QNetworkRequest&) override { return true; }
bool write(QByteArray& data) override
{
- this->data.append(data);
+ this->m_data.append(data);
return true;
}
bool abort() override { return true; }
@@ -40,7 +40,7 @@ class ParsingValidator : public Net::Validator {
{
auto fname = m_entity->localFilename();
try {
- auto doc = Json::requireDocument(data, fname);
+ auto doc = Json::requireDocument(m_data, fname);
auto obj = Json::requireObject(doc, fname);
m_entity->parse(obj);
return true;
@@ -51,7 +51,7 @@ class ParsingValidator : public Net::Validator {
}
private: /* data */
- QByteArray data;
+ QByteArray m_data;
Meta::BaseEntity* m_entity;
};
@@ -104,7 +104,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
auto url = this->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
entry->setStale(true);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
/*
* The validator parses the file and loads it into the object.
* If that fails, the file is not written to storage.
diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp
index 4923f0ef..4ef00707 100644
--- a/launcher/minecraft/AssetsUtils.cpp
+++ b/launcher/minecraft/AssetsUtils.cpp
@@ -46,8 +46,8 @@
#include "AssetsUtils.h"
#include "BuildConfig.h"
#include "FileSystem.h"
+#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
-#include "net/Download.h"
#include "Application.h"
@@ -279,7 +279,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
{
QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
- auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
+ auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
if (hash.size()) {
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
diff --git a/launcher/minecraft/Component.h b/launcher/minecraft/Component.h
index 00a912d6..3474a22e 100644
--- a/launcher/minecraft/Component.h
+++ b/launcher/minecraft/Component.h
@@ -25,7 +25,8 @@ class Component : public QObject, public ProblemProvider {
Component(PackProfile* parent, std::shared_ptr<Meta::Version> version);
Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file);
- virtual ~Component(){};
+ virtual ~Component() {}
+
void applyTo(LaunchProfile* profile);
bool isEnabled();
diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp
index 0b85b81a..bb838043 100644
--- a/launcher/minecraft/ComponentUpdateTask.cpp
+++ b/launcher/minecraft/ComponentUpdateTask.cpp
@@ -2,14 +2,14 @@
#include "Component.h"
#include "ComponentUpdateTask_p.h"
-#include "OneSixVersionFormat.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
#include "Version.h"
#include "cassert"
#include "meta/Index.h"
#include "meta/Version.h"
-#include "meta/VersionList.h"
+#include "minecraft/OneSixVersionFormat.h"
+#include "minecraft/ProfileUtils.h"
#include "net/Mode.h"
#include "Application.h"
diff --git a/launcher/minecraft/LaunchProfile.h b/launcher/minecraft/LaunchProfile.h
index 3acc1f19..12b31238 100644
--- a/launcher/minecraft/LaunchProfile.h
+++ b/launcher/minecraft/LaunchProfile.h
@@ -41,7 +41,7 @@
class LaunchProfile : public ProblemProvider {
public:
- virtual ~LaunchProfile(){};
+ virtual ~LaunchProfile() {}
public: /* application of profile variables from patches */
void applyMinecraftVersion(const QString& id);
diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp
index a9c20845..0e8ddf03 100644
--- a/launcher/minecraft/Library.cpp
+++ b/launcher/minecraft/Library.cpp
@@ -38,8 +38,8 @@
#include <BuildConfig.h>
#include <FileSystem.h>
+#include <net/ApiDownload.h>
#include <net/ChecksumValidator.h>
-#include <net/Download.h>
void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
QStringList& jar,
@@ -115,12 +115,12 @@ QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext
if (sha1.size()) {
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
- auto dl = Net::Download::makeCached(url, entry, options);
+ auto dl = Net::ApiDownload::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl);
} else {
- out.append(Net::Download::makeCached(url, entry, options));
+ out.append(Net::ApiDownload::makeCached(url, entry, options));
qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
}
return true;
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 305bff67..86ef3b30 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -3,8 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
- * Copyright (c) 2023 seth <getchoo at tuta dot io>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -88,6 +87,10 @@
#include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h"
+#include "tools/BaseProfiler.h"
+
+#include <QActionGroup>
+
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
@@ -166,7 +169,9 @@ void MinecraftInstance::loadSpecificSettings()
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("CustomOpenALPath"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
// Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
@@ -179,10 +184,6 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
- // Mod loader specific options
- auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
- m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
-
m_settings->set("InstanceType", "OneSix");
}
@@ -194,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", "");
+ m_settings->registerSetting("ExportName", "");
+ m_settings->registerSetting("ExportVersion", "1.0.0");
+ m_settings->registerSetting("ExportSummary", "");
+ m_settings->registerSetting("ExportAuthor", "");
+ m_settings->registerSetting("ExportOptionalFiles", true);
+
qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true);
@@ -229,6 +236,50 @@ QSet<QString> MinecraftInstance::traits() const
return profile->getTraits();
}
+// FIXME: move UI code out of MinecraftInstance
+void MinecraftInstance::populateLaunchMenu(QMenu* menu)
+{
+ QAction* normalLaunch = menu->addAction(tr("&Launch"));
+ normalLaunch->setShortcut(QKeySequence::Open);
+ QAction* normalLaunchOffline = menu->addAction(tr("Launch &Offline"));
+ normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
+ QAction* normalLaunchDemo = menu->addAction(tr("Launch &Demo"));
+ normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
+
+ normalLaunchDemo->setEnabled(supportsDemo());
+
+ connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); });
+ connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); });
+ connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); });
+
+ QString profilersTitle = tr("Profilers");
+ menu->addSeparator()->setText(profilersTitle);
+
+ auto profilers = new QActionGroup(menu);
+ profilers->setExclusive(true);
+ connect(profilers, &QActionGroup::triggered, [this](QAction* action) {
+ settings()->set("Profiler", action->data());
+ emit profilerChanged();
+ });
+
+ QAction* noProfilerAction = menu->addAction(tr("&No Profiler"));
+ noProfilerAction->setData("");
+ noProfilerAction->setCheckable(true);
+ noProfilerAction->setChecked(true);
+ profilers->addAction(noProfilerAction);
+
+ for (auto profiler = APPLICATION->profilers().begin(); profiler != APPLICATION->profilers().end(); profiler++) {
+ QAction* profilerAction = menu->addAction(profiler.value()->name());
+ profilers->addAction(profilerAction);
+ profilerAction->setData(profiler.key());
+ profilerAction->setCheckable(true);
+ profilerAction->setChecked(settings()->get("Profiler").toString() == profiler.key());
+
+ QString error;
+ profilerAction->setEnabled(profiler.value()->check(&error));
+ }
+}
+
QString MinecraftInstance::gameRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
@@ -260,7 +311,7 @@ QString MinecraftInstance::getLocalLibraryPath() const
bool MinecraftInstance::supportsDemo() const
{
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
- // Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
+ // Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
return instance_ver >= Version("1.3.1");
}
@@ -385,10 +436,31 @@ QStringList MinecraftInstance::extraArguments()
}
{
- const auto loaders = version->getModLoaders();
- if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
- list.append("-Dloader.disable_beacon=true");
+ QString openALPath;
+ QString glfwPath;
+
+ if (settings()->get("UseNativeOpenAL").toBool()) {
+ openALPath = APPLICATION->m_detectedOpenALPath;
+ auto customPath = settings()->get("CustomOpenALPath").toString();
+ if (!customPath.isEmpty())
+ openALPath = customPath;
+ }
+ if (settings()->get("UseNativeGLFW").toBool()) {
+ glfwPath = APPLICATION->m_detectedGLFWPath;
+ auto customPath = settings()->get("CustomGLFWPath").toString();
+ if (!customPath.isEmpty())
+ glfwPath = customPath;
+ }
+
+ QFileInfo openALInfo(openALPath);
+ QFileInfo glfwInfo(glfwPath);
+
+ if (!openALPath.isEmpty() && openALInfo.exists())
+ list.append("-Dorg.lwjgl.openal.libname=" + openALInfo.absoluteFilePath());
+ if (!glfwPath.isEmpty() && glfwInfo.exists())
+ list.append("-Dorg.lwjgl.glfw.libname=" + glfwInfo.absoluteFilePath());
}
+
return list;
}
@@ -868,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription()
if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
- description.append(tr(", last played on %1 for %2")
- .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
- .arg(Time::prettifyDuration(lastTimePlayed())));
+ description.append(
+ tr(", last played on %1 for %2")
+ .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
+ .arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
if (totalTimePlayed() > 0) {
- description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed())));
+ description.append(
+ tr(", total played for %1")
+ .arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
if (hasCrashed()) {
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index c331cc6f..dabd44ba 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -70,6 +70,8 @@ class MinecraftInstance : public BaseInstance {
bool canExport() const override { return true; }
+ void populateLaunchMenu(QMenu* menu) override;
+
////// Directories and files //////
QString jarModsDir() const;
QString resourcePacksDir() const;
diff --git a/launcher/minecraft/MojangDownloadInfo.h b/launcher/minecraft/MojangDownloadInfo.h
index b98a0168..855dbe00 100644
--- a/launcher/minecraft/MojangDownloadInfo.h
+++ b/launcher/minecraft/MojangDownloadInfo.h
@@ -19,8 +19,8 @@ struct MojangDownloadInfo {
};
struct MojangLibraryDownloadInfo {
- MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact) : artifact(artifact){};
- MojangLibraryDownloadInfo(){};
+ MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact_) : artifact(artifact_) {}
+ MojangLibraryDownloadInfo() {}
// types
typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr;
@@ -47,18 +47,18 @@ struct MojangAssetIndexInfo : public MojangDownloadInfo {
// methods
MojangAssetIndexInfo() {}
- MojangAssetIndexInfo(QString id)
+ MojangAssetIndexInfo(QString id_)
{
- this->id = id;
+ this->id = id_;
// HACK: ignore assets from other version files than Minecraft
// workaround for stupid assets issue caused by amazon:
// https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/
- if (id == "legacy") {
+ if (id_ == "legacy") {
url = "https://piston-meta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
}
// HACK
else {
- url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json";
+ url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id_ + ".json";
}
known = false;
}
diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp
index e2455325..306c95a6 100644
--- a/launcher/minecraft/OneSixVersionFormat.cpp
+++ b/launcher/minecraft/OneSixVersionFormat.cpp
@@ -350,7 +350,7 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr& patch
}
}
-LibraryPtr OneSixVersionFormat::plusJarModFromJson(ProblemContainer& problems,
+LibraryPtr OneSixVersionFormat::plusJarModFromJson([[maybe_unused]] ProblemContainer& problems,
const QJsonObject& libObj,
const QString& filename,
const QString& originalName)
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index 82fc779e..9e42c5dd 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -58,14 +58,14 @@
#include "ComponentUpdateTask.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
+#include "minecraft/mod/Mod.h"
+#include "modplatform/ModIndex.h"
-#include "Application.h"
-#include "modplatform/ResourceAPI.h"
-
-static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge },
- { "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
- { "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
- { "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
+static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
+ { "net.minecraftforge", ModPlatform::Forge },
+ { "net.fabricmc.fabric-loader", ModPlatform::Fabric },
+ { "org.quiltmc.quilt-loader", ModPlatform::Quilt },
+ { "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
{
@@ -204,10 +204,10 @@ static bool loadPackProfile(PackProfile* parent,
}
auto orderArray = Json::requireArray(obj.value("components"));
for (auto item : orderArray) {
- auto obj = Json::requireObject(item, "Component must be an object.");
- container.append(componentFromJsonV1(parent, componentJsonPattern, obj));
+ auto comp_obj = Json::requireObject(item, "Component must be an object.");
+ container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
}
- } catch (const JSONValidationError& err) {
+ } catch ([[maybe_unused]] const JSONValidationError& err) {
qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
container.clear();
return false;
@@ -377,7 +377,7 @@ void PackProfile::insertComponent(size_t index, ComponentPtr component)
qWarning() << "Attempt to add a component that is already present!";
return;
}
- beginInsertRows(QModelIndex(), index, index);
+ beginInsertRows(QModelIndex(), static_cast<int>(index), static_cast<int>(index));
d->components.insert(index, component);
d->componentIndex[id] = component;
endInsertRows();
@@ -389,7 +389,7 @@ void PackProfile::componentDataChanged()
{
auto objPtr = qobject_cast<Component*>(sender());
if (!objPtr) {
- qWarning() << "PackProfile got dataChenged signal from a non-Component!";
+ qWarning() << "PackProfile got dataChanged signal from a non-Component!";
return;
}
if (objPtr->getID() == "net.minecraft") {
@@ -405,7 +405,7 @@ void PackProfile::componentDataChanged()
}
index++;
}
- qWarning() << "PackProfile got dataChenged signal from a Component which does not belong to it!";
+ qWarning() << "PackProfile got dataChanged signal from a Component which does not belong to it!";
}
bool PackProfile::remove(const int index)
@@ -483,9 +483,9 @@ ComponentPtr PackProfile::getComponent(const QString& id)
return (*iter);
}
-ComponentPtr PackProfile::getComponent(int index)
+ComponentPtr PackProfile::getComponent(size_t index)
{
- if (index < 0 || index >= d->components.size()) {
+ if (index >= static_cast<size_t>(d->components.size())) {
return nullptr;
}
return d->components[index];
@@ -547,7 +547,7 @@ QVariant PackProfile::data(const QModelIndex& index, int role) const
return QVariant();
}
-bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
+bool PackProfile::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
{
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent())) {
return false;
@@ -989,12 +989,12 @@ void PackProfile::disableInteraction(bool disable)
}
}
-std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
+std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders()
{
- ResourceAPI::ModLoaderTypes result;
+ ModPlatform::ModLoaderTypes result;
bool has_any_loader = false;
- QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
+ QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
while (i.hasNext()) {
i.next();
@@ -1008,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
return {};
return result;
}
+
+std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
+{
+ auto loadersOpt = getModLoaders();
+ if (!loadersOpt.has_value())
+ return loadersOpt;
+ auto loaders = loadersOpt.value();
+ // TODO: remove this or add version condition once Quilt drops official Fabric support
+ if (loaders & ModPlatform::Quilt)
+ loaders |= ModPlatform::Fabric;
+ // TODO: remove this or add version condition once NeoForge drops official Forge support
+ if (loaders & ModPlatform::NeoForge)
+ loaders |= ModPlatform::Forge;
+ return loaders;
+}
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index a5e5cb1a..e72b6ebf 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -44,14 +44,11 @@
#include <QList>
#include <QString>
#include <memory>
+#include <optional>
-#include "BaseVersion.h"
#include "Component.h"
#include "LaunchProfile.h"
-#include "Library.h"
-#include "MojangDownloadInfo.h"
-#include "ProfileUtils.h"
-#include "modplatform/ResourceAPI.h"
+#include "modplatform/ModIndex.h"
#include "net/Mode.h"
class MinecraftInstance;
@@ -140,13 +137,15 @@ class PackProfile : public QAbstractListModel {
ComponentPtr getComponent(const QString& id);
/// get the profile component by index
- ComponentPtr getComponent(int index);
+ ComponentPtr getComponent(size_t index);
/// Add the component to the internal list of patches
// todo(merged): is this the best approach
void appendComponent(ComponentPtr component);
- std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
+ std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
+ // this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
+ std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
private:
void scheduleSave();
diff --git a/launcher/minecraft/ProfileUtils.cpp b/launcher/minecraft/ProfileUtils.cpp
index 18a4b7d0..d56ed14b 100644
--- a/launcher/minecraft/ProfileUtils.cpp
+++ b/launcher/minecraft/ProfileUtils.cpp
@@ -82,7 +82,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
for (auto item : orderArray) {
order.append(Json::requireString(item));
}
- } catch (const JSONValidationError& err) {
+ } catch ([[maybe_unused]] const JSONValidationError& err) {
qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
qWarning() << "Ignoring overriden order";
order.clear();
diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h
index 483cd936..c6cdbc43 100644
--- a/launcher/minecraft/Rule.h
+++ b/launcher/minecraft/Rule.h
@@ -55,7 +55,7 @@ class Rule {
public:
Rule(RuleAction result) : m_result(result) {}
- virtual ~Rule(){};
+ virtual ~Rule() {}
virtual QJsonObject toJson() = 0;
RuleAction apply(const Library* parent, const RuntimeContext& runtimeContext)
{
diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp
index 62e0279a..1a680ac5 100644
--- a/launcher/minecraft/World.cpp
+++ b/launcher/minecraft/World.cpp
@@ -368,11 +368,11 @@ optional<QString> read_string(nbt::value& parent, const char* name)
}
auto& tag_str = namedValue.as<nbt::tag_string>();
return QString::fromStdString(tag_str.get());
- } catch (const std::out_of_range& e) {
+ } catch ([[maybe_unused]] const std::out_of_range& e) {
// fallback for old world formats
qWarning() << "String NBT tag" << name << "could not be found.";
return nullopt;
- } catch (const std::bad_cast& e) {
+ } catch ([[maybe_unused]] const std::bad_cast& e) {
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to string.";
return nullopt;
@@ -388,11 +388,11 @@ optional<int64_t> read_long(nbt::value& parent, const char* name)
}
auto& tag_str = namedValue.as<nbt::tag_long>();
return tag_str.get();
- } catch (const std::out_of_range& e) {
+ } catch ([[maybe_unused]] const std::out_of_range& e) {
// fallback for old world formats
qWarning() << "Long NBT tag" << name << "could not be found.";
return nullopt;
- } catch (const std::bad_cast& e) {
+ } catch ([[maybe_unused]] const std::bad_cast& e) {
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to long.";
return nullopt;
@@ -408,11 +408,11 @@ optional<int> read_int(nbt::value& parent, const char* name)
}
auto& tag_str = namedValue.as<nbt::tag_int>();
return tag_str.get();
- } catch (const std::out_of_range& e) {
+ } catch ([[maybe_unused]] const std::out_of_range& e) {
// fallback for old world formats
qWarning() << "Int NBT tag" << name << "could not be found.";
return nullopt;
- } catch (const std::bad_cast& e) {
+ } catch ([[maybe_unused]] const std::bad_cast& e) {
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to int.";
return nullopt;
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index a7348e57..812b13c7 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -255,7 +255,7 @@ QVariant WorldList::data(const QModelIndex& index, int role) const
}
}
-QVariant WorldList::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant WorldList::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@@ -294,7 +294,6 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
default:
return QVariant();
}
- return QVariant();
}
QStringList WorldList::mimeTypes() const
@@ -339,19 +338,19 @@ QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const
if (indexes.size() == 0)
return new QMimeData();
- QList<World> worlds;
+ QList<World> worlds_;
for (auto idx : indexes) {
if (idx.column() != 0)
continue;
int row = idx.row();
if (row < 0 || row >= this->worlds.size())
continue;
- worlds.append(this->worlds[row]);
+ worlds_.append(this->worlds[row]);
}
- if (!worlds.size()) {
+ if (!worlds_.size()) {
return new QMimeData();
}
- return new WorldMimeData(worlds);
+ return new WorldMimeData(worlds_);
}
Qt::ItemFlags WorldList::flags(const QModelIndex& index) const
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
index b56f49cb..474bf7c6 100644
--- a/launcher/minecraft/auth/AccountData.cpp
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -311,7 +311,7 @@ bool AccountData::resumeStateFromV2(QJsonObject data)
QJsonObject profileObject = profileVal.toObject();
QString id = profileObject.value("id").toString("");
QString name = profileObject.value("name").toString("");
- bool legacy = profileObject.value("legacy").toBool(false);
+ bool legacy_ = profileObject.value("legacy").toBool(false);
if (id.isEmpty() || name.isEmpty()) {
qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
continue;
@@ -319,7 +319,7 @@ bool AccountData::resumeStateFromV2(QJsonObject data)
if (id == currentProfile) {
currentProfileIndex = index;
}
- profiles.append({ id, name, legacy });
+ profiles.append({ id, name, legacy_ });
}
auto& profile = profiles[currentProfileIndex];
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index 317af714..84dbd841 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -353,7 +353,7 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
}
}
-QVariant AccountList::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@@ -415,7 +415,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
{
- if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
+ if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()) || !idx.isValid()) {
return false;
}
@@ -423,7 +423,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol
if (value == Qt::Checked) {
MinecraftAccountPtr account = at(idx.row());
setDefaultAccount(account);
- }
+ } else if (m_defaultAccount == at(idx.row()))
+ setDefaultAccount(nullptr);
}
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index 1dcea207..6c2f0805 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -37,6 +37,7 @@
#include "MinecraftAccount.h"
+#include <QColor>
#include <QCryptographicHash>
#include <QJsonArray>
#include <QJsonDocument>
@@ -126,6 +127,11 @@ QPixmap MinecraftAccount::getFace() const
return QPixmap();
}
QPixmap skin = QPixmap(8, 8);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ skin.fill(QColorConstants::Transparent);
+#else
+ skin.fill(QColor(0, 0, 0, 0));
+#endif
QPainter painter(&skin);
painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8));
painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8));
diff --git a/launcher/minecraft/auth/Parsers.cpp b/launcher/minecraft/auth/Parsers.cpp
index 8dbe446a..f6179a93 100644
--- a/launcher/minecraft/auth/Parsers.cpp
+++ b/launcher/minecraft/auth/Parsers.cpp
@@ -113,16 +113,16 @@ bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString nam
if (!item.isObject()) {
continue;
}
- auto obj = item.toObject();
- if (obj.contains("uhs")) {
+ auto obj_ = item.toObject();
+ if (obj_.contains("uhs")) {
foundUHS = true;
} else {
continue;
}
// consume all 'display claims' ... whatever that means
- for (auto iter = obj.begin(); iter != obj.end(); iter++) {
+ for (auto iter = obj_.begin(); iter != obj_.end(); iter++) {
QString claim;
- if (!getString(obj.value(iter.key()), claim)) {
+ if (!getString(obj_.value(iter.key()), claim)) {
qWarning() << "display claim " << iter.key() << " is not a string...";
return false;
}
diff --git a/launcher/minecraft/auth/steps/EntitlementsStep.cpp b/launcher/minecraft/auth/steps/EntitlementsStep.cpp
index e942db52..0573dcb6 100644
--- a/launcher/minecraft/auth/steps/EntitlementsStep.cpp
+++ b/launcher/minecraft/auth/steps/EntitlementsStep.cpp
@@ -36,7 +36,9 @@ void EntitlementsStep::rehydrate()
// NOOP, for now. We only save bools and there's nothing to check.
}
-void EntitlementsStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
+void EntitlementsStep::onRequestDone([[maybe_unused]] QNetworkReply::NetworkError error,
+ QByteArray data,
+ [[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
diff --git a/launcher/minecraft/gameoptions/GameOptions.cpp b/launcher/minecraft/gameoptions/GameOptions.cpp
index 443525ae..4f4fb99a 100644
--- a/launcher/minecraft/gameoptions/GameOptions.cpp
+++ b/launcher/minecraft/gameoptions/GameOptions.cpp
@@ -97,12 +97,11 @@ QVariant GameOptions::data(const QModelIndex& index, int role) const
default:
return QVariant();
}
- return QVariant();
}
int GameOptions::rowCount(const QModelIndex&) const
{
- return contents.size();
+ return static_cast<int>(contents.size());
}
int GameOptions::columnCount(const QModelIndex&) const
diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp
index cebeaedd..8f3cac4d 100644
--- a/launcher/minecraft/launch/ExtractNatives.cpp
+++ b/launcher/minecraft/launch/ExtractNatives.cpp
@@ -39,7 +39,7 @@ static QString replaceSuffix(QString target, const QString& suffix, const QStrin
return target + replacement;
}
-static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW)
+static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack)
{
QuaZip zip(source);
if (!zip.open(QuaZip::mdUnzip)) {
@@ -52,12 +52,6 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
do {
QString name = zip.getCurrentFileName();
auto lowercase = name.toLower();
- if (nativeGLFW && name.contains("glfw")) {
- continue;
- }
- if (nativeOpenAL && name.contains("openal")) {
- continue;
- }
if (applyJnilibHack) {
name = replaceSuffix(name, ".jnilib", ".dylib");
}
@@ -83,14 +77,12 @@ void ExtractNatives::executeTask()
return;
}
auto settings = minecraftInstance->settings();
- bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
- bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for (const auto& source : toExtract) {
- if (!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) {
+ if (!unzipNatives(source, outputPath, jniHackEnabled)) {
const char* reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal);
emitFailed(tr(reason).arg(source, outputPath));
diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp
index 05562ef2..44e5d0a6 100644
--- a/launcher/minecraft/launch/LauncherPartLaunch.cpp
+++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp
@@ -53,7 +53,7 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent)
auto instance = parent->instance();
if (instance->settings()->get("CloseAfterLaunch").toBool()) {
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
- *connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, MessageLevel::Enum level) {
+ *connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, [[maybe_unused]] MessageLevel::Enum level) {
qDebug() << lines;
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
APPLICATION->closeAllWindows();
diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp
index 7bf5a311..fc2d3f68 100644
--- a/launcher/minecraft/mod/DataPack.cpp
+++ b/launcher/minecraft/mod/DataPack.cpp
@@ -28,7 +28,7 @@
#include "Version.h"
// Values taken from:
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
+// https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h
index fc2703c7..b3787b23 100644
--- a/launcher/minecraft/mod/DataPack.h
+++ b/launcher/minecraft/mod/DataPack.h
@@ -63,7 +63,7 @@ class DataPack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
- * See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
+ * See https://minecraft.wiki/w/Data_pack#pack.mcmeta
*/
int m_pack_format = 0;
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index ae3dea8d..31094637 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -132,17 +132,23 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
- if (metadata()) {
- Metadata::remove(index_dir, metadata()->slug);
- } else {
- auto n = name();
- Metadata::remove(index_dir, n);
- }
+ destroyMetadata(index_dir);
}
return Resource::destroy(attempt_trash);
}
+void Mod::destroyMetadata(QDir& index_dir)
+{
+ if (metadata()) {
+ Metadata::remove(index_dir, metadata()->slug);
+ } else {
+ auto n = name();
+ Metadata::remove(index_dir, n);
+ }
+ m_local_details.metadata = nullptr;
+}
+
auto Mod::details() const -> const ModDetails&
{
return m_local_details;
@@ -246,7 +252,8 @@ void Mod::setIcon(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -259,7 +266,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index ca3ee11f..e97ee9d3 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -72,7 +72,7 @@ class Mod : public Resource {
auto metaurl() const -> QString;
/** Get the intneral path to the mod's icon file*/
- QString iconPath() const { return m_local_details.icon_file; };
+ QString iconPath() const { return m_local_details.icon_file; }
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */
@@ -93,6 +93,8 @@ class Mod : public Resource {
// Delete all the files of this mod
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
+ // Delete the metadata only
+ void destroyMetadata(QDir& index_dir);
void finishResolvingWithDetails(ModDetails&& details);
diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h
index 6a15219a..a00d5a24 100644
--- a/launcher/minecraft/mod/ModDetails.h
+++ b/launcher/minecraft/mod/ModDetails.h
@@ -60,17 +60,17 @@ struct ModLicense {
ModLicense(const QString license)
{
- // FIXME: come up with a better license parseing.
+ // FIXME: come up with a better license parsing.
// handle SPDX identifiers? https://spdx.org/licenses/
auto parts = license.split(' ');
QStringList notNameParts = {};
for (auto part : parts) {
- auto url = QUrl(part);
+ auto _url = QUrl(part);
if (part.startsWith("(") && part.endsWith(")"))
- url = QUrl(part.mid(1, part.size() - 2));
+ _url = QUrl(part.mid(1, part.size() - 2));
- if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
- this->url = url.toString();
+ if (_url.isValid() && !_url.scheme().isEmpty() && !_url.host().isEmpty()) {
+ this->url = _url.toString();
notNameParts.append(part);
continue;
}
@@ -89,13 +89,9 @@ struct ModLicense {
}
}
- ModLicense(const QString name, const QString id, const QString url, const QString description)
- {
- this->name = name;
- this->id = id;
- this->url = url;
- this->description = description;
- }
+ ModLicense(const QString& name_, const QString& id_, const QString& url_, const QString& description_)
+ : name(name_), id(id_), url(url_), description(description_)
+ {}
ModLicense(const ModLicense& other) : name(other.name), id(other.id), url(other.url), description(other.description) {}
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 8dbe583d..a5f1489d 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -51,8 +51,13 @@
#include "Application.h"
+#include "Json.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
+#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
@@ -62,6 +67,7 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
+ m_columnsHideable = { false, true, false, true, true, true };
}
QVariant ModFolderModel::data(const QModelIndex& index, int role) const
@@ -137,7 +143,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
}
}
-QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@@ -227,6 +233,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
return true;
}
+bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
+{
+ if (indexes.isEmpty())
+ return true;
+
+ for (auto i : indexes) {
+ if (i.column() != 0) {
+ continue;
+ }
+ auto m = at(i.row());
+ auto index_dir = indexDir();
+ m->destroyMetadata(index_dir);
+ }
+
+ update();
+
+ return true;
+}
+
bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
@@ -308,3 +333,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
+
+static const FlameAPI flameAPI;
+bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
+{
+ if (vers.addonId.isValid()) {
+ ModPlatform::IndexedPack pack{
+ vers.addonId,
+ ModPlatform::ResourceProvider::FLAME,
+ };
+
+ QEventLoop loop;
+
+ auto response = std::make_shared<QByteArray>();
+ auto job = flameAPI.getProject(vers.addonId.toString(), response);
+
+ QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
+ QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
+ QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qDebug() << *response;
+ return;
+ }
+ try {
+ auto obj = Json::requireObject(Json::requireObject(doc), "data");
+ FlameMod::loadIndexedPack(pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading mod info: " << e.cause();
+ }
+ LocalModUpdateTask update_metadata(indexDir(), pack, vers);
+ QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
+ update_metadata.start();
+ });
+
+ job->start();
+
+ loop.exec();
+ }
+ return ResourceFolderModel::installResource(file_path);
+}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index 06fd7814..61d840f9 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -48,6 +48,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+#include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance;
@@ -75,10 +76,12 @@ class ModFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
+ bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList& indexes);
+ bool deleteModsMetadata(const QModelIndexList& indexes);
bool isValid();
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index 0106d5c9..d3237b34 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -447,7 +447,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
}
}
-bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
+bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
{
int row = index.row();
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
@@ -471,7 +471,7 @@ bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& valu
return false;
}
-QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@@ -551,6 +551,9 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
menu->addSeparator()->setText(tr("Show / Hide Columns"));
for (int col = 0; col < columnCount(); ++col) {
+ // Skip creating actions for columns that should not be hidden
+ if (!m_columnsHideable.at(col))
+ continue;
auto act = new QAction(menu);
setupHeaderAction(act, col);
@@ -584,7 +587,8 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const
}
/* Standard Proxy Model for createFilterProxyModel */
-[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
+[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row,
+ [[maybe_unused]] const QModelIndex& source_parent) const
{
auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
if (!model)
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 595b9762..80c31e45 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -49,8 +49,8 @@ class ResourceFolderModel : public QAbstractListModel {
bool stopWatching(const QStringList paths);
/* Helper methods for subclasses, using a predetermined list of paths. */
- virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); };
- virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); };
+ virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
+ virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
/** Given a path in the system, install that resource, moving it to its place in the
* instance file hierarchy.
@@ -78,7 +78,7 @@ class ResourceFolderModel : public QAbstractListModel {
/** Creates a new parse task, if needed, for 'res' and start it.*/
virtual void resolveResource(Resource* res);
- [[nodiscard]] size_t size() const { return m_resources.size(); };
+ [[nodiscard]] qsizetype size() const { return m_resources.size(); }
[[nodiscard]] bool empty() const { return size() == 0; }
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
@@ -97,10 +97,10 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
- QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; };
+ QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
- [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
+ [[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; }
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
@@ -159,7 +159,7 @@ class ResourceFolderModel : public QAbstractListModel {
* This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed
* in the background, so it slowly updates the UI as tasks get done.
*/
- [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; };
+ [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; }
/** Standard implementation of the model update logic.
*
@@ -203,6 +203,7 @@ 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 };
+ QList<bool> m_columnsHideable = { false, false, true };
QDir m_dir;
BaseInstance* m_instance;
@@ -224,15 +225,15 @@ class ResourceFolderModel : public QAbstractListModel {
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
- [[nodiscard]] T* operator[](size_t index) \
+ [[nodiscard]] T* operator[](int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
- [[nodiscard]] T* at(size_t index) \
+ [[nodiscard]] T* at(int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
- [[nodiscard]] const T* at(size_t index) const \
+ [[nodiscard]] const T* at(int index) const \
{ \
return static_cast<const T*>(m_resources.at(index).get()); \
} \
diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp
index dab0f6d6..07453440 100644
--- a/launcher/minecraft/mod/ResourcePack.cpp
+++ b/launcher/minecraft/mod/ResourcePack.cpp
@@ -11,7 +11,7 @@
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
// Values taken from:
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
@@ -50,7 +50,8 @@ void ResourcePack::setImage(QImage new_image) const
PixmapCache::instance().remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -68,7 +69,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h
index da354bc1..c06f3793 100644
--- a/launcher/minecraft/mod/ResourcePack.h
+++ b/launcher/minecraft/mod/ResourcePack.h
@@ -51,7 +51,7 @@ class ResourcePack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
- * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+ * See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
*/
int m_pack_format = 0;
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index 70206cb0..f2743157 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -54,6 +54,7 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents,
QHeaderView::ResizeToContents };
+ m_columnsHideable = { false, true, false, true, true };
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@@ -128,7 +129,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
}
}
-QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@@ -165,7 +166,6 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
default:
return {};
}
- return {};
}
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp
index 7d8c6713..04cc3631 100644
--- a/launcher/minecraft/mod/TexturePack.cpp
+++ b/launcher/minecraft/mod/TexturePack.cpp
@@ -44,7 +44,8 @@ void TexturePack::setImage(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -56,7 +57,7 @@ QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp
index 9a9910fa..5c5f2b7c 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.cpp
+++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp
@@ -49,6 +49,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance*
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
QHeaderView::ResizeToContents };
+ m_columnsHideable = { false, true, false, true };
}
Task* TexturePackFolderModel::createUpdateTask()
@@ -113,7 +114,7 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
}
}
-QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
index f8ecdb33..ee84b1f5 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
@@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst)
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
}
-static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
+static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst)
{
- return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
+ return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders().value();
}
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
@@ -61,7 +61,7 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
if (auto meta = mod->metadata(); meta)
m_mods.append(meta);
prepare();
-};
+}
void GetModDependenciesTask::prepare()
{
@@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName)
{
- if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
+ if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
@@ -130,7 +130,7 @@ QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion
c_dependencies.append(getOverride(ver_dep, providerName));
}
return c_dependencies;
-};
+}
Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep)
{
@@ -181,7 +181,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks;
- callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, auto& pack) {
+ callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
try {
QJsonArray arr;
if (dep.version.length() != 0 && doc.isObject()) {
@@ -191,7 +191,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
}
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) {
- if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
+ if (m_loaderType & ModPlatform::Quilt) { // falback for quilt
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
@@ -201,6 +201,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return;
}
}
+ removePack(dep.addonId);
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;
@@ -215,27 +216,27 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return;
}
if (level == 0) {
- qWarning() << "Dependency cycle exeeded";
+ qWarning() << "Dependency cycle exceeded";
return;
}
if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) {
pDep->pack->addonId = pDep->version.addonId;
- auto dep = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
- if (dep.addonId != pDep->version.addonId) {
+ auto dep_ = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
+ if (dep_.addonId != pDep->version.addonId) {
removePack(pDep->version.addonId);
- addTask(prepareDependencyTask(dep, provider.name, level));
+ addTask(prepareDependencyTask(dep_, provider.name, level));
} else
addTask(getProjectInfoTask(pDep));
}
- for (auto dep : getDependenciesForVersion(pDep->version, provider.name)) {
- addTask(prepareDependencyTask(dep, provider.name, level - 1));
+ for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) {
+ addTask(prepareDependencyTask(dep_, provider.name, level - 1));
}
};
auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks));
tasks->addTask(version);
return tasks;
-};
+}
void GetModDependenciesTask::removePack(const QVariant addonId)
{
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
index 50eba6af..a8b9953d 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
@@ -80,5 +80,5 @@ class GetModDependenciesTask : public SequentialTask {
Provider m_modrinth_provider;
Version m_version;
- ResourceAPI::ModLoaderTypes m_loaderType;
+ ModPlatform::ModLoaderTypes m_loaderType;
};
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
index 5bb44877..82f6b9df 100644
--- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
@@ -133,7 +133,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
return true;
}
-// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
+// https://minecraft.wiki/w/Data_pack#pack.mcmeta
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
{
try {
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index 75bb6d25..7ec00c0c 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -104,14 +104,15 @@ ModDetails ReadMCModTOML(QByteArray contents)
#if TOML_EXCEPTIONS
try {
tomlData = toml::parse(contents.toStdString());
- } catch (const toml::parse_error& err) {
+ } catch ([[maybe_unused]] const toml::parse_error& err) {
return {};
}
#else
- tomlData = toml::parse(contents.toStdString());
- if (!tomlData) {
+ toml::parse_result result = toml::parse(contents.toStdString());
+ if (!result) {
return {};
}
+ tomlData = result.table();
#endif
// array defined by [[mods]]
@@ -151,8 +152,8 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString authors = "";
if (auto authorsDatum = tomlData["authors"].as_string()) {
authors = QString::fromStdString(authorsDatum->get());
- } else if (auto authorsDatum = (*modsTable)["authors"].as_string()) {
- authors = QString::fromStdString(authorsDatum->get());
+ } else if (auto authorsDatumMods = (*modsTable)["authors"].as_string()) {
+ authors = QString::fromStdString(authorsDatumMods->get());
}
if (!authors.isEmpty()) {
details.authors.append(authors);
@@ -161,8 +162,8 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString homeurl = "";
if (auto homeurlDatum = tomlData["displayURL"].as_string()) {
homeurl = QString::fromStdString(homeurlDatum->get());
- } else if (auto homeurlDatum = (*modsTable)["displayURL"].as_string()) {
- homeurl = QString::fromStdString(homeurlDatum->get());
+ } else if (auto homeurlDatumMods = (*modsTable)["displayURL"].as_string()) {
+ homeurl = QString::fromStdString(homeurlDatumMods->get());
}
// fix up url.
if (!homeurl.isEmpty() && !homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) {
@@ -173,16 +174,16 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString issueTrackerURL = "";
if (auto issueTrackerURLDatum = tomlData["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
- } else if (auto issueTrackerURLDatum = (*modsTable)["issueTrackerURL"].as_string()) {
- issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
+ } else if (auto issueTrackerURLDatumMods = (*modsTable)["issueTrackerURL"].as_string()) {
+ issueTrackerURL = QString::fromStdString(issueTrackerURLDatumMods->get());
}
details.issue_tracker = issueTrackerURL;
QString license = "";
if (auto licenseDatum = tomlData["license"].as_string()) {
license = QString::fromStdString(licenseDatum->get());
- } else if (auto licenseDatum = (*modsTable)["license"].as_string()) {
- license = QString::fromStdString(licenseDatum->get());
+ } else if (auto licenseDatumMods = (*modsTable)["license"].as_string()) {
+ license = QString::fromStdString(licenseDatumMods->get());
}
if (!license.isEmpty())
details.licenses.append(ModLicense(license));
@@ -190,8 +191,8 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString logoFile = "";
if (auto logoFileDatum = tomlData["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatum->get());
- } else if (auto logoFileDatum = (*modsTable)["logoFile"].as_string()) {
- logoFile = QString::fromStdString(logoFileDatum->get());
+ } else if (auto logoFileDatumMods = (*modsTable)["logoFile"].as_string()) {
+ logoFile = QString::fromStdString(logoFileDatumMods->get());
}
details.icon_file = logoFile;
@@ -458,7 +459,7 @@ bool process(Mod& mod, ProcessingLevel level)
}
}
-bool processZIP(Mod& mod, ProcessingLevel level)
+bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
{
ModDetails details;
@@ -591,7 +592,7 @@ bool processZIP(Mod& mod, ProcessingLevel level)
return false; // no valid mod found in archive
}
-bool processFolder(Mod& mod, ProcessingLevel level)
+bool processFolder(Mod& mod, [[maybe_unused]] ProcessingLevel level)
{
ModDetails details;
@@ -612,7 +613,7 @@ bool processFolder(Mod& mod, ProcessingLevel level)
return false; // no valid mcmod.info file found
}
-bool processLitemod(Mod& mod, ProcessingLevel level)
+bool processLitemod(Mod& mod, [[maybe_unused]] ProcessingLevel level)
{
ModDetails details;
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index 73cbf891..7b9f4f59 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -178,7 +178,7 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
return true;
}
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{
try {
diff --git a/launcher/minecraft/services/CapeChange.cpp b/launcher/minecraft/services/CapeChange.cpp
index f87da13e..2ba38a6a 100644
--- a/launcher/minecraft/services/CapeChange.cpp
+++ b/launcher/minecraft/services/CapeChange.cpp
@@ -42,7 +42,7 @@
CapeChange::CapeChange(QObject* parent, QString token, QString cape) : Task(parent), m_capeId(cape), m_token(token) {}
-void CapeChange::setCape(QString& cape)
+void CapeChange::setCape([[maybe_unused]] QString& cape)
{
QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"));
auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId);
diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp
index 7043d987..8af01499 100644
--- a/launcher/minecraft/update/AssetUpdateTask.cpp
+++ b/launcher/minecraft/update/AssetUpdateTask.cpp
@@ -7,6 +7,8 @@
#include "Application.h"
+#include "net/ApiDownload.h"
+
AssetUpdateTask::AssetUpdateTask(MinecraftInstance* inst)
{
m_inst = inst;
@@ -29,7 +31,7 @@ void AssetUpdateTask::executeTask()
entry->setStale(true);
auto hexSha1 = assets->sha1.toLatin1();
qDebug() << "Asset index SHA1:" << hexSha1;
- auto dl = Net::Download::makeCached(indexUrl, entry);
+ auto dl = Net::ApiDownload::makeCached(indexUrl, entry);
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl);
diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp
index a6ca2102..ce0c9a72 100644
--- a/launcher/minecraft/update/FMLLibrariesTask.cpp
+++ b/launcher/minecraft/update/FMLLibrariesTask.cpp
@@ -8,6 +8,8 @@
#include "Application.h"
#include "BuildConfig.h"
+#include "net/ApiDownload.h"
+
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance* inst)
{
m_inst = inst;
@@ -62,7 +64,7 @@ void FMLLibrariesTask::executeTask()
for (auto& lib : fmlLibsToProcess) {
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
- dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options));
+ dljob->addNetAction(Net::ApiDownload::makeCached(QUrl(urlString), entry, options));
}
connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h
index 65c80d26..a4c03cb7 100644
--- a/launcher/modplatform/CheckUpdateTask.h
+++ b/launcher/modplatform/CheckUpdateTask.h
@@ -14,7 +14,7 @@ class CheckUpdateTask : public Task {
public:
CheckUpdateTask(QList<Mod*>& mods,
std::list<Version>& mcVersions,
- std::optional<ResourceAPI::ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){};
@@ -62,7 +62,7 @@ class CheckUpdateTask : public Task {
protected:
QList<Mod*>& m_mods;
std::list<Version>& m_game_versions;
- std::optional<ResourceAPI::ModLoaderTypes> m_loaders;
+ std::optional<ModPlatform::ModLoaderTypes> m_loaders;
std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> m_updatable;
diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp
index 17edcdb5..72ba88d9 100644
--- a/launcher/modplatform/ModIndex.cpp
+++ b/launcher/modplatform/ModIndex.cpp
@@ -134,4 +134,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
projectID.toString();
}
+auto getModLoaderString(ModLoaderType type) -> const QString
+{
+ switch (type) {
+ case NeoForge:
+ return "neoforge";
+ case Forge:
+ return "forge";
+ case Cauldron:
+ return "cauldron";
+ case LiteLoader:
+ return "liteloader";
+ case Fabric:
+ return "fabric";
+ case Quilt:
+ return "quilt";
+ default:
+ break;
+ }
+ return "";
+}
+
} // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index e56c282a..4ed8bcd8 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -31,6 +31,9 @@ class QIODevice;
namespace ModPlatform {
+enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
+Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
+
enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
@@ -101,7 +104,7 @@ struct IndexedVersion {
QString downloadUrl;
QString date;
QString fileName;
- QStringList loaders = {};
+ ModLoaderTypes loaders = {};
QString hash_type;
QString hash;
bool is_preferred = true;
@@ -144,7 +147,7 @@ struct IndexedPack {
ExtraPackData extraData;
// For internal use, not provided by APIs
- [[nodiscard]] bool isVersionSelected(size_t index) const
+ [[nodiscard]] bool isVersionSelected(int index) const
{
if (!versionsLoaded)
return false;
@@ -174,9 +177,18 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
{ "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
{ "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
-};
+}
+
QString getMetaURL(ResourceProvider provider, QVariant projectID);
+auto getModLoaderString(ModLoaderType type) -> const QString;
+
+constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept
+{
+ auto x = static_cast<int>(l);
+ return x && !(x & (x - 1));
+}
+
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h
index ef220977..3b195938 100644
--- a/launcher/modplatform/ResourceAPI.h
+++ b/launcher/modplatform/ResourceAPI.h
@@ -54,9 +54,6 @@ class ResourceAPI {
public:
virtual ~ResourceAPI() = default;
- enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 };
- Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
-
struct SortingMethod {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request.
@@ -74,7 +71,7 @@ class ResourceAPI {
std::optional<QString> search;
std::optional<SortingMethod> sorting;
- std::optional<ModLoaderTypes> loaders;
+ std::optional<ModPlatform::ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions;
};
struct SearchCallbacks {
@@ -87,7 +84,7 @@ class ResourceAPI {
ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions;
- std::optional<ModLoaderTypes> loaders;
+ std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other)
@@ -108,13 +105,15 @@ class ResourceAPI {
void operator=(ProjectInfoArgs other) { pack = other.pack; }
};
struct ProjectInfoCallbacks {
- std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
+ std::function<void(QJsonDocument&, const ModPlatform::IndexedPack&)> on_succeed;
+ std::function<void(QString const& reason)> on_fail;
+ std::function<void()> on_abort;
};
struct DependencySearchArgs {
ModPlatform::Dependency dependency;
Version mcVersion;
- ModLoaderTypes loader;
+ ModPlatform::ModLoaderTypes loader;
};
struct DependencySearchCallbacks {
@@ -128,28 +127,30 @@ class ResourceAPI {
public slots:
[[nodiscard]] virtual Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const
{
- qWarning() << "TODO";
+ qWarning() << "TODO: ResourceAPI::searchProjects";
return nullptr;
}
- [[nodiscard]] virtual Task::Ptr getProject(QString addonId, std::shared_ptr<QByteArray> response) const
+ [[nodiscard]] virtual Task::Ptr getProject([[maybe_unused]] QString addonId,
+ [[maybe_unused]] std::shared_ptr<QByteArray> response) const
{
- qWarning() << "TODO";
+ qWarning() << "TODO: ResourceAPI::getProject";
return nullptr;
}
- [[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
+ [[nodiscard]] virtual Task::Ptr getProjects([[maybe_unused]] QStringList addonIds,
+ [[maybe_unused]] std::shared_ptr<QByteArray> response) const
{
- qWarning() << "TODO";
+ qWarning() << "TODO: ResourceAPI::getProjects";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const
{
- qWarning() << "TODO";
+ qWarning() << "TODO: ResourceAPI::getProjectInfo";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const
{
- qWarning() << "TODO";
+ qWarning() << "TODO: ResourceAPI::getProjectVersions";
return nullptr;
}
@@ -159,25 +160,6 @@ class ResourceAPI {
return nullptr;
}
- static auto getModLoaderString(ModLoaderType type) -> const QString
- {
- switch (type) {
- case Forge:
- return "forge";
- case Cauldron:
- return "cauldron";
- case LiteLoader:
- return "liteloader";
- case Fabric:
- return "fabric";
- case Quilt:
- return "quilt";
- default:
- break;
- }
- return "";
- }
-
protected:
[[nodiscard]] inline QString debugName() const { return "External resource API"; }
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index f0198481..e5771b7c 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -53,6 +53,8 @@
#include "net/ChecksumValidator.h"
#include "settings/INISettingsObject.h"
+#include "net/ApiDownload.h"
+
#include "Application.h"
#include "BuildConfig.h"
@@ -83,7 +85,7 @@ void PackInstallTask::executeTask()
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
auto searchUrl =
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
@@ -631,7 +633,7 @@ void PackInstallTask::installConfigs()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!m_version.configs.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
@@ -748,7 +750,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToExtract.insert(entry->getFullPath(), mod);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@@ -759,7 +761,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToDecomp.insert(entry->getFullPath(), mod);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@@ -773,7 +775,7 @@ void PackInstallTask::downloadMods()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
entry->setStale(true);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@@ -1003,15 +1005,30 @@ static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString&
if (!vlist)
return {};
- if (!vlist->isLoaded())
- vlist->load(Net::Mode::Online);
+ if (!vlist->isLoaded()) {
+ QEventLoop loadVersionLoop;
+ auto task = vlist->getLoadTask();
+ QObject::connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
+ if (!task->isRunning())
+ task->start();
+
+ loadVersionLoop.exec();
+ }
auto ver = vlist->getVersion(version);
if (!ver)
return {};
- if (!ver->isLoaded())
+ if (!ver->isLoaded()) {
+ QEventLoop loadVersionLoop;
ver->load(Net::Mode::Online);
+ auto task = ver->getCurrentTask();
+ QObject::connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
+ if (!task->isRunning())
+ task->start();
+
+ loadVersionLoop.exec();
+ }
return ver;
}
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index ae168bbd..5865bee9 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -1,6 +1,9 @@
#include "FileResolvingTask.h"
#include "Json.h"
+#include "modplatform/ModIndex.h"
+#include "net/ApiDownload.h"
+#include "net/ApiUpload.h"
#include "net/Upload.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
@@ -38,7 +41,7 @@ void Flame::FileResolvingTask::executeTask()
return l;
}));
QByteArray data = Json::toText(object);
- auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
+ auto dl = Net::ApiUpload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
m_dljob->addNetAction(dl);
auto step_progress = std::make_shared<TaskStepProgress>();
@@ -93,14 +96,14 @@ void Flame::FileResolvingTask::netJobFinished()
auto& out = m_toProcess.files[fileid];
try {
out.parseFromObject(Json::requireObject(file));
- } catch (const JSONValidationError& e) {
+ } catch ([[maybe_unused]] const JSONValidationError& e) {
qDebug() << "Blocked mod on curseforge" << out.fileName;
auto hash = out.hash;
if (!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>();
- auto dl = Net::Download::makeByteArray(QUrl(url), output);
- QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
+ auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
+ QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [&out]() { out.resolved = true; });
m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output);
@@ -151,7 +154,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
// If there's more than one mod loader for this version, we can't know for sure
// which file is relative to each loader, so it's best to not use any one and
// let the user download it manually.
- if (file.loaders.size() <= 1) {
+ if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) {
out->url = file.downloadUrl;
qDebug() << "Found alternative on modrinth " << out->fileName;
} else {
@@ -171,9 +174,9 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto projectId = mod->projectId;
auto output = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
- auto dl = Net::Download::makeByteArray(url, output);
+ auto dl = Net::ApiDownload::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName;
- QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
+ QObject::connect(dl.get(), &Net::ApiDownload::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 =
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
index 885c4687..a9697893 100644
--- a/launcher/modplatform/flame/FlameAPI.cpp
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -6,8 +6,9 @@
#include "FlameModIndex.h"
#include "Application.h"
-#include "BuildConfig.h"
#include "Json.h"
+#include "net/ApiDownload.h"
+#include "net/ApiUpload.h"
#include "net/NetJob.h"
#include "net/Upload.h"
@@ -26,7 +27,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shar
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
- netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
+ netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
return netJob;
}
@@ -38,7 +39,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
- netJob->addNetAction(Net::Download::makeByteArray(
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
response));
@@ -73,8 +74,8 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
- netJob->addNetAction(
- Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(
+ QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
QJsonParseError parse_error{};
@@ -113,7 +114,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto response = std::make_shared<QByteArray>();
ModPlatform::IndexedVersion ver;
- netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
QJsonParseError parse_error{};
@@ -129,20 +130,15 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto obj = Json::requireObject(doc);
auto arr = Json::requireArray(obj, "data");
- QJsonObject latest_file_obj;
- ModPlatform::IndexedVersion ver_tmp;
-
for (auto file : arr) {
auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
- bool better_release = file_tmp.version_type <= ver_tmp.version_type;
- if (file_tmp.date > ver_tmp.date && better_release) {
- ver_tmp = file_tmp;
- latest_file_obj = file_obj;
- }
+ if (file_tmp.date > ver.date &&
+ (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders) &&
+ file_tmp.version_type <= ver.version_type)
+ ver = file_tmp;
}
- ver = FlameMod::loadIndexedPackVersion(latest_file_obj);
} catch (Json::JsonException& e) {
qCritical() << "Failed to parse response from a version request.";
qCritical() << e.what();
@@ -174,7 +170,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
- netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
+ netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
@@ -196,13 +192,24 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
- netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
+ netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob;
}
+Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const
+{
+ auto netJob = makeShared<NetJob>(QString("Flame::GetFile"), APPLICATION->network());
+ netJob->addNetAction(
+ Net::ApiDownload::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), response));
+
+ QObject::connect(netJob.get(), &NetJob::failed, [addonId, fileId] { qDebug() << "Flame API file failure" << addonId << fileId; });
+
+ return netJob;
+}
+
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 49bc316f..e22d8f0d 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -20,10 +20,14 @@ class FlameAPI : public NetworkResourceAPI {
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response);
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
+ Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
- static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt); }
+ static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
+ {
+ return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt);
+ }
private:
static int getClassId(ModPlatform::ResourceType type)
@@ -34,22 +38,47 @@ class FlameAPI : public NetworkResourceAPI {
return 6;
case ModPlatform::ResourceType::RESOURCE_PACK:
return 12;
+ case ModPlatform::ResourceType::SHADER_PACK:
+ return 6552;
}
}
- static int getMappedModLoader(ModLoaderTypes loaders)
+ static int getMappedModLoader(ModPlatform::ModLoaderType loaders)
{
// https://docs.curseforge.com/?http#tocS_ModLoaderType
- if (loaders & Forge)
- return 1;
- if (loaders & Fabric)
- return 4;
- // TODO: remove this once Quilt drops official Fabric support
- if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
- return 4; // Quilt would probably be 5
+ switch (loaders) {
+ case ModPlatform::Forge:
+ return 1;
+ case ModPlatform::Cauldron:
+ return 2;
+ case ModPlatform::LiteLoader:
+ return 3;
+ case ModPlatform::Fabric:
+ return 4;
+ case ModPlatform::Quilt:
+ return 5;
+ case ModPlatform::NeoForge:
+ return 6;
+ }
return 0;
}
+ static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
+ {
+ QStringList l;
+ for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) {
+ if (types & loader) {
+ l << QString::number(getMappedModLoader(loader));
+ }
+ }
+ return l;
+ }
+
+ static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
+ {
+ return "[" + getModLoaderStrings(types).join(',') + "]";
+ }
+
private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
{
@@ -66,7 +95,7 @@ class FlameAPI : public NetworkResourceAPI {
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc");
if (args.loaders.has_value())
- get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
+ get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value())));
get_arguments.append(gameVersionStr);
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
@@ -80,47 +109,27 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{
auto addonId = args.pack.addonId.toString();
- QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) };
+ QString url = QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000").arg(addonId);
- QStringList get_parameters;
if (args.mcVersions.has_value())
- get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
-
- if (args.loaders.has_value()) {
- int mappedModLoader = getMappedModLoader(args.loaders.value());
-
- if (args.loaders.value() & Quilt) {
- auto overide = ModPlatform::getOverrideDeps();
- auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
- return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
- });
- if (over != overide.cend()) {
- mappedModLoader = 5;
- }
- }
+ url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
- get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
+ if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
+ int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
+ url += QString("&modLoaderType=%1").arg(mappedModLoader);
}
-
- return url + get_parameters.join('&');
+ return url;
};
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
- auto mappedModLoader = getMappedModLoader(args.loader);
auto addonId = args.dependency.addonId.toString();
- if (args.loader & Quilt) {
- auto overide = ModPlatform::getOverrideDeps();
- auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
- return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
- });
- if (over != overide.cend()) {
- mappedModLoader = 5;
- }
+ auto url =
+ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString());
+ if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
+ int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader)));
+ url += QString("&modLoaderType=%1").arg(mappedModLoader);
}
- return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
- .arg(addonId)
- .arg(args.mcVersion.toString())
- .arg(mappedModLoader);
+ return url;
};
};
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp
index 5ff833cf..023e26e4 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.cpp
+++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp
@@ -5,13 +5,13 @@
#include <MurmurHash2.h>
#include <memory>
-#include "FileSystem.h"
#include "Json.h"
#include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
-#include "minecraft/mod/ResourceFolderModel.h"
+
+#include "net/ApiDownload.h"
static FlameAPI api;
@@ -33,7 +33,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
- auto dl = Net::Download::makeByteArray(url, response);
+ auto dl = Net::ApiDownload::makeByteArray(url, response);
get_project_job->addNetAction(dl);
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
@@ -77,7 +77,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
- auto dl = Net::Download::makeByteArray(url, response);
+ auto dl = Net::ApiDownload::makeByteArray(url, response);
get_file_info_job->addNetAction(dl);
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h
index e3465d7e..05c619a7 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.h
+++ b/launcher/modplatform/flame/FlameCheckUpdate.h
@@ -10,7 +10,7 @@ class FlameCheckUpdate : public CheckUpdateTask {
public:
FlameCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
- std::optional<ResourceAPI::ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{}
diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
index 8b662294..45b4e212 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
@@ -61,6 +61,7 @@
#include "meta/VersionList.h"
#include "minecraft/World.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
+#include "net/ApiDownload.h"
static const FlameAPI api;
@@ -283,7 +284,7 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
// filter by minecraft version, if the loader depends on a certain version.
// not all mod loaders depend on a given Minecraft version, so we won't do this
// filtering for those loaders.
- if (loaderType == "forge") {
+ if (loaderType == "forge" || loaderType == "neoforge") {
auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) {
return req.uid == "net.minecraft" && req.equalsVersion == mcVersion;
});
@@ -349,7 +350,11 @@ bool FlameCreationTask::createInstance()
for (auto& loader : m_pack.minecraft.modLoaders) {
auto id = loader.id;
- if (id.startsWith("forge-")) {
+ if (id.startsWith("neoforge-")) {
+ id.remove("neoforge-");
+ loaderType = "neoforge";
+ loaderUid = "net.neoforged";
+ } else if (id.startsWith("forge-")) {
id.remove("forge-");
loaderType = "forge";
loaderUid = "net.minecraftforge";
@@ -523,7 +528,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path;
- auto dl = Net::Download::makeFile(result.url, path);
+ auto dl = Net::ApiDownload::makeFile(result.url, path);
m_files_job->addNetAction(dl);
}
break;
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index 5a44d4db..3754ae1a 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -54,7 +54,7 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
pack.extraDataLoaded = true;
}
-void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
+void FlameMod::loadBody(ModPlatform::IndexedPack& pack, [[maybe_unused]] QJsonObject& obj)
{
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
@@ -75,12 +75,13 @@ static QString enumToString(int hash_algorithm)
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
- const shared_qobject_ptr<QNetworkAccessManager>& network,
+ [[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst)
{
QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
if (!file.addonId.isValid())
file.addonId = pack.addonId;
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
@@ -116,6 +118,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
if (str.contains('.'))
file.mcVersion.append(str);
+ auto loader = str.toLower();
+ if (loader == "neoforge")
+ file.loaders |= ModPlatform::NeoForge;
+ if (loader == "forge")
+ file.loaders |= ModPlatform::Forge;
+ if (loader == "cauldron")
+ file.loaders |= ModPlatform::Cauldron;
+ if (loader == "liteloader")
+ file.loaders |= ModPlatform::LiteLoader;
+ if (loader == "fabric")
+ file.loaders |= ModPlatform::Fabric;
+ if (loader == "quilt")
+ file.loaders |= ModPlatform::Quilt;
}
file.addonId = Json::requireInteger(obj, "modId");
@@ -175,8 +190,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
return file;
}
-ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
+ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
{
+ auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
+ QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@@ -185,7 +203,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
if (!file.addonId.isValid())
file.addonId = m.addonId;
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
versions.append(file);
}
@@ -194,5 +213,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
- return versions.front();
-};
+ if (versions.size() != 0)
+ return versions.front();
+ return {};
+}
diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h
index aa0d6f81..1bcaa44b 100644
--- a/launcher/modplatform/flame/FlameModIndex.h
+++ b/launcher/modplatform/flame/FlameModIndex.h
@@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
-auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
+auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
} // namespace FlameMod \ No newline at end of file
diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
index f5f3af37..d86d34bf 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.cpp
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -43,12 +43,14 @@ const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
FlamePackExportTask::FlamePackExportTask(const QString& name,
const QString& version,
const QString& author,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter)
: name(name)
, version(version)
, author(author)
+ , optionalFiles(optionalFiles)
, instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot())
@@ -381,6 +383,7 @@ QByteArray FlamePackExportTask::generateIndex()
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");
+ const ComponentPtr neoforge = profile->getComponent("net.neoforged");
// convert all available components to mrpack dependencies
if (minecraft != nullptr)
@@ -392,6 +395,8 @@ QByteArray FlamePackExportTask::generateIndex()
id = "fabric-" + fabric->getVersion();
else if (forge != nullptr)
id = "forge-" + forge->getVersion();
+ else if (neoforge != nullptr)
+ id = "neoforge-" + neoforge->getVersion();
version["modLoaders"] = QJsonArray();
if (!id.isEmpty()) {
QJsonObject loader;
@@ -407,7 +412,7 @@ QByteArray FlamePackExportTask::generateIndex()
QJsonObject file;
file["projectID"] = mod.addonId;
file["fileID"] = mod.version;
- file["required"] = mod.enabled;
+ file["required"] = mod.enabled || !optionalFiles;
files << file;
}
obj["files"] = files;
diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h
index d3dc6281..78b46e91 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.h
+++ b/launcher/modplatform/flame/FlamePackExportTask.h
@@ -30,6 +30,7 @@ class FlamePackExportTask : public Task {
FlamePackExportTask(const QString& name,
const QString& version,
const QString& author,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter);
@@ -44,6 +45,7 @@ class FlamePackExportTask : public Task {
// inputs
const QString name, version, author;
+ const bool optionalFiles;
const InstancePtr instance;
MinecraftInstance* mcInstance;
const QDir gameRoot;
diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
index c278f800..dccccdc2 100644
--- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp
+++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
@@ -10,6 +10,8 @@
#include "modplatform/ModIndex.h"
+#include "net/ApiDownload.h"
+
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
{
auto search_url_optional = getSearchURL(args);
@@ -23,7 +25,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
auto response = std::make_shared<QByteArray>();
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(search_url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
QJsonParseError parse_error{};
@@ -70,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo
callbacks.on_succeed(doc, args.pack);
});
-
+ QObject::connect(job.get(), &NetJob::failed, [callbacks](QString reason) { callbacks.on_fail(reason); });
+ QObject::connect(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
return job;
}
@@ -85,7 +88,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
- netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
QJsonParseError parse_error{};
@@ -113,7 +116,7 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteA
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response));
return netJob;
}
@@ -129,7 +132,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
- netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{};
@@ -145,4 +148,4 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
});
return netJob;
-};
+}
diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp
index 4a1bbef9..ecf97345 100644
--- a/launcher/modplatform/import_ftb/PackHelpers.cpp
+++ b/launcher/modplatform/import_ftb/PackHelpers.cpp
@@ -59,16 +59,20 @@ Modpack parseDirectory(QString path)
auto obj = Json::requireObject(target, "target");
auto name = Json::requireString(obj, "name", "name");
auto version = Json::requireString(obj, "version", "version");
- if (name == "forge") {
- modpack.loaderType = ResourceAPI::Forge;
+ if (name == "neoforge") {
+ modpack.loaderType = ModPlatform::NeoForge;
+ modpack.version = version;
+ break;
+ } else if (name == "forge") {
+ modpack.loaderType = ModPlatform::Forge;
modpack.version = version;
break;
} else if (name == "fabric") {
- modpack.loaderType = ResourceAPI::Fabric;
+ modpack.loaderType = ModPlatform::Fabric;
modpack.version = version;
break;
} else if (name == "quilt") {
- modpack.loaderType = ResourceAPI::Quilt;
+ modpack.loaderType = ModPlatform::Quilt;
modpack.version = version;
break;
}
diff --git a/launcher/modplatform/import_ftb/PackHelpers.h b/launcher/modplatform/import_ftb/PackHelpers.h
index 8ea4f3fa..5400252b 100644
--- a/launcher/modplatform/import_ftb/PackHelpers.h
+++ b/launcher/modplatform/import_ftb/PackHelpers.h
@@ -39,7 +39,7 @@ struct Modpack {
// not needed for instance creation
QVariant jvmArgs;
- std::optional<ResourceAPI::ModLoaderType> loaderType;
+ std::optional<ModPlatform::ModLoaderType> loaderType;
QString loaderVersion;
QIcon icon;
diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp
index b5e424d1..9a3b2595 100644
--- a/launcher/modplatform/import_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp
@@ -68,21 +68,25 @@ void PackInstallTask::copySettings()
auto modloader = m_pack.loaderType;
if (modloader.has_value())
switch (modloader.value()) {
- case ResourceAPI::Forge: {
+ case ModPlatform::NeoForge: {
+ components->setComponentVersion("net.neoforged", m_pack.version, true);
+ break;
+ }
+ case ModPlatform::Forge: {
components->setComponentVersion("net.minecraftforge", m_pack.version, true);
break;
}
- case ResourceAPI::Fabric: {
+ case ModPlatform::Fabric: {
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
break;
}
- case ResourceAPI::Quilt: {
+ case ModPlatform::Quilt: {
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
break;
}
- case ResourceAPI::Cauldron:
+ case ModPlatform::Cauldron:
break;
- case ResourceAPI::LiteLoader:
+ case ModPlatform::LiteLoader:
break;
}
components->saveNow();
diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
index 6d27357a..8f1a6e2f 100644
--- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
@@ -40,6 +40,8 @@
#include "Application.h"
#include "BuildConfig.h"
+#include "net/ApiDownload.h"
+
namespace LegacyFTB {
void PackFetchTask::fetch()
@@ -51,7 +53,7 @@ void PackFetchTask::fetch()
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
- jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
+ jobPtr->addNetAction(Net::ApiDownload::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
@@ -71,7 +73,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch)
for (auto& packCode : toFetch) {
auto data = std::make_shared<QByteArray>();
NetJob* job = new NetJob("Fetching private pack", m_network);
- job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data));
+ job->addNetAction(Net::ApiDownload::makeByteArray(privatePackBaseUrl.arg(packCode), data));
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {
ModpackList packs;
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
index 91c821f0..761f622b 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -48,6 +48,8 @@
#include "Application.h"
#include "BuildConfig.h"
+#include "net/ApiDownload.h"
+
namespace LegacyFTB {
PackInstallTask::PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version)
@@ -77,7 +79,7 @@ void PackInstallTask::downloadPack()
} else {
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
}
- netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath));
+ netJobContainer->addNetAction(Net::ApiDownload::makeFile(url, archivePath));
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp
index 364cf3f3..f453f5cb 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.cpp
+++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp
@@ -6,6 +6,8 @@
#include "Application.h"
#include "Json.h"
+#include "net/ApiDownload.h"
+#include "net/ApiUpload.h"
#include "net/NetJob.h"
#include "net/Upload.h"
@@ -13,7 +15,7 @@ Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::sh
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
- netJob->addNetAction(Net::Download::makeByteArray(
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
return netJob;
@@ -31,7 +33,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
- netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
+ netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
return netJob;
}
@@ -39,7 +41,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
@@ -60,7 +62,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
- netJob->addNetAction(Net::Upload::makeByteArray(
+ netJob->addNetAction(Net::ApiUpload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
return netJob;
@@ -69,7 +71,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
@@ -93,7 +95,8 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
- netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
+ netJob->addNetAction(
+ Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
return netJob;
}
@@ -103,7 +106,7 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
return netJob;
}
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index 58af14cc..d0f0811b 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI {
auto latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
@@ -35,20 +35,19 @@ class ModrinthAPI : public NetworkResourceAPI {
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
- static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
+ static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
{
QStringList l;
- for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) {
+ for (auto loader :
+ { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) {
if (types & loader) {
l << getModLoaderString(loader);
}
}
- if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
- l << getModLoaderString(Fabric);
return l;
}
- static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
+ static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
{
QStringList l;
for (auto loader : getModLoaderStrings(types)) {
@@ -141,7 +140,10 @@ class ModrinthAPI : public NetworkResourceAPI {
return s.isEmpty() ? QString() : s;
}
- static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt | LiteLoader); }
+ static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
+ {
+ return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader);
+ }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
index 845e0c61..804eb0ba 100644
--- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
+++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
@@ -11,7 +11,6 @@
#include "tasks/ConcurrentTask.h"
#include "minecraft/mod/ModFolderModel.h"
-#include "minecraft/mod/ResourceFolderModel.h"
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
@@ -111,11 +110,11 @@ void ModrinthCheckUpdate::executeTask()
// so we may want to filter it
QString loader_filter;
if (m_loaders.has_value()) {
- static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric,
- ResourceAPI::ModLoaderType::Quilt };
+ static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
+ ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt };
for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) {
- loader_filter = api.getModLoaderString(flag);
+ loader_filter = ModPlatform::getModLoaderString(flag);
break;
}
}
diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h
index 4583dd6c..f2f2c7e9 100644
--- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h
+++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h
@@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
public:
ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
- std::optional<ResourceAPI::ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{}
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
index 16e375cd..9ff6b374 100644
--- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
@@ -11,6 +11,7 @@
#include "net/ChecksumValidator.h"
+#include "net/ApiDownload.h"
#include "net/NetJob.h"
#include "settings/INISettingsObject.h"
@@ -210,6 +211,8 @@ bool ModrinthCreationTask::createInstance()
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!m_forge_version.isEmpty())
components->setComponentVersion("net.minecraftforge", m_forge_version);
+ if (!m_neoForge_version.isEmpty())
+ components->setComponentVersion("net.neoforged", m_neoForge_version);
if (m_instIcon != "default") {
instance.setIconKey(m_instIcon);
@@ -238,7 +241,7 @@ bool ModrinthCreationTask::createInstance()
}
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
- auto dl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
+ auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_files_job->addNetAction(dl);
@@ -247,7 +250,7 @@ bool ModrinthCreationTask::createInstance()
// MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef();
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
- auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
+ auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_files_job->addNetAction(ndl);
if (auto shared = param.lock())
@@ -397,6 +400,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") {
m_forge_version = Json::requireString(*it, "Forge version");
+ } else if (name == "neoforge") {
+ m_neoForge_version = Json::requireString(*it, "NeoForge version");
} else {
throw JSONValidationError("Unknown dependency type: " + name);
}
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
index 07e417be..1bd5b7de 100644
--- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
@@ -39,7 +39,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
private:
QWidget* m_parent = nullptr;
- QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version;
+ QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version, m_neoForge_version;
QString m_managed_id, m_managed_version_id, m_managed_name;
std::vector<Modrinth::File> m_files;
diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
index 7bf29639..a9ddb0c9 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
@@ -33,12 +33,14 @@ const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "z
ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
const QString& version,
const QString& summary,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter)
: name(name)
, version(version)
, summary(summary)
+ , optionalFiles(optionalFiles)
, instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot())
@@ -174,10 +176,10 @@ void ModrinthPackExportTask::parseApiResponse(const std::shared_ptr<QByteArray>
if (obj.isEmpty())
continue;
- const QJsonArray files = obj["files"].toArray();
- if (auto fileIter = std::find_if(files.begin(), files.end(),
+ const QJsonArray files_array = obj["files"].toArray();
+ if (auto fileIter = std::find_if(files_array.begin(), files_array.end(),
[&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); });
- fileIter != files.end()) {
+ fileIter != files_array.end()) {
// map the file to the url
resolvedFiles[iterator.key()] =
ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), iterator.value(),
@@ -245,6 +247,7 @@ QByteArray ModrinthPackExportTask::generateIndex()
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");
+ const ComponentPtr neoForge = profile->getComponent("net.neoforged");
// convert all available components to mrpack dependencies
QJsonObject dependencies;
@@ -256,6 +259,8 @@ QByteArray ModrinthPackExportTask::generateIndex()
dependencies["fabric-loader"] = fabric->m_version;
if (forge != nullptr)
dependencies["forge"] = forge->m_version;
+ if (neoForge != nullptr)
+ dependencies["neoforge"] = neoForge->m_version;
out["dependencies"] = dependencies;
}
@@ -267,16 +272,18 @@ QByteArray ModrinthPackExportTask::generateIndex()
QString path = iterator.key();
const ResolvedFile& value = iterator.value();
- // 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;
+ if (optionalFiles) {
+ // 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;
diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h
index 1f9e0eb7..83540dfa 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h
@@ -31,6 +31,7 @@ class ModrinthPackExportTask : public Task {
ModrinthPackExportTask(const QString& name,
const QString& version,
const QString& summary,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter);
@@ -50,6 +51,7 @@ class ModrinthPackExportTask : public Task {
// inputs
const QString name, version, summary;
+ const bool optionalFiles;
const InstancePtr instance;
MinecraftInstance* mcInstance;
const QDir gameRoot;
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index dbb6aeed..f1e77d46 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -93,19 +93,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraDataLoaded = true;
}
-void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
- QJsonArray& arr,
- const shared_qobject_ptr<QNetworkAccessManager>& network,
- const BaseInstance* inst)
+void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
{
QVector<ModPlatform::IndexedVersion> unsortedVersions;
- QString mcVersion = (static_cast<const MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft");
+ auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
+ QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@@ -135,7 +135,18 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
}
auto loaders = Json::requireArray(obj, "loaders");
for (auto loader : loaders) {
- file.loaders.append(loader.toString());
+ if (loader == "neoforge")
+ file.loaders |= ModPlatform::NeoForge;
+ if (loader == "forge")
+ file.loaders |= ModPlatform::Forge;
+ if (loader == "cauldron")
+ file.loaders |= ModPlatform::Cauldron;
+ if (loader == "liteloader")
+ file.loaders |= ModPlatform::LiteLoader;
+ if (loader == "fabric")
+ file.loaders |= ModPlatform::Fabric;
+ if (loader == "quilt")
+ file.loaders |= ModPlatform::Quilt;
}
file.version = Json::requireString(obj, "name");
file.version_number = Json::requireString(obj, "version_number");
@@ -221,15 +232,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {};
}
-auto Modrinth::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
+auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
+ -> ModPlatform::IndexedVersion
{
- QVector<ModPlatform::IndexedVersion> versions;
+ auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
+ QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
+ QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
versions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@@ -238,4 +254,4 @@ auto Modrinth::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArr
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.length() != 0 ? versions.front() : ModPlatform::IndexedVersion();
-} \ No newline at end of file
+}
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h
index 58a0f227..93f91eec 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.h
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h
@@ -26,11 +26,8 @@ namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
-void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
- QJsonArray& arr,
- const shared_qobject_ptr<QNetworkAccessManager>& network,
- const BaseInstance* inst);
+void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
-auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
+auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
} // namespace Modrinth
diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp
index 1be28378..71f66bf3 100644
--- a/launcher/modplatform/packwiz/Packwiz.cpp
+++ b/launcher/modplatform/packwiz/Packwiz.cpp
@@ -89,7 +89,8 @@ auto intEntry(toml::table table, QString entry_name) -> int
return node.value_or(0);
}
-auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod
+auto V1::createModFormat([[maybe_unused]] QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version)
+ -> Mod
{
Mod mod;
@@ -114,7 +115,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo
return mod;
}
-auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod
+auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
{
// Try getting metadata if it exists
Mod mod{ getIndexForMod(index_dir, slug) };
@@ -241,12 +242,13 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
return {};
}
#else
- table = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname)));
- if (!table) {
+ toml::parse_result result = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname)));
+ if (!result) {
qWarning() << QString("Could not open file %1!").arg(normalized_fname);
- qWarning() << "Reason: " << QString(table.error().what());
+ qWarning() << "Reason: " << result.error().description();
return {};
}
+ table = result.table();
#endif
// index_file.close();
diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp
index dd59e652..ad08d72d 100644
--- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp
+++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp
@@ -23,6 +23,8 @@
#include "Application.h"
+#include "net/ApiDownload.h"
+
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl& sourceUrl, const QString& minecraftVersion)
{
m_sourceUrl = sourceUrl;
@@ -45,7 +47,7 @@ void Technic::SingleZipPackInstallTask::executeTask()
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
- m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
m_archivePath = entry->getFullPath();
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp
index ad564de0..c162d625 100644
--- a/launcher/modplatform/technic/SolderPackInstallTask.cpp
+++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp
@@ -42,6 +42,7 @@
#include "SolderPackManifest.h"
#include "TechnicPackProcessor.h"
+#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
@@ -71,7 +72,7 @@ void Technic::SolderPackInstallTask::executeTask()
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));
auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
- m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response));
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
@@ -111,7 +112,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
for (const auto& mod : build.mods) {
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
- auto dl = Net::Download::makeFile(mod.url, path);
+ auto dl = Net::ApiDownload::makeFile(mod.url, path);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
diff --git a/launcher/modplatform/technic/TechnicPackProcessor.cpp b/launcher/modplatform/technic/TechnicPackProcessor.cpp
index 778a6531..3b9424bf 100644
--- a/launcher/modplatform/technic/TechnicPackProcessor.cpp
+++ b/launcher/modplatform/technic/TechnicPackProcessor.cpp
@@ -31,7 +31,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings,
const QString& instIcon,
const QString& stagingPath,
const QString& minecraftVersion,
- const bool isSolder)
+ [[maybe_unused]] const bool isSolder)
{
QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft");
QString configPath = FS::PathCombine(stagingPath, "instance.cfg");
@@ -138,15 +138,15 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings,
try {
QJsonDocument doc = Json::requireDocument(data);
QJsonObject root = Json::requireObject(doc, "version.json");
- QString minecraftVersion = Json::ensureString(root, "inheritsFrom", QString(), "");
- if (minecraftVersion.isEmpty()) {
+ QString packMinecraftVersion = Json::ensureString(root, "inheritsFrom", QString(), "");
+ if (packMinecraftVersion.isEmpty()) {
if (fmlMinecraftVersion.isEmpty()) {
emit failed(tr("Could not understand \"version.json\":\ninheritsFrom is missing"));
return;
}
- minecraftVersion = fmlMinecraftVersion;
+ packMinecraftVersion = fmlMinecraftVersion;
}
- components->setComponentVersion("net.minecraft", minecraftVersion, true);
+ components->setComponentVersion("net.minecraft", packMinecraftVersion, true);
for (auto library : Json::ensureArray(root, "libraries", {})) {
if (!library.isObject()) {
continue;
diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp
new file mode 100644
index 00000000..aaa8ff65
--- /dev/null
+++ b/launcher/net/ApiDownload.cpp
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/ApiDownload.h"
+#include "ByteArraySink.h"
+#include "ChecksumValidator.h"
+#include "MetaCacheSink.h"
+#include "net/NetAction.h"
+
+namespace Net {
+
+auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
+{
+ auto dl = makeShared<ApiDownload>();
+ dl->m_url = url;
+ dl->setObjectName(QString("CACHE:") + url.toString());
+ dl->m_options = options;
+ auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
+ auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
+ dl->m_sink.reset(cachedNode);
+ return dl;
+}
+
+auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
+{
+ auto dl = makeShared<ApiDownload>();
+ dl->m_url = url;
+ dl->setObjectName(QString("BYTES:") + url.toString());
+ dl->m_options = options;
+ dl->m_sink.reset(new ByteArraySink(output));
+ return dl;
+}
+
+auto ApiDownload::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
+{
+ auto dl = makeShared<ApiDownload>();
+ dl->m_url = url;
+ dl->setObjectName(QString("FILE:") + url.toString());
+ dl->m_options = options;
+ dl->m_sink.reset(new FileSink(path));
+ return dl;
+}
+
+void ApiDownload::init()
+{
+ qDebug() << "Setting up api download";
+ auto api_headers = new ApiHeaderProxy();
+ addHeaderProxy(api_headers);
+}
+} // namespace Net
diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h
new file mode 100644
index 00000000..638c94e1
--- /dev/null
+++ b/launcher/net/ApiDownload.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "ApiHeaderProxy.h"
+#include "Download.h"
+
+namespace Net {
+
+class ApiDownload : public Download {
+ public:
+ virtual ~ApiDownload() = default;
+
+ static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
+ static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
+ static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
+
+ void init() override;
+};
+
+} // namespace Net
diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h
new file mode 100644
index 00000000..789a6fad
--- /dev/null
+++ b/launcher/net/ApiHeaderProxy.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "Application.h"
+#include "BuildConfig.h"
+#include "net/HeaderProxy.h"
+
+namespace Net {
+
+class ApiHeaderProxy : public HeaderProxy {
+ public:
+ ApiHeaderProxy() : HeaderProxy() {}
+ virtual ~ApiHeaderProxy() = default;
+
+ public:
+ virtual QList<HeaderPair> headers(const QNetworkRequest& request) const override
+ {
+ QList<HeaderPair> hdrs;
+ if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
+ hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() });
+ } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
+ request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
+ QString token = APPLICATION->getModrinthAPIToken();
+ if (!token.isNull())
+ hdrs.append({ "Authorization", token.toUtf8() });
+ }
+ return hdrs;
+ };
+};
+
+} // namespace Net
diff --git a/launcher/net/ApiUpload.cpp b/launcher/net/ApiUpload.cpp
new file mode 100644
index 00000000..c1221b76
--- /dev/null
+++ b/launcher/net/ApiUpload.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/ApiUpload.h"
+#include "ByteArraySink.h"
+#include "ChecksumValidator.h"
+#include "MetaCacheSink.h"
+#include "net/NetAction.h"
+
+namespace Net {
+
+Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
+{
+ auto up = makeShared<ApiUpload>();
+ up->m_url = std::move(url);
+ up->m_sink.reset(new ByteArraySink(output));
+ up->m_post_data = std::move(m_post_data);
+ return up;
+}
+
+void ApiUpload::init()
+{
+ qDebug() << "Setting up api upload";
+ auto api_headers = new ApiHeaderProxy();
+ addHeaderProxy(api_headers);
+}
+} // namespace Net
diff --git a/launcher/net/ApiUpload.h b/launcher/net/ApiUpload.h
new file mode 100644
index 00000000..b12842b0
--- /dev/null
+++ b/launcher/net/ApiUpload.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "ApiHeaderProxy.h"
+#include "Upload.h"
+
+namespace Net {
+
+class ApiUpload : public Upload {
+ public:
+ virtual ~ApiUpload() = default;
+
+ static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
+
+ void init() override;
+};
+
+} // namespace Net
diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h
index d6b17d60..7b8f0f8a 100644
--- a/launcher/net/ByteArraySink.h
+++ b/launcher/net/ByteArraySink.h
@@ -42,8 +42,6 @@ namespace Net {
/*
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
- * FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
- * causing a segmentation fault.
*/
class ByteArraySink : public Sink {
public:
diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp
index 9ad49ded..d25447b2 100644
--- a/launcher/net/Download.cpp
+++ b/launcher/net/Download.cpp
@@ -47,17 +47,11 @@
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
-#include "Application.h"
-#include "BuildConfig.h"
-
-#include "net/Logging.h"
#include "net/NetAction.h"
-#include "MMCTime.h"
-#include "StringUtils.h"
-
namespace Net {
+#if defined(LAUNCHER_APPLICATION)
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{
auto dl = makeShared<Download>();
@@ -69,6 +63,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
dl->m_sink.reset(cachedNode);
return dl;
}
+#endif
auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
{
@@ -90,259 +85,8 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt
return dl;
}
-void Download::addValidator(Validator* v)
-{
- m_sink->addValidator(v);
-}
-
-void Download::executeTask()
-{
- setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
-
- if (getState() == Task::State::AbortedByUser) {
- qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString();
- emitAborted();
- return;
- }
-
- QNetworkRequest request(m_url);
- m_state = m_sink->init(request);
- switch (m_state) {
- case State::Succeeded:
- emit succeeded();
- qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString();
- return;
- case State::Running:
- qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString();
- break;
- case State::Inactive:
- case State::Failed:
- emitFailed();
- return;
- case State::AbortedByUser:
- emitAborted();
- return;
- }
-
- request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
- // TODO remove duplication
- if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
- request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
- } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
- request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
- QString token = APPLICATION->getModrinthAPIToken();
- if (!token.isNull())
- request.setRawHeader("Authorization", token.toUtf8());
- }
-
-#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
- request.setTransferTimeout();
-#endif
-
- m_last_progress_time = m_clock.now();
- m_last_progress_bytes = 0;
-
- QNetworkReply* rep = m_network->get(request);
- m_reply.reset(rep);
- connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
- connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
-#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
- connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError);
-#else
- connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Download::downloadError);
-#endif
- connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
- connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
-}
-
-void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
-{
- auto now = m_clock.now();
- auto elapsed = now - m_last_progress_time;
-
- // use milliseconds for speed precision
- auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
- auto bytes_received_since = bytesReceived - m_last_progress_bytes;
- auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
- auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
-
- //: Current amount of bytes downloaded, out of the total amount of bytes in the download
- QString dl_progress =
- tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
-
- QString dl_speed_str;
- if (elapsed_ms.count() > 0) {
- auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaing_time_s) : tr("unknown");
- //: Download speed, in bytes per second (remaining download time in parenthesis)
- dl_speed_str = tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
- } else {
- //: Download speed at 0 bytes per second
- dl_speed_str = tr("0 B/s");
- }
-
- setDetails(dl_progress + "\n" + dl_speed_str);
-
- setProgress(bytesReceived, bytesTotal);
-}
-
-void Download::downloadError(QNetworkReply::NetworkError error)
-{
- if (error == QNetworkReply::OperationCanceledError) {
- qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString();
- m_state = State::AbortedByUser;
- } else {
- if (m_options & Option::AcceptLocalFiles) {
- if (m_sink->hasLocalData()) {
- m_state = State::Succeeded;
- return;
- }
- }
- // error happened during download.
- qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
- m_state = State::Failed;
- }
-}
-
-void Download::sslErrors(const QList<QSslError>& errors)
-{
- int i = 1;
- for (auto error : errors) {
- qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : "
- << error.errorString();
- auto cert = error.certificate();
- qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
- i++;
- }
-}
-
-auto Download::handleRedirect() -> bool
-{
- QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
- if (!redirect.isValid()) {
- if (!m_reply->hasRawHeader("Location")) {
- // no redirect -> it's fine to continue
- return false;
- }
- // there is a Location header, but it's not correct. we need to apply some workarounds...
- QByteArray redirectBA = m_reply->rawHeader("Location");
- if (redirectBA.size() == 0) {
- // empty, yet present redirect header? WTF?
- return false;
- }
- QString redirectStr = QString::fromUtf8(redirectBA);
-
- if (redirectStr.startsWith("//")) {
- /*
- * IF the URL begins with //, we need to insert the URL scheme.
- * See: https://bugreports.qt.io/browse/QTBUG-41061
- * See: http://tools.ietf.org/html/rfc3986#section-4.2
- */
- redirectStr = m_reply->url().scheme() + ":" + redirectStr;
- } else if (redirectStr.startsWith("/")) {
- /*
- * IF the URL begins with /, we need to process it as a relative URL
- */
- auto url = m_reply->url();
- url.setPath(redirectStr, QUrl::TolerantMode);
- redirectStr = url.toString();
- }
-
- /*
- * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
- * FIXME: report Qt bug for this
- */
- redirect = QUrl(redirectStr, QUrl::TolerantMode);
- if (!redirect.isValid()) {
- qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
- downloadError(QNetworkReply::ProtocolFailure);
- return false;
- }
- qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect;
- } else {
- qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect;
- }
-
- m_url = QUrl(redirect.toString());
- qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
- startAction(m_network);
-
- return true;
-}
-
-void Download::downloadFinished()
-{
- // handle HTTP redirection first
- if (handleRedirect()) {
- qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString();
- return;
- }
-
- // if the download failed before this point ...
- if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
- {
- qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit succeeded();
- return;
- } else if (m_state == State::Failed) {
- qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit failed("");
- return;
- } else if (m_state == State::AbortedByUser) {
- qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit aborted();
- return;
- }
-
- // make sure we got all the remaining data, if any
- auto data = m_reply->readAll();
- if (data.size()) {
- qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
- m_state = m_sink->write(data);
- }
-
- // otherwise, finalize the whole graph
- m_state = m_sink->finalize(*m_reply.get());
- if (m_state != State::Succeeded) {
- qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit failed("");
- return;
- }
-
- m_reply.reset();
- qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString();
- emit succeeded();
-}
-
-void Download::downloadReadyRead()
-{
- if (m_state == State::Running) {
- auto data = m_reply->readAll();
- m_state = m_sink->write(data);
- if (m_state == State::Failed) {
- qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk";
- }
- // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
- } else {
- qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
- }
-}
-
-} // namespace Net
-
-auto Net::Download::abort() -> bool
+QNetworkReply* Download::getReply(QNetworkRequest& request)
{
- if (m_reply) {
- m_reply->abort();
- } else {
- m_state = State::AbortedByUser;
- }
- return true;
+ return m_network->get(request);
}
+} // namespace Net \ No newline at end of file
diff --git a/launcher/net/Download.h b/launcher/net/Download.h
index 2e861732..5f6a5caf 100644
--- a/launcher/net/Download.h
+++ b/launcher/net/Download.h
@@ -38,57 +38,26 @@
#pragma once
-#include <chrono>
-
#include "HttpMetaCache.h"
-#include "NetAction.h"
-#include "Sink.h"
-#include "Validator.h"
#include "QObjectPtr.h"
+#include "net/NetRequest.h"
namespace Net {
-class Download : public NetAction {
+class Download : public NetRequest {
Q_OBJECT
-
public:
using Ptr = shared_qobject_ptr<class Download>;
- enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
- Q_DECLARE_FLAGS(Options, Option)
-
- public:
- ~Download() override = default;
+ explicit Download() : NetRequest() { logCat = taskDownloadLogC; }
+#if defined(LAUNCHER_APPLICATION)
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
+#endif
+
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
- public:
- void addValidator(Validator* v);
- auto abort() -> bool override;
- auto canAbort() const -> bool override { return true; };
-
- private:
- auto handleRedirect() -> bool;
-
- protected slots:
- void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
- void downloadError(QNetworkReply::NetworkError error) override;
- void sslErrors(const QList<QSslError>& errors) override;
- void downloadFinished() override;
- void downloadReadyRead() override;
-
- public slots:
- void executeTask() override;
-
- private:
- std::unique_ptr<Sink> m_sink;
- Options m_options;
-
- std::chrono::steady_clock m_clock;
- std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
- qint64 m_last_progress_bytes;
+ protected:
+ virtual QNetworkReply* getReply(QNetworkRequest&) override;
};
} // namespace Net
-
-Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
diff --git a/launcher/net/HeaderProxy.h b/launcher/net/HeaderProxy.h
new file mode 100644
index 00000000..20362924
--- /dev/null
+++ b/launcher/net/HeaderProxy.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 <QDebug>
+#include <QNetworkRequest>
+
+namespace Net {
+
+struct HeaderPair {
+ QByteArray headerName;
+ QByteArray headerValue;
+};
+
+class HeaderProxy {
+ public:
+ HeaderProxy() {}
+ virtual ~HeaderProxy() {}
+
+ public:
+ virtual QList<HeaderPair> headers(const QNetworkRequest& request) const = 0;
+
+ public:
+ void writeHeaders(QNetworkRequest& request)
+ {
+ for (auto header : headers(request)) {
+ request.setRawHeader(header.headerName, header.headerValue);
+ }
+ }
+};
+
+} // namespace Net
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index 7809d40f..f37bc0bf 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -218,9 +218,24 @@ void HttpMetaCache::Load()
if (!index.open(QIODevice::ReadOnly))
return;
- QJsonDocument json = QJsonDocument::fromJson(index.readAll());
+ QJsonParseError parseError;
+ QJsonDocument json = QJsonDocument::fromJson(index.readAll(), &parseError);
+
+ // Fail if the JSON is invalid.
+ if (parseError.error != QJsonParseError::NoError) {
+ qCritical() << QString("Failed to parse HttpMetaCache file: %1 at offset %2")
+ .arg(parseError.errorString(), QString::number(parseError.offset))
+ .toUtf8();
+ return;
+ }
+
+ // Make sure the root is an object.
+ if (!json.isObject()) {
+ qCritical() << "HttpMetaCache root should be an object.";
+ return;
+ }
- auto root = Json::requireObject(json, "HttpMetaCache root");
+ auto root = json.object();
// check file version first
auto version_val = Json::ensureString(root, "version");
diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h
index 0dcb5668..036a8dd9 100644
--- a/launcher/net/HttpMetaCache.h
+++ b/launcher/net/HttpMetaCache.h
@@ -74,7 +74,7 @@ class MetaEntry {
auto getMaximumAge() -> qint64 { return m_max_age; }
void setMaximumAge(qint64 age) { m_max_age = age; }
- bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); };
+ bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); }
protected:
QString m_baseId;
diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h
index 54eddc72..b66b9194 100644
--- a/launcher/net/NetAction.h
+++ b/launcher/net/NetAction.h
@@ -42,10 +42,12 @@
#include "QObjectPtr.h"
#include "tasks/Task.h"
+#include "HeaderProxy.h"
+
class NetAction : public Task {
Q_OBJECT
protected:
- explicit NetAction() : Task(){};
+ explicit NetAction() : Task() {}
public:
using Ptr = shared_qobject_ptr<NetAction>;
@@ -56,6 +58,9 @@ class NetAction : public Task {
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
+ void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
+ virtual void init() = 0;
+
protected slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
@@ -71,7 +76,7 @@ class NetAction : public Task {
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
- };
+ }
public slots:
void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
@@ -81,7 +86,7 @@ class NetAction : public Task {
}
protected:
- void executeTask() override{};
+ void executeTask() override {}
public:
shared_qobject_ptr<QNetworkAccessManager> m_network;
@@ -91,4 +96,5 @@ class NetAction : public Task {
/// source URL
QUrl m_url;
+ std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
};
diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp
new file mode 100644
index 00000000..ff59da18
--- /dev/null
+++ b/launcher/net/NetRequest.cpp
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NetRequest.h"
+#include <QUrl>
+
+#include <QDateTime>
+#include <QFileInfo>
+#include <memory>
+
+#if defined(LAUNCHER_APPLICATION)
+#include "Application.h"
+#endif
+
+#include "net/NetAction.h"
+
+#include "MMCTime.h"
+#include "StringUtils.h"
+
+namespace Net {
+
+void NetRequest::addValidator(Validator* v)
+{
+ m_sink->addValidator(v);
+}
+
+void NetRequest::executeTask()
+{
+ init();
+
+ setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
+
+ if (getState() == Task::State::AbortedByUser) {
+ qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
+ emitAborted();
+ return;
+ }
+
+ QNetworkRequest request(m_url);
+ m_state = m_sink->init(request);
+ switch (m_state) {
+ case State::Succeeded:
+ qCDebug(logCat) << getUid().toString() << "Request cache hit " << m_url.toString();
+ emit succeeded();
+ emit finished();
+ return;
+ case State::Running:
+ qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString();
+ break;
+ case State::Inactive:
+ case State::Failed:
+ emitFailed();
+ return;
+ case State::AbortedByUser:
+ emitAborted();
+ return;
+ }
+
+#if defined(LAUNCHER_APPLICATION)
+ auto user_agent = APPLICATION->getUserAgent();
+#else
+ auto user_agent = BuildConfig.USER_AGENT;
+#endif
+
+ request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8());
+ for (auto& header_proxy : m_headerProxies) {
+ header_proxy->writeHeaders(request);
+ }
+ // TODO remove duplication
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
+ request.setTransferTimeout();
+#endif
+
+ m_last_progress_time = m_clock.now();
+ m_last_progress_bytes = 0;
+
+ QNetworkReply* rep = getReply(request);
+ m_reply.reset(rep);
+ connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress);
+ connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
+#else
+ connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &NetRequest::downloadError);
+#endif
+ connect(rep, &QNetworkReply::sslErrors, this, &NetRequest::sslErrors);
+ connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
+}
+
+void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+ auto now = m_clock.now();
+ auto elapsed = now - m_last_progress_time;
+
+ // use milliseconds for speed precision
+ auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
+ auto bytes_received_since = bytesReceived - m_last_progress_bytes;
+ auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
+ auto remaining_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
+
+ //: Current amount of bytes downloaded, out of the total amount of bytes in the download
+ QString dl_progress =
+ tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
+
+ QString dl_speed_str;
+ if (elapsed_ms.count() > 0) {
+ auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaining_time_s) : tr("unknown");
+ //: Download speed, in bytes per second (remaining download time in parenthesis)
+ dl_speed_str = tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
+ } else {
+ //: Download speed at 0 bytes per second
+ dl_speed_str = tr("0 B/s");
+ }
+
+ setDetails(dl_progress + "\n" + dl_speed_str);
+
+ setProgress(bytesReceived, bytesTotal);
+}
+
+void NetRequest::downloadError(QNetworkReply::NetworkError error)
+{
+ if (error == QNetworkReply::OperationCanceledError) {
+ qCCritical(logCat) << getUid().toString() << "Aborted " << m_url.toString();
+ m_state = State::Failed;
+ } else {
+ if (m_options & Option::AcceptLocalFiles) {
+ if (m_sink->hasLocalData()) {
+ m_state = State::Succeeded;
+ return;
+ }
+ }
+ // error happened during download.
+ qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
+ m_state = State::Failed;
+ }
+}
+
+void NetRequest::sslErrors(const QList<QSslError>& errors)
+{
+ int i = 1;
+ for (auto error : errors) {
+ qCCritical(logCat) << getUid().toString() << "Request" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+}
+
+auto NetRequest::handleRedirect() -> bool
+{
+ QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
+ if (!redirect.isValid()) {
+ if (!m_reply->hasRawHeader("Location")) {
+ // no redirect -> it's fine to continue
+ return false;
+ }
+ // there is a Location header, but it's not correct. we need to apply some workarounds...
+ QByteArray redirectBA = m_reply->rawHeader("Location");
+ if (redirectBA.size() == 0) {
+ // empty, yet present redirect header? WTF?
+ return false;
+ }
+ QString redirectStr = QString::fromUtf8(redirectBA);
+
+ if (redirectStr.startsWith("//")) {
+ /*
+ * IF the URL begins with //, we need to insert the URL scheme.
+ * See: https://bugreports.qt.io/browse/QTBUG-41061
+ * See: http://tools.ietf.org/html/rfc3986#section-4.2
+ */
+ redirectStr = m_reply->url().scheme() + ":" + redirectStr;
+ } else if (redirectStr.startsWith("/")) {
+ /*
+ * IF the URL begins with /, we need to process it as a relative URL
+ */
+ auto url = m_reply->url();
+ url.setPath(redirectStr, QUrl::TolerantMode);
+ redirectStr = url.toString();
+ }
+
+ /*
+ * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
+ * FIXME: report Qt bug for this
+ */
+ redirect = QUrl(redirectStr, QUrl::TolerantMode);
+ if (!redirect.isValid()) {
+ qCWarning(logCat) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
+ downloadError(QNetworkReply::ProtocolFailure);
+ return false;
+ }
+ qCDebug(logCat) << getUid().toString() << "Fixed location header:" << redirect;
+ } else {
+ qCDebug(logCat) << getUid().toString() << "Location header:" << redirect;
+ }
+
+ m_url = QUrl(redirect.toString());
+ qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
+ startAction(m_network);
+
+ return true;
+}
+
+void NetRequest::downloadFinished()
+{
+ // handle HTTP redirection first
+ if (handleRedirect()) {
+ qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString();
+ return;
+ }
+
+ // if the download failed before this point ...
+ if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
+ {
+ qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit succeeded();
+ emit finished();
+ return;
+ } else if (m_state == State::Failed) {
+ qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit failed("");
+ emit finished();
+ return;
+ } else if (m_state == State::AbortedByUser) {
+ qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit aborted();
+ emit finished();
+ return;
+ }
+
+ // make sure we got all the remaining data, if any
+ auto data = m_reply->readAll();
+ if (data.size()) {
+ qCDebug(logCat) << getUid().toString() << "Writing extra" << data.size() << "bytes";
+ m_state = m_sink->write(data);
+ if (m_state != State::Succeeded) {
+ qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
+ m_sink->abort();
+ emit failed("");
+ emit finished();
+ return;
+ }
+ }
+
+ // otherwise, finalize the whole graph
+ m_state = m_sink->finalize(*m_reply.get());
+ if (m_state != State::Succeeded) {
+ qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
+ m_sink->abort();
+ m_reply.reset();
+ emit failed("");
+ emit finished();
+ return;
+ }
+
+ m_reply.reset();
+ qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
+ emit succeeded();
+ emit finished();
+}
+
+void NetRequest::downloadReadyRead()
+{
+ if (m_state == State::Running) {
+ auto data = m_reply->readAll();
+ m_state = m_sink->write(data);
+ if (m_state == State::Failed) {
+ qCCritical(logCat) << getUid().toString() << "Failed to process response chunk";
+ }
+ // qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes";
+ } else {
+ qCCritical(logCat) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
+ }
+}
+
+auto NetRequest::abort() -> bool
+{
+ m_state = State::AbortedByUser;
+ if (m_reply) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
+ disconnect(m_reply.get(), &QNetworkReply::errorOccurred, nullptr, nullptr);
+#else
+ disconnect(m_reply.get(), QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), nullptr, nullptr);
+#endif
+ m_reply->abort();
+ }
+ return true;
+}
+
+} // namespace Net
diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h
new file mode 100644
index 00000000..ee47ab2a
--- /dev/null
+++ b/launcher/net/NetRequest.h
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <qloggingcategory.h>
+#include <chrono>
+
+#include "NetAction.h"
+#include "Sink.h"
+#include "Validator.h"
+
+#include "QObjectPtr.h"
+#include "net/Logging.h"
+
+namespace Net {
+class NetRequest : public NetAction {
+ Q_OBJECT
+ protected:
+ explicit NetRequest() : NetAction() {}
+
+ public:
+ using Ptr = shared_qobject_ptr<class NetRequest>;
+ enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
+ Q_DECLARE_FLAGS(Options, Option)
+
+ public:
+ ~NetRequest() override = default;
+
+ void init() override {}
+
+ public:
+ void addValidator(Validator* v);
+ auto abort() -> bool override;
+ auto canAbort() const -> bool override { return true; }
+
+ private:
+ auto handleRedirect() -> bool;
+ virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
+
+ protected slots:
+ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
+ void downloadError(QNetworkReply::NetworkError error) override;
+ void sslErrors(const QList<QSslError>& errors) override;
+ void downloadFinished() override;
+ void downloadReadyRead() override;
+
+ public slots:
+ void executeTask() override;
+
+ protected:
+ std::unique_ptr<Sink> m_sink;
+ Options m_options;
+
+ typedef const QLoggingCategory& (*logCatFunc)();
+ logCatFunc logCat = taskUploadLogC;
+
+ std::chrono::steady_clock m_clock;
+ std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
+ qint64 m_last_progress_bytes;
+};
+} // namespace Net
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(Net::NetRequest::Options)
diff --git a/launcher/net/RawHeaderProxy.h b/launcher/net/RawHeaderProxy.h
new file mode 100644
index 00000000..09b3d4d0
--- /dev/null
+++ b/launcher/net/RawHeaderProxy.h
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/HeaderProxy.h"
+
+namespace Net {
+
+class RawHeaderProxy : public HeaderProxy {
+ public:
+ RawHeaderProxy() : HeaderProxy() {}
+ virtual ~RawHeaderProxy() = default;
+
+ public:
+ virtual QList<HeaderPair> headers(const QNetworkRequest&) const override { return m_headers; };
+
+ void addHeader(const HeaderPair& header) { m_headers.append(header); }
+ void addHeader(const QByteArray& headerName, const QByteArray& headerValue) { m_headers.append({ headerName, headerValue }); }
+ void addHeaders(const QList<HeaderPair>& headers) { m_headers.append(headers); }
+
+ private:
+ QList<HeaderPair> m_headers;
+};
+
+} // namespace Net
diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp
index 3f6f5829..726572e5 100644
--- a/launcher/net/Upload.cpp
+++ b/launcher/net/Upload.cpp
@@ -38,219 +38,16 @@
#include "Upload.h"
+#include <memory>
#include <utility>
-#include "Application.h"
-#include "BuildConfig.h"
#include "ByteArraySink.h"
-#include "net/Logging.h"
-
namespace Net {
-bool Upload::abort()
-{
- if (m_reply) {
- m_reply->abort();
- } else {
- m_state = State::AbortedByUser;
- }
- return true;
-}
-
-void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
-{
- setProgress(bytesReceived, bytesTotal);
-}
-
-void Upload::downloadError(QNetworkReply::NetworkError error)
-{
- if (error == QNetworkReply::OperationCanceledError) {
- qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
- m_state = State::AbortedByUser;
- } else {
- // error happened during download.
- qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
- m_state = State::Failed;
- }
-}
-
-void Upload::sslErrors(const QList<QSslError>& errors)
-{
- int i = 1;
- for (const auto& error : errors) {
- qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : "
- << error.errorString();
- auto cert = error.certificate();
- qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
- i++;
- }
-}
-
-bool Upload::handleRedirect()
-{
- QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
- if (!redirect.isValid()) {
- if (!m_reply->hasRawHeader("Location")) {
- // no redirect -> it's fine to continue
- return false;
- }
- // there is a Location header, but it's not correct. we need to apply some workarounds...
- QByteArray redirectBA = m_reply->rawHeader("Location");
- if (redirectBA.size() == 0) {
- // empty, yet present redirect header? WTF?
- return false;
- }
- QString redirectStr = QString::fromUtf8(redirectBA);
-
- if (redirectStr.startsWith("//")) {
- /*
- * IF the URL begins with //, we need to insert the URL scheme.
- * See: https://bugreports.qt.io/browse/QTBUG-41061
- * See: http://tools.ietf.org/html/rfc3986#section-4.2
- */
- redirectStr = m_reply->url().scheme() + ":" + redirectStr;
- } else if (redirectStr.startsWith("/")) {
- /*
- * IF the URL begins with /, we need to process it as a relative URL
- */
- auto url = m_reply->url();
- url.setPath(redirectStr, QUrl::TolerantMode);
- redirectStr = url.toString();
- }
-
- /*
- * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
- * FIXME: report Qt bug for this
- */
- redirect = QUrl(redirectStr, QUrl::TolerantMode);
- if (!redirect.isValid()) {
- qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
- downloadError(QNetworkReply::ProtocolFailure);
- return false;
- }
- qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect;
- } else {
- qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
- }
-
- m_url = QUrl(redirect.toString());
- qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
- startAction(m_network);
- return true;
-}
-
-void Upload::downloadFinished()
-{
- // handle HTTP redirection first
- // very unlikely for post requests, still can happen
- if (handleRedirect()) {
- qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString();
- return;
- }
-
- // if the download failed before this point ...
- if (m_state == State::Succeeded) {
- qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit succeeded();
- return;
- } else if (m_state == State::Failed) {
- qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit failed("");
- return;
- } else if (m_state == State::AbortedByUser) {
- qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit aborted();
- return;
- }
-
- // make sure we got all the remaining data, if any
- auto data = m_reply->readAll();
- if (data.size()) {
- qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
- m_state = m_sink->write(data);
- }
-
- // otherwise, finalize the whole graph
- m_state = m_sink->finalize(*m_reply.get());
- if (m_state != State::Succeeded) {
- qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
- m_sink->abort();
- m_reply.reset();
- emit failed("");
- return;
- }
- m_reply.reset();
- qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
- emit succeeded();
-}
-
-void Upload::downloadReadyRead()
-{
- if (m_state == State::Running) {
- auto data = m_reply->readAll();
- m_state = m_sink->write(data);
- }
-}
-
-void Upload::executeTask()
+QNetworkReply* Upload::getReply(QNetworkRequest& request)
{
- setStatus(tr("Uploading %1").arg(m_url.toString()));
-
- if (m_state == State::AbortedByUser) {
- qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString();
- emit aborted();
- return;
- }
- QNetworkRequest request(m_url);
- m_state = m_sink->init(request);
- switch (m_state) {
- case State::Succeeded:
- emitSucceeded();
- qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString();
- return;
- case State::Running:
- qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString();
- break;
- case State::Inactive:
- case State::Failed:
- emitFailed("");
- return;
- case State::AbortedByUser:
- emitAborted();
- return;
- }
-
- request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
- // TODO remove duplication
- if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
- request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
- } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
- request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
- QString token = APPLICATION->getModrinthAPIToken();
- if (!token.isNull())
- request.setRawHeader("Authorization", token.toUtf8());
- }
-
- // TODO other types of post requests ?
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- QNetworkReply* rep = m_network->post(request, m_post_data);
-
- m_reply.reset(rep);
- connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
- connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
-#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
- connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
-#else
- connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError);
-#endif
- connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
- connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
+ return m_network->post(request, m_post_data);
}
Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h
index 0b0c9497..f920e556 100644
--- a/launcher/net/Upload.h
+++ b/launcher/net/Upload.h
@@ -37,36 +37,21 @@
#pragma once
-#include "NetAction.h"
-#include "Sink.h"
+#include "net/NetRequest.h"
namespace Net {
-class Upload : public NetAction {
+class Upload : public NetRequest {
Q_OBJECT
-
public:
using Ptr = shared_qobject_ptr<Upload>;
+ explicit Upload() : NetRequest() { logCat = taskUploadLogC; };
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
- auto abort() -> bool override;
- auto canAbort() const -> bool override { return true; };
-
- protected slots:
- void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
- void downloadError(QNetworkReply::NetworkError error) override;
- void sslErrors(const QList<QSslError>& errors) override;
- void downloadFinished() override;
- void downloadReadyRead() override;
- public slots:
- void executeTask() override;
-
- private:
- std::unique_ptr<Sink> m_sink;
+ protected:
+ virtual QNetworkReply* getReply(QNetworkRequest&) override;
QByteArray m_post_data;
-
- bool handleRedirect();
};
} // namespace Net
diff --git a/launcher/net/Validator.h b/launcher/net/Validator.h
index 33c4cfee..92ac6ea1 100644
--- a/launcher/net/Validator.h
+++ b/launcher/net/Validator.h
@@ -39,8 +39,8 @@
namespace Net {
class Validator {
public: /* con/des */
- Validator(){};
- virtual ~Validator(){};
+ Validator() {}
+ virtual ~Validator() {}
public: /* methods */
virtual bool init(QNetworkRequest& request) = 0;
diff --git a/launcher/news/NewsEntry.cpp b/launcher/news/NewsEntry.cpp
index cf6d3e46..ea25f2e5 100644
--- a/launcher/news/NewsEntry.cpp
+++ b/launcher/news/NewsEntry.cpp
@@ -39,14 +39,14 @@ inline QString childValue(const QDomElement& element, const QString& childName,
{
QDomNodeList nodes = element.elementsByTagName(childName);
if (nodes.count() > 0) {
- QDomElement element = nodes.at(0).toElement();
- return element.text();
+ QDomElement elem = nodes.at(0).toElement();
+ return elem.text();
} else {
return defaultVal;
}
}
-bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg)
+bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, [[maybe_unused]] QString* errorMsg)
{
QString title = childValue(element, "title", tr("Untitled"));
QString content = childValue(element, "content", tr("No content."));
diff --git a/launcher/pathmatcher/IPathMatcher.h b/launcher/pathmatcher/IPathMatcher.h
index 8121de5c..cd612197 100644
--- a/launcher/pathmatcher/IPathMatcher.h
+++ b/launcher/pathmatcher/IPathMatcher.h
@@ -7,6 +7,6 @@ class IPathMatcher {
typedef std::shared_ptr<IPathMatcher> Ptr;
public:
- virtual ~IPathMatcher(){};
+ virtual ~IPathMatcher() {}
virtual bool matches(const QString& string) const = 0;
};
diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h
index fe4aee6a..a6a3e616 100644
--- a/launcher/pathmatcher/RegexpMatcher.h
+++ b/launcher/pathmatcher/RegexpMatcher.h
@@ -3,7 +3,7 @@
class RegexpMatcher : public IPathMatcher {
public:
- virtual ~RegexpMatcher(){};
+ virtual ~RegexpMatcher() {}
RegexpMatcher(const QString& regexp)
{
m_regexp.setPattern(regexp);
diff --git a/launcher/resources/multimc/128x128/instances/forge.png b/launcher/resources/multimc/128x128/instances/forge.png
new file mode 100644
index 00000000..d8ff79a5
--- /dev/null
+++ b/launcher/resources/multimc/128x128/instances/forge.png
Binary files differ
diff --git a/launcher/resources/multimc/128x128/instances/liteloader.png b/launcher/resources/multimc/128x128/instances/liteloader.png
new file mode 100644
index 00000000..646217de
--- /dev/null
+++ b/launcher/resources/multimc/128x128/instances/liteloader.png
Binary files differ
diff --git a/launcher/resources/multimc/index.theme b/launcher/resources/multimc/index.theme
index 070e23f1..4da8072d 100644
--- a/launcher/resources/multimc/index.theme
+++ b/launcher/resources/multimc/index.theme
@@ -1,5 +1,5 @@
[Icon Theme]
-Name=multimc
+Name=Legacy
Comment=Default Icons
Inherits=default
Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable,scalable/instances
diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc
index 2c00f28f..80981559 100644
--- a/launcher/resources/multimc/multimc.qrc
+++ b/launcher/resources/multimc/multimc.qrc
@@ -347,5 +347,11 @@
<file>scalable/export.svg</file>
<file>scalable/launch.svg</file>
<file>scalable/server.svg</file>
+
+ <file>scalable/instances/quiltmc.svg</file> <!-- CC0 QuiltMC -->
+ <file>scalable/instances/fabricmc.svg</file> <!-- CC0 unascribed, https://github.com/FabricMC/community/blob/main/media/unascribed/README.md -->
+ <file>scalable/instances/neoforged.svg</file>
+ <file>128x128/instances/forge.png</file> <!-- LGPL3 Forge Development LLC -->
+ <file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader -->
</qresource>
</RCC>
diff --git a/launcher/resources/multimc/scalable/instances/fabricmc.svg b/launcher/resources/multimc/scalable/instances/fabricmc.svg
new file mode 100644
index 00000000..7bfc7548
--- /dev/null
+++ b/launcher/resources/multimc/scalable/instances/fabricmc.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="128"
+ height="128"
+ version="1.1"
+ viewBox="0 0 33.867 33.867"
+ xml:space="preserve"
+ id="svg252"
+ sodipodi:docname="fabricmc.svg"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs256" /><sodipodi:namedview
+ id="namedview254"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ showgrid="false"
+ inkscape:zoom="2.8284271"
+ inkscape:cx="39.421203"
+ inkscape:cy="132.22897"
+ inkscape:window-width="2560"
+ inkscape:window-height="1386"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g2166" /><g
+ id="g2166"><path
+ style="fill:#38342a;fill-opacity:1;stroke-width:0;stroke-linejoin:round"
+ d="m 16.9336,2.1165994 c 0.705567,0 1.411134,0 2.116701,0 0,0.7055667 0,1.4111333 0,2.1167 0.705566,0 1.411132,0 2.116698,0 0,0.7055665 0,1.4111331 0,2.1166996 0.705567,0 1.411134,0 2.116701,0 0,0.7055667 0,1.4111333 0,2.1167 0.705567,0 1.411133,0 2.1167,0 0,0.705567 0,1.411134 0,2.116701 0.705567,0 1.411133,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 0.705567,0 1.411133,0 2.1167,0 0,1.4111 0,2.8222 0,4.2333 -0.7056,-3.3e-5 -1.4112,-6.7e-5 -2.1168,-10e-5 0,0.705567 0,1.411133 0,2.1167 -1.4111,0 -2.8222,0 -4.2333,0 0,0.705567 0,1.411133 0,2.1167 -0.705567,0 -1.411134,0 -2.116701,0 0,0.705567 0,1.411133 0,2.1167 -0.705566,0 -1.411132,0 -2.116698,0 0,0.705567 0,1.411134 0,2.116701 -0.705567,0 -1.411134,0 -2.116701,0 -3.4e-5,1.411133 -6.7e-5,2.822266 -1.01e-4,4.233399 -0.705567,0 -1.411133,0 -2.1167,0 0,0.705567 0,1.411134 0,2.116701 -1.4111,0 -2.822199,0 -4.233299,0 3.4e-5,-0.705567 6.7e-5,-1.411134 1.01e-4,-2.116701 -0.705567,0 -1.411134,0 -2.116701,0 0,-0.705567 0,-1.411134 0,-2.116701 -0.705567,0 -1.411134,0 -2.116701,0 0,-0.705566 0,-1.411132 0,-2.116698 -0.705567,0 -1.411134,0 -2.116701,0 0,-0.705567 0,-1.411134 0,-2.116701 -0.705566,0 -1.411132,0 -2.116698,0 0,-1.4111 0,-2.8222 0,-4.2333 0.705566,0 1.411132,0 2.116698,0 0,-0.705567 0,-1.411133 0,-2.1167 0.705567,0 1.411134,0 2.116701,0 0,-0.705567 0,-1.411133 0,-2.1167 0.705567,0 1.411134,0 2.116701,0 0,-0.705567 0,-1.411133 0,-2.1167 0.705567,0 1.411134,0 2.116701,0 0,-0.705567 0,-1.411133 0,-2.1167 0.705566,0 1.411132,0 2.116698,0 0,-0.705567 0,-1.411134 0,-2.116701 0.705567,0 1.411134,0 2.116701,0 -3.4e-5,-1.4110999 -6.7e-5,-2.8221997 -1.01e-4,-4.2332996 0.705567,0 1.411134,0 2.116701,0 0,-0.7055667 0,-1.4111333 0,-2.1167 z"
+ id="path1587-0" /><rect
+ x="25.400299"
+ y="14.8166"
+ width="2.1166999"
+ height="2.1166999"
+ fill="#807a6d"
+ id="rect246-7"
+ style="stroke-width:0" /><path
+ id="path1670"
+ style="fill:#dbd0b4;fill-opacity:1;stroke-width:0;stroke-linejoin:round"
+ d="m 4.233501,21.166701 2.1167,-2.117 6.09e-4,-2.1165 2.1161,-10e-5 6.09e-4,-2.1166 2.116099,-10e-5 6.09e-4,-2.1166 h 2.115899 l 2.1168,-2.117 6.72e-4,-2.1164 2.1159,-10e-5 v 2.1167 l 2.1171,10e-5 -3.97e-4,2.1166 2.1171,1e-4 v 2.1167 l 2.1167,2.11665 -11.642055,11.642008"
+ sodipodi:nodetypes="cccccccccccccccccc" /><path
+ style="fill:#dbd0b4;fill-opacity:1;stroke-width:0;stroke-linejoin:round"
+ d="m 16.9336,4.2332985 c 0.705333,-6.65e-5 1.410667,-1.329e-4 2.116,-1.994e-4 0,0.7055666 0,1.4111333 0,2.1166999 0.7058,6.67e-5 1.411599,1.333e-4 2.117399,2e-4 -2.29e-4,0.7055 -4.59e-4,1.411 -6.88e-4,2.1165 0.7058,6.63e-5 1.4116,1.327e-4 2.1174,1.99e-4 -2.29e-4,0.7055003 -4.59e-4,1.4110007 -6.88e-4,2.116501 0.705567,0 1.411133,0 2.1167,0 -0.0492,0.747877 0.09844,1.542199 -0.07194,2.26148 -0.32867,0.65734 -0.65734,1.31468 -0.98601,1.97202 -0.429296,-0.748959 -0.696496,-1.614758 -1.230307,-2.288382 -2.059217,-2.059636 -4.118734,-4.1189729 -6.178143,-6.178418 9.3e-5,-0.7055335 1.87e-4,-1.411067 2.8e-4,-2.1166005 z"
+ id="path2115" /><path
+ d="m 12.700401,25.400025 h 2.1167 v 2.1167 h -2.1167 z m 2.1167,-4.2333 v 4.2333 h 2.1167 v -2.1167 h 2.1167 v -2.1167 c -1.411133,-1.9e-5 -2.822267,1.4e-5 -4.2334,10e-5 z m 6.35,-6.35 v 2.1167 h -2.1167 v 4.2333 h 2.1167 v -2.1167 h 2.1167 v -4.2333 z m 2.1167,-2.1167 h 2.1167 v 2.1167 h -2.1167 z m -2.1167,-2.1167 h 2.1167 v 2.1167 h -2.1167 z m -2.1167,-2.1167 h 2.1167 v 2.1167 h -2.1167 z m -2.1167,-2.1167 h 2.1167 v 2.1167 h -2.1167 z"
+ fill="#bcb29c"
+ id="path238-2"
+ style="stroke-width:0"
+ sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccc" /><path
+ d="m 10.583601,27.516801 c 0.705567,0 1.411133,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.705567,0 -1.411133,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z m -2.1167,-2.1167 c 0.7055667,0 1.4111333,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.7055667,0 -1.4111333,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z m -2.1167,-2.1167 c 0.7055667,0 1.4111333,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.7055667,0 -1.4111333,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z m -2.1167,-2.1167 c 0.7055667,0 1.4111333,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.7055667,0 -1.4111333,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z"
+ fill="#9a927e"
+ id="path240-6"
+ style="stroke-width:0" /><path
+ d="m 10.583701,25.400025 c 0.705567,0 1.411133,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.705567,0 -1.411133,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z m -2.1167,-2.1167 c 0.7055667,0 1.4111333,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.7055667,0 -1.4111333,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z m -2.1167,-2.1167 c 0.7055667,0 1.4111333,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.7055667,0 -1.4111333,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z m -2.1167,-2.1167 c 0.7055667,0 1.4111333,0 2.1167,0 0,0.705567 0,1.411133 0,2.1167 -0.7055667,0 -1.4111333,0 -2.1167,0 0,-0.705567 0,-1.411133 0,-2.1167 z"
+ fill="#aea694"
+ id="path244-1"
+ style="stroke-width:0" /><path
+ d="m 10.583751,16.933325 h 2.1167 v 2.1167 h -2.1167 z m 2.1167,2.1167 v 2.1167 h 4.2333 v -2.1167 z m 4.2333,-4.2333 v 2.1167 h 4.2333 v -2.1167 z m -2.1167,-2.1167 h 2.1167 v 2.1167 h -2.1167 z m -2.1167,-2.1167 h 2.1167 v 2.1167 h -2.1167 z"
+ fill="#c6bca5"
+ id="path248-8"
+ style="stroke-width:0"
+ sodipodi:nodetypes="ccccccccccccccccccccccccc" /></g></svg>
diff --git a/launcher/resources/multimc/scalable/instances/neoforged.svg b/launcher/resources/multimc/scalable/instances/neoforged.svg
new file mode 100644
index 00000000..706d53a0
--- /dev/null
+++ b/launcher/resources/multimc/scalable/instances/neoforged.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="272" height="272" version="1.1" viewBox="0 0 272 272" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="matrix(1.25,0,0,1.25,-544,-34)" stroke-width="0"><path d="m512 224v16h64v-16h32v-16h16v-32h16v-16h-16l-8-8v-32l8-8v-32l-8-8-16 16h-16l-24-24h-32l-24 24h-16l-16-16-8 8v32l8 8v32l-8 8h-16v16h16v32h16v16z" fill="#a44e37"/><path d="m480 176v16l8 8h112l8-8v-16h16v-16l-8-8 16-16-32-32h-16l-8-8h-16l-8 8v16l-16 16v-32l-8-8h-16l-8 8h-16l-32 32 16 16-8 8v16z" fill="#d7742f"/><path d="m528 144h16v-16h16v-32l-8-8h-16l-8 8z" fill="#e68c37"/><path d="m576 96h32v16h-32z" fill="#bf6134"/><path d="m528 80h32v16h-32z" fill="#bf6134"/><path d="m480 96h32v16h-32z" fill="#bb5f33"/><g fill="#bf6134"><path d="m528 160h32v16h-32z"/><path d="m480 192h32v16h-32z"/><path d="m576 192h32v16h-32z"/></g><path d="m512 192v32h16l8-8h16l8 8h16v-32h-16l-8 8h-16l-8-8z" fill="#f9f4f4"/><path d="m528 208v16h32v-16z" fill="#e7d9d3"/><path d="m528 208v-16h32v16z" fill="#13151a"/><path d="m480 128h16l8 8v24l-8 8h-8l-8-8z" fill="#f9f4f4"/><path d="m480 160h16l8 8-8 8h-16z" fill="#e7d9d3"/><path d="m512 128v16l-8 8-8-8v-16z" fill="#e7d9d3"/><path d="m512 144v16l-8 8-8-8v-16z" fill="#262a33"/><path d="m512 160v16h-16v-16z" fill="#13151a"/><g transform="matrix(-1,0,0,1,1088,0)"><path d="m480 128h16l8 8v24l-8 8h-8l-8-8z" fill="#f9f4f4"/><path d="m480 160h16l8 8-8 8h-16z" fill="#e7d9d3"/><path d="m512 128v16l-8 8-8-8v-16z" fill="#e7d9d3"/><path d="m512 144v16l-8 8-8-8v-16z" fill="#262a33"/><path d="m512 160v16h-16v-16z" fill="#13151a"/></g><path d="m608 96v-16h16v-16h-16v-16h-16v-16h-32v48h16l16 16z" fill="#66534d"/><path d="m608 64v16h-16l-16-16v-16h16v16z" fill="#8d7168"/><path d="m584 88-8-8v-16h16v16z" fill="#e7d9d3"/><path d="m576 80v16h16v-16z" fill="#c7a3b9"/><g transform="matrix(-1,0,0,1,1088,0)"><path d="m608 96v-16h16v-16h-16v-16h-16v-16h-32v48h16l16 16z" fill="#66534d"/><path d="m608 64v16h-16l-16-16v-16h16v16z" fill="#8d7168"/><path d="m584 88-8-8v-16h16v16z" fill="#e7d9d3"/><path d="m576 80v16h16v-16z" fill="#c7a3b9"/></g><g fill="#bb5f33"><path d="m480 112h-16v16h16z"/><path d="m464 128h-16v16h16z"/><path d="m480 144h-16v16h16z"/><path d="m624 144h-16v16h16z"/><path d="m640 128h-16v16h16z"/><path d="m624 112h-16v16h16z"/></g></g><metadata><rdf:RDF><cc:Work rdf:about=""><dc:creator><cc:Agent><dc:title>Sefa Eyeoglu &lt;contact@scrumplex.net&gt;</dc:title></cc:Agent></dc:creator></cc:Work></rdf:RDF></metadata></svg>
diff --git a/launcher/resources/multimc/scalable/instances/quiltmc.svg b/launcher/resources/multimc/scalable/instances/quiltmc.svg
new file mode 100644
index 00000000..a7aaca53
--- /dev/null
+++ b/launcher/resources/multimc/scalable/instances/quiltmc.svg
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ id="Layer_1"
+ data-name="Layer 1"
+ viewBox="0 0 23.999999 23.999999"
+ version="1.1"
+ sodipodi:docname="quiltmc.svg"
+ width="24"
+ height="24"
+ inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview27"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ showgrid="false"
+ inkscape:zoom="10.390684"
+ inkscape:cx="24.685575"
+ inkscape:cy="9.5277659"
+ inkscape:window-width="1499"
+ inkscape:window-height="749"
+ inkscape:window-x="100"
+ inkscape:window-y="118"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="Layer_1" />
+ <defs
+ id="defs4">
+ <style
+ id="style2">.cls-1{fill:#1b112b;}.cls-2{fill:#9722ff;}.cls-3{fill:#dc29dd;}.cls-4{fill:#27a2fd;}.cls-5{fill:#34f;}</style>
+ </defs>
+ <rect
+ class="cls-1"
+ width="24"
+ height="24"
+ rx="5.9670944"
+ id="rect6"
+ x="0"
+ y="0"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-2"
+ d="M 8.53568,6.3874419 A 0.27217496,0.27217496 0 0 0 8.8075922,6.6593542 H 9.2699744 V 7.6928834 H 8.8075922 a 0.27191225,0.27191225 0 0 0 0,0.5438245 H 9.2699744 V 9.0293255 A 0.24091162,0.24091162 0 0 1 9.0290628,9.2702371 H 8.2364452 V 8.807855 a 0.27191225,0.27191225 0 0 0 -0.5438245,0 V 9.2702371 H 6.6590914 V 8.807855 a 0.27191224,0.27191224 0 0 0 -0.5438244,0 V 9.2702371 H 5.3226493 A 0.24117434,0.24117434 0 0 1 5.0817377,9.0293255 V 5.322912 A 0.24091162,0.24091162 0 0 1 5.3226493,5.0820004 H 9.0290628 A 0.2406489,0.2406489 0 0 1 9.2699744,5.322912 V 6.115267 H 8.8075922 A 0.27217496,0.27217496 0 0 0 8.53568,6.3874419 Z"
+ id="path8"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-3"
+ d="m 13.267216,6.3874419 a 0.27191224,0.27191224 0 0 0 0.271912,0.2719123 h 0.463696 v 1.0335292 h -0.462382 a 0.27191225,0.27191225 0 0 0 0,0.5438245 h 0.462382 V 9.0293255 A 0.24117434,0.24117434 0 0 1 13.761912,9.2702371 H 10.054973 A 0.24091162,0.24091162 0 0 1 9.8140616,9.0293255 V 8.2367079 h 0.4621194 a 0.27191225,0.27191225 0 1 0 0,-0.5438245 H 9.8140616 V 6.6593542 h 0.4621194 a 0.27217496,0.27217496 0 1 0 0,-0.5440872 H 9.8140616 V 5.322912 A 0.2406489,0.2406489 0 0 1 10.054973,5.0820004 h 3.706414 a 0.24091162,0.24091162 0 0 1 0.240911,0.2409116 v 0.792355 h -0.462382 a 0.27191224,0.27191224 0 0 0 -0.2727,0.2721749 z"
+ id="path10"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-4"
+ d="M 18.73436,5.322912 V 9.0293255 A 0.24091162,0.24091162 0 0 1 18.493448,9.2702371 H 17.70083 V 8.807855 a 0.27191224,0.27191224 0 0 0 -0.543824,0 V 9.2702371 H 16.123477 V 8.807855 a 0.27191251,0.27191251 0 0 0 -0.543825,0 V 9.2702371 H 14.787034 A 0.24117434,0.24117434 0 0 1 14.546123,9.0293255 V 8.2367079 h 0.462382 a 0.27191225,0.27191225 0 1 0 0,-0.5438245 H 14.546123 V 6.6593542 h 0.462382 a 0.27217496,0.27217496 0 0 0 0,-0.5440872 H 14.546123 V 5.322912 a 0.24091162,0.24091162 0 0 1 0.240911,-0.2409116 h 3.706414 A 0.2406489,0.2406489 0 0 1 18.73436,5.322912 Z"
+ id="path12"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-3"
+ d="m 9.2699744,10.054973 v 3.706414 A 0.2406489,0.2406489 0 0 1 9.0290628,14.002298 H 8.2364452 v -0.462382 a 0.27191225,0.27191225 0 1 0 -0.5438245,0 v 0.462908 H 6.6590914 v -0.462382 a 0.27191224,0.27191224 0 0 0 -0.5438244,0 v 0.462382 H 5.3226493 A 0.24091162,0.24091162 0 0 1 5.0817377,13.761912 V 10.054973 A 0.24117434,0.24117434 0 0 1 5.3226493,9.8140616 H 6.115267 v 0.4623824 a 0.27191224,0.27191224 0 0 0 0.5438244,0 V 9.8140616 h 1.0335293 v 0.4623824 a 0.27191225,0.27191225 0 1 0 0.5438245,0 V 9.8140616 h 0.7926176 a 0.24091162,0.24091162 0 0 1 0.2409116,0.2409114 z"
+ id="path14"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-4"
+ d="m 13.267216,11.119503 a 0.27191224,0.27191224 0 0 0 0.271912,0.271912 h 0.463696 v 1.03353 h -0.462382 a 0.27191224,0.27191224 0 0 0 0,0.543824 h 0.462382 v 0.792618 a 0.24091162,0.24091162 0 0 1 -0.240912,0.240911 h -0.793143 v -0.462382 a 0.27191224,0.27191224 0 1 0 -0.543824,0 v 0.462908 h -1.03353 v -0.462382 a 0.27217496,0.27217496 0 0 0 -0.544087,0 v 0.462382 H 10.054973 A 0.2406489,0.2406489 0 0 1 9.8140616,13.761912 V 10.054973 A 0.24091162,0.24091162 0 0 1 10.054973,9.8140616 h 3.706414 a 0.24117434,0.24117434 0 0 1 0.240911,0.2409114 v 0.792618 h -0.462382 a 0.27191224,0.27191224 0 0 0 -0.2727,0.271912 z"
+ id="path16"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-5"
+ d="m 18.73436,10.054973 v 3.706414 a 0.2406489,0.2406489 0 0 1 -0.240912,0.240911 h -3.706414 a 0.24091162,0.24091162 0 0 1 -0.240911,-0.240911 v -0.792618 h 0.462382 a 0.27191224,0.27191224 0 1 0 0,-0.543824 h -0.462382 v -1.03353 h 0.462382 a 0.27191224,0.27191224 0 0 0 0,-0.543824 h -0.462382 v -0.792618 a 0.24117434,0.24117434 0 0 1 0.240911,-0.2409114 h 0.792093 v 0.4623824 a 0.27191224,0.27191224 0 0 0 0.543824,0 V 9.8140616 h 1.033529 v 0.4623824 a 0.27191251,0.27191251 0 0 0 0.543825,0 V 9.8140616 h 0.792617 a 0.24091162,0.24091162 0 0 1 0.241438,0.2409114 z"
+ id="path18"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-2"
+ d="m 8.53568,15.851827 a 0.27217496,0.27217496 0 0 0 0.2719122,0.271912 h 0.4623822 v 1.033267 H 8.8075922 a 0.27217496,0.27217496 0 0 0 0,0.544087 h 0.4623822 v 0.792618 A 0.24091162,0.24091162 0 0 1 9.0290628,18.734622 H 5.3226493 A 0.24117434,0.24117434 0 0 1 5.0817377,18.493711 V 14.787297 A 0.24091162,0.24091162 0 0 1 5.3226493,14.546386 H 6.115267 v 0.462119 a 0.27191224,0.27191224 0 1 0 0.5438244,0 v -0.462119 h 1.0335293 v 0.462119 a 0.27191225,0.27191225 0 1 0 0.5438245,0 v -0.462119 h 0.7926176 a 0.2406489,0.2406489 0 0 1 0.2409116,0.240911 v 0.79183 H 8.8075922 a 0.27217496,0.27217496 0 0 0 -0.2719122,0.2727 z"
+ id="path20"
+ style="stroke-width:0.0262717" />
+ <path
+ class="cls-5"
+ d="m 14.002824,14.787297 v 3.706414 a 0.24117434,0.24117434 0 0 1 -0.240912,0.240911 H 10.054973 A 0.24091162,0.24091162 0 0 1 9.8140616,18.493711 v -0.792618 h 0.4621194 a 0.27217496,0.27217496 0 1 0 0,-0.544087 H 9.8140616 v -1.033267 h 0.4621194 a 0.27217496,0.27217496 0 1 0 0,-0.544087 H 9.8140616 v -0.792355 a 0.2406489,0.2406489 0 0 1 0.2409114,-0.240911 h 0.792355 v 0.462119 a 0.27217496,0.27217496 0 1 0 0.544087,0 v -0.462119 h 1.03353 v 0.462119 a 0.27191224,0.27191224 0 1 0 0.543824,0 v -0.462119 h 0.792618 a 0.24091162,0.24091162 0 0 1 0.241437,0.240911 z"
+ id="path22"
+ style="stroke-width:0.0262717" />
+ <rect
+ class="cls-2"
+ x="-2.0941913"
+ y="22.665482"
+ width="4.1882367"
+ height="4.1882367"
+ rx="0.24091162"
+ transform="rotate(-45)"
+ id="rect24"
+ style="stroke-width:0.0262717" />
+</svg>
diff --git a/launcher/resources/pe_blue/index.theme b/launcher/resources/pe_blue/index.theme
index c9e0d93a..6d842b5d 100644
--- a/launcher/resources/pe_blue/index.theme
+++ b/launcher/resources/pe_blue/index.theme
@@ -1,5 +1,5 @@
[Icon Theme]
-Name=pe_blue
+Name=Simple (Blue)
Comment=Icons by pexner (blue)
Inherits=multimc
Directories=scalable
diff --git a/launcher/resources/pe_colored/index.theme b/launcher/resources/pe_colored/index.theme
index b757bbd7..bca5494f 100644
--- a/launcher/resources/pe_colored/index.theme
+++ b/launcher/resources/pe_colored/index.theme
@@ -1,5 +1,5 @@
[Icon Theme]
-Name=pe_colored
+Name=Simple (Colored)
Comment=Icons by pexner (colored)
Inherits=multimc
Directories=scalable
diff --git a/launcher/resources/pe_dark/index.theme b/launcher/resources/pe_dark/index.theme
index b7d1ad01..4cfbf09c 100644
--- a/launcher/resources/pe_dark/index.theme
+++ b/launcher/resources/pe_dark/index.theme
@@ -1,5 +1,5 @@
[Icon Theme]
-Name=pe_dark
+Name=Simple (Dark)
Comment=Icons by pexner (dark)
Inherits=multimc
Directories=scalable
diff --git a/launcher/resources/pe_light/index.theme b/launcher/resources/pe_light/index.theme
index c106acc8..87b76d13 100644
--- a/launcher/resources/pe_light/index.theme
+++ b/launcher/resources/pe_light/index.theme
@@ -1,5 +1,5 @@
[Icon Theme]
-Name=pe_light
+Name=Simple (Light)
Comment=Icons by pexner (light)
Inherits=multimc
Directories=scalable
diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp
index d598d77e..d548c89a 100644
--- a/launcher/screenshots/ImgurAlbumCreation.cpp
+++ b/launcher/screenshots/ImgurAlbumCreation.cpp
@@ -81,7 +81,7 @@ void ImgurAlbumCreation::executeTask()
connect(rep, &QNetworkReply::sslErrors, this, &ImgurAlbumCreation::sslErrors);
}
-void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error)
+void ImgurAlbumCreation::downloadError([[maybe_unused]] QNetworkReply::NetworkError error)
{
qDebug() << m_reply->errorString();
m_state = State::Failed;
diff --git a/launcher/screenshots/ImgurAlbumCreation.h b/launcher/screenshots/ImgurAlbumCreation.h
index 3a82a956..bb71bec2 100644
--- a/launcher/screenshots/ImgurAlbumCreation.h
+++ b/launcher/screenshots/ImgurAlbumCreation.h
@@ -50,6 +50,8 @@ class ImgurAlbumCreation : public NetAction {
QString deleteHash() const { return m_deleteHash; }
QString id() const { return m_id; }
+ void init() override{};
+
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp
index 706af7ba..29499a20 100644
--- a/launcher/screenshots/ImgurUpload.cpp
+++ b/launcher/screenshots/ImgurUpload.cpp
@@ -96,7 +96,7 @@ void ImgurUpload::executeTask()
connect(rep, &QNetworkReply::sslErrors, this, &ImgurUpload::sslErrors);
}
-void ImgurUpload::downloadError(QNetworkReply::NetworkError error)
+void ImgurUpload::downloadError([[maybe_unused]] QNetworkReply::NetworkError error)
{
qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll();
if (finished) {
diff --git a/launcher/screenshots/ImgurUpload.h b/launcher/screenshots/ImgurUpload.h
index 8714e0c0..542255b2 100644
--- a/launcher/screenshots/ImgurUpload.h
+++ b/launcher/screenshots/ImgurUpload.h
@@ -44,6 +44,7 @@ class ImgurUpload : public NetAction {
explicit ImgurUpload(ScreenShot::Ptr shot);
static Ptr make(ScreenShot::Ptr shot) { return Ptr(new ImgurUpload(shot)); }
+ void init() override{};
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp
index 88492346..6cfd864c 100644
--- a/launcher/tasks/ConcurrentTask.cpp
+++ b/launcher/tasks/ConcurrentTask.cpp
@@ -88,6 +88,7 @@ bool ConcurrentTask::abort()
QMutableHashIterator<Task*, Task::Ptr> doing_iter(m_doing);
while (doing_iter.hasNext()) {
auto task = doing_iter.next();
+ disconnect(task->get(), &Task::aborted, this, 0);
suceedeed &= (task.value())->abort();
}
@@ -130,6 +131,7 @@ void ConcurrentTask::startNext()
connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); });
connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); });
+ connect(next.get(), &Task::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); });
connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); });
connect(next.get(), &Task::details, this, [this, next](QString msg) { subTaskDetails(next, msg); });
@@ -171,7 +173,7 @@ void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
startNext();
}
-void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg)
+void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg)
{
m_done.insert(task.get(), task);
m_failed.insert(task.get(), task);
diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h
index 6325fc9e..8b696bf5 100644
--- a/launcher/tasks/ConcurrentTask.h
+++ b/launcher/tasks/ConcurrentTask.h
@@ -53,7 +53,7 @@ class ConcurrentTask : public Task {
bool canAbort() const override { return true; }
- inline auto isMultiStep() const -> bool override { return totalSize() > 1; };
+ inline auto isMultiStep() const -> bool override { return totalSize() > 1; }
auto getStepProgress() const -> TaskStepProgressList override;
void addTask(Task::Ptr task);
@@ -80,7 +80,7 @@ class ConcurrentTask : public Task {
protected:
// NOTE: This is not thread-safe.
- [[nodiscard]] unsigned int totalSize() const { return m_queue.size() + m_doing.size() + m_done.size(); }
+ [[nodiscard]] unsigned int totalSize() const { return static_cast<unsigned int>(m_queue.size() + m_doing.size() + m_done.size()); }
enum class Operation { ADDED, REMOVED, CHANGED };
void updateStepProgress(TaskStepProgress const& changed_progress, Operation);
diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h
index 7e1defd8..94b4089f 100644
--- a/launcher/tasks/Task.h
+++ b/launcher/tasks/Task.h
@@ -59,16 +59,18 @@ struct TaskStepProgress {
QString status = "";
QString details = "";
TaskStepState state = TaskStepState::Waiting;
+
TaskStepProgress() { this->uid = QUuid::createUuid(); }
- TaskStepProgress(QUuid uid) { this->uid = uid; }
+ TaskStepProgress(QUuid uid_) : uid(uid_) {}
+
bool isDone() const { return (state == TaskStepState::Failed) || (state == TaskStepState::Succeeded); }
- void update(qint64 current, qint64 total)
+ void update(qint64 new_current, qint64 new_total)
{
this->old_current = this->current;
this->old_total = this->total;
- this->current = current;
- this->total = total;
+ this->current = new_current;
+ this->total = new_total;
this->state = TaskStepState::Running;
}
};
@@ -150,7 +152,7 @@ class Task : public QObject, public QRunnable {
if (canAbort())
emitAborted();
return canAbort();
- };
+ }
void setAbortable(bool can_abort)
{
diff --git a/launcher/tools/JProfiler.cpp b/launcher/tools/JProfiler.cpp
index 3fa9e94e..7a532a3d 100644
--- a/launcher/tools/JProfiler.cpp
+++ b/launcher/tools/JProfiler.cpp
@@ -29,7 +29,7 @@ void JProfiler::profilerStarted()
emit readyToLaunch(tr("Listening on port: %1").arg(listeningPort));
}
-void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status)
+void JProfiler::profilerFinished([[maybe_unused]] int exit, QProcess::ExitStatus status)
{
if (status == QProcess::CrashExit) {
emit abortLaunch(tr("Profiler aborted"));
diff --git a/launcher/tools/JVisualVM.cpp b/launcher/tools/JVisualVM.cpp
index 5f2762eb..4da4e1e5 100644
--- a/launcher/tools/JVisualVM.cpp
+++ b/launcher/tools/JVisualVM.cpp
@@ -27,7 +27,7 @@ void JVisualVM::profilerStarted()
emit readyToLaunch(tr("JVisualVM started"));
}
-void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status)
+void JVisualVM::profilerFinished([[maybe_unused]] int exit, QProcess::ExitStatus status)
{
if (status == QProcess::CrashExit) {
emit abortLaunch(tr("Profiler aborted"));
diff --git a/launcher/translations/POTranslator.cpp b/launcher/translations/POTranslator.cpp
index 71d7878d..51ef4852 100644
--- a/launcher/translations/POTranslator.cpp
+++ b/launcher/translations/POTranslator.cpp
@@ -275,7 +275,7 @@ POTranslator::~POTranslator()
delete d;
}
-QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const
+QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, [[maybe_unused]] int n) const
{
if (disambiguation) {
auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation);
diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp
index 06d5bfd8..933fe2d3 100644
--- a/launcher/translations/TranslationsModel.cpp
+++ b/launcher/translations/TranslationsModel.cpp
@@ -228,12 +228,11 @@ void readIndex(const QString& path, QMap<QString, Language>& languages)
QByteArray data;
try {
data = FS::read(path);
- } catch (const Exception& e) {
+ } catch ([[maybe_unused]] const Exception& e) {
qCritical() << "Translations Download Failed: index file not readable";
return;
}
- int index = 1;
try {
auto toplevel_doc = Json::requireDocument(data);
auto doc = Json::requireObject(toplevel_doc);
@@ -259,9 +258,8 @@ void readIndex(const QString& path, QMap<QString, Language>& languages)
lang.file_size = Json::requireInteger(langObj, "size");
languages.insert(lang.key, lang);
- index++;
}
- } catch (Json::JsonException& e) {
+ } catch ([[maybe_unused]] Json::JsonException& e) {
qCritical() << "Translations Download Failed: index file could not be parsed as json";
}
}
@@ -409,12 +407,12 @@ QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation,
return QAbstractListModel::headerData(section, orientation, role);
}
-int TranslationsModel::rowCount(const QModelIndex& parent) const
+int TranslationsModel::rowCount([[maybe_unused]] const QModelIndex& parent) const
{
return d->m_languages.size();
}
-int TranslationsModel::columnCount(const QModelIndex& parent) const
+int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) const
{
return 2;
}
diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp
index 9c7886fa..bf83a56c 100644
--- a/launcher/ui/InstanceWindow.cpp
+++ b/launcher/ui/InstanceWindow.cpp
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -43,8 +44,6 @@
#include <QPushButton>
#include <QScrollBar>
-#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ProgressDialog.h"
#include "ui/widgets/PageContainer.h"
#include "InstancePageProvider.h"
@@ -75,40 +74,44 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWin
// Add custom buttons to the page container layout.
{
- auto horizontalLayout = new QHBoxLayout();
+ auto horizontalLayout = new QHBoxLayout(this);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setContentsMargins(6, -1, 6, -1);
- auto btnHelp = new QPushButton();
+ auto btnHelp = new QPushButton(this);
btnHelp->setText(tr("Help"));
horizontalLayout->addWidget(btnHelp);
- connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help()));
+ connect(btnHelp, &QPushButton::clicked, m_container, &PageContainer::help);
auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addSpacerItem(spacer);
- m_killButton = new QPushButton();
- horizontalLayout->addWidget(m_killButton);
- connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked()));
-
- m_launchOfflineButton = new QPushButton();
- horizontalLayout->addWidget(m_launchOfflineButton);
- m_launchOfflineButton->setText(tr("Launch Offline"));
+ m_launchButton = new QToolButton(this);
+ m_launchButton->setText(tr("&Launch"));
+ m_launchButton->setToolTip(tr("Launch the instance"));
+ m_launchButton->setPopupMode(QToolButton::MenuButtonPopup);
+ m_launchButton->setMinimumWidth(80); // HACK!!
+ horizontalLayout->addWidget(m_launchButton);
+ connect(m_launchButton, &QPushButton::clicked, this, [this] { APPLICATION->launch(m_instance); });
- m_launchDemoButton = new QPushButton();
- horizontalLayout->addWidget(m_launchDemoButton);
- m_launchDemoButton->setText(tr("Launch Demo"));
+ m_killButton = new QPushButton(this);
+ m_killButton->setText(tr("&Kill"));
+ m_killButton->setToolTip(tr("Kill the running instance"));
+ m_killButton->setShortcut(QKeySequence(tr("Ctrl+K")));
+ horizontalLayout->addWidget(m_killButton);
+ connect(m_killButton, &QPushButton::clicked, this, [this] { APPLICATION->kill(m_instance); });
- updateLaunchButtons();
- connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked()));
- connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked()));
+ updateButtons();
- m_closeButton = new QPushButton();
+ m_closeButton = new QPushButton(this);
m_closeButton->setText(tr("Close"));
horizontalLayout->addWidget(m_closeButton);
- connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked()));
+ connect(m_closeButton, &QPushButton::clicked, this, &QMainWindow::close);
m_container->addButtons(horizontalLayout);
+
+ connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons);
+ connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceWindow::updateButtons);
}
// restore window state
@@ -148,47 +151,18 @@ void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance
}
}
-void InstanceWindow::updateLaunchButtons()
-{
- if (m_instance->isRunning()) {
- m_launchOfflineButton->setEnabled(false);
- m_launchDemoButton->setEnabled(false);
- m_killButton->setText(tr("Kill"));
- m_killButton->setObjectName("killButton");
- m_killButton->setToolTip(tr("Kill the running instance"));
- } else if (!m_instance->canLaunch()) {
- m_launchOfflineButton->setEnabled(false);
- m_launchDemoButton->setEnabled(false);
- m_killButton->setText(tr("Launch"));
- m_killButton->setObjectName("launchButton");
- m_killButton->setToolTip(tr("Launch the instance"));
- m_killButton->setEnabled(false);
- } else {
- m_launchOfflineButton->setEnabled(true);
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get());
- if (instance) {
- m_launchDemoButton->setEnabled(instance->supportsDemo());
- }
-
- m_killButton->setText(tr("Launch"));
- m_killButton->setObjectName("launchButton");
- m_killButton->setToolTip(tr("Launch the instance"));
- }
- // NOTE: this is a hack to force the button to recalculate its style
- m_killButton->setStyleSheet("/* */");
- m_killButton->setStyleSheet(QString());
-}
-
-void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
-{
- APPLICATION->launch(m_instance, false, false, nullptr);
-}
-
-void InstanceWindow::on_btnLaunchMinecraftDemo_clicked()
+void InstanceWindow::updateButtons()
{
- APPLICATION->launch(m_instance, false, true, nullptr);
+ m_launchButton->setEnabled(m_instance->canLaunch());
+ m_killButton->setEnabled(m_instance->isRunning());
+
+ QMenu* launchMenu = m_launchButton->menu();
+ if (launchMenu)
+ launchMenu->clear();
+ else
+ launchMenu = new QMenu(this);
+ m_instance->populateLaunchMenu(launchMenu);
+ m_launchButton->setMenu(launchMenu);
}
void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)
@@ -198,18 +172,13 @@ void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> pr
void InstanceWindow::runningStateChanged(bool running)
{
- updateLaunchButtons();
+ updateButtons();
m_container->refreshContainer();
if (running) {
selectPage("log");
}
}
-void InstanceWindow::on_closeButton_clicked()
-{
- close();
-}
-
void InstanceWindow::closeEvent(QCloseEvent* event)
{
bool proceed = true;
@@ -232,15 +201,6 @@ bool InstanceWindow::saveAll()
return m_container->saveAll();
}
-void InstanceWindow::on_btnKillMinecraft_clicked()
-{
- if (m_instance->isRunning()) {
- APPLICATION->kill(m_instance);
- } else {
- APPLICATION->launch(m_instance, true, false, nullptr);
- }
-}
-
QString InstanceWindow::instanceId()
{
return m_instance->id();
@@ -256,7 +216,10 @@ void InstanceWindow::refreshContainer()
m_container->refreshContainer();
}
-InstanceWindow::~InstanceWindow() {}
+BasePage* InstanceWindow::selectedPage() const
+{
+ return m_container->selectedPage();
+}
bool InstanceWindow::requestClose()
{
diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h
index 508bcaa1..e5bc24d4 100644
--- a/launcher/ui/InstanceWindow.h
+++ b/launcher/ui/InstanceWindow.h
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,6 +38,7 @@
#include <QMainWindow>
#include <QSystemTrayIcon>
+#include <QToolButton>
#include "LaunchController.h"
#include "launch/LaunchTask.h"
@@ -52,9 +54,10 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
public:
explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0);
- virtual ~InstanceWindow();
+ virtual ~InstanceWindow() = default;
bool selectPage(QString pageId) override;
+ BasePage* selectedPage() const override;
void refreshContainer() override;
QString instanceId();
@@ -69,11 +72,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void isClosing();
private slots:
- void on_closeButton_clicked();
- void on_btnKillMinecraft_clicked();
- void on_btnLaunchMinecraftOffline_clicked();
- void on_btnLaunchMinecraftDemo_clicked();
-
void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
void runningStateChanged(bool running);
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
@@ -82,7 +80,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void closeEvent(QCloseEvent*) override;
private:
- void updateLaunchButtons();
+ void updateButtons();
private:
shared_qobject_ptr<LaunchTask> m_proc;
@@ -90,7 +88,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
bool m_doNotSave = false;
PageContainer* m_container = nullptr;
QPushButton* m_closeButton = nullptr;
+ QToolButton* m_launchButton = nullptr;
QPushButton* m_killButton = nullptr;
- QPushButton* m_launchOfflineButton = nullptr;
- QPushButton* m_launchDemoButton = nullptr;
};
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index e342e833..5e55a5ab 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -43,7 +43,6 @@
#include "FileSystem.h"
#include "MainWindow.h"
-#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h"
#include <QDir>
@@ -85,22 +84,19 @@
#include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h>
#include <minecraft/auth/AccountList.h>
-#include <net/Download.h>
+#include <net/ApiDownload.h>
#include <net/NetJob.h>
#include <news/NewsChecker.h>
#include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h>
-#include "InstancePageProvider.h"
#include "InstanceWindow.h"
-#include "JavaCommon.h"
-#include "LaunchController.h"
#include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"
#include "ui/dialogs/ExportPackDialog.h"
+#include "ui/dialogs/ExportToModListDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/ImportResourceDialog.h"
#include "ui/dialogs/NewInstanceDialog.h"
@@ -113,15 +109,24 @@
#include "ui/themes/ThemeManager.h"
#include "ui/widgets/LabeledToolButton.h"
+#include "minecraft/PackProfile.h"
+#include "minecraft/VersionFile.h"
#include "minecraft/WorldList.h"
#include "minecraft/mod/ModFolderModel.h"
+#include "minecraft/mod/ResourcePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h"
+#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
+
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
-#include "InstanceImportTask.h"
+
+#include "Json.h"
#include "MMCTime.h"
@@ -515,9 +520,9 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
QAction* actionCreateInstance = new QAction(tr("Create instance"), this);
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
if (!group.isNull()) {
- QVariantMap data;
- data["group"] = group;
- actionCreateInstance->setData(data);
+ QVariantMap instance_action_data;
+ instance_action_data["group"] = group;
+ actionCreateInstance->setData(instance_action_data);
}
connect(actionCreateInstance, SIGNAL(triggered(bool)), SLOT(on_actionAddInstance_triggered()));
@@ -527,9 +532,9 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
actions.append(actionCreateInstance);
if (!group.isNull()) {
QAction* actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this);
- QVariantMap data;
- data["group"] = group;
- actionDeleteGroup->setData(data);
+ QVariantMap delete_group_action_data;
+ delete_group_action_data["group"] = group;
+ actionDeleteGroup->setData(delete_group_action_data);
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
actions.append(actionDeleteGroup);
}
@@ -549,71 +554,15 @@ void MainWindow::updateMainToolBar()
ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool());
}
-void MainWindow::updateToolsMenu()
+void MainWindow::updateLaunchButton()
{
- bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning();
-
- ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning);
- ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning);
- ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning);
-
QMenu* launchMenu = ui->actionLaunchInstance->menu();
- if (launchMenu) {
+ if (launchMenu)
launchMenu->clear();
- } else {
+ else
launchMenu = new QMenu(this);
- }
- QAction* normalLaunch = launchMenu->addAction(tr("Launch"));
- normalLaunch->setShortcut(QKeySequence::Open);
- QAction* normalLaunchOffline = launchMenu->addAction(tr("Launch Offline"));
- normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
- QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo"));
- normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
- if (m_selectedInstance) {
- normalLaunch->setEnabled(m_selectedInstance->canLaunch());
- normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch());
- normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch());
-
- connect(normalLaunch, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, true, false); });
- connect(normalLaunchOffline, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, false); });
- connect(normalLaunchDemo, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, true); });
- } else {
- normalLaunch->setDisabled(true);
- normalLaunchOffline->setDisabled(true);
- normalLaunchDemo->setDisabled(true);
- }
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
- if (instance) {
- normalLaunchDemo->setEnabled(instance->supportsDemo());
- }
-
- QString profilersTitle = tr("Profilers");
- launchMenu->addSeparator()->setText(profilersTitle);
- for (auto profiler : APPLICATION->profilers().values()) {
- QAction* profilerAction = launchMenu->addAction(profiler->name());
- QAction* profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name()));
- QString error;
- if (!profiler->check(&error)) {
- profilerAction->setDisabled(true);
- profilerOfflineAction->setDisabled(true);
- QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\".");
- profilerAction->setToolTip(profilerToolTip);
- profilerOfflineAction->setToolTip(profilerToolTip);
- } else if (m_selectedInstance) {
- profilerAction->setEnabled(m_selectedInstance->canLaunch());
- profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch());
-
- connect(profilerAction, &QAction::triggered,
- [this, profiler]() { APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); });
- connect(profilerOfflineAction, &QAction::triggered,
- [this, profiler]() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); });
- } else {
- profilerAction->setDisabled(true);
- profilerOfflineAction->setDisabled(true);
- }
- }
+ if (m_selectedInstance)
+ m_selectedInstance->populateLaunchMenu(launchMenu);
ui->actionLaunchInstance->setMenu(launchMenu);
}
@@ -627,7 +576,7 @@ void MainWindow::updateThemeMenu()
themeMenu = new QMenu(this);
}
- auto themes = APPLICATION->getValidApplicationThemes();
+ auto themes = APPLICATION->themeManager()->getValidApplicationThemes();
QActionGroup* themesGroup = new QActionGroup(this);
@@ -641,7 +590,7 @@ void MainWindow::updateThemeMenu()
themeAction->setActionGroup(themesGroup);
connect(themeAction, &QAction::triggered, [theme]() {
- APPLICATION->setApplicationTheme(theme->id());
+ APPLICATION->themeManager()->setApplicationTheme(theme->id());
APPLICATION->settings()->set("ApplicationTheme", theme->id());
});
}
@@ -744,9 +693,9 @@ void MainWindow::changeActiveAccount()
if (sAction->data().type() != QVariant::Type::Int)
return;
- QVariant data = sAction->data();
+ QVariant action_data = sAction->data();
bool valid = false;
- int index = data.toInt(&valid);
+ int index = action_data.toInt(&valid);
if (!valid) {
index = -1;
}
@@ -923,13 +872,13 @@ void MainWindow::finalizeInstance(InstancePtr inst)
} else {
CustomMessageBox::selectable(this, tr("Error"),
tr("The launcher cannot download Minecraft or update instances unless you have at least "
- "one account added.\nPlease add your Mojang or Minecraft account."),
+ "one account added.\nPlease add your Microsoft or Mojang account."),
QMessageBox::Warning)
->show();
}
}
-void MainWindow::addInstance(QString url)
+void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info)
{
QString groupName;
do {
@@ -949,7 +898,7 @@ void MainWindow::addInstance(QString url)
groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString();
}
- NewInstanceDialog newInstDlg(groupName, url, this);
+ NewInstanceDialog newInstDlg(groupName, url, extra_info, this);
if (!newInstDlg.exec())
return;
@@ -976,18 +925,105 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty())
url.setScheme("file");
- if (!url.isLocalFile()) { // probably instance/modpack
- addInstance(url.toString());
- break;
+ ModPlatform::IndexedVersion version;
+ QMap<QString, QString> extra_info;
+ QUrl local_url;
+ if (!url.isLocalFile()) { // download the remote resource and identify
+ QUrl dl_url;
+ if (url.scheme() == "curseforge") {
+ // need to find the download link for the modpack / resource
+ // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
+ QUrlQuery query(url);
+
+ if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) {
+ qDebug() << "Invalid curseforge link:" << url;
+ continue;
+ }
+
+ auto addonId = query.allQueryItemValues("addonId")[0];
+ auto fileId = query.allQueryItemValues("fileId")[0];
+
+ extra_info.insert("pack_id", addonId);
+ extra_info.insert("pack_version_id", fileId);
+
+ auto array = std::make_shared<QByteArray>();
+
+ auto api = FlameAPI();
+ auto job = api.getFile(addonId, fileId, array);
+
+ connect(job.get(), &Task::failed, this,
+ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &version] {
+ qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
+ auto doc = Json::requireDocument(*array);
+ auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
+ // No way to find out if it's a mod or a modpack before here
+ // And also we need to check if it ends with .zip, instead of any better way
+ version = FlameMod::loadIndexedPackVersion(data);
+ auto fileName = version.fileName;
+
+ // Have to use ensureString then use QUrl to get proper url encoding
+ dl_url = QUrl(version.downloadUrl);
+ if (!dl_url.isValid()) {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("The modpack, mod, or resource %1 is blocked for third-parties! Please download it manually.").arg(fileName),
+ QMessageBox::Critical)
+ ->show();
+ return;
+ }
+
+ QFileInfo dl_file(dl_url.fileName());
+ });
+
+ { // drop stack
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(job.get());
+ }
+
+ } else {
+ dl_url = url;
+ }
+
+ if (!dl_url.isValid()) {
+ continue; // no valid url to download this resource
+ }
+
+ const QString path = dl_url.host() + '/' + dl_url.path();
+ auto entry = APPLICATION->metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ auto dl_job = unique_qobject_ptr<NetJob>(new NetJob(tr("Modpack download"), APPLICATION->network()));
+ dl_job->addNetAction(Net::ApiDownload::makeCached(dl_url, entry));
+ auto archivePath = entry->getFullPath();
+
+ bool dl_success = false;
+ connect(dl_job.get(), &Task::failed, this,
+ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(dl_job.get(), &Task::succeeded, this, [&dl_success] { dl_success = true; });
+
+ { // drop stack
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(dl_job.get());
+ }
+
+ if (!dl_success) {
+ continue; // no local file to identify
+ }
+ local_url = QUrl::fromLocalFile(archivePath);
+
+ } else {
+ local_url = url;
}
- auto localFileName = QDir::toNativeSeparators(url.toLocalFile());
+ auto localFileName = QDir::toNativeSeparators(local_url.toLocalFile());
QFileInfo localFileInfo(localFileName);
auto type = ResourceUtils::identify(localFileInfo);
if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack
- addInstance(localFileName);
+ addInstance(localFileName, extra_info);
continue;
}
@@ -1012,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
break;
case PackedResourceType::Mod:
- minecraftInst->loaderModList()->installMod(localFileName);
+ minecraftInst->loaderModList()->installMod(localFileName, version);
break;
case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName);
@@ -1061,9 +1097,9 @@ void MainWindow::on_actionChangeInstIcon_triggered()
void MainWindow::iconUpdated(QString icon)
{
if (icon == m_currentInstIcon) {
- auto icon = APPLICATION->icons()->getIcon(m_currentInstIcon);
- ui->actionChangeInstIcon->setIcon(icon);
- changeIconButton->setIcon(icon);
+ auto new_icon = APPLICATION->icons()->getIcon(m_currentInstIcon);
+ ui->actionChangeInstIcon->setIcon(new_icon);
+ changeIconButton->setIcon(new_icon);
}
}
@@ -1134,26 +1170,40 @@ void MainWindow::undoTrashInstance()
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
}
+void MainWindow::on_actionViewLauncherRootFolder_triggered()
+{
+ DesktopServices::openDirectory(".");
+}
+
void MainWindow::on_actionViewInstanceFolder_triggered()
{
QString str = APPLICATION->settings()->get("InstanceDir").toString();
DesktopServices::openDirectory(str);
}
-void MainWindow::on_actionViewLauncherRootFolder_triggered()
+void MainWindow::on_actionViewCentralModsFolder_triggered()
{
- const QString dataPath = QDir::currentPath();
- DesktopServices::openDirectory(dataPath);
+ DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true);
}
-void MainWindow::refreshInstances()
+void MainWindow::on_actionViewIconThemeFolder_triggered()
{
- APPLICATION->instances()->loadList();
+ DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path());
}
-void MainWindow::on_actionViewCentralModsFolder_triggered()
+void MainWindow::on_actionViewWidgetThemeFolder_triggered()
{
- DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true);
+ DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path());
+}
+
+void MainWindow::on_actionViewCatPackFolder_triggered()
+{
+ DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path());
+}
+
+void MainWindow::refreshInstances()
+{
+ APPLICATION->instances()->loadList();
}
void MainWindow::checkForUpdates()
@@ -1177,7 +1227,7 @@ void MainWindow::globalSettingsClosed()
proxymodel->invalidate();
proxymodel->sort(0);
updateMainToolBar();
- updateToolsMenu();
+ updateLaunchButton();
updateThemeMenu();
updateStatusCenter();
// This needs to be done to prevent UI elements disappearing in the event the config is changed
@@ -1307,10 +1357,11 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
- return;
+ } else {
+ APPLICATION->instances()->deleteInstance(id);
}
-
- APPLICATION->instances()->deleteInstance(id);
+ APPLICATION->settings()->set("SelectedInstance", QString());
+ selectionBad();
}
void MainWindow::on_actionExportInstanceZip_triggered()
@@ -1412,20 +1463,6 @@ void MainWindow::activateInstance(InstancePtr instance)
APPLICATION->launch(instance);
}
-void MainWindow::on_actionLaunchInstanceOffline_triggered()
-{
- if (m_selectedInstance) {
- APPLICATION->launch(m_selectedInstance, false);
- }
-}
-
-void MainWindow::on_actionLaunchInstanceDemo_triggered()
-{
- if (m_selectedInstance) {
- APPLICATION->launch(m_selectedInstance, false, true);
- }
-}
-
void MainWindow::on_actionKillInstance_triggered()
{
if (m_selectedInstance && m_selectedInstance->isRunning()) {
@@ -1590,7 +1627,7 @@ void MainWindow::startTask(Task* task)
task->start();
}
-void MainWindow::instanceChanged(const QModelIndex& current, const QModelIndex& previous)
+void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
if (!current.isValid()) {
APPLICATION->settings()->set("SelectedInstance", QString());
@@ -1599,6 +1636,7 @@ void MainWindow::instanceChanged(const QModelIndex& current, const QModelIndex&
}
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ disconnect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
@@ -1606,14 +1644,6 @@ void MainWindow::instanceChanged(const QModelIndex& current, const QModelIndex&
ui->instanceToolBar->setEnabled(true);
setInstanceActionsEnabled(true);
ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
- ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch());
- ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch());
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
- if (instance) {
- ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo());
- }
ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning());
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
@@ -1622,18 +1652,13 @@ void MainWindow::instanceChanged(const QModelIndex& current, const QModelIndex&
updateStatusCenter();
updateInstanceToolIcon(m_selectedInstance->iconKey());
- updateToolsMenu();
+ updateLaunchButton();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
} else {
- ui->instanceToolBar->setEnabled(false);
- setInstanceActionsEnabled(false);
- ui->actionLaunchInstance->setEnabled(false);
- ui->actionLaunchInstanceOffline->setEnabled(false);
- ui->actionLaunchInstanceDemo->setEnabled(false);
- ui->actionKillInstance->setEnabled(false);
APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad();
return;
@@ -1658,11 +1683,12 @@ void MainWindow::selectionBad()
{
// start by reseting everything...
m_selectedInstance = nullptr;
+ m_statusLeft->setText(tr("No instance selected"));
statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false);
- updateToolsMenu();
+ updateLaunchButton();
renameButton->setText(tr("Rename Instance"));
updateInstanceToolIcon("grass");
@@ -1709,7 +1735,9 @@ void MainWindow::updateStatusCenter()
int timePlayed = APPLICATION->instances()->getTotalPlayTime();
if (timePlayed > 0) {
- m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
+ m_statusCenter->setText(
+ tr("Total playtime: %1")
+ .arg(Time::prettifyDuration(timePlayed, APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
// "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)
@@ -1725,7 +1753,7 @@ void MainWindow::setInstanceActionsEnabled(bool enabled)
ui->actionCreateInstanceShortcut->setEnabled(enabled);
}
-void MainWindow::refreshCurrentInstance(bool running)
+void MainWindow::refreshCurrentInstance()
{
auto current = view->selectionModel()->currentIndex();
instanceChanged(current, current);
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index e6434dae..0b614452 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -109,16 +109,19 @@ class MainWindow : public QMainWindow {
void on_actionChangeInstIcon_triggered();
+ void on_actionViewLauncherRootFolder_triggered();
+
void on_actionViewInstanceFolder_triggered();
+ void on_actionViewCentralModsFolder_triggered();
- void on_actionViewLauncherRootFolder_triggered();
+ void on_actionViewIconThemeFolder_triggered();
+ void on_actionViewWidgetThemeFolder_triggered();
+ void on_actionViewCatPackFolder_triggered();
void on_actionViewSelectedInstFolder_triggered();
void refreshInstances();
- void on_actionViewCentralModsFolder_triggered();
-
void checkForUpdates();
void on_actionSettings_triggered();
@@ -141,10 +144,6 @@ class MainWindow : public QMainWindow {
void on_actionLaunchInstance_triggered();
- void on_actionLaunchInstanceOffline_triggered();
-
- void on_actionLaunchInstanceDemo_triggered();
-
void on_actionKillInstance_triggered();
void on_actionDeleteInstance_triggered();
@@ -152,10 +151,7 @@ class MainWindow : public QMainWindow {
void deleteGroup();
void undoTrashInstance();
- inline void on_actionExportInstance_triggered()
- {
- on_actionExportInstanceZip_triggered();
- }
+ inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered();
@@ -178,7 +174,7 @@ class MainWindow : public QMainWindow {
void updateMainToolBar();
- void updateToolsMenu();
+ void updateLaunchButton();
void updateThemeMenu();
@@ -212,12 +208,12 @@ class MainWindow : public QMainWindow {
void keyReleaseEvent(QKeyEvent* event) override;
#endif
- void refreshCurrentInstance(bool running);
+ void refreshCurrentInstance();
private:
void retranslateUi();
- void addInstance(QString url = QString());
+ void addInstance(const QString& url = QString(), const QMap<QString, QString>& extra_info = {});
void activateInstance(InstancePtr instance);
void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index e4421d40..91b2c270 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -186,9 +186,14 @@
<property name="toolTipsVisible">
<bool>true</bool>
</property>
- <addaction name="actionViewInstanceFolder"/>
<addaction name="actionViewLauncherRootFolder"/>
+ <addaction name="separator"/>
+ <addaction name="actionViewInstanceFolder"/>
<addaction name="actionViewCentralModsFolder"/>
+ <addaction name="separator"/>
+ <addaction name="actionViewIconThemeFolder"/>
+ <addaction name="actionViewWidgetThemeFolder"/>
+ <addaction name="actionViewCatPackFolder"/>
</widget>
<widget class="QMenu" name="accountsMenu">
<property name="title">
@@ -435,22 +440,6 @@
<string>Ctrl+D</string>
</property>
</action>
- <action name="actionLaunchInstanceOffline">
- <property name="text">
- <string>Launch &amp;Offline</string>
- </property>
- <property name="toolTip">
- <string>Launch the selected instance in offline mode.</string>
- </property>
- </action>
- <action name="actionLaunchInstanceDemo">
- <property name="text">
- <string>Launch &amp;Demo</string>
- </property>
- <property name="toolTip">
- <string>Launch the selected instance in demo mode.</string>
- </property>
- </action>
<action name="actionExportInstance">
<property name="icon">
<iconset theme="export">
@@ -465,7 +454,8 @@
</action>
<action name="actionExportInstanceZip">
<property name="icon">
- <iconset theme="launcher"/>
+ <iconset theme="launcher">
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Prism Launcher (zip)</string>
@@ -473,7 +463,8 @@
</action>
<action name="actionExportInstanceMrPack">
<property name="icon">
- <iconset theme="modrinth"/>
+ <iconset theme="modrinth">
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Modrinth (mrpack)</string>
@@ -481,15 +472,17 @@
</action>
<action name="actionExportInstanceFlamePack">
<property name="icon">
- <iconset theme="flame"/>
+ <iconset theme="flame">
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
- <string>CurseForge (zip)</string>
+ <string>CurseForge (zip)</string>
</property>
</action>
<action name="actionExportInstanceToModList">
<property name="icon">
- <iconset theme="new"/>
+ <iconset theme="new">
+ <normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Mod List</string>
@@ -552,7 +545,7 @@
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
- <string>&amp;View Instance Folder</string>
+ <string>View &amp;Instance Folder</string>
</property>
<property name="toolTip">
<string>Open the instance folder in a file browser.</string>
@@ -564,7 +557,7 @@
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
- <string>&amp;View Launcher Root Folder</string>
+ <string>View Launcher &amp;Root Folder</string>
</property>
<property name="toolTip">
<string>Open the launcher's root folder in a file browser.</string>
@@ -719,6 +712,38 @@
<string>Open the %1 wiki</string>
</property>
</action>
+ <action name="actionViewWidgetThemeFolder">
+ <property name="icon">
+ <iconset theme="viewfolder">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>View &amp;Widget Themes Folder</string>
+ </property>
+ <property name="toolTip">
+ <string>View Widget Theme Folder</string>
+ </property>
+ </action>
+ <action name="actionViewIconThemeFolder">
+ <property name="icon">
+ <iconset theme="viewfolder">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>View I&amp;con Theme Folder</string>
+ </property>
+ <property name="toolTip">
+ <string>View Icon Theme Folder</string>
+ </property>
+ </action>
+ <action name="actionViewCatPackFolder">
+ <property name="icon">
+ <iconset theme="viewfolder"/>
+ </property>
+ <property name="text">
+ <string>View Cat Packs Folder</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 0e410027..8b2f5bb8 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -220,7 +220,7 @@ void CopyInstanceDialog::on_iconButton_clicked()
}
}
-void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString& arg1)
+void CopyInstanceDialog::on_instNameTextBox_textChanged([[maybe_unused]] const QString& arg1)
{
updateDialogState();
}
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp
index d6a503cc..703736d6 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.cpp
+++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp
@@ -195,8 +195,8 @@ void ExportInstanceDialog::loadPackIgnore()
if (!ignoreFile.open(QIODevice::ReadOnly)) {
return;
}
- auto data = ignoreFile.readAll();
- auto string = QString::fromUtf8(data);
+ auto ignoreData = ignoreFile.readAll();
+ auto string = QString::fromUtf8(ignoreData);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
proxyModel->setBlockedPaths(string.split('\n', Qt::SkipEmptyParts));
#else
@@ -206,10 +206,10 @@ void ExportInstanceDialog::loadPackIgnore()
void ExportInstanceDialog::savePackIgnore()
{
- auto data = proxyModel->blockedPaths().toStringList().join('\n').toUtf8();
+ auto ignoreData = proxyModel->blockedPaths().toStringList().join('\n').toUtf8();
auto filename = ignoreFileName();
try {
- FS::write(filename, data);
+ FS::write(filename, ignoreData);
} catch (const Exception& e) {
qWarning() << e.cause();
}
diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp
index ad8db5ff..5af24b1b 100644
--- a/launcher/ui/dialogs/ExportPackDialog.cpp
+++ b/launcher/ui/dialogs/ExportPackDialog.cpp
@@ -37,15 +37,21 @@
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
: QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider)
{
+ Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
+
ui->setupUi(this);
- ui->name->setText(instance->name());
+ ui->name->setPlaceholderText(instance->name());
+ ui->name->setText(instance->settings()->get("ExportName").toString());
+ ui->version->setText(instance->settings()->get("ExportVersion").toString());
+ ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
+
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
- ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
- setWindowTitle("Export Modrinth Pack");
+ setWindowTitle(tr("Export Modrinth Pack"));
+ ui->summary->setText(instance->settings()->get("ExportSummary").toString());
} else {
- setWindowTitle("Export CurseForge Pack");
- ui->version->setText("");
- ui->summaryLabel->setText("Author");
+ setWindowTitle(tr("Export CurseForge Pack"));
+ ui->summaryLabel->setText(tr("&Author"));
+ ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
}
// ensure a valid pack is generated
@@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
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()));
+ proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath()));
}
- ui->treeView->setModel(proxy);
- ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
- ui->treeView->sortByColumn(0, Qt::AscendingOrder);
+ ui->files->setModel(proxy);
+ ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
+ ui->files->sortByColumn(0, Qt::AscendingOrder);
model->setFilter(filter);
model->setRootPath(instance->gameRoot());
- QHeaderView* headerView = ui->treeView->header();
+ QHeaderView* headerView = ui->files->header();
headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
}
@@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog()
void ExportPackDialog::done(int result)
{
+ auto settings = instance->settings();
+ settings->set("ExportName", ui->name->text());
+ settings->set("ExportVersion", ui->version->text());
+ settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
+ settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
+
if (result == Accepted) {
- const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
+ const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
+ const QString filename = FS::RemoveInvalidFilenameChars(name);
+
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;
+ if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
+ output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"),
+ "Modrinth pack (*.mrpack *.zip)", nullptr);
+ if (output.isEmpty())
+ return;
+ if (!(output.endsWith(".zip") || output.endsWith(".mrpack")))
+ output.append(".mrpack");
+ } else {
+ output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"),
+ "CurseForge pack (*.zip)", nullptr);
+ if (output.isEmpty())
+ return;
+ if (!output.endsWith(".zip"))
+ output.append(".zip");
+ }
+
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,
+ if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
+ task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
+ output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
+ } else {
+ task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), 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(); });
@@ -140,7 +160,6 @@ void ExportPackDialog::done(int result)
void ExportPackDialog::validate()
{
- const bool invalid =
- ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty());
- ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
+ ui->buttonBox->button(QDialogButtonBox::Ok)
+ ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty());
}
diff --git a/launcher/ui/dialogs/ExportPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui
index 3976e28f..09dea72a 100644
--- a/launcher/ui/dialogs/ExportPackDialog.ui
+++ b/launcher/ui/dialogs/ExportPackDialog.ui
@@ -7,12 +7,9 @@
<x>0</x>
<y>0</y>
<width>650</width>
- <height>413</height>
+ <height>510</height>
</rect>
</property>
- <property name="windowTitle">
- <string>Export Pack</string>
- </property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
@@ -20,13 +17,16 @@
<item>
<widget class="QGroupBox" name="information">
<property name="title">
- <string>Information</string>
+ <string>&amp;Description</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="summaryLabel">
<property name="text">
- <string>Summary</string>
+ <string>&amp;Summary</string>
+ </property>
+ <property name="buddy">
+ <cstring>summary</cstring>
</property>
</widget>
</item>
@@ -36,14 +36,20 @@
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
- <string>Name</string>
+ <string>&amp;Name</string>
+ </property>
+ <property name="buddy">
+ <cstring>name</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="versionLabel">
<property name="text">
- <string>Version</string>
+ <string>&amp;Version</string>
+ </property>
+ <property name="buddy">
+ <cstring>version</cstring>
</property>
</widget>
</item>
@@ -57,31 +63,52 @@
</property>
</widget>
</item>
-
</layout>
</widget>
</item>
<item>
- <widget class="QLabel" name="filesLabel">
- <property name="text">
- <string>Files</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QTreeView" name="treeView">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
+ <widget class="QGroupBox" name="options">
+ <property name="title">
+ <string>&amp;Options</string>
</property>
- <attribute name="headerStretchLastSection">
- <bool>false</bool>
- </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="filesLabel">
+ <property name="text">
+ <string>&amp;Files</string>
+ </property>
+ <property name="buddy">
+ <cstring>files</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="files">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="optionalFiles">
+ <property name="text">
+ <string>&amp;Mark disabled files as optional</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -97,7 +124,8 @@
<tabstop>name</tabstop>
<tabstop>version</tabstop>
<tabstop>summary</tabstop>
- <tabstop>treeView</tabstop>
+ <tabstop>files</tabstop>
+ <tabstop>optionalFiles</tabstop>
</tabstops>
<resources/>
<connections>
diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp
new file mode 100644
index 00000000..541119d1
--- /dev/null
+++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * 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 "InstallLoaderDialog.h"
+
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QVBoxLayout>
+#include "Application.h"
+#include "BuildConfig.h"
+#include "DesktopServices.h"
+#include "meta/Index.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "ui/widgets/PageContainer.h"
+#include "ui/widgets/VersionSelectWidget.h"
+
+class InstallLoaderPage : public VersionSelectWidget, public BasePage {
+ public:
+ InstallLoaderPage(const QString& id,
+ const QString& iconName,
+ const QString& name,
+ const Version& oldestVersion,
+ const std::shared_ptr<PackProfile> profile)
+ : VersionSelectWidget(nullptr), uid(id), iconName(iconName), name(name)
+ {
+ const QString minecraftVersion = profile->getComponentVersion("net.minecraft");
+ setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
+ setExactIfPresentFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+
+ if (oldestVersion != Version() && Version(minecraftVersion) < oldestVersion)
+ setExactFilter(BaseVersionList::ParentVersionRole, "AAA");
+
+ if (const QString currentVersion = profile->getComponentVersion(id); !currentVersion.isNull())
+ setCurrentVersion(currentVersion);
+ }
+
+ QString id() const override { return uid; }
+ QString displayName() const override { return name; }
+ QIcon icon() const override { return APPLICATION->getThemedIcon(iconName); }
+
+ void openedImpl() override
+ {
+ if (loaded)
+ return;
+
+ const auto versions = APPLICATION->metadataIndex()->get(uid);
+ if (!versions)
+ return;
+
+ initialize(versions.get());
+ loaded = true;
+ }
+
+ void setParentContainer(BasePageContainer* container) override
+ {
+ auto dialog = dynamic_cast<QDialog*>(dynamic_cast<PageContainer*>(container)->parent());
+ connect(view(), &QAbstractItemView::doubleClicked, dialog, &QDialog::accept);
+ }
+
+ private:
+ const QString uid;
+ const QString iconName;
+ const QString name;
+ bool loaded = false;
+};
+
+static InstallLoaderPage* pageCast(BasePage* page)
+{
+ auto result = dynamic_cast<InstallLoaderPage*>(page);
+ Q_ASSERT(result != nullptr);
+ return result;
+}
+
+InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr<PackProfile> profile, const QString& uid, QWidget* parent)
+ : QDialog(parent), profile(std::move(profile)), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this))
+{
+ auto layout = new QVBoxLayout(this);
+
+ container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+ layout->addWidget(container);
+
+ auto buttonLayout = new QHBoxLayout(this);
+
+ auto refreshButton = new QPushButton(tr("&Refresh"), this);
+ connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); });
+ buttonLayout->addWidget(refreshButton);
+
+ buttons->setOrientation(Qt::Horizontal);
+ buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
+ connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ buttonLayout->addWidget(buttons);
+
+ layout->addLayout(buttonLayout);
+
+ setWindowTitle(dialogTitle());
+ setWindowModality(Qt::WindowModal);
+ resize(520, 347);
+
+ for (BasePage* page : container->getPages()) {
+ if (page->id() == uid)
+ container->selectPage(page->id());
+
+ connect(pageCast(page), &VersionSelectWidget::selectedVersionChanged, this, [this, page] {
+ if (page->id() == container->selectedPage()->id())
+ validate(container->selectedPage());
+ });
+ }
+ connect(container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* current) { validate(current); });
+ pageCast(container->selectedPage())->selectSearch();
+ validate(container->selectedPage());
+}
+
+QList<BasePage*> InstallLoaderDialog::getPages()
+{
+ return { // NeoForge
+ new InstallLoaderPage("net.neoforged", "neoforged", tr("NeoForge"), {}, profile),
+ // Forge
+ new InstallLoaderPage("net.minecraftforge", "forge", tr("Forge"), {}, profile),
+ // Fabric
+ new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile),
+ // Quilt
+ new InstallLoaderPage("org.quiltmc.quilt-loader", "quiltmc", tr("Quilt"), Version("1.14"), profile),
+ // LiteLoader
+ new InstallLoaderPage("com.mumfrey.liteloader", "liteloader", tr("LiteLoader"), {}, profile)
+ };
+}
+
+QString InstallLoaderDialog::dialogTitle()
+{
+ return tr("Install Loader");
+}
+
+void InstallLoaderDialog::validate(BasePage* page)
+{
+ buttons->button(QDialogButtonBox::Ok)->setEnabled(pageCast(page)->selectedVersion() != nullptr);
+}
+
+void InstallLoaderDialog::done(int result)
+{
+ if (result == Accepted) {
+ auto* page = pageCast(container->selectedPage());
+ if (page->selectedVersion()) {
+ profile->setComponentVersion(page->id(), page->selectedVersion()->descriptor());
+ profile->resolve(Net::Mode::Online);
+ }
+ }
+
+ QDialog::done(result);
+}
diff --git a/launcher/ui/dialogs/InstallLoaderDialog.h b/launcher/ui/dialogs/InstallLoaderDialog.h
new file mode 100644
index 00000000..86cb3bdd
--- /dev/null
+++ b/launcher/ui/dialogs/InstallLoaderDialog.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * 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 "ui/pages/BasePageProvider.h"
+
+class MinecraftInstance;
+class PageContainer;
+class PackProfile;
+class QDialogButtonBox;
+
+class InstallLoaderDialog final : public QDialog, protected BasePageProvider {
+ Q_OBJECT
+
+ public:
+ explicit InstallLoaderDialog(std::shared_ptr<PackProfile> instance, const QString& uid = QString(), QWidget* parent = nullptr);
+
+ QList<BasePage*> getPages() override;
+ QString dialogTitle() override;
+
+ void validate(BasePage* page);
+ void done(int result) override;
+
+ private:
+ std::shared_ptr<PackProfile> profile;
+ PageContainer* container;
+ QDialogButtonBox* buttons;
+};
diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp
index 79622ff7..0e11ff1a 100644
--- a/launcher/ui/dialogs/ModUpdateDialog.cpp
+++ b/launcher/ui/dialogs/ModUpdateDialog.cpp
@@ -5,8 +5,6 @@
#include "ScrollMessageBox.h"
#include "ui_ReviewMessageBox.h"
-#include "FileSystem.h"
-#include "Json.h"
#include "Markdown.h"
#include "tasks/ConcurrentTask.h"
@@ -30,9 +28,9 @@ static std::list<Version> mcVersions(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
}
-static std::optional<ResourceAPI::ModLoaderTypes> mcLoaders(BaseInstance* inst)
+static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst)
{
- return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() };
+ return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() };
}
ModUpdateDialog::ModUpdateDialog(QWidget* parent,
@@ -238,6 +236,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (skip_rest)
continue;
+ if (candidate->type() == ResourceType::FOLDER) {
+ continue;
+ }
+
if (confirm_rest) {
addToTmp(candidate, provider_rest);
should_try_others.insert(candidate->internal_id(), try_others_rest);
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index 6e5a41ef..9613c6b0 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -62,8 +62,10 @@
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h"
#include "ui/widgets/PageContainer.h"
-
-NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent)
+NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
+ const QString& url,
+ const QMap<QString, QString>& extra_info,
+ QWidget* parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog)
{
ui->setupUi(this);
@@ -125,6 +127,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString&
QUrl actualUrl(url);
m_container->selectPage("import");
importPage->setUrl(url);
+ importPage->setExtraInfo(extra_info);
}
updateDialogState();
@@ -290,7 +293,7 @@ void NewInstanceDialog::on_iconButton_clicked()
}
}
-void NewInstanceDialog::on_instNameTextBox_textChanged(const QString& arg1)
+void NewInstanceDialog::on_instNameTextBox_textChanged([[maybe_unused]] const QString& arg1)
{
updateDialogState();
}
@@ -299,7 +302,7 @@ void NewInstanceDialog::importIconNow()
{
if (importIcon) {
APPLICATION->icons()->installIcon(importIconPath, importIconName);
- InstIconKey = importIconName;
+ InstIconKey = importIconName.mid(0, importIconName.lastIndexOf('.'));
importIcon = false;
}
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h
index b348649f..92357956 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.h
+++ b/launcher/ui/dialogs/NewInstanceDialog.h
@@ -53,7 +53,10 @@ class NewInstanceDialog : public QDialog, public BasePageProvider {
Q_OBJECT
public:
- explicit NewInstanceDialog(const QString& initialGroup, const QString& url = QString(), QWidget* parent = 0);
+ explicit NewInstanceDialog(const QString& initialGroup,
+ const QString& url = QString(),
+ const QMap<QString, QString>& extra_info = {},
+ QWidget* parent = 0);
~NewInstanceDialog();
void updateDialogState();
diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp
index d7758d6d..4b0c5b76 100644
--- a/launcher/ui/dialogs/ProfileSetupDialog.cpp
+++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp
@@ -163,13 +163,15 @@ void ProfileSetupDialog::checkName(const QString& name)
requestor->get(request);
}
-void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
+void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error,
+ QByteArray profileData,
+ [[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
if (error == QNetworkReply::NoError) {
- auto doc = QJsonDocument::fromJson(data);
+ auto doc = QJsonDocument::fromJson(profileData);
auto root = doc.object();
auto statusValue = root.value("status").toString("INVALID");
if (statusValue == "AVAILABLE") {
@@ -202,11 +204,11 @@ void ProfileSetupDialog::setupProfile(const QString& profileName)
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
QString payloadTemplate("{\"profileName\":\"%1\"}");
- auto data = payloadTemplate.arg(profileName).toUtf8();
+ auto profileData = payloadTemplate.arg(profileName).toUtf8();
AuthRequest* requestor = new AuthRequest(this);
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished);
- requestor->post(request, data);
+ requestor->post(request, profileData);
isWorking = true;
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
@@ -243,8 +245,8 @@ struct MojangError {
} // namespace
void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error,
- QByteArray data,
- QList<QNetworkReply::RawHeaderPair> headers)
+ QByteArray errorData,
+ [[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();
@@ -257,7 +259,7 @@ void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error,
*/
accept();
} else {
- auto parsedError = MojangError::fromJSON(data);
+ auto parsedError = MojangError::fromJSON(errorData);
ui->errorLabel->setVisible(true);
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
qDebug() << parsedError.rawError;
diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp
index ba22e334..0ca3a1bd 100644
--- a/launcher/ui/dialogs/ProgressDialog.cpp
+++ b/launcher/ui/dialogs/ProgressDialog.cpp
@@ -85,7 +85,7 @@ void ProgressDialog::on_skipButton_clicked(bool checked)
{
Q_UNUSED(checked);
if (ui->skipButton->isEnabled()) // prevent other triggers from aborting
- task->abort();
+ m_task->abort();
}
ProgressDialog::~ProgressDialog()
@@ -127,7 +127,7 @@ void ProgressDialog::updateSize(bool recenterParent)
int ProgressDialog::execWithTask(Task* task)
{
- this->task = task;
+ this->m_task = task;
if (!task) {
qDebug() << "Programmer error: Progress dialog created with null task.";
@@ -179,8 +179,8 @@ int ProgressDialog::execWithTask(std::unique_ptr<Task>& task)
bool ProgressDialog::handleImmediateResult(QDialog::DialogCode& result)
{
- if (task->isFinished()) {
- if (task->wasSuccessful()) {
+ if (m_task->isFinished()) {
+ if (m_task->wasSuccessful()) {
result = QDialog::Accepted;
} else {
result = QDialog::Rejected;
@@ -192,12 +192,12 @@ bool ProgressDialog::handleImmediateResult(QDialog::DialogCode& result)
Task* ProgressDialog::getTask()
{
- return task;
+ return m_task;
}
void ProgressDialog::onTaskStarted() {}
-void ProgressDialog::onTaskFailed(QString failure)
+void ProgressDialog::onTaskFailed([[maybe_unused]] QString failure)
{
reject();
hide();
@@ -209,11 +209,11 @@ void ProgressDialog::onTaskSucceeded()
hide();
}
-void ProgressDialog::changeStatus(const QString& status)
+void ProgressDialog::changeStatus([[maybe_unused]] const QString& status)
{
- ui->globalStatusLabel->setText(task->getStatus());
+ ui->globalStatusLabel->setText(m_task->getStatus());
ui->globalStatusLabel->adjustSize();
- ui->globalStatusDetailsLabel->setText(task->getDetails());
+ ui->globalStatusDetailsLabel->setText(m_task->getDetails());
ui->globalStatusDetailsLabel->adjustSize();
updateSize();
@@ -279,7 +279,7 @@ void ProgressDialog::keyPressEvent(QKeyEvent* e)
void ProgressDialog::closeEvent(QCloseEvent* e)
{
- if (task && task->isRunning()) {
+ if (m_task && m_task->isRunning()) {
e->ignore();
} else {
QDialog::closeEvent(e);
diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h
index 82067271..15eadf4e 100644
--- a/launcher/ui/dialogs/ProgressDialog.h
+++ b/launcher/ui/dialogs/ProgressDialog.h
@@ -92,7 +92,7 @@ class ProgressDialog : public QDialog {
private:
Ui::ProgressDialog* ui;
- Task* task;
+ Task* m_task;
bool m_is_multi_step = false;
QHash<QUuid, SubTaskProgressBar*> taskProgress;
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
index 16bdc9b0..cfd292f4 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -210,15 +210,17 @@ bool ResourceDownloadDialog::selectPage(QString pageId)
return m_container->selectPage(pageId);
}
-ResourcePage* ResourceDownloadDialog::getSelectedPage()
+ResourcePage* ResourceDownloadDialog::selectedPage()
{
- return m_selectedPage;
+ ResourcePage* result = dynamic_cast<ResourcePage*>(m_container->selectedPage());
+ Q_ASSERT(result != nullptr);
+ return result;
}
void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver)
{
removeResource(pack->name);
- m_selectedPage->addResourceToPage(pack, ver, getBaseModel());
+ selectedPage()->addResourceToPage(pack, ver, getBaseModel());
setButtonStatus();
}
@@ -258,14 +260,8 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s
return;
}
- m_selectedPage = dynamic_cast<ResourcePage*>(selected);
- if (!m_selectedPage) {
- qCritical() << "Page '" << selected->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!";
- return;
- }
-
// Same effect as having a global search bar
- m_selectedPage->setSearchTerm(prev_page->getSearchTerm());
+ selectedPage()->setSearchTerm(prev_page->getSearchTerm());
}
ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr<ModFolderModel>& mods, BaseInstance* instance)
@@ -284,15 +280,13 @@ QList<BasePage*> ModDownloadDialog::getPages()
{
QList<BasePage*> pages;
- auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value();
+ auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getSupportedModLoaders().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]);
-
return pages;
}
@@ -307,7 +301,7 @@ GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask()
return makeShared<GetModDependenciesTask>(this, m_instance, model, selectedVers);
}
return nullptr;
-};
+}
ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent,
const std::shared_ptr<ResourcePackFolderModel>& resource_packs,
@@ -331,8 +325,6 @@ QList<BasePage*> ResourcePackDownloadDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(FlameResourcePackPage::create(this, *m_instance));
- m_selectedPage = dynamic_cast<ResourcePackResourcePage*>(pages[0]);
-
return pages;
}
@@ -358,8 +350,6 @@ QList<BasePage*> TexturePackDownloadDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(FlameTexturePackPage::create(this, *m_instance));
- m_selectedPage = dynamic_cast<TexturePackResourcePage*>(pages[0]);
-
return pages;
}
@@ -380,11 +370,9 @@ ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent,
QList<BasePage*> ShaderPackDownloadDialog::getPages()
{
QList<BasePage*> pages;
-
pages.append(ModrinthShaderPackPage::create(this, *m_instance));
-
- m_selectedPage = dynamic_cast<ShaderPackResourcePage*>(pages[0]);
-
+ if (APPLICATION->capabilities() & Application::SupportsFlame)
+ pages.append(FlameShaderPackPage::create(this, *m_instance));
return pages;
}
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h
index f65daaa3..e9d2cfbe 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.h
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.h
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -61,7 +61,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
QString dialogTitle() override { return tr("Download %1").arg(resourcesString()); };
bool selectPage(QString pageId);
- ResourcePage* getSelectedPage();
+ ResourcePage* selectedPage();
void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&);
void removeResource(const QString&);
@@ -88,7 +88,6 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
const std::shared_ptr<ResourceFolderModel> m_base_model;
PageContainer* m_container = nullptr;
- ResourcePage* m_selectedPage = nullptr;
QDialogButtonBox m_buttons;
QVBoxLayout m_vertical_layout;
diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp
index d1b7bd15..aa668f8c 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.cpp
+++ b/launcher/ui/dialogs/ReviewMessageBox.cpp
@@ -5,7 +5,7 @@
#include <QPushButton>
-ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QString const& icon)
+ReviewMessageBox::ReviewMessageBox(QWidget* parent, [[maybe_unused]] QString const& title, [[maybe_unused]] QString const& icon)
: QDialog(parent), ui(new Ui::ReviewMessageBox)
{
ui->setupUi(this);
diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp
index 8f0c8fa4..5b3ebfa2 100644
--- a/launcher/ui/dialogs/SkinUploadDialog.cpp
+++ b/launcher/ui/dialogs/SkinUploadDialog.cpp
@@ -133,15 +133,15 @@ SkinUploadDialog::SkinUploadDialog(MinecraftAccountPtr acct, QWidget* parent) :
ui->setupUi(this);
// FIXME: add a model for this, download/refresh the capes on demand
- auto& data = *acct->accountData();
+ auto& accountData = *acct->accountData();
int index = 0;
ui->capeCombo->addItem(tr("No Cape"), QVariant());
- auto currentCape = data.minecraftProfile.currentCape;
+ auto currentCape = accountData.minecraftProfile.currentCape;
if (currentCape.isEmpty()) {
ui->capeCombo->setCurrentIndex(index);
}
- for (auto& cape : data.minecraftProfile.capes) {
+ for (auto& cape : accountData.minecraftProfile.capes) {
index++;
if (cape.data.size()) {
QPixmap capeImage;
diff --git a/launcher/ui/dialogs/SkinUploadDialog.ui b/launcher/ui/dialogs/SkinUploadDialog.ui
index c7b16645..c6df92df 100644
--- a/launcher/ui/dialogs/SkinUploadDialog.ui
+++ b/launcher/ui/dialogs/SkinUploadDialog.ui
@@ -35,14 +35,8 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="maximumSize">
- <size>
- <width>28</width>
- <height>16777215</height>
- </size>
- </property>
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp
index 61254c46..c61d1057 100644
--- a/launcher/ui/dialogs/VersionSelectDialog.cpp
+++ b/launcher/ui/dialogs/VersionSelectDialog.cpp
@@ -54,7 +54,7 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList* vlist, QString title,
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
- m_versionWidget = new VersionSelectWidget(true, parent);
+ m_versionWidget = new VersionSelectWidget(parent);
m_verticalLayout->addWidget(m_versionWidget);
m_horizontalLayout = new QHBoxLayout();
@@ -74,8 +74,9 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList* vlist, QString title,
retranslate();
- QObject::connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
- QObject::connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(m_versionWidget->view(), &QAbstractItemView::doubleClicked, this, &QDialog::accept);
+ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
QMetaObject::connectSlotsByName(this);
setWindowModality(Qt::WindowModal);
@@ -121,6 +122,7 @@ int VersionSelectDialog::exec()
{
QDialog::open();
m_versionWidget->initialize(m_vlist);
+ m_versionWidget->selectSearch();
if (resizeOnColumn != -1) {
m_versionWidget->setResizeOn(resizeOnColumn);
}
@@ -147,6 +149,11 @@ void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QStri
m_versionWidget->setExactFilter(role, filter);
}
+void VersionSelectDialog::setExactIfPresentFilter(BaseVersionList::ModelRoles role, QString filter)
+{
+ m_versionWidget->setExactIfPresentFilter(role, filter);
+}
+
void VersionSelectDialog::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter)
{
m_versionWidget->setFuzzyFilter(role, filter);
diff --git a/launcher/ui/dialogs/VersionSelectDialog.h b/launcher/ui/dialogs/VersionSelectDialog.h
index 701020fa..0ccd45e7 100644
--- a/launcher/ui/dialogs/VersionSelectDialog.h
+++ b/launcher/ui/dialogs/VersionSelectDialog.h
@@ -46,6 +46,7 @@ class VersionSelectDialog : public QDialog {
void setCurrentVersion(const QString& version);
void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter);
void setExactFilter(BaseVersionList::ModelRoles role, QString filter);
+ void setExactIfPresentFilter(BaseVersionList::ModelRoles role, QString filter);
void setEmptyString(QString emptyString);
void setEmptyErrorString(QString emptyErrorString);
void setResizeOn(int column);
diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp
index 915b41d8..d947163b 100644
--- a/launcher/ui/instanceview/InstanceDelegate.cpp
+++ b/launcher/ui/instanceview/InstanceDelegate.cpp
@@ -367,7 +367,9 @@ class NoReturnTextEdit : public QTextEdit {
void editingDone();
};
-void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
+void ListViewDelegate::updateEditorGeometry(QWidget* editor,
+ const QStyleOptionViewItem& option,
+ [[maybe_unused]] const QModelIndex& index) const
{
const int iconSize = 48;
QRect textRect = option.rect;
@@ -379,17 +381,17 @@ void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionV
void ListViewDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
auto text = index.data(Qt::EditRole).toString();
- QTextEdit* realeditor = qobject_cast<NoReturnTextEdit*>(editor);
- realeditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
- realeditor->append(text);
- realeditor->selectAll();
- realeditor->document()->clearUndoRedoStacks();
+ QTextEdit* realEditor = qobject_cast<NoReturnTextEdit*>(editor);
+ realEditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
+ realEditor->append(text);
+ realEditor->selectAll();
+ realEditor->document()->clearUndoRedoStacks();
}
void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
- QTextEdit* realeditor = qobject_cast<NoReturnTextEdit*>(editor);
- QString text = realeditor->toPlainText();
+ QTextEdit* realEditor = qobject_cast<NoReturnTextEdit*>(editor);
+ QString text = realEditor->toPlainText();
text.replace(QChar('\n'), QChar(' '));
text = text.trimmed();
// Prevent instance names longer than 128 chars
@@ -399,7 +401,9 @@ void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
}
}
-QWidget* ListViewDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
+QWidget* ListViewDelegate::createEditor(QWidget* parent,
+ [[maybe_unused]] const QStyleOptionViewItem& option,
+ [[maybe_unused]] const QModelIndex& index) const
{
auto editor = new NoReturnTextEdit(parent);
connect(editor, &NoReturnTextEdit::editingDone, this, &ListViewDelegate::editingDone);
diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp
index a9442546..690dc917 100644
--- a/launcher/ui/instanceview/InstanceView.cpp
+++ b/launcher/ui/instanceview/InstanceView.cpp
@@ -47,8 +47,8 @@
#include <QScrollBar>
#include <QtMath>
-#include <QDebug>
#include "VisualGroup.h"
+#include "ui/themes/ThemeManager.h"
#include <Application.h>
#include <InstanceList.h>
@@ -86,16 +86,18 @@ void InstanceView::setModel(QAbstractItemModel* model)
connect(model, &QAbstractItemModel::rowsRemoved, this, &InstanceView::rowsRemoved);
}
-void InstanceView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
+void InstanceView::dataChanged([[maybe_unused]] const QModelIndex& topLeft,
+ [[maybe_unused]] const QModelIndex& bottomRight,
+ [[maybe_unused]] const QVector<int>& roles)
{
scheduleDelayedItemsLayout();
}
-void InstanceView::rowsInserted(const QModelIndex& parent, int start, int end)
+void InstanceView::rowsInserted([[maybe_unused]] const QModelIndex& parent, [[maybe_unused]] int start, [[maybe_unused]] int end)
{
scheduleDelayedItemsLayout();
}
-void InstanceView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
+void InstanceView::rowsAboutToBeRemoved([[maybe_unused]] const QModelIndex& parent, [[maybe_unused]] int start, [[maybe_unused]] int end)
{
scheduleDelayedItemsLayout();
}
@@ -239,9 +241,9 @@ QString InstanceView::groupNameAt(const QPoint& point)
{
executeDelayedItemsLayout();
- VisualGroup::HitResults hitresult;
- auto group = categoryAt(point + offset(), hitresult);
- if (group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) {
+ VisualGroup::HitResults hitResult;
+ auto group = categoryAt(point + offset(), hitResult);
+ if (group && (hitResult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) {
return group->text;
}
return QString();
@@ -275,9 +277,9 @@ void InstanceView::mousePressEvent(QMouseEvent* event)
m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
m_pressedPosition = geometryPos;
- VisualGroup::HitResults hitresult;
- m_pressedCategory = categoryAt(geometryPos, hitresult);
- if (m_pressedCategory && hitresult & VisualGroup::CheckboxHit) {
+ VisualGroup::HitResults hitResult;
+ m_pressedCategory = categoryAt(geometryPos, hitResult);
+ if (m_pressedCategory && hitResult & VisualGroup::CheckboxHit) {
setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState);
event->accept();
return;
@@ -361,10 +363,10 @@ void InstanceView::mouseReleaseEvent(QMouseEvent* event)
QPoint geometryPos = event->pos() + offset();
QPersistentModelIndex index = indexAt(visualPos);
- VisualGroup::HitResults hitresult;
+ VisualGroup::HitResults hitResult;
bool click =
- (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos, hitresult));
+ (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(geometryPos, hitResult));
if (click && m_pressedCategory) {
if (state() == ExpandingState) {
@@ -444,12 +446,12 @@ void InstanceView::setPaintCat(bool visible)
{
m_catVisible = visible;
if (visible)
- m_catPixmap.load(APPLICATION->getCatPack());
+ m_catPixmap.load(APPLICATION->themeManager()->getCatPack());
else
m_catPixmap = QPixmap();
}
-void InstanceView::paintEvent(QPaintEvent* event)
+void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
{
executeDelayedItemsLayout();
@@ -545,7 +547,7 @@ void InstanceView::paintEvent(QPaintEvent* event)
#endif
}
-void InstanceView::resizeEvent(QResizeEvent* event)
+void InstanceView::resizeEvent([[maybe_unused]] QResizeEvent* event)
{
int newItemsPerRow = calculateItemsPerRow();
if (newItemsPerRow != m_currentItemsPerRow) {
@@ -581,7 +583,7 @@ void InstanceView::dragMoveEvent(QDragMoveEvent* event)
event->accept();
}
-void InstanceView::dragLeaveEvent(QDragLeaveEvent* event)
+void InstanceView::dragLeaveEvent([[maybe_unused]] QDragLeaveEvent* event)
{
executeDelayedItemsLayout();
@@ -604,9 +606,9 @@ void InstanceView::dropEvent(QDropEvent* event)
if (event->possibleActions() & Qt::MoveAction) {
std::pair<VisualGroup*, VisualGroup::HitResults> dropPos = rowDropPos(event->pos());
const VisualGroup* group = dropPos.first;
- auto hitresult = dropPos.second;
+ auto hitResult = dropPos.second;
- if (hitresult == VisualGroup::HitResult::NoHit) {
+ if (hitResult == VisualGroup::HitResult::NoHit) {
viewport()->update();
return;
}
@@ -643,15 +645,15 @@ void InstanceView::startDrag(Qt::DropActions supportedActions)
if (indexes.count() == 0)
return;
- QMimeData* data = model()->mimeData(indexes);
- if (!data) {
+ QMimeData* mimeData = model()->mimeData(indexes);
+ if (!mimeData) {
return;
}
QRect rect;
QPixmap pixmap = renderToPixmap(indexes, &rect);
QDrag* drag = new QDrag(this);
drag->setPixmap(pixmap);
- drag->setMimeData(data);
+ drag->setMimeData(mimeData);
drag->setHotSpot(m_pressedPosition - rect.topLeft());
Qt::DropAction defaultDropAction = Qt::IgnoreAction;
if (this->defaultDropAction() != Qt::IgnoreAction && (supportedActions & this->defaultDropAction())) {
@@ -767,16 +769,16 @@ QList<std::pair<QRect, QModelIndex>> InstanceView::draggablePaintPairs(const QMo
return ret;
}
-bool InstanceView::isDragEventAccepted(QDropEvent* event)
+bool InstanceView::isDragEventAccepted([[maybe_unused]] QDropEvent* event)
{
return true;
}
std::pair<VisualGroup*, VisualGroup::HitResults> InstanceView::rowDropPos(const QPoint& pos)
{
- VisualGroup::HitResults hitresult;
- auto group = categoryAt(pos + offset(), hitresult);
- return std::make_pair(group, hitresult);
+ VisualGroup::HitResults hitResult;
+ auto group = categoryAt(pos + offset(), hitResult);
+ return std::make_pair(group, hitResult);
}
QPoint InstanceView::offset() const
@@ -802,7 +804,7 @@ QRegion InstanceView::visualRegionForSelection(const QItemSelection& selection)
return region;
}
-QModelIndex InstanceView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
+QModelIndex InstanceView::moveCursor(QAbstractItemView::CursorAction cursorAction, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
{
auto current = currentIndex();
if (!current.isValid()) {
@@ -822,20 +824,20 @@ QModelIndex InstanceView::moveCursor(QAbstractItemView::CursorAction cursorActio
switch (cursorAction) {
case MoveUp: {
if (row == 0) {
- int prevgroupindex = group_index - 1;
- while (prevgroupindex >= 0) {
- auto prevgroup = m_groups[prevgroupindex];
- if (prevgroup->collapsed) {
- prevgroupindex--;
+ int prevGroupIndex = group_index - 1;
+ while (prevGroupIndex >= 0) {
+ auto prevGroup = m_groups[prevGroupIndex];
+ if (prevGroup->collapsed) {
+ prevGroupIndex--;
continue;
}
- int newRow = prevgroup->numRows() - 1;
- int newRowSize = prevgroup->rows[newRow].size();
+ int newRow = prevGroup->numRows() - 1;
+ int newRowSize = prevGroup->rows[newRow].size();
int newColumn = m_currentCursorColumn;
if (m_currentCursorColumn >= newRowSize) {
newColumn = newRowSize - 1;
}
- return prevgroup->rows[newRow][newColumn];
+ return prevGroup->rows[newRow][newColumn];
}
} else {
int newRow = row - 1;
@@ -850,19 +852,19 @@ QModelIndex InstanceView::moveCursor(QAbstractItemView::CursorAction cursorActio
}
case MoveDown: {
if (row == cat->rows.size() - 1) {
- int nextgroupindex = group_index + 1;
- while (nextgroupindex < m_groups.size()) {
- auto nextgroup = m_groups[nextgroupindex];
- if (nextgroup->collapsed) {
- nextgroupindex++;
+ int nextGroupIndex = group_index + 1;
+ while (nextGroupIndex < m_groups.size()) {
+ auto nextGroup = m_groups[nextGroupIndex];
+ if (nextGroup->collapsed) {
+ nextGroupIndex++;
continue;
}
- int newRowSize = nextgroup->rows[0].size();
+ int newRowSize = nextGroup->rows[0].size();
int newColumn = m_currentCursorColumn;
if (m_currentCursorColumn >= newRowSize) {
newColumn = newRowSize - 1;
}
- return nextgroup->rows[0][newColumn];
+ return nextGroup->rows[0][newColumn];
}
} else {
int newRow = row + 1;
@@ -936,7 +938,7 @@ void InstanceView::scrollTo(const QModelIndex& index, ScrollHint hint)
verticalScrollBar()->setValue(verticalScrollToValue(index, rect, hint));
}
-int InstanceView::verticalScrollToValue(const QModelIndex& index, const QRect& rect, QListView::ScrollHint hint) const
+int InstanceView::verticalScrollToValue([[maybe_unused]] const QModelIndex& index, const QRect& rect, QListView::ScrollHint hint) const
{
const QRect area = viewport()->rect();
const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top());
diff --git a/launcher/ui/pages/BasePageContainer.h b/launcher/ui/pages/BasePageContainer.h
index b750e827..a497ef7b 100644
--- a/launcher/ui/pages/BasePageContainer.h
+++ b/launcher/ui/pages/BasePageContainer.h
@@ -6,6 +6,7 @@ class BasePageContainer {
public:
virtual ~BasePageContainer(){};
virtual bool selectPage(QString pageId) = 0;
+ virtual BasePage* selectedPage() const = 0;
virtual BasePage* getPage(QString pageId) { return nullptr; };
virtual void refreshContainer() = 0;
virtual bool requestClose() = 0;
diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp
index 1d5ecb8d..c95bfabd 100644
--- a/launcher/ui/pages/global/AccountListPage.cpp
+++ b/launcher/ui/pages/global/AccountListPage.cpp
@@ -64,7 +64,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
ui->setupUi(this);
ui->listView->setEmptyString(
tr("Welcome!\n"
- "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account."));
+ "If you're new here, you can select the \"Add Microsoft\" or \"Add Mojang\" buttons to link your Microsoft and/or Mojang "
+ "accounts."));
ui->listView->setEmptyMode(VersionListView::String);
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -83,8 +84,10 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
QItemSelectionModel* selectionModel = ui->listView->selectionModel();
connect(selectionModel, &QItemSelectionModel::selectionChanged,
- [this](const QItemSelection& sel, const QItemSelection& dsel) { updateButtonStates(); });
+ [this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); });
connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu);
+ connect(ui->listView, &VersionListView::activated, this,
+ [this](const QModelIndex& index) { m_accounts->setDefaultAccount(m_accounts->at(index.row())); });
connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged);
connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged);
diff --git a/launcher/ui/pages/global/ExternalToolsPage.ui b/launcher/ui/pages/global/ExternalToolsPage.ui
index 3643094d..47c77842 100644
--- a/launcher/ui/pages/global/ExternalToolsPage.ui
+++ b/launcher/ui/pages/global/ExternalToolsPage.ui
@@ -47,7 +47,7 @@
<item>
<widget class="QPushButton" name="jprofilerPathBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -84,7 +84,7 @@
<item>
<widget class="QPushButton" name="jvisualvmPathBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -121,7 +121,7 @@
<item>
<widget class="QPushButton" name="mceditPathBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -166,7 +166,7 @@
<item row="0" column="2">
<widget class="QToolButton" name="jsonEditorBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp
index a9ede8ed..ac50319e 100644
--- a/launcher/ui/pages/global/JavaPage.cpp
+++ b/launcher/ui/pages/global/JavaPage.cpp
@@ -166,7 +166,7 @@ void JavaPage::on_javaTestBtn_clicked()
checker->run();
}
-void JavaPage::on_maxMemSpinBox_valueChanged(int i)
+void JavaPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i)
{
updateThresholds();
}
@@ -185,6 +185,7 @@ void JavaPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
+ unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@@ -194,6 +195,9 @@ void JavaPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (maxMem < minMem) {
+ iconName = "status-yellow";
+ ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui
index 561cf79b..5a547637 100644
--- a/launcher/ui/pages/global/JavaPage.ui
+++ b/launcher/ui/pages/global/JavaPage.ui
@@ -160,37 +160,73 @@
<string>Java Runtime</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
- <item row="3" column="1">
- <widget class="QPushButton" name="javaDetectBtn">
+ <item row="7" column="0" colspan="3">
+ <widget class="QPlainTextEdit" name="jvmArgsTextBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="text">
- <string>&amp;Auto-detect...</string>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>100</height>
+ </size>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="labelJVMArgs">
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="text">
- <string>JVM arguments:</string>
+ <property name="toolTip">
+ <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ <property name="text">
+ <string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QLabel" name="labelJavaPath">
+ <item row="2" column="0" colspan="3">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="javaDetectBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Auto-detect...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="javaTestBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Test</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -198,69 +234,50 @@
</sizepolicy>
</property>
<property name="text">
- <string>&amp;Java path:</string>
- </property>
- <property name="buddy">
- <cstring>javaPathTextBox</cstring>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QPushButton" name="javaTestBtn">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <string>JVM arguments:</string>
</property>
- <property name="text">
- <string>&amp;Test</string>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
- <item row="0" column="1" colspan="2">
+ <item row="1" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
+ <widget class="QLabel" name="labelJavaPath">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Java path:</string>
+ </property>
+ <property name="buddy">
+ <cstring>javaPathTextBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="javaBrowseBtn">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="maximumSize">
- <size>
- <width>28</width>
- <height>16777215</height>
- </size>
- </property>
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
- <item row="4" column="1">
- <widget class="QCheckBox" name="skipCompatibilityCheckbox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
- </property>
- <property name="text">
- <string>&amp;Skip Java compatibility checks</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
+ <item row="5" column="0">
<widget class="QCheckBox" name="skipJavaWizardCheckbox">
<property name="toolTip">
<string>If enabled, the launcher will not prompt you to choose a Java version if one isn't found.</string>
@@ -270,25 +287,6 @@
</property>
</widget>
</item>
- <item row="2" column="1" colspan="2">
- <widget class="QPlainTextEdit" name="jvmArgsTextBox">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>100</height>
- </size>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
@@ -317,8 +315,6 @@
<tabstop>permGenSpinBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaPathTextBox</tabstop>
- <tabstop>javaDetectBtn</tabstop>
- <tabstop>javaTestBtn</tabstop>
<tabstop>tabWidget</tabstop>
</tabstops>
<resources/>
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 26408f44..bc259a9b 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -99,7 +99,7 @@
<item row="3" column="2">
<widget class="QToolButton" name="downloadsDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -109,7 +109,7 @@
<item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -126,14 +126,14 @@
<item row="0" column="2">
<widget class="QToolButton" name="instDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="iconsDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index 866a4121..553cefd8 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -2,7 +2,6 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,6 +34,7 @@
*/
#include "MinecraftPage.h"
+#include "BuildConfig.h"
#include "ui_MinecraftPage.h"
#include <QDir>
@@ -44,9 +44,15 @@
#include "Application.h"
#include "settings/SettingsObject.h"
+#ifdef Q_OS_LINUX
+#include "MangoHud.h"
+#endif
+
MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage)
{
ui->setupUi(this);
+ connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeGLFWChanged);
+ connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeOpenALChanged);
loadSettings();
updateCheckboxStuff();
}
@@ -74,6 +80,16 @@ void MinecraftPage::on_maximizedCheckBox_clicked(bool checked)
updateCheckboxStuff();
}
+void MinecraftPage::onUseNativeGLFWChanged(bool checked)
+{
+ ui->lineEditGLFWPath->setEnabled(checked);
+}
+
+void MinecraftPage::onUseNativeOpenALChanged(bool checked)
+{
+ ui->lineEditOpenALPath->setEnabled(checked);
+}
+
void MinecraftPage::applySettings()
{
auto s = APPLICATION->settings();
@@ -84,8 +100,10 @@ void MinecraftPage::applySettings()
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
// Native library workarounds
- s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
+ s->set("CustomGLFWPath", ui->lineEditGLFWPath->text());
+ s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
+ s->set("CustomOpenALPath", ui->lineEditOpenALPath->text());
// Peformance related options
s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
@@ -96,13 +114,11 @@ void MinecraftPage::applySettings()
s->set("ShowGameTime", ui->showGameTime->isChecked());
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
s->set("RecordGameTime", ui->recordGameTime->isChecked());
+ s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked());
// Miscellaneous
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
-
- // Mod loader settings
- s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
}
void MinecraftPage::loadSettings()
@@ -114,8 +130,20 @@ void MinecraftPage::loadSettings()
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
- ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool());
+ ui->lineEditGLFWPath->setText(s->get("CustomGLFWPath").toString());
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME));
+#ifdef Q_OS_LINUX
+ if (!APPLICATION->m_detectedGLFWPath.isEmpty())
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedGLFWPath));
+#endif
+ ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
+ ui->lineEditOpenALPath->setText(s->get("CustomOpenALPath").toString());
+ ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
+#ifdef Q_OS_LINUX
+ if (!APPLICATION->m_detectedOpenALPath.isEmpty())
+ ui->lineEditOpenALPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedOpenALPath));
+#endif
ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());
ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool());
@@ -138,11 +166,10 @@ void MinecraftPage::loadSettings()
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
+ ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool());
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
-
- ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool());
}
void MinecraftPage::retranslate()
diff --git a/launcher/ui/pages/global/MinecraftPage.h b/launcher/ui/pages/global/MinecraftPage.h
index 28c31b5d..5facfbb3 100644
--- a/launcher/ui/pages/global/MinecraftPage.h
+++ b/launcher/ui/pages/global/MinecraftPage.h
@@ -70,6 +70,9 @@ class MinecraftPage : public QWidget, public BasePage {
private slots:
void on_maximizedCheckBox_clicked(bool checked);
+ void onUseNativeGLFWChanged(bool checked);
+ void onUseNativeOpenALChanged(bool checked);
+
private:
Ui::MinecraftPage* ui;
};
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 393b0f35..b5cfa659 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -138,6 +138,13 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="showGameTimeWithoutDays">
+ <property name="text">
+ <string>Show time spent playing in hours</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -191,44 +198,59 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
- <widget class="QGroupBox" name="modLoaderSettingsGroupBox">
- <property name="title">
- <string>Mod loader settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_13">
- <item>
- <widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
- <property name="text">
- <string>Disable Quilt Loader Beacon</string>
- </property>
- <property name="toolTip">
- <string>Disable Quilt loader's beacon for counting monthly active users</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title">
<string>Native library workarounds</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_11">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
<string>Use system installation of &amp;GLFW</string>
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelGLFWPath">
+ <property name="text">
+ <string>&amp;GLFW library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditGLFWPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
<widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
<string>Use system installation of &amp;OpenAL</string>
</property>
</widget>
</item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelOpenALPath">
+ <property name="text">
+ <string>&amp;OpenAL library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditOpenALPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lineEditGLFWPath">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="lineEditOpenALPath">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/launcher/ui/pages/global/ProxyPage.cpp b/launcher/ui/pages/global/ProxyPage.cpp
index 19b2bcea..9caffcb3 100644
--- a/launcher/ui/pages/global/ProxyPage.cpp
+++ b/launcher/ui/pages/global/ProxyPage.cpp
@@ -71,7 +71,7 @@ void ProxyPage::updateCheckboxStuff()
ui->proxyAuthBox->setEnabled(enableEditing);
}
-void ProxyPage::proxyGroupChanged(QAbstractButton* button)
+void ProxyPage::proxyGroupChanged([[maybe_unused]] QAbstractButton* button)
{
updateCheckboxStuff();
}
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index 12038f88..1a8fafa9 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -152,6 +152,7 @@ void ExternalResourcesPage::retranslate()
void ExternalResourcesPage::itemActivated(const QModelIndex&)
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
}
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
@@ -305,7 +306,7 @@ bool ExternalResourcesPage::current(const QModelIndex& current, const QModelInde
return onSelectionChanged(current, previous);
}
-bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
+bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index 3c836691..ba703f77 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -168,6 +168,17 @@
<string>Go to mods home page</string>
</property>
</action>
+ <action name="actionRemoveItemMetadata">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove metadata</string>
+ </property>
+ <property name="toolTip">
+ <string>Remove mod's metadata</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index 687b82d7..7aa6bd32 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -3,7 +3,6 @@
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -48,6 +47,7 @@
#include "ui/widgets/CustomCommands.h"
#include "Application.h"
+#include "BuildConfig.h"
#include "JavaCommon.h"
#include "minecraft/auth/AccountList.h"
@@ -66,6 +66,10 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent)
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&InstanceSettingsPage::changeInstanceAccount);
+
+ connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged);
+ connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged);
+
loadSettings();
updateThresholds();
@@ -198,11 +202,15 @@ void InstanceSettingsPage::applySettings()
bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();
m_settings->set("OverrideNativeWorkarounds", workarounds);
if (workarounds) {
- m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
+ m_settings->set("CustomGLFWPath", ui->lineEditGLFWPath->text());
+ m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
+ m_settings->set("CustomOpenALPath", ui->lineEditOpenALPath->text());
} else {
- m_settings->reset("UseNativeOpenAL");
m_settings->reset("UseNativeGLFW");
+ m_settings->reset("CustomGLFWPath");
+ m_settings->reset("UseNativeOpenAL");
+ m_settings->reset("CustomOpenALPath");
}
// Performance
@@ -245,14 +253,6 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("InstanceAccountId");
}
- bool overrideModLoaderSettings = ui->modLoaderSettingsGroupBox->isChecked();
- m_settings->set("OverrideModLoaderSettings", overrideModLoaderSettings);
- if (overrideModLoaderSettings) {
- m_settings->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
- } else {
- m_settings->reset("DisableQuiltBeacon");
- }
-
// FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext();
}
@@ -312,7 +312,19 @@ void InstanceSettingsPage::loadSettings()
// Workarounds
ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());
ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());
+ ui->lineEditGLFWPath->setText(m_settings->get("CustomGLFWPath").toString());
+#ifdef Q_OS_LINUX
+ ui->lineEditGLFWPath->setPlaceholderText(APPLICATION->m_detectedGLFWPath);
+#else
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME));
+#endif
ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool());
+ ui->lineEditOpenALPath->setText(m_settings->get("CustomOpenALPath").toString());
+#ifdef Q_OS_LINUX
+ ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath);
+#else
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
+#endif
// Performance
ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool());
@@ -344,10 +356,6 @@ void InstanceSettingsPage::loadSettings()
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
updateAccountsMenu();
-
- // Mod loader specific settings
- ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool());
- ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool());
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()
@@ -408,6 +416,16 @@ void InstanceSettingsPage::on_javaTestBtn_clicked()
checker->run();
}
+void InstanceSettingsPage::onUseNativeGLFWChanged(bool checked)
+{
+ ui->lineEditGLFWPath->setEnabled(checked);
+}
+
+void InstanceSettingsPage::onUseNativeOpenALChanged(bool checked)
+{
+ ui->lineEditOpenALPath->setEnabled(checked);
+}
+
void InstanceSettingsPage::updateAccountsMenu()
{
ui->instanceAccountSelector->clear();
@@ -440,7 +458,7 @@ void InstanceSettingsPage::changeInstanceAccount(int index)
}
}
-void InstanceSettingsPage::on_maxMemSpinBox_valueChanged(int i)
+void InstanceSettingsPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i)
{
updateThresholds();
}
@@ -460,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
+ unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@@ -469,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (maxMem < minMem) {
+ iconName = "status-yellow";
+ ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h
index 21ecbaf8..8b78dcb7 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.h
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.h
@@ -71,6 +71,9 @@ class InstanceSettingsPage : public QWidget, public BasePage {
void on_javaBrowseBtn_clicked();
void on_maxMemSpinBox_valueChanged(int i);
+ void onUseNativeGLFWChanged(bool checked);
+ void onUseNativeOpenALChanged(bool checked);
+
void applySettings();
void loadSettings();
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index 245433fe..81cf7093 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -61,31 +61,7 @@
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0" colspan="3">
- <widget class="QLineEdit" name="javaPathTextBox"/>
- </item>
- <item row="1" column="0">
- <widget class="QPushButton" name="javaDetectBtn">
- <property name="text">
- <string>Auto-detect...</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QPushButton" name="javaBrowseBtn">
- <property name="text">
- <string>Browse...</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QPushButton" name="javaTestBtn">
- <property name="text">
- <string>Test</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
+ <item row="4" column="0">
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
@@ -95,6 +71,38 @@
</property>
</widget>
</item>
+ <item row="1" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="javaPathTextBox"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="javaBrowseBtn">
+ <property name="text">
+ <string>Browse</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="javaDetectBtn">
+ <property name="text">
+ <string>Auto-detect...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="javaTestBtn">
+ <property name="text">
+ <string>Test</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
@@ -435,18 +443,52 @@
<property name="checked">
<bool>false</bool>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_7">
- <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <property name="text">
+ <string>Use system installation of OpenAL</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelGLFWPath">
+ <property name="text">
+ <string>&amp;GLFW library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditGLFWPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
<string>Use system installation of GLFW</string>
</property>
</widget>
</item>
- <item>
- <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lineEditGLFWPath">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelOpenALPath">
<property name="text">
- <string>Use system installation of OpenAL</string>
+ <string>&amp;OpenAL library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditOpenALPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="lineEditOpenALPath">
+ <property name="enabled">
+ <bool>false</bool>
</property>
</widget>
</item>
@@ -542,31 +584,6 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
- <widget class="QGroupBox" name="modLoaderSettingsGroupBox">
- <property name="checkable">
- <bool>true</bool>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- <property name="title">
- <string>Mod loader settings</string>
- </property>
- <layout class="QVBoxLayout" name="VerticalLayout_16">
- <item>
- <widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
- <property name="text">
- <string>Disable Quilt Loader Beacon</string>
- </property>
- <property name="toolTip">
- <string>Disable Quilt loader's beacon for counting monthly active users</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="gameTimeGroupBox">
<property name="enabled">
<bool>true</bool>
@@ -699,10 +716,6 @@
<tabstop>openGlobalJavaSettingsButton</tabstop>
<tabstop>settingsTabs</tabstop>
<tabstop>javaSettingsGroupBox</tabstop>
- <tabstop>javaPathTextBox</tabstop>
- <tabstop>javaDetectBtn</tabstop>
- <tabstop>javaBrowseBtn</tabstop>
- <tabstop>javaTestBtn</tabstop>
<tabstop>memoryGroupBox</tabstop>
<tabstop>minMemSpinBox</tabstop>
<tabstop>maxMemSpinBox</tabstop>
diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp
index 0443baf1..8fdaf065 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.cpp
+++ b/launcher/ui/pages/instance/ManagedPackPage.cpp
@@ -26,6 +26,8 @@
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
+#include "net/ApiDownload.h"
+
/** This is just to override the combo box popup behavior so that the combo box doesn't take the whole screen.
* ... thanks Qt.
*/
@@ -242,7 +244,7 @@ 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));
+ Net::ApiDownload::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{};
@@ -392,7 +394,7 @@ void FlameManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID();
- m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
+ m_fetch_job->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{};
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index cef292bd..b42bbf46 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -92,6 +92,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
+ ui->actionsToolbar->insertActionAfter(ui->actionRemoveItem, ui->actionRemoveItemMetadata);
+ connect(ui->actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
+
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
@@ -104,11 +108,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
if (selected <= 1) {
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
} else {
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mods' metadata"));
}
ui->actionVisitItemPage->setEnabled(selected != 0);
+ ui->actionRemoveItemMetadata->setEnabled(selected != 0);
});
connect(mods.get(), &ModFolderModel::rowsInserted, this,
@@ -127,7 +136,7 @@ bool ModFolderPage::shouldDisplay() const
return true;
}
-bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
+bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
@@ -297,3 +306,24 @@ void ModFolderPage::visitModPages()
DesktopServices::openUrl(url);
}
}
+
+void ModFolderPage::deleteModMetadata()
+{
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
+ auto selectionCount = m_model->selectedMods(selection).length();
+ if (selectionCount == 0)
+ return;
+ if (selectionCount > 1) {
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
+ tr("You are about to remove the metadata for %1 mods.\n"
+ "Are you sure?")
+ .arg(selectionCount),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+ }
+
+ m_model->deleteModsMetadata(selection);
+}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index a23dcae1..0c654d0d 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -61,6 +61,7 @@ class ModFolderPage : public ExternalResourcesPage {
private slots:
void removeItems(const QItemSelection& selection) override;
+ void deleteModMetadata();
void installMods();
void updateMods();
diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp
index b80c08e1..ab5d9828 100644
--- a/launcher/ui/pages/instance/OtherLogsPage.cpp
+++ b/launcher/ui/pages/instance/OtherLogsPage.cpp
@@ -246,20 +246,20 @@ void OtherLogsPage::on_btnClean_clicked()
}
}
if (!failed.empty()) {
- QMessageBox* messageBox = new QMessageBox(this);
- messageBox->setWindowTitle(tr("Error"));
+ QMessageBox* messageBoxFailure = new QMessageBox(this);
+ messageBoxFailure->setWindowTitle(tr("Error"));
if (failed.size() > 5) {
- messageBox->setText(tr("Couldn't delete some files!"));
- messageBox->setDetailedText(failed.join('\n'));
+ messageBoxFailure->setText(tr("Couldn't delete some files!"));
+ messageBoxFailure->setDetailedText(failed.join('\n'));
} else {
- messageBox->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n')));
+ messageBoxFailure->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n')));
}
- messageBox->setStandardButtons(QMessageBox::Ok);
- messageBox->setDefaultButton(QMessageBox::Ok);
- messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
- messageBox->setIcon(QMessageBox::Critical);
- messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
- messageBox->exec();
+ messageBoxFailure->setStandardButtons(QMessageBox::Ok);
+ messageBoxFailure->setDefaultButton(QMessageBox::Ok);
+ messageBoxFailure->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ messageBoxFailure->setIcon(QMessageBox::Critical);
+ messageBoxFailure->setTextInteractionFlags(Qt::TextBrowserInteraction);
+ messageBoxFailure->exec();
}
}
diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp
index 12b371df..26c14ca4 100644
--- a/launcher/ui/pages/instance/ResourcePackPage.cpp
+++ b/launcher/ui/pages/instance/ResourcePackPage.cpp
@@ -55,7 +55,7 @@ ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr<
ui->actionViewConfigs->setVisible(false);
}
-bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
+bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index 07daca21..2142e6c9 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -3,7 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -354,14 +354,8 @@ class ServersModel : public QAbstractListModel {
}
}
- virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override
- {
- return parent.isValid() ? 0 : m_servers.size();
- }
- int columnCount(const QModelIndex& parent) const override
- {
- return parent.isValid() ? 0 : COLUMN_COUNT;
- }
+ virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : m_servers.size(); }
+ int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : COLUMN_COUNT; }
Server* at(int index)
{
@@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {
qDebug() << "Changed:" << path;
load();
}
- void fileChanged(const QString& path)
- {
- qDebug() << "Changed:" << path;
- }
+ void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }
private slots:
void save_internal()
@@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {
m_saveTimer.stop();
}
- bool saveIsScheduled() const
- {
- return m_dirty;
- }
+ bool saveIsScheduled() const { return m_dirty; }
void updateFSObserver()
{
@@ -607,7 +595,7 @@ void ServersPage::runningStateChanged(bool running)
updateState();
}
-void ServersPage::currentChanged(const QModelIndex& current, const QModelIndex& previous)
+void ServersPage::currentChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
int nextServer = -1;
if (!current.isValid()) {
@@ -620,7 +608,7 @@ void ServersPage::currentChanged(const QModelIndex& current, const QModelIndex&
}
// WARNING: this is here because currentChanged is not accurate when removing rows. the current item needs to be fixed up after removal.
-void ServersPage::rowsRemoved(const QModelIndex& parent, int first, int last)
+void ServersPage::rowsRemoved([[maybe_unused]] const QModelIndex& parent, int first, int last)
{
if (currentServer < first) {
// current was before the removal
@@ -743,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered()
void ServersPage::on_actionJoin_triggered()
{
const auto& address = m_model->at(currentServer)->m_address;
- APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
+ APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
}
#include "ServersPage.moc"
diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp
index e477ceda..fa478a9a 100644
--- a/launcher/ui/pages/instance/TexturePackPage.cpp
+++ b/launcher/ui/pages/instance/TexturePackPage.cpp
@@ -57,7 +57,7 @@ TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr<Te
ui->actionViewConfigs->setVisible(false);
}
-bool TexturePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
+bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index a180c804..e22c764c 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -6,7 +6,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022-2023 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -51,6 +51,7 @@
#include <QUrl>
#include "VersionPage.h"
+#include "ui/dialogs/InstallLoaderDialog.h"
#include "ui_VersionPage.h"
#include "ui/dialogs/CustomMessageBox.h"
@@ -165,14 +166,17 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel();
+ connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
-
connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
updateVersionControls();
preselect(0);
connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
+ connect(ui->packageView, &QAbstractItemView::activated, this, [this](const QModelIndex& index) {
+ auto component = m_profile->getComponent(index.row());
+ component->setEnabled(!component->isEnabled());
+ });
connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
}
@@ -188,7 +192,7 @@ void VersionPage::showContextMenu(const QPoint& pos)
delete menu;
}
-void VersionPage::packageCurrent(const QModelIndex& current, const QModelIndex& previous)
+void VersionPage::packageCurrent(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
if (!current.isValid()) {
ui->frame->clear();
@@ -226,18 +230,6 @@ void VersionPage::packageCurrent(const QModelIndex& current, const QModelIndex&
void VersionPage::updateVersionControls()
{
- // FIXME: this is a dirty hack
- auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft"));
-
- bool supportsFabric = minecraftVersion >= Version("1.14");
- ui->actionInstall_Fabric->setEnabled(supportsFabric);
-
- bool supportsQuilt = minecraftVersion >= Version("1.14");
- ui->actionInstall_Quilt->setEnabled(supportsQuilt);
-
- bool supportsLiteLoader = minecraftVersion <= Version("1.12.2");
- ui->actionInstall_LiteLoader->setEnabled(supportsLiteLoader);
-
updateButtons();
}
@@ -389,20 +381,14 @@ void VersionPage::on_actionChange_version_triggered()
return;
}
auto uid = list->uid();
- // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata...
- if (uid == "net.minecraftforge") {
- on_actionInstall_Forge_triggered();
- return;
- } 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") {
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"));
}
+ vselect.setExactIfPresentFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
+
auto currentVersion = patch->getVersion();
if (!currentVersion.isEmpty()) {
vselect.setCurrentVersion(currentVersion);
@@ -425,7 +411,7 @@ 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."),
+ "one account added.\nPlease add your Microsoft or Mojang account."),
QMessageBox::Warning)
->show();
return;
@@ -443,79 +429,11 @@ void VersionPage::on_actionDownload_All_triggered()
m_container->refreshContainer();
}
-void VersionPage::on_actionInstall_Forge_triggered()
-{
- auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge");
- 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.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!"));
-
- auto currentVersion = m_profile->getComponentVersion("net.minecraftforge");
- if (!currentVersion.isEmpty()) {
- vselect.setCurrentVersion(currentVersion);
- }
-
- 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);
- m_container->refreshContainer();
- }
-}
-
-void VersionPage::on_actionInstall_Fabric_triggered()
-{
- auto vlist = APPLICATION->metadataIndex()->get("net.fabricmc.fabric-loader");
- if (!vlist) {
- return;
- }
- VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this);
- vselect.setEmptyString(tr("No Fabric Loader versions are currently available."));
- 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()) {
- vselect.setCurrentVersion(currentVersion);
- }
-
- 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);
- m_container->refreshContainer();
- }
-}
-
-void VersionPage::on_actionInstall_Quilt_triggered()
+void VersionPage::on_actionInstall_Loader_triggered()
{
- auto vlist = APPLICATION->metadataIndex()->get("org.quiltmc.quilt-loader");
- if (!vlist) {
- return;
- }
- VersionSelectDialog vselect(vlist.get(), tr("Select Quilt Loader version"), this);
- vselect.setEmptyString(tr("No Quilt Loader versions are currently available."));
- 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()) {
- vselect.setCurrentVersion(currentVersion);
- }
-
- 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);
- m_container->refreshContainer();
- }
+ InstallLoaderDialog dialog(m_inst->getPackProfile(), QString(), this);
+ dialog.exec();
+ m_container->refreshContainer();
}
void VersionPage::on_actionAdd_Empty_triggered()
@@ -534,33 +452,6 @@ void VersionPage::on_actionAdd_Empty_triggered()
}
}
-void VersionPage::on_actionInstall_LiteLoader_triggered()
-{
- auto vlist = APPLICATION->metadataIndex()->get("com.mumfrey.liteloader");
- 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.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!"));
-
- auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader");
- if (!currentVersion.isEmpty()) {
- vselect.setCurrentVersion(currentVersion);
- }
-
- 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);
- m_container->refreshContainer();
- }
-}
-
void VersionPage::on_actionLibrariesFolder_triggered()
{
DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true);
@@ -571,7 +462,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, [[maybe_unused]] const QModelIndex& previous)
{
currentIdx = current.row();
updateButtons(currentIdx);
diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h
index 45d383f4..6915be88 100644
--- a/launcher/ui/pages/instance/VersionPage.h
+++ b/launcher/ui/pages/instance/VersionPage.h
@@ -68,11 +68,8 @@ class VersionPage : public QMainWindow, public BasePage {
private slots:
void on_actionChange_version_triggered();
- void on_actionInstall_Forge_triggered();
- void on_actionInstall_Fabric_triggered();
- void on_actionInstall_Quilt_triggered();
+ void on_actionInstall_Loader_triggered();
void on_actionAdd_Empty_triggered();
- void on_actionInstall_LiteLoader_triggered();
void on_actionReload_triggered();
void on_actionRemove_triggered();
void on_actionMove_up_triggered();
diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui
index a73c42d6..9be21d49 100644
--- a/launcher/ui/pages/instance/VersionPage.ui
+++ b/launcher/ui/pages/instance/VersionPage.ui
@@ -98,11 +98,7 @@
<addaction name="actionEdit"/>
<addaction name="actionRevert"/>
<addaction name="separator"/>
- <addaction name="actionInstall_Forge"/>
- <addaction name="actionInstall_Fabric"/>
- <addaction name="actionInstall_Quilt"/>
- <addaction name="actionInstall_LiteLoader"/>
- <addaction name="separator"/>
+ <addaction name="actionInstall_Loader"/>
<addaction name="actionAdd_to_Minecraft_jar"/>
<addaction name="actionReplace_Minecraft_jar"/>
<addaction name="actionAdd_Agents"/>
@@ -116,26 +112,26 @@
</widget>
<action name="actionChange_version">
<property name="text">
- <string>Change version</string>
+ <string>Change Version</string>
</property>
<property name="toolTip">
- <string>Change version of the selected package.</string>
+ <string>Change version of the selected component.</string>
</property>
</action>
<action name="actionMove_up">
<property name="text">
- <string>Move up</string>
+ <string>Move Up</string>
</property>
<property name="toolTip">
- <string>Make the selected package apply sooner.</string>
+ <string>Make the selected component apply sooner.</string>
</property>
</action>
<action name="actionMove_down">
<property name="text">
- <string>Move down</string>
+ <string>Move Down</string>
</property>
<property name="toolTip">
- <string>Make the selected package apply later.</string>
+ <string>Make the selected component apply later.</string>
</property>
</action>
<action name="actionRemove">
@@ -143,7 +139,7 @@
<string>Remove</string>
</property>
<property name="toolTip">
- <string>Remove selected package from the instance.</string>
+ <string>Remove selected component from the instance.</string>
</property>
</action>
<action name="actionCustomize">
@@ -151,7 +147,7 @@
<string>Customize</string>
</property>
<property name="toolTip">
- <string>Customize selected package.</string>
+ <string>Customize selected component.</string>
</property>
</action>
<action name="actionEdit">
@@ -159,7 +155,7 @@
<string>Edit</string>
</property>
<property name="toolTip">
- <string>Edit selected package.</string>
+ <string>Edit selected component.</string>
</property>
</action>
<action name="actionRevert">
@@ -167,39 +163,15 @@
<string>Revert</string>
</property>
<property name="toolTip">
- <string>Revert the selected package to default.</string>
- </property>
- </action>
- <action name="actionInstall_Forge">
- <property name="text">
- <string>Install Forge</string>
- </property>
- <property name="toolTip">
- <string>Install the Minecraft Forge package.</string>
- </property>
- </action>
- <action name="actionInstall_Fabric">
- <property name="text">
- <string>Install Fabric</string>
- </property>
- <property name="toolTip">
- <string>Install the Fabric Loader package.</string>
- </property>
- </action>
- <action name="actionInstall_Quilt">
- <property name="text">
- <string>Install Quilt</string>
- </property>
- <property name="toolTip">
- <string>Install the Quilt Loader package.</string>
+ <string>Revert the selected component to default.</string>
</property>
</action>
- <action name="actionInstall_LiteLoader">
+ <action name="actionInstall_Loader">
<property name="text">
- <string>Install LiteLoader</string>
+ <string>Install Loader</string>
</property>
<property name="toolTip">
- <string>Install the LiteLoader package.</string>
+ <string>Install a mod loader.</string>
</property>
</action>
<action name="actionAdd_to_Minecraft_jar">
@@ -228,7 +200,7 @@
<string>Add Empty</string>
</property>
<property name="toolTip">
- <string>Add an empty custom package.</string>
+ <string>Add an empty custom component.</string>
</property>
</action>
<action name="actionReload">
@@ -236,12 +208,12 @@
<string>Reload</string>
</property>
<property name="toolTip">
- <string>Reload all packages.</string>
+ <string>Reload all components.</string>
</property>
</action>
<action name="actionDownload_All">
<property name="text">
- <string>Download All</string>
+ <string>Download all</string>
</property>
<property name="toolTip">
<string>Download the files needed to launch the instance now.</string>
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index fe477616..587bb6ce 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -327,7 +327,7 @@ void WorldListPage::mceditState(LoggedProcess::State state)
}
}
-void WorldListPage::worldChanged(const QModelIndex& current, const QModelIndex& previous)
+void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
QModelIndex index = getSelectedWorld();
bool enable = index.isValid();
diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp
index 4ac21b01..068fb3a3 100644
--- a/launcher/ui/pages/modplatform/CustomPage.cpp
+++ b/launcher/ui/pages/modplatform/CustomPage.cpp
@@ -127,6 +127,9 @@ void CustomPage::loaderFilterChanged()
ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));
ui->loaderVersionList->setEmptyMode(VersionListView::String);
return;
+ } else if (ui->neoForgeFilter->isChecked()) {
+ ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+ m_selectedLoader = "net.neoforged";
} else if (ui->forgeFilter->isChecked()) {
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
m_selectedLoader = "net.minecraftforge";
diff --git a/launcher/ui/pages/modplatform/CustomPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui
index 0d89b595..23351ccd 100644
--- a/launcher/ui/pages/modplatform/CustomPage.ui
+++ b/launcher/ui/pages/modplatform/CustomPage.ui
@@ -195,6 +195,16 @@
</widget>
</item>
<item>
+ <widget class="QRadioButton" name="neoForgeFilter">
+ <property name="text">
+ <string>NeoForge</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">loaderBtnGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
<widget class="QRadioButton" name="forgeFilter">
<property name="text">
<string>Forge</string>
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index ba53d033..3e3c36b7 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -35,20 +35,28 @@
*/
#include "ImportPage.h"
+
+#include "ui/dialogs/ProgressDialog.h"
#include "ui_ImportPage.h"
#include <QFileDialog>
#include <QValidator>
+#include <utility>
+#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/NewInstanceDialog.h"
+#include "modplatform/flame/FlameAPI.h"
+
+#include "Json.h"
+
#include "InstanceImportTask.h"
class UrlValidator : public QValidator {
public:
using QValidator::QValidator;
- State validate(QString& in, int& pos) const
+ State validate(QString& in, [[maybe_unused]] int& pos) const
{
const QUrl url(in);
if (url.isValid() && !url.isRelative() && !url.isEmpty()) {
@@ -106,10 +114,61 @@ void ImportPage::updateState()
bool isMRPack = fi.suffix() == "mrpack";
if (fi.exists() && (isZip || isMRPack)) {
- QFileInfo fi(url.fileName());
- dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this));
+ auto extra_info = QMap(m_extra_info);
+ qDebug() << "Pack Extra Info" << extra_info << m_extra_info;
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default");
}
+ } else if (url.scheme() == "curseforge") {
+ // need to find the download link for the modpack
+ // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
+ QUrlQuery query(url);
+ auto addonId = query.allQueryItemValues("addonId")[0];
+ auto fileId = query.allQueryItemValues("fileId")[0];
+ auto array = std::make_shared<QByteArray>();
+
+ auto api = FlameAPI();
+ auto job = api.getFile(addonId, fileId, array);
+
+ connect(job.get(), &NetJob::failed, this,
+ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(job.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] {
+ qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
+ auto doc = Json::requireDocument(*array);
+ auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
+ // No way to find out if it's a mod or a modpack before here
+ // And also we need to check if it ends with .zip, instead of any better way
+ auto fileName = Json::ensureString(data, "fileName");
+ if (fileName.endsWith(".zip")) {
+ // Have to use ensureString then use QUrl to get proper url encoding
+ auto dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
+ if (!dl_url.isValid()) {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("The modpack %1 is blocked for third-parties! Please download it manually.").arg(fileName),
+ QMessageBox::Critical)
+ ->show();
+ return;
+ }
+
+ QFileInfo dl_file(dl_url.fileName());
+ QString pack_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
+
+ QMap<QString, QString> extra_info;
+ extra_info.insert("pack_id", addonId);
+ extra_info.insert("pack_version_id", fileId);
+
+ dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info)));
+ dialog->setSuggestedIcon("default");
+
+ } else {
+ CustomMessageBox::selectable(this, tr("Error"), tr("This url isn't a valid modpack !"), QMessageBox::Critical)->show();
+ }
+ });
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(job.get());
+ return;
} else {
if (input.endsWith("?client=y")) {
input.chop(9);
@@ -118,7 +177,8 @@ void ImportPage::updateState()
}
// hook, line and sinker.
QFileInfo fi(url.fileName());
- dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this));
+ auto extra_info = QMap(m_extra_info);
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default");
}
} else {
@@ -132,6 +192,12 @@ void ImportPage::setUrl(const QString& url)
updateState();
}
+void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info)
+{
+ m_extra_info = extra_info;
+ updateState();
+}
+
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
diff --git a/launcher/ui/pages/modplatform/ImportPage.h b/launcher/ui/pages/modplatform/ImportPage.h
index d846d566..70d7736e 100644
--- a/launcher/ui/pages/modplatform/ImportPage.h
+++ b/launcher/ui/pages/modplatform/ImportPage.h
@@ -62,7 +62,7 @@ class ImportPage : public QWidget, public BasePage {
void setUrl(const QString& url);
void openedImpl() override;
-
+ void setExtraInfo(const QMap<QString, QString>& extra_info);
private slots:
void on_modpackBtn_clicked();
void updateState();
@@ -73,4 +73,5 @@ class ImportPage : public QWidget, public BasePage {
private:
Ui::ImportPage* ui = nullptr;
NewInstanceDialog* dialog = nullptr;
+ QMap<QString, QString> m_extra_info = {};
};
diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui
index 3583cf90..9a9736b8 100644
--- a/launcher/ui/pages/modplatform/ImportPage.ui
+++ b/launcher/ui/pages/modplatform/ImportPage.ui
@@ -40,7 +40,7 @@
<item>
<widget class="QLabel" name="label_5">
<property name="text">
- <string>- CurseForge modpacks (ZIP)</string>
+ <string>- CurseForge modpacks (ZIP / curseforge:// URL)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index b7537890..c628f74a 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
auto sort = getCurrentSortingMethodByIndex();
- return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
+ return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };
}
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
@@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
if (!m_filter->versions.empty())
versions = m_filter->versions;
- return { pack, versions, profile->getModLoaders() };
+ return { pack, versions, profile->getSupportedModLoaders() };
}
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 9b178048..d6cc1fdc 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -124,8 +124,7 @@ void ModPage::updateVersionList()
auto version = current_pack->versions[i];
bool valid = false;
for (auto& mcVer : m_filter->versions) {
- // NOTE: Flame doesn't care about loader, so passing it changes nothing.
- if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
+ if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {
valid = true;
break;
}
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index 5510c191..5a43e49a 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
+ std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0;
[[nodiscard]] bool supportsFiltering() const override { return true; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
index 49405a02..cb8f1920 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -17,7 +17,7 @@
#include "BuildConfig.h"
#include "Json.h"
-#include "net/Download.h"
+#include "net/ApiDownload.h"
#include "net/NetJob.h"
#include "modplatform/ModIndex.h"
@@ -102,7 +102,7 @@ QHash<int, QByteArray> ResourceModel::roleNames() const
return roles;
}
-bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role)
+bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
{
int pos = index.row();
if (pos >= m_packs.size() || pos < 0 || !index.isValid())
@@ -132,6 +132,32 @@ void ResourceModel::search()
if (hasActiveSearchJob())
return;
+ if (m_search_term.startsWith("#")) {
+ auto projectId = m_search_term.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestFailed(reason, -1);
+ };
+ callbacks.on_abort = [this] {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestAborted();
+ };
+
+ callbacks.on_succeed = [this](auto& doc, auto& pack) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestForOneSucceeded(doc);
+ };
+ if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job)
+ runSearchJob(job);
+ return;
+ }
+ }
auto args{ createSearchArguments() };
auto callbacks{ createSearchCallbacks() };
@@ -189,11 +215,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)
// Use default if no callbacks are set
if (!callbacks.on_succeed)
- callbacks.on_succeed = [this, entry](auto& doc, auto pack) {
+ callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) {
if (!s_running_models.constFind(this).value())
return;
+ auto pack = newpack;
infoRequestSucceeded(doc, pack, entry);
};
+ if (!callbacks.on_fail)
+ callbacks.on_fail = [this](QString reason) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
+ };
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
runInfoJob(job);
@@ -281,7 +314,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
auto cache_entry = APPLICATION->metacache()->resolveEntry(
metaEntryBase(),
QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
- auto icon_fetch_action = Net::Download::makeCached(url, cache_entry);
+ auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
auto full_file_path = cache_entry->getFullPath();
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
@@ -310,7 +343,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
#define NEED_FOR_CALLBACK_ASSERT(name) \
Q_ASSERT_X(0 != 0, #name, "You NEED to re-implement this if you intend on using the default callbacks.")
-QJsonArray ResourceModel::documentToArray(QJsonDocument& doc) const
+QJsonArray ResourceModel::documentToArray([[maybe_unused]] QJsonDocument& doc) const
{
NEED_FOR_CALLBACK_ASSERT("documentToArray");
return {};
@@ -372,7 +405,28 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
endInsertRows();
}
-void ResourceModel::searchRequestFailed(QString reason, int network_error_code)
+void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
+
+ try {
+ auto obj = Json::requireObject(doc);
+ if (obj.contains("data"))
+ obj = Json::requireObject(obj, "data");
+ loadIndexedPack(*pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
+ }
+
+ m_search_state = SearchState::Finished;
+
+ beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1);
+ m_packs.append(pack);
+ endInsertRows();
+}
+
+void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
{
switch (network_error_code) {
default:
diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h
index 6533d9c6..ecf4f8f7 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.h
+++ b/launcher/ui/pages/modplatform/ResourceModel.h
@@ -42,7 +42,10 @@ class ResourceModel : public QAbstractListModel {
[[nodiscard]] virtual auto debugName() const -> QString;
[[nodiscard]] virtual auto metaEntryBase() const -> QString = 0;
- [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); }
+ [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override
+ {
+ return parent.isValid() ? 0 : static_cast<int>(m_packs.size());
+ }
[[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }
[[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }
@@ -146,6 +149,7 @@ class ResourceModel : public QAbstractListModel {
private:
/* Default search request callbacks */
void searchRequestSucceeded(QJsonDocument&);
+ void searchRequestForOneSucceeded(QJsonDocument&);
void searchRequestFailed(QString reason, int network_error_code);
void searchRequestAborted();
diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp
index 18c14bf8..d436f320 100644
--- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp
@@ -9,7 +9,8 @@
namespace ResourceDownload {
ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
- : ResourceModel(api), m_base_instance(base_inst){};
+ : ResourceModel(api), m_base_instance(base_inst)
+{}
/******** Make data requests ********/
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index 2925ec87..44a91003 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -4,7 +4,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -44,9 +44,6 @@
#include <QKeyEvent>
#include "Markdown.h"
-#include "ResourceDownloadTask.h"
-
-#include "minecraft/MinecraftInstance.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h"
@@ -283,7 +280,7 @@ void ResourcePage::updateVersionList()
updateSelectionButton();
}
-void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
+void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
{
if (!curr.isValid()) {
return;
@@ -310,9 +307,9 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
updateUi();
}
-void ResourcePage::onVersionSelectionChanged(QString data)
+void ResourcePage::onVersionSelectionChanged(QString versionData)
{
- if (data.isNull() || data.isEmpty()) {
+ if (versionData.isNull() || versionData.isEmpty()) {
m_selected_version_index = -1;
return;
}
@@ -398,7 +395,7 @@ void ResourcePage::openUrl(const QUrl& url)
if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) {
m_parent_dialog->selectPage(page);
- auto newPage = m_parent_dialog->getSelectedPage();
+ auto newPage = m_parent_dialog->selectedPage();
QLineEdit* searchEdit = newPage->m_ui->searchEdit;
auto model = newPage->m_model;
diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h
index b4a87f57..7bec0a37 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.h
+++ b/launcher/ui/pages/modplatform/ResourcePage.h
@@ -97,7 +97,11 @@ class ResourcePage : public QWidget, public BasePage {
virtual void openUrl(const QUrl&);
/** Whether the version is opted out or not. Currently only makes sense in CF. */
- virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; };
+ virtual bool optedOut(ModPlatform::IndexedVersion& ver) const
+ {
+ Q_UNUSED(ver);
+ return false;
+ };
public:
BaseInstance& m_base_instance;
diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp
index aabd3be6..8c913657 100644
--- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp
+++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp
@@ -9,7 +9,8 @@
namespace ResourceDownload {
ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
- : ResourceModel(api), m_base_instance(base_inst){};
+ : ResourceModel(api), m_base_instance(base_inst)
+{}
/******** Make data requests ********/
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
index fbf94e84..586dffc5 100644
--- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "ShaderPackPage.h"
+#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h"
#include "ShaderPackModel.h"
@@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
const std::shared_ptr<ResourceFolderModel> base_model)
{
QString custom_target_folder;
- if (version.loaders.contains(QStringLiteral("canvas")))
+ if (version.loaders & ModPlatform::Cauldron)
custom_target_folder = QStringLiteral("resourcepacks");
m_model->addPack(pack, version, base_model, false, custom_target_folder);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
index 9cd5eed5..dee3784e 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
@@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen
if (searchTerm.isEmpty()) {
return true;
}
-
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ if (searchTerm.startsWith("#"))
+ return QString::number(pack.id) == searchTerm.mid(1);
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index c6b087d6..b6fb7153 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -20,6 +20,9 @@
#include <BuildConfig.h>
#include <Json.h>
+#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
+
namespace Atl {
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
@@ -44,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
ATLauncher::IndexedPack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::ToolTipRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.safeName)) {
- return (m_logoMap.value(pack.safeName));
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
}
- auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.safeName)) {
+ return (m_logoMap.value(pack.safeName));
+ }
+ auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
- auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
- ((ListModel*)this)->requestLogo(pack.safeName, url);
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
+ ((ListModel*)this)->requestLogo(pack.safeName, url);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
void ListModel::request()
@@ -75,7 +101,7 @@ void ListModel::request()
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
jobPtr = netJob;
jobPtr->start();
@@ -137,8 +163,7 @@ void ListModel::requestFailed(QString reason)
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(
- APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo, logoUrl);
}
@@ -168,9 +193,9 @@ void ListModel::requestLogo(QString file, QString url)
return;
}
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file));
auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+ job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index 62e406d3..6fb86773 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -43,6 +43,8 @@
#include "Json.h"
#include "modplatform/atlauncher/ATLShareCode.h"
+#include "net/ApiDownload.h"
+
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
: QAbstractListModel(parent), m_version(version), m_mods(mods)
{
@@ -113,7 +115,7 @@ QVariant AtlOptionalModListModel::data(const QModelIndex& index, int role) const
return {};
}
-bool AtlOptionalModListModel::setData(const QModelIndex& index, const QVariant& value, int role)
+bool AtlOptionalModListModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
{
if (role == Qt::CheckStateRole) {
auto row = index.row();
@@ -155,7 +157,7 @@ void AtlOptionalModListModel::useShareCode(const QString& code)
{
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
- m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response));
+ m_jobPtr->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), m_response));
connect(m_jobPtr.get(), &NetJob::succeeded, this, &AtlOptionalModListModel::shareCodeSuccess);
connect(m_jobPtr.get(), &NetJob::failed, this, &AtlOptionalModListModel::shareCodeFailure);
@@ -206,7 +208,7 @@ void AtlOptionalModListModel::shareCodeSuccess()
emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
}
-void AtlOptionalModListModel::shareCodeFailure(const QString& reason)
+void AtlOptionalModListModel::shareCodeFailure([[maybe_unused]] const QString& reason)
{
m_jobPtr.reset();
@@ -281,15 +283,15 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
// if the dependency is 'effectively hidden', then track which mods
// depend on it - so we can efficiently disable it when no more dependents
// depend on it.
- auto dependants = m_dependants[dependencyName];
+ auto dependents = m_dependents[dependencyName];
if (enable) {
- dependants.append(mod.name);
+ dependents.append(mod.name);
} else {
- dependants.removeAll(mod.name);
+ dependents.removeAll(mod.name);
// if there are no longer any dependents, let's disable the mod
- if (dependencyMod.effectively_hidden && dependants.isEmpty()) {
+ if (dependencyMod.effectively_hidden && dependents.isEmpty()) {
setMod(dependencyMod, dependencyIndex, false, shouldEmit);
}
}
@@ -297,8 +299,8 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
// disable mods that depend on this one, if disabling
if (!enable) {
- auto dependants = m_dependants[mod.name];
- for (const auto& dependencyName : dependants) {
+ auto dependents = m_dependents[mod.name];
+ for (const auto& dependencyName : dependents) {
auto dependencyIndex = m_index[dependencyName];
auto dependencyMod = m_mods.at(dependencyIndex);
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
index 72a946da..55903003 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
@@ -90,7 +90,7 @@ class AtlOptionalModListModel : public QAbstractListModel {
QMap<QString, bool> m_selection;
QMap<QString, int> m_index;
- QMap<QString, QVector<QString>> m_dependants;
+ QMap<QString, QVector<QString>> m_dependents;
};
class AtlOptionalModDialog : public QDialog {
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
index 4c9dec58..c7e80027 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -35,11 +35,11 @@
*/
#include "AtlPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_AtlPage.h"
#include "BuildConfig.h"
-#include "AtlOptionalModDialog.h"
#include "AtlUserInteractionSupportImpl.h"
#include "modplatform/atlauncher/ATLPackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
@@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
AtlPage::~AtlPage()
@@ -123,13 +125,13 @@ void AtlPage::triggerSearch()
filterModel->setSearchTerm(ui->searchEdit->text());
}
-void AtlPage::onSortingSelectionChanged(QString data)
+void AtlPage::onSortingSelectionChanged(QString sort)
{
- auto toSet = filterModel->getAvailableSortings().value(data);
+ auto toSet = filterModel->getAvailableSortings().value(sort);
filterModel->setSorting(toSet);
}
-void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+void AtlPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)
{
ui->versionSelectionBox->clear();
@@ -151,13 +153,13 @@ void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)
suggestCurrent();
}
-void AtlPage::onVersionSelectionChanged(QString data)
+void AtlPage::onVersionSelectionChanged(QString version)
{
- if (data.isNull() || data.isEmpty()) {
+ if (version.isNull() || version.isEmpty()) {
selectedVersion = "";
return;
}
- selectedVersion = data;
+ selectedVersion = version;
suggestCurrent();
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
index 746aa6d1..8b674733 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
@@ -11,21 +11,28 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QTreeView" name="packView">
- <property name="alternatingRowColors">
- <bool>true</bool>
+ <item row="3" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
</property>
- <property name="iconSize">
- <size>
- <width>96</width>
- <height>48</height>
- </size>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
@@ -36,39 +43,22 @@
</property>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
- </property>
- <property name="wordWrap">
+ <item row="1" column="0">
+ <widget class="QTreeView" name="packView">
+ <property name="alternatingRowColors">
<bool>true</bool>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ <property name="iconSize">
+ <size>
+ <width>96</width>
+ <height>48</height>
+ </size>
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
</layout>
</item>
- <item row="0" column="0">
+ <item row="1" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
@@ -78,6 +68,31 @@
</property>
</widget>
</item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<tabstops>
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index 83c1270b..8875a945 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -1,8 +1,12 @@
#include "FlameModel.h"
#include <Json.h>
#include "Application.h"
+#include "modplatform/ResourceAPI.h"
+#include "modplatform/flame/FlameAPI.h"
#include "ui/widgets/ProjectItem.h"
+#include "net/ApiDownload.h"
+
#include <Version.h>
#include <QtMath>
@@ -70,7 +74,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
return QVariant();
}
-bool ListModel::setData(const QModelIndex& index, const QVariant& value, int role)
+bool ListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
{
int pos = index.row();
if (pos >= modpacks.size() || pos < 0 || !index.isValid())
@@ -104,9 +108,9 @@ void ListModel::requestLogo(QString logo, QString url)
return;
}
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo));
auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+ job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
@@ -130,7 +134,7 @@ void ListModel::requestLogo(QString logo, QString url)
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo, logoUrl);
}
@@ -141,7 +145,7 @@ Qt::ItemFlags ListModel::flags(const QModelIndex& index) const
return QAbstractListModel::flags(index);
}
-bool ListModel::canFetchMore(const QModelIndex& parent) const
+bool ListModel::canFetchMore([[maybe_unused]] const QModelIndex& parent) const
{
return searchState == CanPossiblyFetchMore;
}
@@ -159,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch()
{
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const FlameAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ }
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
auto searchUrl = QString(
"https://api.curseforge.com/v1/mods/search?"
@@ -173,7 +192,7 @@ void ListModel::performPaginatedSearch()
.arg(currentSearchTerm)
.arg(currentSort + 1);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@@ -187,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
}
currentSearchTerm = term;
currentSort = sort;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
void Flame::ListModel::searchRequestFinished()
{
- jobPtr.reset();
+ if (hasActiveSearchJob())
+ return;
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
@@ -244,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
endInsertRows();
}
+void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = Json::ensureObject(doc.object(), "data");
+
+ Flame::IndexedPack pack;
+ try {
+ Flame::loadIndexedPack(pack, packObj);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading pack from CurseForge: " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void Flame::ListModel::searchRequestFailed(QString reason)
{
jobPtr.reset();
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h
index b3bc96b8..fd8496df 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.h
@@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void performPaginatedSearch();
@@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished();
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
private:
void requestLogo(QString file, QString url);
@@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
int currentSort = 0;
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 9be7ce8b..584d94ad 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -46,9 +46,12 @@
#include "ui/dialogs/NewInstanceDialog.h"
#include "ui/widgets/ProjectItem.h"
+#include "net/ApiDownload.h"
+
static FlameAPI api;
-FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
+FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
@@ -59,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
// index is used to set the sorting with the curseforge api
ui->sortByBox->addItem(tr("Sort by Featured"));
ui->sortByBox->addItem(tr("Sort by Popularity"));
@@ -88,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -112,9 +131,10 @@ void FlamePage::openedImpl()
void FlamePage::triggerSearch()
{
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(listModel->activeSearchJob().get());
}
-void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
+void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
{
ui->versionSelectionBox->clear();
@@ -132,7 +152,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
int addonId = current.addonId;
- netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
+ netJob->addNetAction(
+ Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {
if (addonId != current.addonId) {
@@ -208,17 +229,17 @@ void FlamePage::suggestCurrent()
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
QString editedLogoName;
- editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
+ editedLogoName = "curseforge_" + current.logoName;
listModel->getLogo(current.logoName, current.logoUrl,
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
}
-void FlamePage::onVersionSelectionChanged(QString data)
+void FlamePage::onVersionSelectionChanged(QString version)
{
bool is_blocked = false;
ui->versionSelectionBox->currentData().toInt(&is_blocked);
- if (data.isNull() || data.isEmpty() || is_blocked) {
+ if (version.isNull() || version.isEmpty() || is_blocked) {
m_selected_version_index = -1;
return;
}
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h
index ff5c7975..d35858fb 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.h
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.h
@@ -39,8 +39,9 @@
#include <Application.h>
#include <modplatform/flame/FlamePackIndex.h>
-#include "tasks/Task.h"
+#include <QTimer>
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class FlamePage;
@@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
Flame::IndexedPack current;
int m_selected_version_index = -1;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui
index 71d19513..f9e1fe67 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item row="2" column="0">
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item row="3" column="0">
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
index 0fb67c50..7d18e72a 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -31,8 +31,8 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
- return FlameMod::loadDependencyVersions(m, arr);
-};
+ return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
+}
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
@@ -121,4 +121,27 @@ auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonAr
return Json::ensureArray(obj.object(), "data");
}
+FlameShaderPackModel::FlameShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new FlameAPI) {}
+
+void FlameShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadIndexedPack(m, obj);
+}
+
+// We already deal with the URLs when initializing the pack, due to the API response's structure
+void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadBody(m, obj);
+}
+
+void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return Json::ensureArray(obj.object(), "data");
+}
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
index 6cfd6a6f..76dbd7b3 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
@@ -68,4 +68,21 @@ class FlameTexturePackModel : public TexturePackResourceModel {
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
+class FlameShaderPackModel : public ShaderPackResourceModel {
+ Q_OBJECT
+
+ public:
+ FlameShaderPackModel(const BaseInstance&);
+ ~FlameShaderPackModel() override = default;
+
+ private:
+ [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
+ [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
+
+ void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
+ void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+ auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
+};
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
index dc17e705..23373ec9 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
@@ -68,10 +68,10 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
+ std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{
- Q_UNUSED(loaders);
- return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
+ return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() &&
+ (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
}
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
@@ -173,6 +173,45 @@ void FlameTexturePackPage::openUrl(const QUrl& url)
TexturePackResourcePage::openUrl(url);
}
+FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ : ShaderPackResourcePage(dialog, instance)
+{
+ m_model = new FlameShaderPackModel(instance);
+ m_ui->packView->setModel(m_model);
+
+ addSortings();
+
+ // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
+ // so it's best not to connect them in the parent's constructor...
+ connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameShaderPackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameShaderPackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+bool FlameShaderPackPage::optedOut(ModPlatform::IndexedVersion& ver) const
+{
+ return isOptedOut(ver);
+}
+
+void FlameShaderPackPage::openUrl(const QUrl& url)
+{
+ if (url.scheme().isEmpty()) {
+ QString query = url.query(QUrl::FullyDecoded);
+
+ if (query.startsWith("remoteUrl=")) {
+ // attempt to resolve url from warning page
+ query.remove(0, 10);
+ ShaderPackResourcePage::openUrl({ QUrl::fromPercentEncoding(query.toUtf8()) }); // double decoding is necessary
+ return;
+ }
+ }
+
+ ShaderPackResourcePage::openUrl(url);
+}
+
// I don't know why, but doing this on the parent class makes it so that
// other mod providers start loading before being selected, at least with
// my Qt, so we need to implement this in every derived class...
@@ -188,5 +227,9 @@ auto FlameTexturePackPage::shouldDisplay() const -> bool
{
return true;
}
+auto FlameShaderPackPage::shouldDisplay() const -> bool
+{
+ return true;
+}
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
index c6ebc1ea..f2f5ceca 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
@@ -44,6 +44,7 @@
#include "ui/pages/modplatform/ModPage.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
+#include "ui/pages/modplatform/ShaderPackPage.h"
#include "ui/pages/modplatform/TexturePackPage.h"
namespace ResourceDownload {
@@ -95,7 +96,7 @@ class FlameModPage : public ModPage {
bool validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override;
+ std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
void openUrl(const QUrl& url) override;
@@ -155,4 +156,31 @@ class FlameTexturePackPage : public TexturePackResourcePage {
void openUrl(const QUrl& url) override;
};
+class FlameShaderPackPage : public ShaderPackResourcePage {
+ Q_OBJECT
+
+ public:
+ static FlameShaderPackPage* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ShaderPackResourcePage::create<FlameShaderPackPage>(dialog, instance);
+ }
+
+ FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);
+ ~FlameShaderPackPage() override = default;
+
+ [[nodiscard]] bool shouldDisplay() const override;
+
+ [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); }
+ [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); }
+ [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); }
+ [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); }
+ [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); }
+
+ [[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
+
+ bool optedOut(ModPlatform::IndexedVersion& ver) const override;
+
+ void openUrl(const QUrl& url) override;
+};
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
index 5c9ff63b..d3ead083 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
@@ -17,6 +17,7 @@
*/
#include "ImportFTBPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QWidget>
@@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
ui->setupUi(this);
{
+ currentModel = new FilterModel(this);
listModel = new ListModel(this);
+ currentModel->setSourceModel(listModel);
- ui->modpackList->setModel(listModel);
+ ui->modpackList->setModel(currentModel);
ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0);
ui->modpackList->setIconSize(QSize(42, 42));
+
+ for (int i = 0; i < currentModel->getAvailableSortings().size(); i++) {
+ ui->sortByBox->addItem(currentModel->getAvailableSortings().keys().at(i));
+ }
+
+ ui->sortByBox->setCurrentText(currentModel->translateCurrentSorting());
}
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged);
+
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
+
+ ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}
@@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr
onPackSelectionChanged();
return;
}
- Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
+ Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
@@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
dialog->setSuggestedPack();
}
+void ImportFTBPage::onSortingSelectionChanged(QString sort)
+{
+ FilterModel::Sorting toSet = currentModel->getAvailableSortings().value(sort);
+ currentModel->setSorting(toSet);
+}
+
+void ImportFTBPage::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace FTBImportAPP
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
index 54c49f7b..8e966127 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
@@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage {
void suggestCurrent();
void onPackSelectionChanged(Modpack* pack = nullptr);
private slots:
+ void onSortingSelectionChanged(QString data);
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
+ void triggerSearch();
private:
bool initialized = false;
Modpack selected;
ListModel* listModel = nullptr;
+ FilterModel* currentModel = nullptr;
NewInstanceDialog* dialog = nullptr;
Ui::ImportFTBPage* ui = nullptr;
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
index 32d548b0..5e09fb6d 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
@@ -10,8 +10,8 @@
<height>1011</height>
</rect>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@@ -21,6 +21,54 @@
</property>
</widget>
</item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="sortByBox">
+ <property name="minimumSize">
+ <size>
+ <width>265</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
index dc78f451..134bdc0c 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
@@ -23,7 +23,9 @@
#include <QIcon>
#include <QProcessEnvironment>
#include "FileSystem.h"
+#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
+#include "ui/widgets/ProjectItem.h"
namespace FTBImportAPP {
@@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
auto pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- return pack.icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
- } else if (role == Qt::ToolTipRole) {
- return tr("Minecraft %1").arg(pack.mcVersion);
+ if (role == Qt::ToolTipRole) {
}
- return QVariant();
+ switch (role) {
+ case Qt::ToolTipRole:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case Qt::DecorationRole:
+ return pack.icon;
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
+{
+ currentSorting = Sorting::ByGameVersion;
+ sortings.insert(tr("Sort by Name"), Sorting::ByName);
+ sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
+}
+
+bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
+{
+ Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
+ Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
+
+ if (currentSorting == Sorting::ByGameVersion) {
+ Version lv(leftPack.mcVersion);
+ Version rv(rightPack.mcVersion);
+ return lv < rv;
+
+ } else if (currentSorting == Sorting::ByName) {
+ return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ }
+
+ // UHM, some inavlid value set?!
+ qWarning() << "Invalid sorting set!";
+ return true;
+}
+
+bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
+{
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
+}
+
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
+{
+ return sortings;
+}
+
+QString FilterModel::translateCurrentSorting()
+{
+ return sortings.key(currentSorting);
+}
+
+void FilterModel::setSorting(Sorting s)
+{
+ currentSorting = s;
+ invalidate();
+}
+
+FilterModel::Sorting FilterModel::getCurrentSorting()
+{
+ return currentSorting;
}
} // namespace FTBImportAPP \ No newline at end of file
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
index c67aa896..11192827 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
@@ -20,11 +20,33 @@
#include <QAbstractListModel>
#include <QIcon>
+#include <QSortFilterProxyModel>
#include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
+class FilterModel : public QSortFilterProxyModel {
+ Q_OBJECT
+ public:
+ FilterModel(QObject* parent = Q_NULLPTR);
+ enum Sorting { ByName, ByGameVersion };
+ const QMap<QString, Sorting> getAvailableSortings();
+ QString translateCurrentSorting();
+ void setSorting(Sorting sorting);
+ Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
+
+ protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
+ bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
+
+ private:
+ QMap<QString, Sorting> sortings;
+ Sorting currentSorting;
+ QString searchTerm;
+};
+
class ListModel : public QAbstractListModel {
Q_OBJECT
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 1731ff2c..49666cf6 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -35,11 +35,13 @@
#include "ListModel.h"
#include "Application.h"
+#include "net/ApiDownload.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
#include <Version.h>
#include "StringUtils.h"
+#include "ui/widgets/ProjectItem.h"
#include <QLabel>
#include <QtMath>
@@ -76,9 +78,22 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
return true;
}
-bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
+bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
{
- return true;
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ if (searchTerm.startsWith("#"))
+ return pack.packCode == searchTerm.mid(1);
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
}
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
@@ -138,45 +153,63 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name + "\n" + translatePackType(pack.type);
- } else if (role == Qt::ToolTipRole) {
- if (pack.description.length() > 100) {
- // some magic to prevent to long tooltips and replace html linebreaks
- QString edit = pack.description.left(97);
- edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
- return edit;
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ }
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.logo)) {
+ return (m_logoMap.value(pack.logo));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel*)this)->requestLogo(pack.logo);
+ return icon;
}
- return pack.description;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logo)) {
- return (m_logoMap.value(pack.logo));
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logo);
- return icon;
- } else if (role == Qt::ForegroundRole) {
- if (pack.broken) {
- // FIXME: Hardcoded color
- return QColor(255, 0, 50);
- } else if (pack.bugged) {
- // FIXME: Hardcoded color
- // bugged pack, currently only indicates bugged xml
- return QColor(244, 229, 66);
+ case Qt::ForegroundRole: {
+ if (pack.broken) {
+ // FIXME: Hardcoded color
+ return QColor(255, 0, 50);
+ } else if (pack.bugged) {
+ // FIXME: Hardcoded color
+ // bugged pack, currently only indicates bugged xml
+ return QColor(244, 229, 66);
+ }
}
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
-void ListModel::fill(ModpackList modpacks)
+void ListModel::fill(ModpackList modpacks_)
{
beginResetModel();
- this->modpacks = modpacks;
+ this->modpacks = modpacks_;
endResetModel();
}
@@ -229,9 +262,9 @@ void ListModel::requestLogo(QString file)
return;
}
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file));
NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
+ job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {
@@ -255,7 +288,7 @@ void ListModel::requestLogo(QString file)
void ListModel::getLogo(const QString& logo, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo);
}
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
index 51a58d99..c802a4b5 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
@@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel {
QString translateCurrentSorting();
void setSorting(Sorting sorting);
Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
@@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel {
private:
QMap<QString, Sorting> sortings;
Sorting currentSorting;
+ QString searchTerm;
};
class ListModel : public QAbstractListModel {
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
index ef8e9892..4104f139 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
@@ -35,6 +35,7 @@
*/
#include "Page.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_Page.h"
#include <QInputDialog>
@@ -110,6 +111,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &Page::triggerSearch);
+
connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
@@ -125,6 +128,9 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
ui->thirdPartyPackList->selectionModel()->reset();
ui->privatePackList->selectionModel()->reset();
+ ui->publicPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->thirdPartyPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->privatePackList->setItemDelegate(new ProjectItemDelegate(this));
onTabChanged(ui->tabWidget->currentIndex());
}
@@ -215,7 +221,7 @@ void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack)
privateListModel->addPack(pack);
}
-void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
+void Page::ftbPrivatePackDataDownloadFailed([[maybe_unused]] QString reason, QString packCode)
{
auto reply = QMessageBox::question(this, tr("FTB private packs"),
tr("Failed to download pack information for code %1.\nShould it be removed now?").arg(packCode));
@@ -224,7 +230,7 @@ void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
}
}
-void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
+void Page::onPublicPackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)
{
if (!now.isValid()) {
onPackSelectionChanged();
@@ -234,7 +240,7 @@ void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
onPackSelectionChanged(&selectedPack);
}
-void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)
+void Page::onThirdPartyPackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)
{
if (!now.isValid()) {
onPackSelectionChanged();
@@ -244,7 +250,7 @@ void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)
onPackSelectionChanged(&selectedPack);
}
-void Page::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev)
+void Page::onPrivatePackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)
{
if (!now.isValid()) {
onPackSelectionChanged();
@@ -284,20 +290,20 @@ void Page::onPackSelectionChanged(Modpack* pack)
suggestCurrent();
}
-void Page::onVersionSelectionItemChanged(QString data)
+void Page::onVersionSelectionItemChanged(QString version)
{
- if (data.isNull() || data.isEmpty()) {
+ if (version.isNull() || version.isEmpty()) {
selectedVersion = "";
return;
}
- selectedVersion = data;
+ selectedVersion = version;
suggestCurrent();
}
-void Page::onSortingSelectionChanged(QString data)
+void Page::onSortingSelectionChanged(QString sort)
{
- FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data);
+ FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(sort);
publicFilterModel->setSorting(toSet);
thirdPartyFilterModel->setSorting(toSet);
privateFilterModel->setSorting(toSet);
@@ -319,6 +325,8 @@ void Page::onTabChanged(int tab)
currentModpackInfo = ui->publicPackDescription;
}
+ triggerSearch();
+
currentList->selectionModel()->reset();
QModelIndex idx = currentList->currentIndex();
if (idx.isValid()) {
@@ -358,4 +366,9 @@ void Page::onRemovePackClicked()
onPackSelectionChanged();
}
+void Page::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace LegacyFTB
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
index a12b0745..4d317b7c 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
@@ -43,7 +43,6 @@
#include "QObjectPtr.h"
#include "modplatform/legacy_ftb/PackFetchTask.h"
#include "modplatform/legacy_ftb/PackHelpers.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
class NewInstanceDialog;
@@ -56,8 +55,6 @@ class Page;
class ListModel;
class FilterModel;
-class PrivatePackListModel;
-class PrivatePackFilterModel;
class PrivatePackManager;
class Page : public QWidget, public BasePage {
@@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage {
void onAddPackClicked();
void onRemovePackClicked();
+ void triggerSearch();
+
private:
FilterModel* currentModel = nullptr;
QTreeView* currentList = nullptr;
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
index ad08dc25..56cba748 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
@@ -10,8 +10,29 @@
<height>602</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
@@ -36,9 +57,9 @@
</item>
<item row="0" column="1">
<widget class="QTextBrowser" name="publicPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
@@ -50,10 +71,10 @@
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QTextBrowser" name="thirdPartyPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
<item row="0" column="0">
<widget class="QTreeView" name="thirdPartyPackList">
@@ -104,16 +125,16 @@
</item>
<item row="0" column="1" rowspan="3">
<widget class="QTextBrowser" name="privatePackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
</widget>
</item>
- <item>
+ <item row="5" column="0">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QLabel" name="label">
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index 761e265d..f691a185 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -38,10 +38,12 @@
#include "BuildConfig.h"
#include "Json.h"
-#include "minecraft/MinecraftInstance.h"
-#include "minecraft/PackProfile.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "net/NetJob.h"
#include "ui/widgets/ProjectItem.h"
+#include "net/ApiDownload.h"
+
#include <QMessageBox>
namespace Modrinth {
@@ -115,7 +117,7 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
return {};
}
-bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, int role)
+bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
{
int pos = index.row();
if (pos >= modpacks.size() || pos < 0 || !index.isValid())
@@ -128,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,
void ModpackListModel::performPaginatedSearch()
{
- // TODO: Move to standalone API
+ if (hasActiveSearchJob())
+ return;
+
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const ModrinthAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ } // TODO: Move to standalone API
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?"
@@ -142,7 +161,7 @@ void ModpackListModel::performPaginatedSearch()
.arg(currentSearchTerm)
.arg(currentSort);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
QJsonParseError parse_error_all{};
@@ -165,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()
void ModpackListModel::refresh()
{
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
@@ -194,8 +214,6 @@ static auto sortFromIndex(int index) -> QString
case 4:
return "updated";
}
-
- return {};
}
void ModpackListModel::searchWithTerm(const QString& term, const int sort)
@@ -218,9 +236,7 @@ void ModpackListModel::searchWithTerm(const QString& term, const int sort)
void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(APPLICATION->metacache()
- ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))
- ->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo, logoUrl);
}
@@ -232,10 +248,9 @@ void ModpackListModel::requestLogo(QString logo, QString url)
return;
}
- MetaEntryPtr entry =
- APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo));
auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+ job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
@@ -310,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
endInsertRows();
}
+void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = doc.object();
+
+ Modrinth::Modpack pack;
+ try {
+ Modrinth::loadIndexedPack(pack, packObj);
+ pack.id = Json::ensureString(packObj, "id", pack.id);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void ModpackListModel::searchRequestFailed(QString reason)
{
- auto failed_action = jobPtr->getFailedActions().at(0);
+ auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) {
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 721c69f5..2a9d6226 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -73,6 +73,9 @@ class ModpackListModel : public QAbstractListModel {
void refresh();
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
inline auto canFetchMore(const QModelIndex& parent) const -> bool override
@@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {
public slots:
void searchRequestFinished(QJsonDocument& doc_all);
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
protected slots:
@@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
QByteArray m_specific_response;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index b4d73b2f..8e2b9a90 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -46,11 +46,14 @@
#include "ui/widgets/ProjectItem.h"
+#include "net/ApiDownload.h"
+
#include <QComboBox>
#include <QKeyEvent>
#include <QPushButton>
-ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
+ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
@@ -62,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &ModrinthPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
ui->sortByBox->addItem(tr("Sort by Relevance"));
ui->sortByBox->addItem(tr("Sort by Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows"));
@@ -100,12 +114,17 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
this->triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QObject::eventFilter(watched, event);
}
-void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
+void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
{
ui->versionSelectionBox->clear();
@@ -127,7 +146,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
QString id = current.id;
- netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
if (id != current.id) {
@@ -176,7 +195,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
QString id = current.id;
netJob->addNetAction(
- Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
+ Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
if (id != current.id) {
@@ -308,11 +327,12 @@ void ModrinthPage::suggestCurrent()
void ModrinthPage::triggerSearch()
{
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(m_model->activeSearchJob().get());
}
-void ModrinthPage::onVersionSelectionChanged(QString data)
+void ModrinthPage::onVersionSelectionChanged(QString version)
{
- if (data.isNull() || data.isEmpty()) {
+ if (version.isNull() || version.isEmpty()) {
selectedVersion = "";
return;
}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
index b7054c88..4240dcaf 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -41,7 +41,9 @@
#include "ui/pages/BasePage.h"
#include "modplatform/modrinth/ModrinthPackManifest.h"
+#include "ui/widgets/ProgressWidget.h"
+#include <QTimer>
#include <QWidget>
namespace Ui {
@@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {
Modrinth::Modpack current;
QString selectedVersion;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
index 6d8b2b67..78a25fea 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -10,8 +10,8 @@
<height>600</height>
</rect>
</property>
- <layout class="QVBoxLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
@@ -29,7 +29,7 @@
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
index 00a0108a..85601829 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -39,13 +39,13 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec
void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
- return ::Modrinth::loadDependencyVersions(m, arr);
-};
+ return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance);
+}
auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
@@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q
void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ
void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs
void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
index 616c1a81..a4197b22 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
@@ -65,21 +65,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
+ std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{
- auto loaderCompatible = !loaders.has_value();
-
- if (!loaderCompatible) {
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value());
- for (auto remoteLoader : ver.loaders) {
- if (loaderStrings.contains(remoteLoader)) {
- loaderCompatible = true;
- break;
- }
- }
- }
-
- return ver.mcVersion.contains(mineVer) && loaderCompatible;
+ return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
}
ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
index 86ba1ccb..311bcfe3 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
@@ -93,7 +93,7 @@ class ModrinthModPage : public ModPage {
[[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const
-> bool override;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index 02d7fd5b..3cd1d9a2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -38,6 +38,9 @@
#include "BuildConfig.h"
#include "Json.h"
+#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
+
#include <QIcon>
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
@@ -52,21 +55,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logoName)) {
- return (m_logoMap.value(pack.logoName));
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ }
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.logoName)) {
+ return (m_logoMap.value(pack.logoName));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
+ return icon;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+
+ return {};
}
int Technic::ListModel::columnCount(const QModelIndex& parent) const
@@ -85,21 +114,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)
return;
}
currentSearchTerm = term;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
performSearch();
}
void Technic::ListModel::performSearch()
{
+ if (hasActiveSearchJob())
+ return;
+
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
@@ -111,12 +144,15 @@ void Technic::ListModel::performSearch()
} else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
+ } else if (currentSearchTerm.startsWith("#")) {
+ searchUrl = QString("https://api.technicpack.net/modpack/%1?build=%2").arg(currentSearchTerm.mid(1), BuildConfig.TECHNIC_API_BUILD);
+ searchMode = Single;
} else {
searchUrl =
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
searchMode = List;
}
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@@ -157,7 +193,7 @@ void Technic::ListModel::searchRequestFinished()
pack.logoName = "null";
} else {
pack.logoUrl = rawURL;
- pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
+ pack.logoName = rawURL.section(QLatin1Char('/'), -1);
}
pack.broken = false;
newList.append(pack);
@@ -179,7 +215,7 @@ void Technic::ListModel::searchRequestFinished()
auto iconUrl = Json::requireString(iconObj, "url");
pack.logoUrl = iconUrl;
- pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
+ pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
} else {
pack.logoUrl = "null";
pack.logoName = "null";
@@ -254,7 +290,7 @@ void Technic::ListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+ job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h
index d7a635d4..aeb4f308 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h
@@ -58,6 +58,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void searchRequestFinished();
void searchRequestFailed();
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
index 88132754..190b7c68 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
@@ -34,6 +34,7 @@
*/
#include "TechnicPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h"
#include <QKeyEvent>
@@ -49,7 +50,10 @@
#include "Application.h"
#include "modplatform/technic/SolderPackManifest.h"
-TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
+#include "net/ApiDownload.h"
+
+TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
@@ -57,8 +61,21 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p
model = new Technic::ListModel(this);
ui->packView->setModel(model);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &TechnicPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
@@ -69,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -98,9 +120,10 @@ void TechnicPage::openedImpl()
void TechnicPage::triggerSearch()
{
model->searchWithTerm(ui->searchEdit->text());
+ m_fetch_progress.watch(model->activeSearchJob().get());
}
-void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)
{
ui->versionSelectionBox->clear();
@@ -125,7 +148,7 @@ void TechnicPage::suggestCurrent()
return;
}
- QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0);
+ QString editedLogoName = "technic_" + current.logoName;
model->getLogo(current.logoName, current.logoUrl,
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
@@ -136,7 +159,7 @@ void TechnicPage::suggestCurrent()
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
QString slug = current.slug;
- netJob->addNetAction(Net::Download::makeByteArray(
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(
QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug] {
jobPtr.reset();
@@ -232,7 +255,7 @@ void TechnicPage::metadataLoaded()
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
@@ -304,13 +327,13 @@ void TechnicPage::onSolderLoaded()
metadataLoaded();
}
-void TechnicPage::onVersionSelectionChanged(QString data)
+void TechnicPage::onVersionSelectionChanged(QString version)
{
- if (data.isNull() || data.isEmpty()) {
+ if (version.isNull() || version.isEmpty()) {
selectedVersion = "";
return;
}
- selectedVersion = data;
+ selectedVersion = version;
selectVersion();
}
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h
index 91b61eaf..01439337 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h
@@ -35,13 +35,14 @@
#pragma once
+#include <QTimer>
#include <QWidget>
#include <Application.h>
#include "TechnicData.h"
#include "net/NetJob.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class TechnicPage;
@@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {
NetJob::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
index 15bf645f..b988eda2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="3" column="0" colspan="2">
+ <item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
@@ -44,7 +44,7 @@
</item>
</layout>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QListView" name="packView">
diff --git a/launcher/ui/setupwizard/ThemeWizardPage.cpp b/launcher/ui/setupwizard/ThemeWizardPage.cpp
index 1c336921..fe11ed9a 100644
--- a/launcher/ui/setupwizard/ThemeWizardPage.cpp
+++ b/launcher/ui/setupwizard/ThemeWizardPage.cpp
@@ -61,7 +61,7 @@ void ThemeWizardPage::updateIcons()
void ThemeWizardPage::updateCat()
{
qDebug() << "Setting Cat";
- ui->catImagePreviewButton->setIcon(QIcon(QString(R"(%1)").arg(APPLICATION->getCatPack())));
+ ui->catImagePreviewButton->setIcon(QIcon(QString(R"(%1)").arg(APPLICATION->themeManager()->getCatPack())));
}
void ThemeWizardPage::retranslate()
diff --git a/launcher/ui/themes/CatPack.cpp b/launcher/ui/themes/CatPack.cpp
index f0d8ddd5..bbcb58bc 100644
--- a/launcher/ui/themes/CatPack.cpp
+++ b/launcher/ui/themes/CatPack.cpp
@@ -99,18 +99,22 @@ QDate ensureDay(int year, int month, int day)
QString JsonCatPack::path()
{
- const QDate now = QDate::currentDate();
+ return path(QDate::currentDate());
+}
+
+QString JsonCatPack::path(QDate now)
+{
for (auto var : m_variants) {
QDate startDate = ensureDay(now.year(), var.startTime.month, var.startTime.day);
QDate endDate = ensureDay(now.year(), var.endTime.month, var.endTime.day);
if (startDate > endDate) { // it's spans over multiple years
- if (endDate <= now) // end date is in the past so jump one year into the future for endDate
+ if (endDate < now) // end date is in the past so jump one year into the future for endDate
endDate = endDate.addYears(1);
else // end date is in the future so jump one year into the past for startDate
startDate = startDate.addYears(-1);
}
- if (startDate >= now && now >= endDate)
+ if (startDate <= now && now <= endDate)
return var.path;
}
return m_defaultPath;
diff --git a/launcher/ui/themes/CatPack.h b/launcher/ui/themes/CatPack.h
index b03a19f0..1d310e79 100644
--- a/launcher/ui/themes/CatPack.h
+++ b/launcher/ui/themes/CatPack.h
@@ -52,9 +52,9 @@ class BasicCatPack : public CatPack {
public:
BasicCatPack(QString id, QString name) : m_id(id), m_name(name) {}
BasicCatPack(QString id) : BasicCatPack(id, id) {}
- virtual QString id() { return m_id; };
- virtual QString name() { return m_name; };
- virtual QString path();
+ virtual QString id() override { return m_id; }
+ virtual QString name() override { return m_name; }
+ virtual QString path() override;
protected:
QString m_id;
@@ -83,7 +83,8 @@ class JsonCatPack : public BasicCatPack {
PartialDate endTime;
};
JsonCatPack(QFileInfo& manifestInfo);
- virtual QString path();
+ virtual QString path() override;
+ QString path(QDate now);
private:
QString m_defaultPath;
diff --git a/launcher/ui/themes/CustomTheme.cpp b/launcher/ui/themes/CustomTheme.cpp
index d1eaf157..29ecf625 100644
--- a/launcher/ui/themes/CustomTheme.cpp
+++ b/launcher/ui/themes/CustomTheme.cpp
@@ -148,7 +148,7 @@ static bool writeThemeJson(const QString& path,
try {
Json::write(rootObj, path);
return true;
- } catch (const Exception& e) {
+ } catch ([[maybe_unused]] const Exception& e) {
themeWarningLog() << "Failed to write theme json to" << path;
return false;
}
diff --git a/launcher/ui/themes/IconTheme.cpp b/launcher/ui/themes/IconTheme.cpp
new file mode 100644
index 00000000..4bd88985
--- /dev/null
+++ b/launcher/ui/themes/IconTheme.cpp
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * 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 "IconTheme.h"
+
+#include <QFile>
+#include <QSettings>
+
+IconTheme::IconTheme(const QString& id, const QString& path) : m_id(id), m_path(path) {}
+
+bool IconTheme::load()
+{
+ const QString path = m_path + "/index.theme";
+
+ if (!QFile::exists(path))
+ return false;
+
+ QSettings settings(path, QSettings::IniFormat);
+ settings.beginGroup("Icon Theme");
+ m_name = settings.value("Name").toString();
+ settings.endGroup();
+ return !m_name.isNull();
+}
+
+QString IconTheme::id()
+{
+ return m_id;
+}
+
+QString IconTheme::path()
+{
+ return m_path;
+}
+
+QString IconTheme::name()
+{
+ return m_name;
+}
diff --git a/launcher/ui/themes/IconTheme.h b/launcher/ui/themes/IconTheme.h
new file mode 100644
index 00000000..4e466c6a
--- /dev/null
+++ b/launcher/ui/themes/IconTheme.h
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * 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 <QString>
+
+class IconTheme {
+ public:
+ IconTheme(const QString& id, const QString& path);
+ IconTheme() = default;
+
+ bool load();
+ QString id();
+ QString path();
+ QString name();
+
+ private:
+ QString m_id;
+ QString m_path;
+ QString m_name;
+};
diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp
index 321f7db4..0bcac100 100644
--- a/launcher/ui/themes/ThemeManager.cpp
+++ b/launcher/ui/themes/ThemeManager.cpp
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <git@tayou.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -31,9 +32,8 @@
#include "Application.h"
-ThemeManager::ThemeManager(MainWindow* mainWindow)
+ThemeManager::ThemeManager()
{
- m_mainWindow = mainWindow;
initializeThemes();
initializeCatPacks();
}
@@ -59,53 +59,110 @@ ITheme* ThemeManager::getTheme(QString themeId)
return m_themes[themeId].get();
}
+QString ThemeManager::addIconTheme(IconTheme theme)
+{
+ QString id = theme.id();
+ if (m_icons.find(id) == m_icons.end())
+ m_icons.emplace(id, std::move(theme));
+ else
+ themeWarningLog() << "IconTheme(" << id << ") not added to prevent id duplication";
+ return id;
+}
+
void ThemeManager::initializeThemes()
{
// Icon themes
- {
- // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
- // set icon theme search path!
- auto searchPaths = QIcon::themeSearchPaths();
- searchPaths.append("iconthemes");
- QIcon::setThemeSearchPaths(searchPaths);
- themeDebugLog() << "<> Icon themes initialized.";
- }
+ initializeIcons();
// Initialize widget themes
- {
- themeDebugLog() << "<> Initializing Widget Themes";
- themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<SystemTheme>());
- auto darkThemeId = addTheme(std::make_unique<DarkTheme>());
- themeDebugLog() << "Loading Built-in Theme:" << darkThemeId;
- themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<BrightTheme>());
-
- // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in
- // dropdown?)
- QString themeFolder = QDir("./themes/").absoluteFilePath("");
- themeDebugLog() << "Theme Folder Path: " << themeFolder;
-
- QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot);
- while (directoryIterator.hasNext()) {
- QDir dir(directoryIterator.next());
- QFileInfo themeJson(dir.absoluteFilePath("theme.json"));
- if (themeJson.exists()) {
- // Load "theme.json" based themes
- themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath();
- addTheme(std::make_unique<CustomTheme>(getTheme(darkThemeId), themeJson, true));
- } else {
- // Load pure QSS Themes
- QDirIterator stylesheetFileIterator(dir.absoluteFilePath(""), { "*.qss", "*.css" }, QDir::Files);
- while (stylesheetFileIterator.hasNext()) {
- QFile customThemeFile(stylesheetFileIterator.next());
- QFileInfo customThemeFileInfo(customThemeFile);
- themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath();
- addTheme(std::make_unique<CustomTheme>(getTheme(darkThemeId), customThemeFileInfo, false));
- }
+ initializeWidgets();
+}
+
+void ThemeManager::initializeIcons()
+{
+ // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
+ // set icon theme search path!
+ themeDebugLog() << "<> Initializing Icon Themes";
+
+ auto searchPaths = QIcon::themeSearchPaths();
+ searchPaths.append(m_iconThemeFolder.path());
+ QIcon::setThemeSearchPaths(searchPaths);
+
+ for (const QString& id : builtinIcons) {
+ IconTheme theme(id, QString(":/icons/%1").arg(id));
+ if (!theme.load()) {
+ themeWarningLog() << "Couldn't load built-in icon theme" << id;
+ continue;
+ }
+
+ addIconTheme(std::move(theme));
+ themeDebugLog() << "Loaded Built-In Icon Theme" << id;
+ }
+
+ if (!m_iconThemeFolder.mkpath("."))
+ themeWarningLog() << "Couldn't create icon theme folder";
+ themeDebugLog() << "Icon Theme Folder Path: " << m_iconThemeFolder.absolutePath();
+
+ QDirIterator directoryIterator(m_iconThemeFolder.path(), QDir::Dirs | QDir::NoDotAndDotDot);
+ while (directoryIterator.hasNext()) {
+ QDir dir(directoryIterator.next());
+ IconTheme theme(dir.dirName(), dir.path());
+ if (!theme.load())
+ continue;
+
+ addIconTheme(std::move(theme));
+ themeDebugLog() << "Loaded Custom Icon Theme from" << dir.path();
+ }
+
+ themeDebugLog() << "<> Icon themes initialized.";
+}
+
+void ThemeManager::initializeWidgets()
+{
+ themeDebugLog() << "<> Initializing Widget Themes";
+ themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<SystemTheme>());
+ auto darkThemeId = addTheme(std::make_unique<DarkTheme>());
+ themeDebugLog() << "Loading Built-in Theme:" << darkThemeId;
+ themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique<BrightTheme>());
+
+ // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in
+ // dropdown?)
+
+ if (!m_applicationThemeFolder.mkpath("."))
+ themeWarningLog() << "Couldn't create theme folder";
+ themeDebugLog() << "Theme Folder Path: " << m_applicationThemeFolder.absolutePath();
+
+ QDirIterator directoryIterator(m_applicationThemeFolder.path(), QDir::Dirs | QDir::NoDotAndDotDot);
+ while (directoryIterator.hasNext()) {
+ QDir dir(directoryIterator.next());
+ QFileInfo themeJson(dir.absoluteFilePath("theme.json"));
+ if (themeJson.exists()) {
+ // Load "theme.json" based themes
+ themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath();
+ addTheme(std::make_unique<CustomTheme>(getTheme(darkThemeId), themeJson, true));
+ } else {
+ // Load pure QSS Themes
+ QDirIterator stylesheetFileIterator(dir.absoluteFilePath(""), { "*.qss", "*.css" }, QDir::Files);
+ while (stylesheetFileIterator.hasNext()) {
+ QFile customThemeFile(stylesheetFileIterator.next());
+ QFileInfo customThemeFileInfo(customThemeFile);
+ themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath();
+ addTheme(std::make_unique<CustomTheme>(getTheme(darkThemeId), customThemeFileInfo, false));
}
}
+ }
- themeDebugLog() << "<> Widget themes initialized.";
+ themeDebugLog() << "<> Widget themes initialized.";
+}
+
+QList<IconTheme*> ThemeManager::getValidIconThemes()
+{
+ QList<IconTheme*> ret;
+ ret.reserve(m_icons.size());
+ for (auto&& [id, theme] : m_icons) {
+ ret.append(&theme);
}
+ return ret;
}
QList<ITheme*> ThemeManager::getValidApplicationThemes()
@@ -128,17 +185,39 @@ QList<CatPack*> ThemeManager::getValidCatPacks()
return ret;
}
-void ThemeManager::setIconTheme(const QString& name)
+bool ThemeManager::isValidIconTheme(const QString& id)
{
- QIcon::setThemeName(name);
+ return !id.isEmpty() && m_icons.find(id) != m_icons.end();
}
-void ThemeManager::applyCurrentlySelectedTheme(bool initial)
+bool ThemeManager::isValidApplicationTheme(const QString& id)
{
- setIconTheme(APPLICATION->settings()->get("IconTheme").toString());
- themeDebugLog() << "<> Icon theme set.";
- setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString(), initial);
- themeDebugLog() << "<> Application theme set.";
+ return !id.isEmpty() && m_themes.find(id) != m_themes.end();
+}
+
+QDir ThemeManager::getIconThemesFolder()
+{
+ return m_iconThemeFolder;
+}
+
+QDir ThemeManager::getApplicationThemesFolder()
+{
+ return m_applicationThemeFolder;
+}
+
+QDir ThemeManager::getCatPacksFolder()
+{
+ return m_catPacksFolder;
+}
+
+void ThemeManager::setIconTheme(const QString& name)
+{
+ if (m_icons.find(name) == m_icons.end()) {
+ themeWarningLog() << "Tried to set invalid icon theme:" << name;
+ return;
+ }
+
+ QIcon::setThemeName(name);
}
void ThemeManager::setApplicationTheme(const QString& name, bool initial)
@@ -154,6 +233,15 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial)
}
}
+void ThemeManager::applyCurrentlySelectedTheme(bool initial)
+{
+ auto settings = APPLICATION->settings();
+ setIconTheme(settings->get("IconTheme").toString());
+ themeDebugLog() << "<> Icon theme set.";
+ setApplicationTheme(settings->get("ApplicationTheme").toString(), initial);
+ themeDebugLog() << "<> Application theme set.";
+}
+
QString ThemeManager::getCatPack(QString catName)
{
auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
@@ -187,9 +275,9 @@ void ThemeManager::initializeCatPacks()
for (auto [id, name] : defaultCats) {
addCatPack(std::unique_ptr<CatPack>(new BasicCatPack(id, name)));
}
- QDir catpacksDir("catpacks");
- QString catpacksFolder = catpacksDir.absoluteFilePath("");
- themeDebugLog() << "CatPacks Folder Path:" << catpacksFolder;
+ if (!m_catPacksFolder.mkpath("."))
+ themeWarningLog() << "Couldn't create catpacks folder";
+ themeDebugLog() << "CatPacks Folder Path:" << m_catPacksFolder.absolutePath();
QStringList supportedImageFormats;
for (auto format : QImageReader::supportedImageFormats()) {
@@ -206,9 +294,9 @@ void ThemeManager::initializeCatPacks()
}
};
- loadFiles(catpacksDir);
+ loadFiles(m_catPacksFolder);
- QDirIterator directoryIterator(catpacksFolder, QDir::Dirs | QDir::NoDotAndDotDot);
+ QDirIterator directoryIterator(m_catPacksFolder.path(), QDir::Dirs | QDir::NoDotAndDotDot);
while (directoryIterator.hasNext()) {
QDir dir(directoryIterator.next());
QFileInfo manifest(dir.absoluteFilePath("catpack.json"));
diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h
index 1ce8c6f4..b5c66677 100644
--- a/launcher/ui/themes/ThemeManager.h
+++ b/launcher/ui/themes/ThemeManager.h
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Tayou <git@tayou.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -19,6 +20,7 @@
#include <QString>
+#include "IconTheme.h"
#include "ui/MainWindow.h"
#include "ui/themes/CatPack.h"
#include "ui/themes/ITheme.h"
@@ -34,11 +36,17 @@ inline auto themeWarningLog()
class ThemeManager {
public:
- ThemeManager(MainWindow* mainWindow);
+ ThemeManager();
+ QList<IconTheme*> getValidIconThemes();
QList<ITheme*> getValidApplicationThemes();
- void setIconTheme(const QString& name);
+ bool isValidIconTheme(const QString& id);
+ bool isValidApplicationTheme(const QString& id);
+ QDir getIconThemesFolder();
+ QDir getApplicationThemesFolder();
+ QDir getCatPacksFolder();
void applyCurrentlySelectedTheme(bool initial = false);
+ void setIconTheme(const QString& name);
void setApplicationTheme(const QString& name, bool initial = false);
/// @brief Returns the background based on selected and with events (Birthday, XMas, etc.)
@@ -49,12 +57,21 @@ class ThemeManager {
private:
std::map<QString, std::unique_ptr<ITheme>> m_themes;
+ std::map<QString, IconTheme> m_icons;
+ QDir m_iconThemeFolder{ "iconthemes" };
+ QDir m_applicationThemeFolder{ "themes" };
+ QDir m_catPacksFolder{ "catpacks" };
std::map<QString, std::unique_ptr<CatPack>> m_catPacks;
- MainWindow* m_mainWindow;
void initializeThemes();
void initializeCatPacks();
QString addTheme(std::unique_ptr<ITheme> theme);
ITheme* getTheme(QString themeId);
+ QString addIconTheme(IconTheme theme);
QString addCatPack(std::unique_ptr<CatPack> catPack);
+ void initializeIcons();
+ void initializeWidgets();
+
+ const QStringList builtinIcons{ "pe_colored", "pe_light", "pe_dark", "pe_blue", "breeze_light", "breeze_dark",
+ "OSX", "iOS", "flat", "flat_white", "multimc" };
};
diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp
index a0fda952..69f72fea 100644
--- a/launcher/ui/widgets/InfoFrame.cpp
+++ b/launcher/ui/widgets/InfoFrame.cpp
@@ -158,12 +158,12 @@ QString InfoFrame::renderColorCodes(QString input)
//
// TODO: Wrap links inside <a> tags
- // https://minecraft.fandom.com/wiki/Formatting_codes#Color_codes
+ // https://minecraft.wiki/w/Formatting_codes#Color_codes
const QMap<QChar, QString> color_codes_map = { { '0', "#000000" }, { '1', "#0000AA" }, { '2', "#00AA00" }, { '3', "#00AAAA" },
{ '4', "#AA0000" }, { '5', "#AA00AA" }, { '6', "#FFAA00" }, { '7', "#AAAAAA" },
{ '8', "#555555" }, { '9', "#5555FF" }, { 'a', "#55FF55" }, { 'b', "#55FFFF" },
{ 'c', "#FF5555" }, { 'd', "#FF55FF" }, { 'e', "#FFFF55" }, { 'f', "#FFFFFF" } };
- // https://minecraft.fandom.com/wiki/Formatting_codes#Formatting_codes
+ // https://minecraft.wiki/w/Formatting_codes#Formatting_codes
const QMap<QChar, QString> formatting_codes_map = { { 'l', "b" }, { 'm', "s" }, { 'n', "u" }, { 'o', "i" } };
QString html("<html>");
@@ -348,7 +348,7 @@ void InfoFrame::setImage(QPixmap img)
}
}
-void InfoFrame::descriptionEllipsisHandler(QString link)
+void InfoFrame::descriptionEllipsisHandler([[maybe_unused]] QString link)
{
if (!m_current_box) {
m_current_box = CustomMessageBox::selectable(this, "", m_description);
@@ -359,7 +359,7 @@ void InfoFrame::descriptionEllipsisHandler(QString link)
}
}
-void InfoFrame::licenseEllipsisHandler(QString link)
+void InfoFrame::licenseEllipsisHandler([[maybe_unused]] QString link)
{
if (!m_current_box) {
m_current_box = CustomMessageBox::selectable(this, "", m_license);
@@ -370,7 +370,7 @@ void InfoFrame::licenseEllipsisHandler(QString link)
}
}
-void InfoFrame::boxClosed(int result)
+void InfoFrame::boxClosed([[maybe_unused]] int result)
{
m_current_box = nullptr;
}
diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp
index 8c4048ff..d2d92191 100644
--- a/launcher/ui/widgets/JavaSettingsWidget.cpp
+++ b/launcher/ui/widgets/JavaSettingsWidget.cpp
@@ -46,7 +46,7 @@ void JavaSettingsWidget::setupUi()
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
- m_versionWidget = new VersionSelectWidget(true, this);
+ m_versionWidget = new VersionSelectWidget(this);
m_verticalLayout->addWidget(m_versionWidget);
m_horizontalLayout = new QHBoxLayout();
@@ -126,6 +126,7 @@ void JavaSettingsWidget::setupUi()
void JavaSettingsWidget::initialize()
{
m_versionWidget->initialize(APPLICATION->javalist().get());
+ m_versionWidget->selectSearch();
m_versionWidget->setResizeOn(2);
auto s = APPLICATION->settings();
// Memory
@@ -185,12 +186,20 @@ QString JavaSettingsWidget::javaPath() const
int JavaSettingsWidget::maxHeapSize() const
{
- return m_maxMemSpinBox->value();
+ auto min = m_minMemSpinBox->value();
+ auto max = m_maxMemSpinBox->value();
+ if (max < min)
+ max = min;
+ return max;
}
int JavaSettingsWidget::minHeapSize() const
{
- return m_minMemSpinBox->value();
+ auto min = m_minMemSpinBox->value();
+ auto max = m_maxMemSpinBox->value();
+ if (min > max)
+ min = max;
+ return min;
}
bool JavaSettingsWidget::permGenEnabled() const
@@ -213,17 +222,9 @@ void JavaSettingsWidget::memoryValueChanged(int)
if (obj == m_minMemSpinBox && min != observedMinMemory) {
observedMinMemory = min;
actuallyChanged = true;
- if (min > max) {
- observedMaxMemory = min;
- m_maxMemSpinBox->setValue(min);
- }
} else if (obj == m_maxMemSpinBox && max != observedMaxMemory) {
observedMaxMemory = max;
actuallyChanged = true;
- if (min > max) {
- observedMinMemory = max;
- m_minMemSpinBox->setValue(max);
- }
} else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) {
observedPermGenMemory = permgen;
actuallyChanged = true;
@@ -360,8 +361,8 @@ void JavaSettingsWidget::checkJavaPath(const QString& path)
setJavaStatus(JavaStatus::Pending);
m_checker.reset(new JavaChecker());
m_checker->m_path = path;
- m_checker->m_minMem = m_minMemSpinBox->value();
- m_checker->m_maxMem = m_maxMemSpinBox->value();
+ m_checker->m_minMem = minHeapSize();
+ m_checker->m_maxMem = maxHeapSize();
if (m_permGenSpinBox->isVisible()) {
m_checker->m_permGen = m_permGenSpinBox->value();
}
@@ -414,6 +415,9 @@ void JavaSettingsWidget::updateThresholds()
} else if (observedMaxMemory > (m_availableMemory * 0.9)) {
iconName = "status-yellow";
m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (observedMaxMemory < observedMinMemory) {
+ iconName = "status-yellow";
+ m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
m_labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp
index f132643a..514e1d25 100644
--- a/launcher/ui/widgets/PageContainer.cpp
+++ b/launcher/ui/widgets/PageContainer.cpp
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -131,7 +132,12 @@ BasePage* PageContainer::getPage(QString pageId)
return m_model->findPageEntryById(pageId);
}
-const QList<BasePage*> PageContainer::getPages() const
+BasePage* PageContainer::selectedPage() const
+{
+ return m_currentPage;
+}
+
+const QList<BasePage*>& PageContainer::getPages() const
{
return m_model->pages();
}
diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h
index 43aa0e7c..05be1c3a 100644
--- a/launcher/ui/widgets/PageContainer.h
+++ b/launcher/ui/widgets/PageContainer.h
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -75,9 +76,10 @@ class PageContainer : public QWidget, public BasePageContainer {
return false;
}
- virtual bool selectPage(QString pageId) override;
+ bool selectPage(QString pageId) override;
+ BasePage* selectedPage() const override;
BasePage* getPage(QString pageId) override;
- const QList<BasePage*> getPages() const;
+ const QList<BasePage*>& getPages() const;
void refreshContainer() override;
virtual void setParentContainer(BasePageContainer* container) { m_container = container; };
diff --git a/launcher/ui/widgets/PageContainer_p.h b/launcher/ui/widgets/PageContainer_p.h
index 9a7651c7..e61f6e15 100644
--- a/launcher/ui/widgets/PageContainer_p.h
+++ b/launcher/ui/widgets/PageContainer_p.h
@@ -89,6 +89,8 @@ class PageView : public QListView {
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
setItemDelegate(new PageViewDelegate(this));
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ // Adjust margins when using Breeze theme
+ setProperty("_kde_side_panel_view", true);
}
virtual QSize sizeHint() const
diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp
index 1481c1b6..60b92b28 100644
--- a/launcher/ui/widgets/ProjectItem.cpp
+++ b/launcher/ui/widgets/ProjectItem.cpp
@@ -34,8 +34,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
icon_width = icon_size.width();
icon_height = icon_size.height();
- icon_x_margin = (rect.height() - icon_width) / 2;
icon_y_margin = (rect.height() - icon_height) / 2;
+ icon_x_margin = icon_y_margin; // use same margins for consistency
}
// Centralize icon with a margin to separate from the other elements
diff --git a/launcher/ui/widgets/SubTaskProgressBar.cpp b/launcher/ui/widgets/SubTaskProgressBar.cpp
index 39102197..b0e62e0f 100644
--- a/launcher/ui/widgets/SubTaskProgressBar.cpp
+++ b/launcher/ui/widgets/SubTaskProgressBar.cpp
@@ -26,7 +26,7 @@ unique_qobject_ptr<SubTaskProgressBar> SubTaskProgressBar::create(QWidget* paren
return unique_qobject_ptr<SubTaskProgressBar>(progress_bar);
}
-SubTaskProgressBar::SubTaskProgressBar(QWidget* parent) : ui(new Ui::SubTaskProgressBar)
+SubTaskProgressBar::SubTaskProgressBar(QWidget* parent) : QWidget(parent), ui(new Ui::SubTaskProgressBar)
{
ui->setupUi(this);
}
diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp
index d26906bd..0de97441 100644
--- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp
+++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp
@@ -19,6 +19,7 @@
#include "ui_ThemeCustomizationWidget.h"
#include "Application.h"
+#include "DesktopServices.h"
#include "ui/themes/ITheme.h"
#include "ui/themes/ThemeManager.h"
@@ -31,6 +32,13 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa
connect(ui->widgetStyleComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ThemeCustomizationWidget::applyWidgetTheme);
connect(ui->backgroundCatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme);
+
+ connect(ui->iconsFolder, &QPushButton::clicked, this,
+ [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); });
+ connect(ui->widgetStyleFolder, &QPushButton::clicked, this,
+ [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
+ connect(ui->catPackFolder, &QPushButton::clicked, this,
+ [] { DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path()); });
}
ThemeCustomizationWidget::~ThemeCustomizationWidget()
@@ -67,7 +75,7 @@ void ThemeCustomizationWidget::showFeatures(ThemeFields features)
ui->iconsComboBox->setEnabled(features & ThemeFields::ICONS);
ui->iconsLabel->setEnabled(features & ThemeFields::ICONS);
ui->widgetStyleComboBox->setEnabled(features & ThemeFields::WIDGETS);
- ui->widgetThemeLabel->setEnabled(features & ThemeFields::WIDGETS);
+ ui->widgetStyleLabel->setEnabled(features & ThemeFields::WIDGETS);
ui->backgroundCatComboBox->setEnabled(features & ThemeFields::CAT);
ui->backgroundCatLabel->setEnabled(features & ThemeFields::CAT);
}
@@ -76,11 +84,10 @@ void ThemeCustomizationWidget::applyIconTheme(int index)
{
auto settings = APPLICATION->settings();
auto originalIconTheme = settings->get("IconTheme").toString();
- auto& newIconTheme = m_iconThemeOptions[index].first;
- settings->set("IconTheme", newIconTheme);
-
+ auto newIconTheme = ui->iconsComboBox->currentData().toString();
if (originalIconTheme != newIconTheme) {
- APPLICATION->applyCurrentlySelectedTheme();
+ settings->set("IconTheme", newIconTheme);
+ APPLICATION->themeManager()->applyCurrentlySelectedTheme();
}
emit currentIconThemeChanged(index);
@@ -93,7 +100,7 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index)
auto newAppTheme = ui->widgetStyleComboBox->currentData().toString();
if (originalAppTheme != newAppTheme) {
settings->set("ApplicationTheme", newAppTheme);
- APPLICATION->applyCurrentlySelectedTheme();
+ APPLICATION->themeManager()->applyCurrentlySelectedTheme();
}
emit currentWidgetThemeChanged(index);
@@ -121,18 +128,23 @@ void ThemeCustomizationWidget::loadSettings()
{
auto settings = APPLICATION->settings();
- auto iconTheme = settings->get("IconTheme").toString();
- for (auto& iconThemeFromList : m_iconThemeOptions) {
- QIcon iconForComboBox = QIcon(QString(":/icons/%1/scalable/settings").arg(iconThemeFromList.first));
- ui->iconsComboBox->addItem(iconForComboBox, iconThemeFromList.second);
- if (iconTheme == iconThemeFromList.first) {
- ui->iconsComboBox->setCurrentIndex(ui->iconsComboBox->count() - 1);
+ {
+ auto currentIconTheme = settings->get("IconTheme").toString();
+ auto iconThemes = APPLICATION->themeManager()->getValidIconThemes();
+ int idx = 0;
+ for (auto iconTheme : iconThemes) {
+ QIcon iconForComboBox = QIcon(iconTheme->path() + "/scalable/settings");
+ ui->iconsComboBox->addItem(iconForComboBox, iconTheme->name(), iconTheme->id());
+ if (currentIconTheme == iconTheme->id()) {
+ ui->iconsComboBox->setCurrentIndex(idx);
+ }
+ idx++;
}
}
{
auto currentTheme = settings->get("ApplicationTheme").toString();
- auto themes = APPLICATION->getValidApplicationThemes();
+ auto themes = APPLICATION->themeManager()->getValidApplicationThemes();
int idx = 0;
for (auto& theme : themes) {
ui->widgetStyleComboBox->addItem(theme->name(), theme->id());
@@ -144,7 +156,7 @@ void ThemeCustomizationWidget::loadSettings()
}
auto cat = settings->get("BackgroundCat").toString();
- for (auto& catFromList : APPLICATION->getValidCatPacks()) {
+ for (auto& catFromList : APPLICATION->themeManager()->getValidCatPacks()) {
QIcon catIcon = QIcon(QString("%1").arg(catFromList->path()));
ui->backgroundCatComboBox->addItem(catIcon, catFromList->name(), catFromList->id());
if (cat == catFromList->id()) {
diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h
index af47c788..cef5fb6c 100644
--- a/launcher/ui/widgets/ThemeCustomizationWidget.h
+++ b/launcher/ui/widgets/ThemeCustomizationWidget.h
@@ -31,7 +31,7 @@ class ThemeCustomizationWidget : public QWidget {
public:
explicit ThemeCustomizationWidget(QWidget* parent = nullptr);
- ~ThemeCustomizationWidget();
+ ~ThemeCustomizationWidget() override;
void showFeatures(ThemeFields features);
@@ -52,18 +52,4 @@ class ThemeCustomizationWidget : public QWidget {
private:
Ui::ThemeCustomizationWidget* ui;
-
- // TODO finish implementing
- QList<std::pair<QString, QString>> m_iconThemeOptions{ { "pe_colored", QObject::tr("Simple (Colored Icons)") },
- { "pe_light", QObject::tr("Simple (Light Icons)") },
- { "pe_dark", QObject::tr("Simple (Dark Icons)") },
- { "pe_blue", QObject::tr("Simple (Blue Icons)") },
- { "breeze_light", QObject::tr("Breeze Light") },
- { "breeze_dark", QObject::tr("Breeze Dark") },
- { "OSX", QObject::tr("OSX") },
- { "iOS", QObject::tr("iOS") },
- { "flat", QObject::tr("Flat") },
- { "flat_white", QObject::tr("Flat (White)") },
- { "multimc", QObject::tr("Legacy") },
- { "custom", QObject::tr("Custom") } };
};
diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui
index f216a610..4503181c 100644
--- a/launcher/ui/widgets/ThemeCustomizationWidget.ui
+++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui
@@ -40,22 +40,43 @@
</widget>
</item>
<item row="0" column="1">
- <widget class="QComboBox" name="iconsComboBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="iconsLayout">
+ <item>
+ <widget class="QComboBox" name="iconsComboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="iconsFolder">
+ <property name="toolTip">
+ <string>View icon themes folder.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset theme="viewfolder">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item row="1" column="0">
- <widget class="QLabel" name="widgetThemeLabel">
+ <widget class="QLabel" name="widgetStyleLabel">
<property name="text">
- <string>&amp;Colors</string>
+ <string>&amp;Widgets</string>
</property>
<property name="buddy">
<cstring>widgetStyleComboBox</cstring>
@@ -63,17 +84,38 @@
</widget>
</item>
<item row="1" column="1">
- <widget class="QComboBox" name="widgetStyleComboBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="widgetStyleLayout">
+ <item>
+ <widget class="QComboBox" name="widgetStyleComboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="widgetStyleFolder">
+ <property name="toolTip">
+ <string>View widget themes folder.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset theme="viewfolder">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="backgroundCatLabel">
@@ -89,7 +131,7 @@
</widget>
</item>
<item row="2" column="1">
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QHBoxLayout" name="catLayout">
<item>
<widget class="QComboBox" name="backgroundCatComboBox">
<property name="sizePolicy">
@@ -107,15 +149,15 @@
</widget>
</item>
<item>
- <widget class="QPushButton" name="catInfoLabel">
+ <widget class="QPushButton" name="catPackFolder">
<property name="toolTip">
- <string>The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar.</string>
+ <string>View cat packs folder.</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
- <iconset theme="about">
+ <iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
diff --git a/launcher/ui/widgets/VariableSizedImageObject.cpp b/launcher/ui/widgets/VariableSizedImageObject.cpp
index 4daf513a..f655fc38 100644
--- a/launcher/ui/widgets/VariableSizedImageObject.cpp
+++ b/launcher/ui/widgets/VariableSizedImageObject.cpp
@@ -25,6 +25,7 @@
#include "Application.h"
+#include "net/ApiDownload.h"
#include "net/NetJob.h"
enum FormatProperties { ImageData = QTextFormat::UserProperty + 1 };
@@ -97,7 +98,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(source, entry));
+ job->addNetAction(Net::ApiDownload::makeCached(source, entry));
auto full_entry_path = entry->getFullPath();
auto source_url = source;
diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp
index 9647dc79..a24630b3 100644
--- a/launcher/ui/widgets/VersionSelectWidget.cpp
+++ b/launcher/ui/widgets/VersionSelectWidget.cpp
@@ -11,9 +11,7 @@
#include "ui/dialogs/CustomMessageBox.h"
-VersionSelectWidget::VersionSelectWidget(QWidget* parent) : VersionSelectWidget(false, parent) {}
-
-VersionSelectWidget::VersionSelectWidget(bool focusSearch, QWidget* parent) : QWidget(parent), focusSearch(focusSearch)
+VersionSelectWidget::VersionSelectWidget(QWidget* parent) : QWidget(parent)
{
setObjectName(QStringLiteral("VersionSelectWidget"));
verticalLayout = new QVBoxLayout(this);
@@ -114,9 +112,6 @@ void VersionSelectWidget::initialize(BaseVersionList* vlist)
listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
- if (focusSearch)
- search->setFocus();
-
if (!m_vlist->isLoaded()) {
loadList();
} else {
@@ -199,6 +194,16 @@ void VersionSelectWidget::selectCurrent()
}
}
+void VersionSelectWidget::selectSearch()
+{
+ search->setFocus();
+}
+
+VersionListView* VersionSelectWidget::view()
+{
+ return listView;
+}
+
void VersionSelectWidget::selectRecommended()
{
auto idx = m_proxyModel->getRecommended();
@@ -221,14 +226,19 @@ BaseVersion::Ptr VersionSelectWidget::selectedVersion() const
return variant.value<BaseVersion::Ptr>();
}
+void VersionSelectWidget::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter)
+{
+ m_proxyModel->setFilter(role, new ContainsFilter(filter));
+}
+
void VersionSelectWidget::setExactFilter(BaseVersionList::ModelRoles role, QString filter)
{
m_proxyModel->setFilter(role, new ExactFilter(filter));
}
-void VersionSelectWidget::setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter)
+void VersionSelectWidget::setExactIfPresentFilter(BaseVersionList::ModelRoles role, QString filter)
{
- m_proxyModel->setFilter(role, new ContainsFilter(filter));
+ m_proxyModel->setFilter(role, new ExactIfPresentFilter(filter));
}
void VersionSelectWidget::setFilter(BaseVersionList::ModelRoles role, Filter* filter)
diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h
index b7f40101..99729fbd 100644
--- a/launcher/ui/widgets/VersionSelectWidget.h
+++ b/launcher/ui/widgets/VersionSelectWidget.h
@@ -51,7 +51,6 @@ class VersionSelectWidget : public QWidget {
Q_OBJECT
public:
explicit VersionSelectWidget(QWidget* parent);
- explicit VersionSelectWidget(bool focusSearch = false, QWidget* parent = 0);
~VersionSelectWidget();
//! loads the list if needed.
@@ -64,15 +63,19 @@ class VersionSelectWidget : public QWidget {
BaseVersion::Ptr selectedVersion() const;
void selectRecommended();
void selectCurrent();
+ void selectSearch();
+ VersionListView* view();
void setCurrentVersion(const QString& version);
void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter);
void setExactFilter(BaseVersionList::ModelRoles role, QString filter);
+ void setExactIfPresentFilter(BaseVersionList::ModelRoles role, QString filter);
void setFilter(BaseVersionList::ModelRoles role, Filter* filter);
void setEmptyString(QString emptyString);
void setEmptyErrorString(QString emptyErrorString);
void setEmptyMode(VersionListView::EmptyMode mode);
void setResizeOn(int column);
+
bool eventFilter(QObject* watched, QEvent* event) override;
signals:
@@ -97,7 +100,6 @@ class VersionSelectWidget : public QWidget {
int resizeOnColumn = 0;
Task* loadTask;
bool preselectedAlready = false;
- bool focusSearch;
QVBoxLayout* verticalLayout = nullptr;
VersionListView* listView = nullptr;
diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp
index b793d84f..46caaaef 100644
--- a/launcher/ui/widgets/WideBar.cpp
+++ b/launcher/ui/widgets/WideBar.cpp
@@ -204,8 +204,10 @@ static void copyAction(QAction* from, QAction* to)
void WideBar::showVisibilityMenu(QPoint const& position)
{
- if (!m_bar_menu)
+ if (!m_bar_menu) {
m_bar_menu = std::make_unique<QMenu>(this);
+ m_bar_menu->setTearOffEnabled(true);
+ }
if (m_menu_state == MenuState::Dirty) {
for (auto* old_action : m_bar_menu->actions())