From 6961a39cd20a63116bb562d61472c31f28ea8738 Mon Sep 17 00:00:00 2001 From: txtsd Date: Fri, 4 Nov 2022 11:58:58 +0530 Subject: [PATCH 001/341] feat: Assign java max mem based on system RAM If the system has <6GB RAM, it uses (system RAM / 1.5) If the system has >=6GB, it uses 4GB Signed-off-by: txtsd --- launcher/Application.cpp | 16 +++++++++++++++- launcher/Application.h | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 5772d7cad..c3c76854c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -566,7 +566,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512); - m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096); + m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, suitableMaxMem()); m_settings->registerSetting("PermGen", 128); // Java Settings @@ -1633,3 +1633,17 @@ QString Application::getUserAgentUncached() return BuildConfig.USER_AGENT_UNCACHED; } + +int Application::suitableMaxMem() +{ + float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte; + int maxMemoryAlloc; + + // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB + if (totalRAM < (4096 * 1.5)) + maxMemoryAlloc = (int) (totalRAM / 1.5); + else + maxMemoryAlloc = 4096; + + return maxMemoryAlloc; +} diff --git a/launcher/Application.h b/launcher/Application.h index 8fa0ab10e..280c842fc 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -198,6 +198,8 @@ public: void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); + int suitableMaxMem(); + signals: void updateAllowedChanged(bool status); void globalSettingsAboutToOpen(); From d87f743a2bd2fac761b94de77ed7255d7a983f03 Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:49:35 +0100 Subject: [PATCH 002/341] Add flat white icons This is mainly for dark mode users, as I think that the regular colour of the flat icons do not look good with dark mode. --- launcher/resources/flat_white/flat_white.qrc | 46 ++++++++ launcher/resources/flat_white/index.theme | 11 ++ .../resources/flat_white/scalable/about.svg | 3 + .../flat_white/scalable/accounts.svg | 3 + .../resources/flat_white/scalable/bug.svg | 3 + .../resources/flat_white/scalable/cat.svg | 3 + .../flat_white/scalable/centralmods.svg | 3 + .../flat_white/scalable/checkupdate.svg | 3 + .../resources/flat_white/scalable/copy.svg | 3 + .../flat_white/scalable/coremods.svg | 3 + .../flat_white/scalable/custom-commands.svg | 86 +++++++++++++++ .../resources/flat_white/scalable/discord.svg | 4 + .../flat_white/scalable/externaltools.svg | 3 + .../resources/flat_white/scalable/help.svg | 17 +++ .../flat_white/scalable/instance-settings.svg | 3 + .../resources/flat_white/scalable/jarmods.svg | 3 + .../resources/flat_white/scalable/java.svg | 3 + .../flat_white/scalable/language.svg | 103 ++++++++++++++++++ .../flat_white/scalable/launcher.svg | 2 + .../flat_white/scalable/loadermods.svg | 3 + .../resources/flat_white/scalable/log.svg | 3 + .../flat_white/scalable/minecraft.svg | 3 + .../resources/flat_white/scalable/multimc.svg | 3 + .../resources/flat_white/scalable/new.svg | 3 + .../resources/flat_white/scalable/news.svg | 3 + .../resources/flat_white/scalable/notes.svg | 3 + .../flat_white/scalable/packages.svg | 3 + .../resources/flat_white/scalable/patreon.svg | 3 + .../resources/flat_white/scalable/proxy.svg | 3 + .../flat_white/scalable/quickmods.svg | 3 + .../flat_white/scalable/reddit-alien.svg | 3 + .../resources/flat_white/scalable/refresh.svg | 3 + .../flat_white/scalable/resourcepacks.svg | 3 + .../scalable/screenshot-placeholder.svg | 3 + .../flat_white/scalable/screenshots.svg | 3 + .../flat_white/scalable/settings.svg | 3 + .../flat_white/scalable/shaderpacks.svg | 56 ++++++++++ .../resources/flat_white/scalable/star.svg | 3 + .../flat_white/scalable/status-bad.svg | 3 + .../flat_white/scalable/status-good.svg | 3 + .../flat_white/scalable/status-running.svg | 3 + .../flat_white/scalable/status-yellow.svg | 3 + .../flat_white/scalable/viewfolder.svg | 3 + .../resources/flat_white/scalable/worlds.svg | 3 + 44 files changed, 433 insertions(+) create mode 100644 launcher/resources/flat_white/flat_white.qrc create mode 100644 launcher/resources/flat_white/index.theme create mode 100644 launcher/resources/flat_white/scalable/about.svg create mode 100644 launcher/resources/flat_white/scalable/accounts.svg create mode 100644 launcher/resources/flat_white/scalable/bug.svg create mode 100644 launcher/resources/flat_white/scalable/cat.svg create mode 100644 launcher/resources/flat_white/scalable/centralmods.svg create mode 100644 launcher/resources/flat_white/scalable/checkupdate.svg create mode 100644 launcher/resources/flat_white/scalable/copy.svg create mode 100644 launcher/resources/flat_white/scalable/coremods.svg create mode 100644 launcher/resources/flat_white/scalable/custom-commands.svg create mode 100644 launcher/resources/flat_white/scalable/discord.svg create mode 100644 launcher/resources/flat_white/scalable/externaltools.svg create mode 100644 launcher/resources/flat_white/scalable/help.svg create mode 100644 launcher/resources/flat_white/scalable/instance-settings.svg create mode 100644 launcher/resources/flat_white/scalable/jarmods.svg create mode 100644 launcher/resources/flat_white/scalable/java.svg create mode 100644 launcher/resources/flat_white/scalable/language.svg create mode 100644 launcher/resources/flat_white/scalable/launcher.svg create mode 100644 launcher/resources/flat_white/scalable/loadermods.svg create mode 100644 launcher/resources/flat_white/scalable/log.svg create mode 100644 launcher/resources/flat_white/scalable/minecraft.svg create mode 100644 launcher/resources/flat_white/scalable/multimc.svg create mode 100644 launcher/resources/flat_white/scalable/new.svg create mode 100644 launcher/resources/flat_white/scalable/news.svg create mode 100644 launcher/resources/flat_white/scalable/notes.svg create mode 100644 launcher/resources/flat_white/scalable/packages.svg create mode 100644 launcher/resources/flat_white/scalable/patreon.svg create mode 100644 launcher/resources/flat_white/scalable/proxy.svg create mode 100644 launcher/resources/flat_white/scalable/quickmods.svg create mode 100644 launcher/resources/flat_white/scalable/reddit-alien.svg create mode 100644 launcher/resources/flat_white/scalable/refresh.svg create mode 100644 launcher/resources/flat_white/scalable/resourcepacks.svg create mode 100644 launcher/resources/flat_white/scalable/screenshot-placeholder.svg create mode 100644 launcher/resources/flat_white/scalable/screenshots.svg create mode 100644 launcher/resources/flat_white/scalable/settings.svg create mode 100644 launcher/resources/flat_white/scalable/shaderpacks.svg create mode 100644 launcher/resources/flat_white/scalable/star.svg create mode 100644 launcher/resources/flat_white/scalable/status-bad.svg create mode 100644 launcher/resources/flat_white/scalable/status-good.svg create mode 100644 launcher/resources/flat_white/scalable/status-running.svg create mode 100644 launcher/resources/flat_white/scalable/status-yellow.svg create mode 100644 launcher/resources/flat_white/scalable/viewfolder.svg create mode 100644 launcher/resources/flat_white/scalable/worlds.svg diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc new file mode 100644 index 000000000..d2b752b1a --- /dev/null +++ b/launcher/resources/flat_white/flat_white.qrc @@ -0,0 +1,46 @@ + + + + index.theme + scalable/about.svg + scalable/accounts.svg + scalable/bug.svg + scalable/cat.svg + scalable/centralmods.svg + scalable/checkupdate.svg + scalable/copy.svg + scalable/coremods.svg + scalable/custom-commands.svg + scalable/discord.svg + scalable/externaltools.svg + scalable/help.svg + scalable/instance-settings.svg + scalable/jarmods.svg + scalable/java.svg + scalable/language.svg + scalable/launcher.svg + scalable/loadermods.svg + scalable/log.svg + scalable/minecraft.svg + scalable/new.svg + scalable/news.svg + scalable/notes.svg + scalable/packages.svg + scalable/proxy.svg + scalable/quickmods.svg + scalable/reddit-alien.svg + scalable/refresh.svg + scalable/resourcepacks.svg + scalable/shaderpacks.svg + scalable/screenshot-placeholder.svg + scalable/screenshots.svg + scalable/settings.svg + scalable/star.svg + scalable/status-bad.svg + scalable/status-good.svg + scalable/status-running.svg + scalable/status-yellow.svg + scalable/viewfolder.svg + scalable/worlds.svg + + diff --git a/launcher/resources/flat_white/index.theme b/launcher/resources/flat_white/index.theme new file mode 100644 index 000000000..a0b3ba6f4 --- /dev/null +++ b/launcher/resources/flat_white/index.theme @@ -0,0 +1,11 @@ +[Icon Theme] +Name=White Flat +Comment=White version of the flat icons (for dark mode) +Inherits=multimc +Directories=scalable + +[scalable] +Size=48 +Type=Scalable +MinSize=16 +MaxSize=256 diff --git a/launcher/resources/flat_white/scalable/about.svg b/launcher/resources/flat_white/scalable/about.svg new file mode 100644 index 000000000..e42ca9481 --- /dev/null +++ b/launcher/resources/flat_white/scalable/about.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/accounts.svg b/launcher/resources/flat_white/scalable/accounts.svg new file mode 100644 index 000000000..e714bde13 --- /dev/null +++ b/launcher/resources/flat_white/scalable/accounts.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/bug.svg b/launcher/resources/flat_white/scalable/bug.svg new file mode 100644 index 000000000..3122702e3 --- /dev/null +++ b/launcher/resources/flat_white/scalable/bug.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/cat.svg b/launcher/resources/flat_white/scalable/cat.svg new file mode 100644 index 000000000..18da097a7 --- /dev/null +++ b/launcher/resources/flat_white/scalable/cat.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/centralmods.svg b/launcher/resources/flat_white/scalable/centralmods.svg new file mode 100644 index 000000000..d8d10f2f5 --- /dev/null +++ b/launcher/resources/flat_white/scalable/centralmods.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/checkupdate.svg b/launcher/resources/flat_white/scalable/checkupdate.svg new file mode 100644 index 000000000..0fa66fc2f --- /dev/null +++ b/launcher/resources/flat_white/scalable/checkupdate.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/copy.svg b/launcher/resources/flat_white/scalable/copy.svg new file mode 100644 index 000000000..1aaed97b1 --- /dev/null +++ b/launcher/resources/flat_white/scalable/copy.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/coremods.svg b/launcher/resources/flat_white/scalable/coremods.svg new file mode 100644 index 000000000..32c343839 --- /dev/null +++ b/launcher/resources/flat_white/scalable/coremods.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/custom-commands.svg b/launcher/resources/flat_white/scalable/custom-commands.svg new file mode 100644 index 000000000..3d67d8f10 --- /dev/null +++ b/launcher/resources/flat_white/scalable/custom-commands.svg @@ -0,0 +1,86 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/launcher/resources/flat_white/scalable/discord.svg b/launcher/resources/flat_white/scalable/discord.svg new file mode 100644 index 000000000..ee07ed25e --- /dev/null +++ b/launcher/resources/flat_white/scalable/discord.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/externaltools.svg b/launcher/resources/flat_white/scalable/externaltools.svg new file mode 100644 index 000000000..e7c0930c9 --- /dev/null +++ b/launcher/resources/flat_white/scalable/externaltools.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/help.svg b/launcher/resources/flat_white/scalable/help.svg new file mode 100644 index 000000000..82b413fe5 --- /dev/null +++ b/launcher/resources/flat_white/scalable/help.svg @@ -0,0 +1,17 @@ + + + + diff --git a/launcher/resources/flat_white/scalable/instance-settings.svg b/launcher/resources/flat_white/scalable/instance-settings.svg new file mode 100644 index 000000000..7dac7b141 --- /dev/null +++ b/launcher/resources/flat_white/scalable/instance-settings.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/jarmods.svg b/launcher/resources/flat_white/scalable/jarmods.svg new file mode 100644 index 000000000..f0f298f32 --- /dev/null +++ b/launcher/resources/flat_white/scalable/jarmods.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/java.svg b/launcher/resources/flat_white/scalable/java.svg new file mode 100644 index 000000000..56bb481f2 --- /dev/null +++ b/launcher/resources/flat_white/scalable/java.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/language.svg b/launcher/resources/flat_white/scalable/language.svg new file mode 100644 index 000000000..18c22efb5 --- /dev/null +++ b/launcher/resources/flat_white/scalable/language.svg @@ -0,0 +1,103 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/launcher/resources/flat_white/scalable/launcher.svg b/launcher/resources/flat_white/scalable/launcher.svg new file mode 100644 index 000000000..d7ad0dd3f --- /dev/null +++ b/launcher/resources/flat_white/scalable/launcher.svg @@ -0,0 +1,2 @@ + + diff --git a/launcher/resources/flat_white/scalable/loadermods.svg b/launcher/resources/flat_white/scalable/loadermods.svg new file mode 100644 index 000000000..100f7a93e --- /dev/null +++ b/launcher/resources/flat_white/scalable/loadermods.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/log.svg b/launcher/resources/flat_white/scalable/log.svg new file mode 100644 index 000000000..69b7c1dcc --- /dev/null +++ b/launcher/resources/flat_white/scalable/log.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/minecraft.svg b/launcher/resources/flat_white/scalable/minecraft.svg new file mode 100644 index 000000000..a0348e797 --- /dev/null +++ b/launcher/resources/flat_white/scalable/minecraft.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/multimc.svg b/launcher/resources/flat_white/scalable/multimc.svg new file mode 100644 index 000000000..3dce2699b --- /dev/null +++ b/launcher/resources/flat_white/scalable/multimc.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/new.svg b/launcher/resources/flat_white/scalable/new.svg new file mode 100644 index 000000000..46dc3361a --- /dev/null +++ b/launcher/resources/flat_white/scalable/new.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/news.svg b/launcher/resources/flat_white/scalable/news.svg new file mode 100644 index 000000000..414e54540 --- /dev/null +++ b/launcher/resources/flat_white/scalable/news.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/notes.svg b/launcher/resources/flat_white/scalable/notes.svg new file mode 100644 index 000000000..4ce5f6f19 --- /dev/null +++ b/launcher/resources/flat_white/scalable/notes.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/packages.svg b/launcher/resources/flat_white/scalable/packages.svg new file mode 100644 index 000000000..909ad0b27 --- /dev/null +++ b/launcher/resources/flat_white/scalable/packages.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/patreon.svg b/launcher/resources/flat_white/scalable/patreon.svg new file mode 100644 index 000000000..b745765b4 --- /dev/null +++ b/launcher/resources/flat_white/scalable/patreon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/proxy.svg b/launcher/resources/flat_white/scalable/proxy.svg new file mode 100644 index 000000000..a86703f45 --- /dev/null +++ b/launcher/resources/flat_white/scalable/proxy.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/quickmods.svg b/launcher/resources/flat_white/scalable/quickmods.svg new file mode 100644 index 000000000..9e0045b20 --- /dev/null +++ b/launcher/resources/flat_white/scalable/quickmods.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/reddit-alien.svg b/launcher/resources/flat_white/scalable/reddit-alien.svg new file mode 100644 index 000000000..be22148ca --- /dev/null +++ b/launcher/resources/flat_white/scalable/reddit-alien.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/refresh.svg b/launcher/resources/flat_white/scalable/refresh.svg new file mode 100644 index 000000000..08c63bdf7 --- /dev/null +++ b/launcher/resources/flat_white/scalable/refresh.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/resourcepacks.svg b/launcher/resources/flat_white/scalable/resourcepacks.svg new file mode 100644 index 000000000..9dd73c3af --- /dev/null +++ b/launcher/resources/flat_white/scalable/resourcepacks.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/screenshot-placeholder.svg b/launcher/resources/flat_white/scalable/screenshot-placeholder.svg new file mode 100644 index 000000000..41eb6fcfc --- /dev/null +++ b/launcher/resources/flat_white/scalable/screenshot-placeholder.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/screenshots.svg b/launcher/resources/flat_white/scalable/screenshots.svg new file mode 100644 index 000000000..68cf89696 --- /dev/null +++ b/launcher/resources/flat_white/scalable/screenshots.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/settings.svg b/launcher/resources/flat_white/scalable/settings.svg new file mode 100644 index 000000000..7dac7b141 --- /dev/null +++ b/launcher/resources/flat_white/scalable/settings.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/shaderpacks.svg b/launcher/resources/flat_white/scalable/shaderpacks.svg new file mode 100644 index 000000000..ccae221c1 --- /dev/null +++ b/launcher/resources/flat_white/scalable/shaderpacks.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + diff --git a/launcher/resources/flat_white/scalable/star.svg b/launcher/resources/flat_white/scalable/star.svg new file mode 100644 index 000000000..116f952eb --- /dev/null +++ b/launcher/resources/flat_white/scalable/star.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-bad.svg b/launcher/resources/flat_white/scalable/status-bad.svg new file mode 100644 index 000000000..5a121c09c --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-bad.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-good.svg b/launcher/resources/flat_white/scalable/status-good.svg new file mode 100644 index 000000000..ccb732ab8 --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-good.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-running.svg b/launcher/resources/flat_white/scalable/status-running.svg new file mode 100644 index 000000000..aa2d5fbff --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-running.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/status-yellow.svg b/launcher/resources/flat_white/scalable/status-yellow.svg new file mode 100644 index 000000000..772699d32 --- /dev/null +++ b/launcher/resources/flat_white/scalable/status-yellow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/viewfolder.svg b/launcher/resources/flat_white/scalable/viewfolder.svg new file mode 100644 index 000000000..145f86248 --- /dev/null +++ b/launcher/resources/flat_white/scalable/viewfolder.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/worlds.svg b/launcher/resources/flat_white/scalable/worlds.svg new file mode 100644 index 000000000..cea30cf81 --- /dev/null +++ b/launcher/resources/flat_white/scalable/worlds.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 92dfd659f1a3e11accdbf0ebbdc7cb91f74d9a21 Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:54:26 +0100 Subject: [PATCH 003/341] Fix .QRC file for flat white icons --- launcher/resources/flat_white/flat_white.qrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc index d2b752b1a..feda4762e 100644 --- a/launcher/resources/flat_white/flat_white.qrc +++ b/launcher/resources/flat_white/flat_white.qrc @@ -1,6 +1,6 @@ - + index.theme scalable/about.svg scalable/accounts.svg From 7eecf454e83314cd6f8029f54678dea568d41843 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 21 Oct 2022 14:07:43 -0300 Subject: [PATCH 004/341] fix: remove max height logic for mod update changelogs It's not worth it to keep this, it's just a heuristic that fails from time to time. Signed-off-by: flow --- launcher/ui/dialogs/ModUpdateDialog.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 4171586e9..cedd4a96e 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -366,33 +366,28 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) auto changelog = new QTreeWidgetItem(changelog_item); auto changelog_area = new QTextBrowser(); + QString text = info.changelog; switch (info.provider) { case ModPlatform::Provider::MODRINTH: { HoeDown h; // HoeDown bug?: \n aren't converted to
- auto text = h.process(info.changelog.toUtf8()); + text = h.process(info.changelog.toUtf8()); // Don't convert if there's an HTML tag right after (Qt rendering weirdness) text.remove(QRegularExpression("(\n+)(?=<)")); text.replace('\n', "
"); - changelog_area->setHtml(text); break; } - case ModPlatform::Provider::FLAME: { - changelog_area->setHtml(info.changelog); + default: break; - } } + changelog_area->setHtml(text); changelog_area->setOpenExternalLinks(true); - changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::NoWrap); + changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - // HACK: Is there a better way of achieving this? - auto font_height = QFontMetrics(changelog_area->font()).height(); - changelog_area->setMaximumHeight((changelog_area->toPlainText().count(QRegularExpression("\n|
")) + 2) * font_height); - ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area); ui->modTreeWidget->addTopLevelItem(item_top); From a406aeb3ea8e44f2f511a0755f10d4639f8bfca2 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 22 Oct 2022 20:51:46 +0800 Subject: [PATCH 005/341] feat: register as zip/mrpack handler on macOS Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- cmake/MacOSXBundleInfo.plist.in | 23 +++++++++++++++++++++++ launcher/Application.cpp | 7 +++++++ 2 files changed, 30 insertions(+) diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index 1b22e21fd..597beaa61 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -44,5 +44,28 @@ ${MACOSX_SPARKLE_UPDATE_PUBLIC_KEY} SUFeedURL ${MACOSX_SPARKLE_UPDATE_FEED_URL} + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + zip + mrpack + + CFBundleTypeName + Prism Launcher instance + CFBundleTypeOSTypes + + TEXT + utxt + TUTX + **** + + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 6ffec1ae4..bcb3aa0d3 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -943,6 +943,13 @@ bool Application::event(QEvent* event) { emit clickedOnDock(); } m_prevAppState = ev->applicationState(); + return true; + } + + if (event->type() == QEvent::FileOpen) { + auto ev = static_cast(event); + m_mainWindow->droppedURLs({ ev->url() }); + return true; } #endif return QApplication::event(event); From 1dad35ca760662b9fc7c22cc0799403f75780bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=B6ritz?= Date: Sat, 22 Oct 2022 15:00:52 +0200 Subject: [PATCH 006/341] update macos plist contents (fixes #132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tobias Möritz --- CMakeLists.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 131d3e53c..09f62bb3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,19 +211,19 @@ if(NOT (UNIX AND APPLE)) endif() if(UNIX AND APPLE) - set(BINARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") - set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") - set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") - set(FRAMEWORK_DEST_DIR "${Launcher_Name}.app/Contents/Frameworks") - set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources") - set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars") + set(BINARY_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS") + set(LIBRARY_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS") + set(PLUGIN_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS") + set(FRAMEWORK_DEST_DIR "${Launcher_DisplayName}.app/Contents/Frameworks") + set(RESOURCES_DEST_DIR "${Launcher_DisplayName}.app/Contents/Resources") + set(JARS_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS/jars") # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") + set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_DisplayName}.app") # Mac bundle settings - set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}") - set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") + set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}") + set(MACOSX_BUNDLE_INFO_STRING "${Launcher_DisplayName}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.prismlauncher.${Launcher_Name}") set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") From 500a7eceabd292a7ddef5a5e2cec0fd26d66cef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20M=C3=B6ritz?= Date: Sat, 22 Oct 2022 15:10:32 +0200 Subject: [PATCH 007/341] update macOS build process to work with new .app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tobias Möritz --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6400791d..f67ba4c7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -249,8 +249,8 @@ jobs: cmake --install ${{ env.BUILD_DIR }} cd ${{ env.INSTALL_DIR }} - chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" - sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" + chmod +x "Prism Launcher.app/Contents/MacOS/prismlauncher" + sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "Prism Launcher.app/Contents/MacOS/prismlauncher" tar -czf ../PrismLauncher.tar.gz * - name: Make Sparkle signature (macOS) From a6e65dfc7a7e8080297b59ecf322b788d1c9bad2 Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Sat, 22 Oct 2022 15:40:39 +0100 Subject: [PATCH 008/341] Add new flat white icons --- launcher/resources/flat_white/scalable/delete.svg | 5 +++++ launcher/resources/flat_white/scalable/export.svg | 5 +++++ launcher/resources/flat_white/scalable/rename.svg | 4 ++++ launcher/resources/flat_white/scalable/tag.svg | 4 ++++ 4 files changed, 18 insertions(+) create mode 100644 launcher/resources/flat_white/scalable/delete.svg create mode 100644 launcher/resources/flat_white/scalable/export.svg create mode 100644 launcher/resources/flat_white/scalable/rename.svg create mode 100644 launcher/resources/flat_white/scalable/tag.svg diff --git a/launcher/resources/flat_white/scalable/delete.svg b/launcher/resources/flat_white/scalable/delete.svg new file mode 100644 index 000000000..4cf7206e8 --- /dev/null +++ b/launcher/resources/flat_white/scalable/delete.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/export.svg b/launcher/resources/flat_white/scalable/export.svg new file mode 100644 index 000000000..a28bb2540 --- /dev/null +++ b/launcher/resources/flat_white/scalable/export.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/rename.svg b/launcher/resources/flat_white/scalable/rename.svg new file mode 100644 index 000000000..f2067c16b --- /dev/null +++ b/launcher/resources/flat_white/scalable/rename.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/tag.svg b/launcher/resources/flat_white/scalable/tag.svg new file mode 100644 index 000000000..2473126f6 --- /dev/null +++ b/launcher/resources/flat_white/scalable/tag.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 99e1c13e80d12764f57a993661656d9ed7de27bb Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Sat, 22 Oct 2022 15:50:06 +0100 Subject: [PATCH 009/341] fix: add flat_white to code --- launcher/CMakeLists.txt | 1 + launcher/main.cpp | 1 + launcher/resources/flat_white/index.theme | 2 +- launcher/ui/pages/global/LauncherPage.cpp | 13 ++++++++++--- launcher/ui/pages/global/LauncherPage.ui | 5 +++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 79ac49c76..0dae47df8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -602,6 +602,7 @@ SET(LAUNCHER_SOURCES resources/OSX/OSX.qrc resources/iOS/iOS.qrc resources/flat/flat.qrc + resources/flat_white/flat_white.qrc resources/documents/documents.qrc ../${Launcher_Branding_LogoQRC} diff --git a/launcher/main.cpp b/launcher/main.cpp index c6a7614c4..e2116f384 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -84,6 +84,7 @@ int main(int argc, char *argv[]) Q_INIT_RESOURCE(OSX); Q_INIT_RESOURCE(iOS); Q_INIT_RESOURCE(flat); + Q_INIT_RESOURCE(flat_white); return app.exec(); } case Application::Failed: diff --git a/launcher/resources/flat_white/index.theme b/launcher/resources/flat_white/index.theme index a0b3ba6f4..0292509ff 100644 --- a/launcher/resources/flat_white/index.theme +++ b/launcher/resources/flat_white/index.theme @@ -1,5 +1,5 @@ [Icon Theme] -Name=White Flat +Name=Flat (White) Comment=White version of the flat icons (for dark mode) Inherits=multimc Directories=scalable diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index b8431e8c4..536ab22e1 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -310,9 +310,12 @@ void LauncherPage::applySettings() s->set("IconTheme", "flat"); break; case 7: - s->set("IconTheme", "multimc"); + s->set("IconTheme", "flat_white"); break; case 8: + s->set("IconTheme", "multimc"); + break; + case 9: s->set("IconTheme", "custom"); break; } @@ -408,14 +411,18 @@ void LauncherPage::loadSettings() { ui->themeComboBox->setCurrentIndex(6); } - else if (theme == "multimc") + else if (theme == "flat_white") { ui->themeComboBox->setCurrentIndex(7); } - else if (theme == "custom") + else if (theme == "multimc") { ui->themeComboBox->setCurrentIndex(8); } + else if (theme == "custom") + { + ui->themeComboBox->setCurrentIndex(9); + } { auto currentTheme = s->get("ApplicationTheme").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 0d14f147f..76a25f2ed 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -300,6 +300,11 @@ Flat + + + Flat (White) + + Legacy From 46fe7e77b3285a77ee71df5f055b927b777ca112 Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Sat, 22 Oct 2022 16:14:50 +0100 Subject: [PATCH 010/341] DCO Remediation Commit for he3als <65787561+he3als@users.noreply.github.com> I, he3als <65787561+he3als@users.noreply.github.com>, hereby add my Signed-off-by to this commit: d87f743a2bd2fac761b94de77ed7255d7a983f03 I, he3als <65787561+he3als@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 92dfd659f1a3e11accdbf0ebbdc7cb91f74d9a21 I, he3als <65787561+he3als@users.noreply.github.com>, hereby add my Signed-off-by to this commit: a6e65dfc7a7e8080297b59ecf322b788d1c9bad2 I, he3als <65787561+he3als@users.noreply.github.com>, hereby add my Signed-off-by to this commit: 99e1c13e80d12764f57a993661656d9ed7de27bb Signed-off-by: he3als <65787561+he3als@users.noreply.github.com> --- launcher/resources/flat_white/index.theme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/resources/flat_white/index.theme b/launcher/resources/flat_white/index.theme index 0292509ff..54dd0e102 100644 --- a/launcher/resources/flat_white/index.theme +++ b/launcher/resources/flat_white/index.theme @@ -1,6 +1,6 @@ [Icon Theme] Name=Flat (White) -Comment=White version of the flat icons (for dark mode) +Comment=White version of the flat icons (dark mode) Inherits=multimc Directories=scalable From 1a8de111a379cfb01cee820730d9729a4f40ac0d Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 22 Oct 2022 23:46:53 +0800 Subject: [PATCH 011/341] add linux support Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- program_info/org.prismlauncher.PrismLauncher.desktop.in | 1 + 1 file changed, 1 insertion(+) diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in index e608f5885..f08f2ba43 100644 --- a/program_info/org.prismlauncher.PrismLauncher.desktop.in +++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in @@ -10,3 +10,4 @@ Icon=org.prismlauncher.PrismLauncher Categories=Game;ActionGame;AdventureGame;Simulation; Keywords=game;minecraft;launcher;mc;multimc;polymc; StartupWMClass=PrismLauncher +MimeType=application/zip;application/x-modrinth-modpack+zip From f35a30d371c414ce5961d518510155de7a0b9835 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 22 Oct 2022 23:47:53 +0800 Subject: [PATCH 012/341] oops Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- launcher/Application.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bcb3aa0d3..ae832b6bf 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -943,15 +943,14 @@ bool Application::event(QEvent* event) { emit clickedOnDock(); } m_prevAppState = ev->applicationState(); - return true; } +#endif if (event->type() == QEvent::FileOpen) { auto ev = static_cast(event); m_mainWindow->droppedURLs({ ev->url() }); - return true; } -#endif + return QApplication::event(event); } From 4777a4572267a604dc4fa8e1cd1f4bf2fc068929 Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Sun, 23 Oct 2022 14:32:35 +0100 Subject: [PATCH 013/341] Fix new flat white icons Signed-off-by: he3als <65787561+he3als@users.noreply.github.com> --- launcher/resources/flat_white/flat_white.qrc | 6 +++++- launcher/resources/flat_white/scalable/delete.svg | 2 +- launcher/resources/flat_white/scalable/tag.svg | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc index feda4762e..4243760b4 100644 --- a/launcher/resources/flat_white/flat_white.qrc +++ b/launcher/resources/flat_white/flat_white.qrc @@ -41,6 +41,10 @@ scalable/status-running.svg scalable/status-yellow.svg scalable/viewfolder.svg - scalable/worlds.svg + scalable/worlds.svg + scalable/delete.svg + scalable/export.svg + scalable/rename.svg + scalable/tag.svg
diff --git a/launcher/resources/flat_white/scalable/delete.svg b/launcher/resources/flat_white/scalable/delete.svg index 4cf7206e8..3365a96f8 100644 --- a/launcher/resources/flat_white/scalable/delete.svg +++ b/launcher/resources/flat_white/scalable/delete.svg @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/launcher/resources/flat_white/scalable/tag.svg b/launcher/resources/flat_white/scalable/tag.svg index 2473126f6..f91fb0b42 100644 --- a/launcher/resources/flat_white/scalable/tag.svg +++ b/launcher/resources/flat_white/scalable/tag.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file From 0aac85dda145603434e64bf04f39823f44509605 Mon Sep 17 00:00:00 2001 From: he3als <65787561+he3als@users.noreply.github.com> Date: Sun, 23 Oct 2022 14:35:33 +0100 Subject: [PATCH 014/341] Replace tab with spaces in flat_white.qrc Signed-off-by: he3als <65787561+he3als@users.noreply.github.com> --- launcher/resources/flat_white/flat_white.qrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc index 4243760b4..9df690600 100644 --- a/launcher/resources/flat_white/flat_white.qrc +++ b/launcher/resources/flat_white/flat_white.qrc @@ -42,7 +42,7 @@ scalable/status-yellow.svg scalable/viewfolder.svg scalable/worlds.svg - scalable/delete.svg + scalable/delete.svg scalable/export.svg scalable/rename.svg scalable/tag.svg From 43bf601f120c9b1eac1c73feb31c67df54955b6d Mon Sep 17 00:00:00 2001 From: leo78913 Date: Sun, 23 Oct 2022 11:34:49 -0300 Subject: [PATCH 015/341] add icons to the instance toolbar Signed-off-by: leo78913 --- launcher/resources/OSX/OSX.qrc | 1 + launcher/resources/OSX/scalable/launch.svg | 33 +++++++++++++++++++ launcher/resources/flat/flat.qrc | 1 + launcher/resources/flat/scalable/launch.svg | 16 +++++++++ launcher/resources/iOS/iOS.qrc | 1 + launcher/resources/iOS/scalable/launch.svg | 17 ++++++++++ launcher/resources/pe_blue/pe_blue.qrc | 1 + .../resources/pe_blue/scalable/launch.svg | 20 +++++++++++ launcher/resources/pe_colored/pe_colored.qrc | 1 + .../resources/pe_colored/scalable/launch.svg | 23 +++++++++++++ launcher/resources/pe_dark/pe_dark.qrc | 1 + .../resources/pe_dark/scalable/launch.svg | 17 ++++++++++ launcher/resources/pe_light/pe_light.qrc | 1 + .../resources/pe_light/scalable/launch.svg | 17 ++++++++++ launcher/ui/MainWindow.cpp | 17 ++++++++-- 15 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 launcher/resources/OSX/scalable/launch.svg create mode 100644 launcher/resources/flat/scalable/launch.svg create mode 100644 launcher/resources/iOS/scalable/launch.svg create mode 100644 launcher/resources/pe_blue/scalable/launch.svg create mode 100644 launcher/resources/pe_colored/scalable/launch.svg create mode 100644 launcher/resources/pe_dark/scalable/launch.svg create mode 100644 launcher/resources/pe_light/scalable/launch.svg diff --git a/launcher/resources/OSX/OSX.qrc b/launcher/resources/OSX/OSX.qrc index 55be28b5f..19fe312b6 100644 --- a/launcher/resources/OSX/OSX.qrc +++ b/launcher/resources/OSX/OSX.qrc @@ -38,5 +38,6 @@ scalable/tag.svg scalable/export.svg scalable/rename.svg + scalable/launch.svg diff --git a/launcher/resources/OSX/scalable/launch.svg b/launcher/resources/OSX/scalable/launch.svg new file mode 100644 index 000000000..fb1891625 --- /dev/null +++ b/launcher/resources/OSX/scalable/launch.svg @@ -0,0 +1,33 @@ + + + + diff --git a/launcher/resources/flat/flat.qrc b/launcher/resources/flat/flat.qrc index 7f59da7b8..508e0a9f5 100644 --- a/launcher/resources/flat/flat.qrc +++ b/launcher/resources/flat/flat.qrc @@ -46,5 +46,6 @@ scalable/tag.svg scalable/export.svg scalable/rename.svg + scalable/launch.svg diff --git a/launcher/resources/flat/scalable/launch.svg b/launcher/resources/flat/scalable/launch.svg new file mode 100644 index 000000000..b462f2e45 --- /dev/null +++ b/launcher/resources/flat/scalable/launch.svg @@ -0,0 +1,16 @@ + + + + + diff --git a/launcher/resources/iOS/iOS.qrc b/launcher/resources/iOS/iOS.qrc index 1d7520420..aa08d811a 100644 --- a/launcher/resources/iOS/iOS.qrc +++ b/launcher/resources/iOS/iOS.qrc @@ -38,5 +38,6 @@ scalable/tag.svg scalable/export.svg scalable/rename.svg + scalable/launch.svg diff --git a/launcher/resources/iOS/scalable/launch.svg b/launcher/resources/iOS/scalable/launch.svg new file mode 100644 index 000000000..c16d5c37c --- /dev/null +++ b/launcher/resources/iOS/scalable/launch.svg @@ -0,0 +1,17 @@ + + + + diff --git a/launcher/resources/pe_blue/pe_blue.qrc b/launcher/resources/pe_blue/pe_blue.qrc index 3d3857133..3121ffe6b 100644 --- a/launcher/resources/pe_blue/pe_blue.qrc +++ b/launcher/resources/pe_blue/pe_blue.qrc @@ -38,5 +38,6 @@ scalable/tag.svg scalable/export.svg scalable/rename.svg + scalable/launch.svg diff --git a/launcher/resources/pe_blue/scalable/launch.svg b/launcher/resources/pe_blue/scalable/launch.svg new file mode 100644 index 000000000..b3bd124f4 --- /dev/null +++ b/launcher/resources/pe_blue/scalable/launch.svg @@ -0,0 +1,20 @@ + + + + diff --git a/launcher/resources/pe_colored/pe_colored.qrc b/launcher/resources/pe_colored/pe_colored.qrc index fa6cd9cd4..ce5ad8e21 100644 --- a/launcher/resources/pe_colored/pe_colored.qrc +++ b/launcher/resources/pe_colored/pe_colored.qrc @@ -38,5 +38,6 @@ scalable/tag.svg scalable/export.svg scalable/rename.svg + scalable/launch.svg diff --git a/launcher/resources/pe_colored/scalable/launch.svg b/launcher/resources/pe_colored/scalable/launch.svg new file mode 100644 index 000000000..76713387d --- /dev/null +++ b/launcher/resources/pe_colored/scalable/launch.svg @@ -0,0 +1,23 @@ + + + + diff --git a/launcher/resources/pe_dark/pe_dark.qrc b/launcher/resources/pe_dark/pe_dark.qrc index 6b9c7cb60..156d8f8b4 100644 --- a/launcher/resources/pe_dark/pe_dark.qrc +++ b/launcher/resources/pe_dark/pe_dark.qrc @@ -38,5 +38,6 @@ scalable/tag.svg scalable/export.svg scalable/rename.svg + scalable/launch.svg diff --git a/launcher/resources/pe_dark/scalable/launch.svg b/launcher/resources/pe_dark/scalable/launch.svg new file mode 100644 index 000000000..3746e2dd6 --- /dev/null +++ b/launcher/resources/pe_dark/scalable/launch.svg @@ -0,0 +1,17 @@ + + + + diff --git a/launcher/resources/pe_light/pe_light.qrc b/launcher/resources/pe_light/pe_light.qrc index 963bfcdeb..d8e4a1bd7 100644 --- a/launcher/resources/pe_light/pe_light.qrc +++ b/launcher/resources/pe_light/pe_light.qrc @@ -38,5 +38,6 @@ scalable/tag.svg scalable/export.svg scalable/rename.svg + scalable/launch.svg diff --git a/launcher/resources/pe_light/scalable/launch.svg b/launcher/resources/pe_light/scalable/launch.svg new file mode 100644 index 000000000..6c27b7e0c --- /dev/null +++ b/launcher/resources/pe_light/scalable/launch.svg @@ -0,0 +1,17 @@ + + + + diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 97152a485..559ebc316 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -656,6 +656,7 @@ public: actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Launch")); actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); + actionLaunchInstance->setIcon(APPLICATION->getThemedIcon("launch")); all_actions.append(&actionLaunchInstance); actionLaunchInstanceOffline = TranslatedAction(MainWindow); @@ -741,7 +742,9 @@ public: // See https://github.com/PolyMC/PolyMC/issues/493 connect(instanceToolBar, &QToolBar::orientationChanged, [=](Qt::Orientation){ instanceToolBar->setOrientation(Qt::Vertical); }); instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); - instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); + instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + instanceToolBar->setIconSize(QSize(16, 16)); + instanceToolBar->setFloatable(false); instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); @@ -761,8 +764,18 @@ public: instanceToolBar->addAction(actionViewSelectedInstFolder); instanceToolBar->addAction(actionExportInstance); - instanceToolBar->addAction(actionDeleteInstance); instanceToolBar->addAction(actionCopyInstance); + instanceToolBar->addAction(actionDeleteInstance); + + QLayout * lay = instanceToolBar->layout(); + for(int i = 0; i < lay->count(); i++) + { + QLayoutItem * item = lay->itemAt(i); + if (item->widget()->metaObject()->className() == QString("QToolButton")) + { + item->setAlignment(Qt::AlignLeft); + } + } all_toolbars.append(&instanceToolBar); MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); From e7e56eb1e397a528df91f9ce99f738c49bde363c Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez Date: Sat, 22 Oct 2022 14:50:32 -0400 Subject: [PATCH 016/341] add more options to copy instance dialog - Copy game options, copy resource packs, copy shaders, copy servers, and copy mods - Also made a new InstanceCopyPrefs struct to store those options rather than passing 7 different booleans into InstanceCopyTask's constructor Signed-off-by: Marcelo Hernandez --- launcher/CMakeLists.txt | 1 + launcher/InstanceCopyPrefs.h | 18 +++++ launcher/InstanceCopyTask.cpp | 62 +++++++++++++-- launcher/InstanceCopyTask.h | 20 +++-- launcher/ui/MainWindow.cpp | 12 ++- launcher/ui/dialogs/CopyInstanceDialog.cpp | 90 ++++++++++++++++++++++ launcher/ui/dialogs/CopyInstanceDialog.h | 15 ++++ launcher/ui/dialogs/CopyInstanceDialog.ui | 58 ++++++++++++-- 8 files changed, 254 insertions(+), 22 deletions(-) create mode 100644 launcher/InstanceCopyPrefs.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 79ac49c76..77440cca9 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -31,6 +31,7 @@ set(CORE_SOURCES # Basic instance manipulation tasks (derived from InstanceTask) InstanceCreationTask.h InstanceCreationTask.cpp + InstanceCopyPrefs.h InstanceCopyTask.h InstanceCopyTask.cpp InstanceImportTask.h diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h new file mode 100644 index 000000000..ac2feab8a --- /dev/null +++ b/launcher/InstanceCopyPrefs.h @@ -0,0 +1,18 @@ +// +// Created by marcelohdez on 10/22/22. +// + +#ifndef LAUNCHER_INSTANCECOPYPREFS_H +#define LAUNCHER_INSTANCECOPYPREFS_H + +struct InstanceCopyPrefs { + bool copySaves; + bool keepPlaytime; + bool copyGameOptions; + bool copyResourcePacks; + bool copyShaderPacks; + bool copyServers; + bool copyMods; +}; + +#endif // LAUNCHER_INSTANCECOPYPREFS_H diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index b1e338844..360f6cfad 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -5,18 +5,66 @@ #include "pathmatcher/RegexpMatcher.h" #include -InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime) +InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, InstanceCopyPrefs prefs) { m_origInstance = origInstance; - m_keepPlaytime = keepPlaytime; + m_keepPlaytime = prefs.keepPlaytime; + QString filter; - if(!copySaves) + if(!prefs.copySaves) { - // FIXME: get this from the original instance type... - auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); - matcherReal->caseSensitive(false); - m_matcher.reset(matcherReal); + appendToFilter(filter, "saves"); } + + if(!prefs.copyGameOptions) { + appendToFilter(filter, "options.txt"); + } + + if(!prefs.copyResourcePacks) + { + appendToFilter(filter, "resourcepacks"); + appendToFilter(filter, "texturepacks"); + } + + if(!prefs.copyShaderPacks) + { + appendToFilter(filter, "shaderpacks"); + } + + if(!prefs.copyServers) + { + appendToFilter(filter, "servers.dat"); + appendToFilter(filter, "servers.dat_old"); + appendToFilter(filter, "server-resource-packs"); + } + + if(!prefs.copyMods) + { + appendToFilter(filter, "coremods"); + appendToFilter(filter, "mods"); + appendToFilter(filter, "config"); + } + + if (!filter.isEmpty()) + { + resetFromMatcher(filter); + } +} + +void InstanceCopyTask::appendToFilter(QString& filter, const QString &append) +{ + if (!filter.isEmpty()) + filter.append('|'); // OR regex + + filter.append("[.]?minecraft/" + append); +} + +void InstanceCopyTask::resetFromMatcher(const QString& regexp) +{ + // FIXME: get this from the original instance type... + auto matcherReal = new RegexpMatcher(regexp); + matcherReal->caseSensitive(false); + m_matcher.reset(matcherReal); } void InstanceCopyTask::executeTask() diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 829017326..d66bec55b 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -1,20 +1,21 @@ #pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -#include #include #include -#include "settings/SettingsObject.h" -#include "BaseVersion.h" +#include #include "BaseInstance.h" +#include "BaseVersion.h" +#include "InstanceCopyPrefs.h" #include "InstanceTask.h" +#include "net/NetJob.h" +#include "settings/SettingsObject.h" +#include "tasks/Task.h" class InstanceCopyTask : public InstanceTask { Q_OBJECT public: - explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime); + explicit InstanceCopyTask(InstancePtr origInstance, InstanceCopyPrefs prefs); protected: //! Entry point for tasks. @@ -22,7 +23,12 @@ protected: void copyFinished(); void copyAborted(); -private: /* data */ +private: + // Helper functions to avoid repeating code + static void appendToFilter(QString &filter, const QString &append); + void resetFromMatcher(const QString ®exp); + + /* data */ InstancePtr m_origInstance; QFuture m_copyFuture; QFutureWatcher m_copyFutureWatcher; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 97152a485..d51f799c6 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1616,7 +1616,17 @@ void MainWindow::on_actionCopyInstance_triggered() if (!copyInstDlg.exec()) return; - auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime()); + auto copyTask = new InstanceCopyTask( + m_selectedInstance, + InstanceCopyPrefs { + copyInstDlg.shouldCopySaves(), + copyInstDlg.shouldKeepPlaytime(), + copyInstDlg.shouldCopyGameOptions(), + copyInstDlg.shouldCopyResourcePacks(), + copyInstDlg.shouldCopyShaderPacks(), + copyInstDlg.shouldCopyServers(), + copyInstDlg.shouldCopyMods() + }); copyTask->setName(copyInstDlg.instName()); copyTask->setGroup(copyInstDlg.instGroup()); copyTask->setIcon(copyInstDlg.iconKey()); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 9ec341bc8..d19888edb 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -80,6 +80,11 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); ui->copySavesCheckbox->setChecked(m_copySaves); ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime); + ui->copyGameOptionsCheckbox->setChecked(m_copyGameOptions); + ui->copyResPacksCheckbox->setChecked(m_copyResourcePacks); + ui->copyShaderPacksCheckbox->setChecked(m_copyShaderPacks); + ui->copyServersCheckbox->setChecked(m_copyServers); + ui->copyModsCheckbox->setChecked(m_copyMods); } CopyInstanceDialog::~CopyInstanceDialog() @@ -168,3 +173,88 @@ void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) m_keepPlaytime = true; } } + +bool CopyInstanceDialog::shouldCopyGameOptions() const +{ + return m_copyGameOptions; +} + +void CopyInstanceDialog::on_copyGameOptionsCheckbox_stateChanged(int state) +{ + if(state == Qt::Unchecked) + { + m_copyGameOptions = false; + } + else if(state == Qt::Checked) + { + m_copyGameOptions = true; + } +} + +bool CopyInstanceDialog::shouldCopyResourcePacks() const +{ + return m_copyResourcePacks; +} + +void CopyInstanceDialog::on_copyResPacksCheckbox_stateChanged(int state) +{ + if(state == Qt::Unchecked) + { + m_copyResourcePacks = false; + } + else if(state == Qt::Checked) + { + m_copyResourcePacks = true; + } +} + +bool CopyInstanceDialog::shouldCopyShaderPacks() const +{ + return m_copyShaderPacks; +} + +void CopyInstanceDialog::on_copyShaderPacksCheckbox_stateChanged(int state) +{ + if(state == Qt::Unchecked) + { + m_copyShaderPacks = false; + } + else if(state == Qt::Checked) + { + m_copyShaderPacks = true; + } +} + +bool CopyInstanceDialog::shouldCopyServers() const +{ + return m_copyServers; +} + +void CopyInstanceDialog::on_copyServersCheckbox_stateChanged(int state) +{ + if(state == Qt::Unchecked) + { + m_copyServers = false; + } + else if(state == Qt::Checked) + { + m_copyServers = true; + } +} + +bool CopyInstanceDialog::shouldCopyMods() const +{ + return m_copyMods; +} + +void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state) +{ + if(state == Qt::Unchecked) + { + m_copyMods = false; + } + else if(state == Qt::Checked) + { + m_copyMods = true; + } +} diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index bf3cd920b..e4c70494f 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -41,6 +41,11 @@ public: QString iconKey() const; bool shouldCopySaves() const; bool shouldKeepPlaytime() const; + bool shouldCopyGameOptions() const; + bool shouldCopyResourcePacks() const; + bool shouldCopyShaderPacks() const; + bool shouldCopyServers() const; + bool shouldCopyMods() const; private slots: @@ -48,6 +53,11 @@ slots: void on_instNameTextBox_textChanged(const QString &arg1); void on_copySavesCheckbox_stateChanged(int state); void on_keepPlaytimeCheckbox_stateChanged(int state); + void on_copyGameOptionsCheckbox_stateChanged(int state); + void on_copyResPacksCheckbox_stateChanged(int state); + void on_copyShaderPacksCheckbox_stateChanged(int state); + void on_copyServersCheckbox_stateChanged(int state); + void on_copyModsCheckbox_stateChanged(int state); private: Ui::CopyInstanceDialog *ui; @@ -55,4 +65,9 @@ private: InstancePtr m_original; bool m_copySaves = true; bool m_keepPlaytime = true; + bool m_copyGameOptions = true; + bool m_copyResourcePacks = true; + bool m_copyShaderPacks = true; + bool m_copyServers = true; + bool m_copyMods = true; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index f4b191e27..e89439e6b 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 345 - 323 + 265 + 425 @@ -33,7 +33,7 @@ - 40 + 60 20 @@ -123,6 +123,50 @@ + + + + Copy the in-game options like FOV, max framerate, etc. + + + Copy game options + + + + + + + true + + + Copy resource packs + + + + + + + Copy shader packs + + + + + + + Copy servers + + + + + + + Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. + + + Copy mods + + + @@ -153,8 +197,8 @@ accept() - 248 - 254 + 254 + 316 157 @@ -169,8 +213,8 @@ reject() - 316 - 260 + 322 + 316 286 From 15593b5c0912b4fe5ad77d6a27e336e9b68ed861 Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez Date: Sat, 22 Oct 2022 23:04:36 -0400 Subject: [PATCH 017/341] Add "Select all" checkbox + ui revamp + code cleanup Signed-off-by: Marcelo Hernandez --- launcher/CMakeLists.txt | 1 + launcher/InstanceCopyPrefs.cpp | 15 +++ launcher/InstanceCopyPrefs.h | 3 + launcher/InstanceCopyTask.cpp | 4 +- launcher/InstanceCopyTask.h | 2 +- launcher/ui/MainWindow.cpp | 12 +- launcher/ui/dialogs/CopyInstanceDialog.cpp | 102 ++++++++-------- launcher/ui/dialogs/CopyInstanceDialog.h | 22 ++-- launcher/ui/dialogs/CopyInstanceDialog.ui | 136 +++++++++++---------- 9 files changed, 153 insertions(+), 144 deletions(-) create mode 100644 launcher/InstanceCopyPrefs.cpp diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 77440cca9..7dc060fb4 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -32,6 +32,7 @@ set(CORE_SOURCES InstanceCreationTask.h InstanceCreationTask.cpp InstanceCopyPrefs.h + InstanceCopyPrefs.cpp InstanceCopyTask.h InstanceCopyTask.cpp InstanceImportTask.h diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp new file mode 100644 index 000000000..56b43a03e --- /dev/null +++ b/launcher/InstanceCopyPrefs.cpp @@ -0,0 +1,15 @@ +// +// Created by marcelohdez on 10/22/22. +// + +#include "InstanceCopyPrefs.h" + +InstanceCopyPrefs::InstanceCopyPrefs(bool setAll) + : copySaves(setAll), + keepPlaytime(setAll), + copyGameOptions(setAll), + copyResourcePacks(setAll), + copyShaderPacks(setAll), + copyServers(setAll), + copyMods(setAll) +{} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index ac2feab8a..d360a8a73 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -6,6 +6,9 @@ #define LAUNCHER_INSTANCECOPYPREFS_H struct InstanceCopyPrefs { + explicit InstanceCopyPrefs(bool setAll); + ~InstanceCopyPrefs() = default; + bool copySaves; bool keepPlaytime; bool copyGameOptions; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 360f6cfad..e0f682244 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -5,7 +5,7 @@ #include "pathmatcher/RegexpMatcher.h" #include -InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, InstanceCopyPrefs prefs) +InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { m_origInstance = origInstance; m_keepPlaytime = prefs.keepPlaytime; @@ -51,7 +51,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, InstanceCopyPrefs p } } -void InstanceCopyTask::appendToFilter(QString& filter, const QString &append) +void InstanceCopyTask::appendToFilter(QString& filter, const QString& append) { if (!filter.isEmpty()) filter.append('|'); // OR regex diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index d66bec55b..4abbf6e69 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -15,7 +15,7 @@ class InstanceCopyTask : public InstanceTask { Q_OBJECT public: - explicit InstanceCopyTask(InstancePtr origInstance, InstanceCopyPrefs prefs); + explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs); protected: //! Entry point for tasks. diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d51f799c6..08005b86c 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1616,17 +1616,7 @@ void MainWindow::on_actionCopyInstance_triggered() if (!copyInstDlg.exec()) return; - auto copyTask = new InstanceCopyTask( - m_selectedInstance, - InstanceCopyPrefs { - copyInstDlg.shouldCopySaves(), - copyInstDlg.shouldKeepPlaytime(), - copyInstDlg.shouldCopyGameOptions(), - copyInstDlg.shouldCopyResourcePacks(), - copyInstDlg.shouldCopyShaderPacks(), - copyInstDlg.shouldCopyServers(), - copyInstDlg.shouldCopyMods() - }); + auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.getChosenOptions()); copyTask->setName(copyInstDlg.instName()); copyTask->setGroup(copyInstDlg.instGroup()); copyTask->setIcon(copyInstDlg.iconKey()); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index d19888edb..0a23cd34f 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -78,13 +78,13 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) } ui->groupBox->setCurrentIndex(index); ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); - ui->copySavesCheckbox->setChecked(m_copySaves); - ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime); - ui->copyGameOptionsCheckbox->setChecked(m_copyGameOptions); - ui->copyResPacksCheckbox->setChecked(m_copyResourcePacks); - ui->copyShaderPacksCheckbox->setChecked(m_copyShaderPacks); - ui->copyServersCheckbox->setChecked(m_copyServers); - ui->copyModsCheckbox->setChecked(m_copyMods); + ui->copySavesCheckbox->setChecked(m_selectedOptions.copySaves); + ui->keepPlaytimeCheckbox->setChecked(m_selectedOptions.keepPlaytime); + ui->copyGameOptionsCheckbox->setChecked(m_selectedOptions.copyGameOptions); + ui->copyResPacksCheckbox->setChecked(m_selectedOptions.copyResourcePacks); + ui->copyShaderPacksCheckbox->setChecked(m_selectedOptions.copyShaderPacks); + ui->copyServersCheckbox->setChecked(m_selectedOptions.copyServers); + ui->copyModsCheckbox->setChecked(m_selectedOptions.copyMods); } CopyInstanceDialog::~CopyInstanceDialog() @@ -139,122 +139,118 @@ void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) updateDialogState(); } -bool CopyInstanceDialog::shouldCopySaves() const +const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const { - return m_copySaves; + return m_selectedOptions; +} + +void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state) +{ + bool checked; + if(state == Qt::Unchecked) + { + checked = false; + } + else if(state == Qt::Checked) + { + checked = true; + } + + checkAllCheckboxes(checked); +} + +void CopyInstanceDialog::checkAllCheckboxes(bool b) +{ + ui->keepPlaytimeCheckbox->setChecked(b); + ui->copySavesCheckbox->setChecked(b); + ui->copyGameOptionsCheckbox->setChecked(b); + ui->copyResPacksCheckbox->setChecked(b); + ui->copyShaderPacksCheckbox->setChecked(b); + ui->copyServersCheckbox->setChecked(b); + ui->copyModsCheckbox->setChecked(b); } void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { if(state == Qt::Unchecked) { - m_copySaves = false; + m_selectedOptions.copySaves = false; } else if(state == Qt::Checked) { - m_copySaves = true; + m_selectedOptions.copySaves = true; } } -bool CopyInstanceDialog::shouldKeepPlaytime() const -{ - return m_keepPlaytime; -} - void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) { if(state == Qt::Unchecked) { - m_keepPlaytime = false; + m_selectedOptions.keepPlaytime = false; } else if(state == Qt::Checked) { - m_keepPlaytime = true; + m_selectedOptions.keepPlaytime = true; } } -bool CopyInstanceDialog::shouldCopyGameOptions() const -{ - return m_copyGameOptions; -} - void CopyInstanceDialog::on_copyGameOptionsCheckbox_stateChanged(int state) { if(state == Qt::Unchecked) { - m_copyGameOptions = false; + m_selectedOptions.copyGameOptions = false; } else if(state == Qt::Checked) { - m_copyGameOptions = true; + m_selectedOptions.copyGameOptions = true; } } -bool CopyInstanceDialog::shouldCopyResourcePacks() const -{ - return m_copyResourcePacks; -} - void CopyInstanceDialog::on_copyResPacksCheckbox_stateChanged(int state) { if(state == Qt::Unchecked) { - m_copyResourcePacks = false; + m_selectedOptions.copyResourcePacks = false; } else if(state == Qt::Checked) { - m_copyResourcePacks = true; + m_selectedOptions.copyResourcePacks = true; } } -bool CopyInstanceDialog::shouldCopyShaderPacks() const -{ - return m_copyShaderPacks; -} - void CopyInstanceDialog::on_copyShaderPacksCheckbox_stateChanged(int state) { if(state == Qt::Unchecked) { - m_copyShaderPacks = false; + m_selectedOptions.copyShaderPacks = false; } else if(state == Qt::Checked) { - m_copyShaderPacks = true; + m_selectedOptions.copyShaderPacks = true; } } -bool CopyInstanceDialog::shouldCopyServers() const -{ - return m_copyServers; -} - void CopyInstanceDialog::on_copyServersCheckbox_stateChanged(int state) { if(state == Qt::Unchecked) { - m_copyServers = false; + m_selectedOptions.copyServers = false; } else if(state == Qt::Checked) { - m_copyServers = true; + m_selectedOptions.copyServers = true; } } -bool CopyInstanceDialog::shouldCopyMods() const -{ - return m_copyMods; -} - void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state) { if(state == Qt::Unchecked) { - m_copyMods = false; + m_selectedOptions.copyMods = false; } else if(state == Qt::Checked) { - m_copyMods = true; + m_selectedOptions.copyMods = true; } } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index e4c70494f..e57de0a1e 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -17,7 +17,7 @@ #include #include "BaseVersion.h" -#include +#include "InstanceCopyPrefs.h" class BaseInstance; @@ -39,18 +39,16 @@ public: QString instName() const; QString instGroup() const; QString iconKey() const; - bool shouldCopySaves() const; - bool shouldKeepPlaytime() const; - bool shouldCopyGameOptions() const; - bool shouldCopyResourcePacks() const; - bool shouldCopyShaderPacks() const; - bool shouldCopyServers() const; - bool shouldCopyMods() const; + const InstanceCopyPrefs& getChosenOptions() const; private slots: void on_iconButton_clicked(); void on_instNameTextBox_textChanged(const QString &arg1); + + // Checkbox options: + void checkAllCheckboxes(bool b); + void on_selectAllCheckbox_stateChanged(int state); void on_copySavesCheckbox_stateChanged(int state); void on_keepPlaytimeCheckbox_stateChanged(int state); void on_copyGameOptionsCheckbox_stateChanged(int state); @@ -63,11 +61,5 @@ private: Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; - bool m_copySaves = true; - bool m_keepPlaytime = true; - bool m_copyGameOptions = true; - bool m_copyResourcePacks = true; - bool m_copyShaderPacks = true; - bool m_copyServers = true; - bool m_copyMods = true; + InstanceCopyPrefs m_selectedOptions = InstanceCopyPrefs(true); // Default to all options as true }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index e89439e6b..822ed7977 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -9,8 +9,8 @@ 0 0 - 265 - 425 + 341 + 385 @@ -60,7 +60,7 @@ - 40 + 60 20 @@ -83,7 +83,10 @@ - + + + 6 + @@ -110,62 +113,73 @@ - - - Copy saves - - - - - - - Keep play time - - - - - - - Copy the in-game options like FOV, max framerate, etc. - - - Copy game options - - - - - - - true - - - Copy resource packs - - - - - - - Copy shader packs - - - - - - - Copy servers - - - - - - - Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. - - - Copy mods - - + + + + + Copy saves + + + + + + + true + + + Copy resource packs + + + + + + + Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs. + + + Copy mods + + + + + + + Copy the in-game options like FOV, max framerate, etc. + + + Copy game options + + + + + + + Copy servers + + + + + + + Keep play time + + + + + + + Copy shader packs + + + + + + + Select all + + + + @@ -183,8 +197,6 @@ iconButton instNameTextBox groupBox - copySavesCheckbox - keepPlaytimeCheckbox From 4caf06bc99dfe34f10fae943374c98b88ad8814d Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez Date: Sun, 23 Oct 2022 00:25:38 -0400 Subject: [PATCH 018/341] Check "Select all" checkbox if all options are already selected + code cleanup Signed-off-by: Marcelo Hernandez --- launcher/InstanceCopyPrefs.cpp | 19 ++-- launcher/InstanceCopyPrefs.h | 17 ++- launcher/ui/dialogs/CopyInstanceDialog.cpp | 124 +++++++-------------- launcher/ui/dialogs/CopyInstanceDialog.h | 6 +- 4 files changed, 65 insertions(+), 101 deletions(-) diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 56b43a03e..fad55d1e5 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -4,12 +4,13 @@ #include "InstanceCopyPrefs.h" -InstanceCopyPrefs::InstanceCopyPrefs(bool setAll) - : copySaves(setAll), - keepPlaytime(setAll), - copyGameOptions(setAll), - copyResourcePacks(setAll), - copyShaderPacks(setAll), - copyServers(setAll), - copyMods(setAll) -{} +bool InstanceCopyPrefs::allTrue() const +{ + return copySaves && + keepPlaytime && + copyGameOptions && + copyResourcePacks && + copyShaderPacks && + copyServers && + copyMods; +} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index d360a8a73..c5c1a7ae2 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -6,16 +6,15 @@ #define LAUNCHER_INSTANCECOPYPREFS_H struct InstanceCopyPrefs { - explicit InstanceCopyPrefs(bool setAll); - ~InstanceCopyPrefs() = default; + bool copySaves = true; + bool keepPlaytime = true; + bool copyGameOptions = true; + bool copyResourcePacks = true; + bool copyShaderPacks = true; + bool copyServers = true; + bool copyMods = true; - bool copySaves; - bool keepPlaytime; - bool copyGameOptions; - bool copyResourcePacks; - bool copyShaderPacks; - bool copyServers; - bool copyMods; + bool allTrue() const; }; #endif // LAUNCHER_INSTANCECOPYPREFS_H diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 0a23cd34f..44e70012d 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -122,6 +122,40 @@ QString CopyInstanceDialog::instGroup() const return ui->groupBox->currentText(); } +const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const +{ + return m_selectedOptions; +} + +void CopyInstanceDialog::checkAllCheckboxes(const bool& b) +{ + ui->keepPlaytimeCheckbox->setChecked(b); + ui->copySavesCheckbox->setChecked(b); + ui->copyGameOptionsCheckbox->setChecked(b); + ui->copyResPacksCheckbox->setChecked(b); + ui->copyShaderPacksCheckbox->setChecked(b); + ui->copyServersCheckbox->setChecked(b); + ui->copyModsCheckbox->setChecked(b); +} + +// Sets b to true if state is a checked checkbox +void CopyInstanceDialog::checkBool(bool& b, const int& state) +{ + if(state == Qt::Unchecked) + { + b = false; + } + else if(state == Qt::Checked) + { + b = true; + } + + // Have "Select all" checkbox checked if all options are already checked: + ui->selectAllCheckbox->blockSignals(true); + ui->selectAllCheckbox->setChecked(m_selectedOptions.allTrue()); + ui->selectAllCheckbox->blockSignals(false); +} + void CopyInstanceDialog::on_iconButton_clicked() { IconPickerDialog dlg(this); @@ -134,123 +168,51 @@ void CopyInstanceDialog::on_iconButton_clicked() } } + void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) { updateDialogState(); } -const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const -{ - return m_selectedOptions; -} - void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state) { bool checked; - if(state == Qt::Unchecked) - { - checked = false; - } - else if(state == Qt::Checked) - { - checked = true; - } - + checkBool(checked, state); checkAllCheckboxes(checked); } -void CopyInstanceDialog::checkAllCheckboxes(bool b) -{ - ui->keepPlaytimeCheckbox->setChecked(b); - ui->copySavesCheckbox->setChecked(b); - ui->copyGameOptionsCheckbox->setChecked(b); - ui->copyResPacksCheckbox->setChecked(b); - ui->copyShaderPacksCheckbox->setChecked(b); - ui->copyServersCheckbox->setChecked(b); - ui->copyModsCheckbox->setChecked(b); -} - void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_selectedOptions.copySaves = false; - } - else if(state == Qt::Checked) - { - m_selectedOptions.copySaves = true; - } + checkBool(m_selectedOptions.copySaves, state); } void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_selectedOptions.keepPlaytime = false; - } - else if(state == Qt::Checked) - { - m_selectedOptions.keepPlaytime = true; - } + checkBool(m_selectedOptions.keepPlaytime, state); } void CopyInstanceDialog::on_copyGameOptionsCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_selectedOptions.copyGameOptions = false; - } - else if(state == Qt::Checked) - { - m_selectedOptions.copyGameOptions = true; - } + checkBool(m_selectedOptions.copyGameOptions, state); } void CopyInstanceDialog::on_copyResPacksCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_selectedOptions.copyResourcePacks = false; - } - else if(state == Qt::Checked) - { - m_selectedOptions.copyResourcePacks = true; - } + checkBool(m_selectedOptions.copyResourcePacks, state); } void CopyInstanceDialog::on_copyShaderPacksCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_selectedOptions.copyShaderPacks = false; - } - else if(state == Qt::Checked) - { - m_selectedOptions.copyShaderPacks = true; - } + checkBool(m_selectedOptions.copyShaderPacks, state); } void CopyInstanceDialog::on_copyServersCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_selectedOptions.copyServers = false; - } - else if(state == Qt::Checked) - { - m_selectedOptions.copyServers = true; - } + checkBool(m_selectedOptions.copyServers, state); } void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state) { - if(state == Qt::Unchecked) - { - m_selectedOptions.copyMods = false; - } - else if(state == Qt::Checked) - { - m_selectedOptions.copyMods = true; - } + checkBool(m_selectedOptions.copyMods, state); } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index e57de0a1e..4171c440f 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -47,7 +47,9 @@ slots: void on_instNameTextBox_textChanged(const QString &arg1); // Checkbox options: - void checkAllCheckboxes(bool b); + void checkAllCheckboxes(const bool& b); + void checkBool(bool& b, const int& state); + void on_selectAllCheckbox_stateChanged(int state); void on_copySavesCheckbox_stateChanged(int state); void on_keepPlaytimeCheckbox_stateChanged(int state); @@ -61,5 +63,5 @@ private: Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; - InstanceCopyPrefs m_selectedOptions = InstanceCopyPrefs(true); // Default to all options as true + InstanceCopyPrefs m_selectedOptions; }; From 6a474a01258d375228a0883b0b5c792049ef74ef Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 20 Oct 2022 14:00:11 +0200 Subject: [PATCH 019/341] fix: update icon generate script Signed-off-by: Sefa Eyeoglu --- launcher/resources/OSX/scalable/launcher.svg | 14 ++- launcher/resources/flat/scalable/launcher.svg | 14 ++- launcher/resources/iOS/scalable/launcher.svg | 14 ++- .../resources/multimc/scalable/launcher.svg | 14 ++- .../resources/pe_blue/scalable/launcher.svg | 14 ++- .../pe_colored/scalable/launcher.svg | 14 ++- .../resources/pe_dark/scalable/launcher.svg | 14 ++- .../resources/pe_light/scalable/launcher.svg | 14 ++- program_info/genicons.sh | 86 ++++++++++++------ program_info/prismlauncher.ico | Bin 102134 -> 372526 bytes 10 files changed, 132 insertions(+), 66 deletions(-) diff --git a/launcher/resources/OSX/scalable/launcher.svg b/launcher/resources/OSX/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/OSX/scalable/launcher.svg +++ b/launcher/resources/OSX/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/launcher/resources/flat/scalable/launcher.svg b/launcher/resources/flat/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/flat/scalable/launcher.svg +++ b/launcher/resources/flat/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/launcher/resources/iOS/scalable/launcher.svg b/launcher/resources/iOS/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/iOS/scalable/launcher.svg +++ b/launcher/resources/iOS/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/launcher/resources/multimc/scalable/launcher.svg b/launcher/resources/multimc/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/multimc/scalable/launcher.svg +++ b/launcher/resources/multimc/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/launcher/resources/pe_blue/scalable/launcher.svg b/launcher/resources/pe_blue/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/pe_blue/scalable/launcher.svg +++ b/launcher/resources/pe_blue/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/launcher/resources/pe_colored/scalable/launcher.svg b/launcher/resources/pe_colored/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/pe_colored/scalable/launcher.svg +++ b/launcher/resources/pe_colored/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/launcher/resources/pe_dark/scalable/launcher.svg b/launcher/resources/pe_dark/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/pe_dark/scalable/launcher.svg +++ b/launcher/resources/pe_dark/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/launcher/resources/pe_light/scalable/launcher.svg b/launcher/resources/pe_light/scalable/launcher.svg index 69dd84b17..aeee84338 100644 --- a/launcher/resources/pe_light/scalable/launcher.svg +++ b/launcher/resources/pe_light/scalable/launcher.svg @@ -37,17 +37,21 @@ https://github.com/PrismLauncher/PrismLauncher - - - CC BY-SA 4.0 - - Prism Launcher + + + + + + + + + diff --git a/program_info/genicons.sh b/program_info/genicons.sh index bfe756d82..42592c4ee 100755 --- a/program_info/genicons.sh +++ b/program_info/genicons.sh @@ -1,39 +1,73 @@ -#/bin/bash +#!/bin/bash -# ICO +svg2png() { + input_file="$1" + output_file="$2" + width="$3" + height="$4" -inkscape -w 16 -h 16 -o prismlauncher_16.png org.prismlauncher.PrismLauncher.svg -inkscape -w 24 -h 24 -o prismlauncher_24.png org.prismlauncher.PrismLauncher.svg -inkscape -w 32 -h 32 -o prismlauncher_32.png org.prismlauncher.PrismLauncher.svg -inkscape -w 48 -h 48 -o prismlauncher_48.png org.prismlauncher.PrismLauncher.svg -inkscape -w 64 -h 64 -o prismlauncher_64.png org.prismlauncher.PrismLauncher.svg -inkscape -w 128 -h 128 -o prismlauncher_128.png org.prismlauncher.PrismLauncher.svg + inkscape -w "$width" -h "$height" -o "$output_file" "$input_file" +} -convert prismlauncher_128.png prismlauncher_64.png prismlauncher_48.png prismlauncher_32.png prismlauncher_24.png prismlauncher_16.png prismlauncher.ico +sipsresize() { + input_file="$1" + output_file="$2" + width="$3" + height="$4" -rm -f prismlauncher_*.png + sips -z "$width" "$height" "$input_file" --out "$output_file" +} -inkscape -w 1024 -h 1024 -o prismlauncher_1024.png org.prismlauncher.PrismLauncher.bigsur.svg +if command -v "inkscape" && command -v "icotool"; then + # Windows ICO + d=$(mktemp -d) -mkdir prismlauncher.iconset + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_16.png" 16 16 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_24.png" 24 24 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_32.png" 32 32 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_48.png" 48 48 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_64.png" 64 64 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_128.png" 128 128 + svg2png org.prismlauncher.PrismLauncher.svg "$d/prismlauncher_256.png" 256 256 -sips -z 16 16 prismlauncher_1024.png --out prismlauncher.iconset/icon_16x16.png -sips -z 32 32 prismlauncher_1024.png --out prismlauncher.iconset/icon_16x16@2x.png -sips -z 32 32 prismlauncher_1024.png --out prismlauncher.iconset/icon_32x32.png -sips -z 64 64 prismlauncher_1024.png --out prismlauncher.iconset/icon_32x32@2x.png -sips -z 128 128 prismlauncher_1024.png --out prismlauncher.iconset/icon_128x128.png -sips -z 256 256 prismlauncher_1024.png --out prismlauncher.iconset/icon_128x128@2x.png -sips -z 256 256 prismlauncher_1024.png --out prismlauncher.iconset/icon_256x256.png -sips -z 512 512 prismlauncher_1024.png --out prismlauncher.iconset/icon_256x256@2x.png -sips -z 512 512 prismlauncher_1024.png --out prismlauncher.iconset/icon_512x512.png -cp prismlauncher_1024.png prismlauncher.iconset/icon_512x512@2x.png + rm prismlauncher.ico && icotool -o prismlauncher.ico -c \ + "$d/prismlauncher_256.png" \ + "$d/prismlauncher_128.png" \ + "$d/prismlauncher_64.png" \ + "$d/prismlauncher_48.png" \ + "$d/prismlauncher_32.png" \ + "$d/prismlauncher_24.png" \ + "$d/prismlauncher_16.png" +else + echo "ERROR: Windows icons were NOT generated!" >&2 + echo "ERROR: requires inkscape and icotool in PATH" +fi -iconutil -c icns prismlauncher.iconset +if command -v "inkscape" && command -v "sips" && command -v "iconutil"; then + # macOS ICNS + d=$(mktemp -d) -rm -f prismlauncher_*.png -rm -rf prismlauncher.iconset + d="$d/prismlauncher.iconset" + mkdir -p "$d" + + svg2png org.prismlauncher.PrismLauncher.bigsur.svg "$d/icon_512x512@2x.png" 1024 1024 + sipsresize "$d/icon_512x512@2.png" "$d/icon_16x16.png" 16 16 + sipsresize "$d/icon_512x512@2.png" "$d/icon_16x16@2.png" 32 32 + sipsresize "$d/icon_512x512@2.png" "$d/icon_32x32.png" 32 32 + sipsresize "$d/icon_512x512@2.png" "$d/icon_32x32@2.png" 64 64 + sipsresize "$d/icon_512x512@2.png" "$d/icon_128x128.png" 128 128 + sipsresize "$d/icon_512x512@2.png" "$d/icon_128x128@2.png" 256 256 + sipsresize "$d/icon_512x512@2.png" "$d/icon_256x256.png" 256 256 + sipsresize "$d/icon_512x512@2.png" "$d/icon_256x256@2.png" 512 512 + iconutil -c icns "$d" +else + echo "ERROR: macOS icons were NOT generated!" >&2 + echo "ERROR: requires inkscape, sips and iconutil in PATH" +fi + +# replace icon in themes for dir in ../launcher/resources/*/scalable do - cp -v org.prismlauncher.PrismLauncher.svg $dir/launcher.svg + cp -v org.prismlauncher.PrismLauncher.svg "$dir/launcher.svg" done diff --git a/program_info/prismlauncher.ico b/program_info/prismlauncher.ico index e4529f93807cd8f40c25820338e2821dc35105cf..2f0fa67ff6be7681f76aa8ccbdfedabcef766b72 100644 GIT binary patch literal 372526 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

ol&v!0pbHjkGtsBcQlsT;+OpWK_) z{js~=4ZB`tY*(=Te=_Qy#HBZc*ovEZhU@D54##lUGP2>G+3}O>q-j-TEi}SMZhRWp zwa-i%9FX(i#kA`iv2%if8)ya zhiBIQ1IC{ieRvg)O;!}*nlcH7n42P$@2*f|`3+`jFSv!r6u>dC$vFPi7RR`L;Z^DN zidVkZOM(s(mV86LC2^3rjB#Szq#j0whhD?OTn}?nqFuZ9e;vm_{)kaH^?UQ};Qzz< z!dtPdY{GJ#99!q~N_OZIhI?rJ8pjA$;&;Ze7*f`n>ji$v^*N*aF5hnm`n4@$+WQ8F zWBB{z8}`9`zxu-nrd+{0Hsd4YjC4bb7BJWGq<;I~Q++j6S1aM@lgB zJ#3%W_tB~^k_Iqw5MRhHxLz=`S7KEBSo7R6Qtn+odj^gyOMzX6-mrdoOmn%Kqo2Wi zkG{BznKCXk$#NeZcDcE3Cwt$U)CJ7<=!?9P`TnESg%)Ka`}+;(56kq~&fhj=gT3!< zm3wnaADH{#u&i6oe9xwTNXmSuu(7|z-G`51`sF9)@6lG9=_m3d<(A*H8SvqO#gJaQ z10J({D<4-i!on@s2ekytlV;!BCiKxZ%kTa1Q$hysU1+fuA@Obe{OTdt^7#Xp5Puco z(q6RK^wo7uaR+M#G4zu&+VDZP{CpU@G-|BbkMKckMngFJTjn~laMk}qd$S4;^Ll{H z_h{1^<7YknEM_j)%)d+=*)P!|Key3Ez&q_ hz~1A>IL2Q2ikDaZOI}`vHntD)j)Wm$$v5QN{|~PZ9Vq|+ literal 102134 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 H688T9&?`5g From b638a6ae950e472c6e6139cfdbaa2950e848efb0 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 24 Oct 2022 09:07:41 -0300 Subject: [PATCH 020/341] fix: retry mod search job after aborting it This way, we don't get stuck with an aborted job in our way! :o Signed-off-by: flow --- .../modplatform/helpers/NetworkModAPI.cpp | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 25 ++++++++++++------- launcher/ui/pages/modplatform/ModModel.h | 1 + 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp index 866e7540c..7633030e1 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ b/launcher/modplatform/helpers/NetworkModAPI.cpp @@ -15,6 +15,7 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); }); QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); + QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted); QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 49766fa66..ed58eb32f 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -267,18 +267,25 @@ void ListModel::searchRequestFailed(QString reason) .arg(m_parent->displayName()) .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); } + + jobPtr.reset(); + searchState = Finished; +} + +void ListModel::searchRequestAborted() +{ + if (searchState != ResetRequested) + qCritical() << "Search task in ModModel aborted by an unknown reason!"; + + // Retry fetching jobPtr.reset(); - if (searchState == ResetRequested) { - beginResetModel(); - modpacks.clear(); - endResetModel(); + beginResetModel(); + modpacks.clear(); + endResetModel(); - nextSearchOffset = 0; - performPaginatedSearch(); - } else { - searchState = Finished; - } + nextSearchOffset = 0; + performPaginatedSearch(); } void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index a58c7c55b..d2636d87e 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -51,6 +51,7 @@ class ListModel : public QAbstractListModel { public slots: void searchRequestFinished(QJsonDocument& doc); void searchRequestFailed(QString reason); + void searchRequestAborted(); void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); From eeb95f89e1200bb23cc2559b5f298b9fdaca7e11 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:40:07 +0800 Subject: [PATCH 021/341] add .mrpack mime type Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- program_info/modrinth-mrpack-mime.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 program_info/modrinth-mrpack-mime.xml diff --git a/program_info/modrinth-mrpack-mime.xml b/program_info/modrinth-mrpack-mime.xml new file mode 100644 index 000000000..5001e5e77 --- /dev/null +++ b/program_info/modrinth-mrpack-mime.xml @@ -0,0 +1,9 @@ + + + + Modrinth Modpack File + + + + + From a89df42561cc3089c4878c0c44353fcd1359bf53 Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez <76508651+marcelohdez@users.noreply.github.com> Date: Mon, 24 Oct 2022 19:27:21 -0400 Subject: [PATCH 022/341] Simplify bool check in CopyInstanceDialog.cpp Signed-off-by: Marcelo Hernandez --- launcher/ui/dialogs/CopyInstanceDialog.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 44e70012d..1b8e2aa0b 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -141,14 +141,7 @@ void CopyInstanceDialog::checkAllCheckboxes(const bool& b) // Sets b to true if state is a checked checkbox void CopyInstanceDialog::checkBool(bool& b, const int& state) { - if(state == Qt::Unchecked) - { - b = false; - } - else if(state == Qt::Checked) - { - b = true; - } + b = (state == Qt::Checked); // Have "Select all" checkbox checked if all options are already checked: ui->selectAllCheckbox->blockSignals(true); From 385c452ddffa2f40b21d7decede9f255e2b24d45 Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez Date: Mon, 24 Oct 2022 20:49:40 -0400 Subject: [PATCH 023/341] remove checkBool function, add updateSelectAllCheckbox function Signed-off-by: Marcelo Hernandez --- launcher/ui/dialogs/CopyInstanceDialog.cpp | 30 ++++++++++++---------- launcher/ui/dialogs/CopyInstanceDialog.h | 9 +++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 1b8e2aa0b..8445f0a91 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -138,12 +138,9 @@ void CopyInstanceDialog::checkAllCheckboxes(const bool& b) ui->copyModsCheckbox->setChecked(b); } -// Sets b to true if state is a checked checkbox -void CopyInstanceDialog::checkBool(bool& b, const int& state) +// Check the "Select all" checkbox checked if all options are already checked: +void CopyInstanceDialog::updateSelectAllCheckbox() { - b = (state == Qt::Checked); - - // Have "Select all" checkbox checked if all options are already checked: ui->selectAllCheckbox->blockSignals(true); ui->selectAllCheckbox->setChecked(m_selectedOptions.allTrue()); ui->selectAllCheckbox->blockSignals(false); @@ -170,42 +167,49 @@ void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1) void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state) { bool checked; - checkBool(checked, state); + checked = (state == Qt::Checked); checkAllCheckboxes(checked); } void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { - checkBool(m_selectedOptions.copySaves, state); + m_selectedOptions.copySaves = (state == Qt::Checked); + updateSelectAllCheckbox(); } void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) { - checkBool(m_selectedOptions.keepPlaytime, state); + m_selectedOptions.keepPlaytime = (state == Qt::Checked); + updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyGameOptionsCheckbox_stateChanged(int state) { - checkBool(m_selectedOptions.copyGameOptions, state); + m_selectedOptions.copyGameOptions = (state == Qt::Checked); + updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyResPacksCheckbox_stateChanged(int state) { - checkBool(m_selectedOptions.copyResourcePacks, state); + m_selectedOptions.copyResourcePacks = (state == Qt::Checked); + updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyShaderPacksCheckbox_stateChanged(int state) { - checkBool(m_selectedOptions.copyShaderPacks, state); + m_selectedOptions.copyShaderPacks = (state == Qt::Checked); + updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyServersCheckbox_stateChanged(int state) { - checkBool(m_selectedOptions.copyServers, state); + m_selectedOptions.copyServers = (state == Qt::Checked); + updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state) { - checkBool(m_selectedOptions.copyMods, state); + m_selectedOptions.copyMods = (state == Qt::Checked); + updateSelectAllCheckbox(); } diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 4171c440f..94015334e 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -45,11 +45,7 @@ private slots: void on_iconButton_clicked(); void on_instNameTextBox_textChanged(const QString &arg1); - - // Checkbox options: - void checkAllCheckboxes(const bool& b); - void checkBool(bool& b, const int& state); - + // Checkboxes void on_selectAllCheckbox_stateChanged(int state); void on_copySavesCheckbox_stateChanged(int state); void on_keepPlaytimeCheckbox_stateChanged(int state); @@ -60,6 +56,9 @@ slots: void on_copyModsCheckbox_stateChanged(int state); private: + void checkAllCheckboxes(const bool& b); + void updateSelectAllCheckbox(); + /* data */ Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; From d9463620e601aa62342cafc3fb2fbeae5f56511c Mon Sep 17 00:00:00 2001 From: tobimori Date: Tue, 25 Oct 2022 09:34:07 +0200 Subject: [PATCH 024/341] move .app rename to packaging process, update mac copyright string Signed-off-by: tobimori --- .github/workflows/build.yml | 5 +++-- CMakeLists.txt | 16 ++++++++-------- program_info/CMakeLists.txt | 1 + 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f67ba4c7e..ac701c1e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -249,8 +249,9 @@ jobs: cmake --install ${{ env.BUILD_DIR }} cd ${{ env.INSTALL_DIR }} - chmod +x "Prism Launcher.app/Contents/MacOS/prismlauncher" - sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "Prism Launcher.app/Contents/MacOS/prismlauncher" + chmod +x "PrismLauncher.app/Contents/MacOS/prismlauncher" + sudo codesign --sign - --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher" + mv "PrismLauncher.app" "Prism Launcher.app" tar -czf ../PrismLauncher.tar.gz * - name: Make Sparkle signature (macOS) diff --git a/CMakeLists.txt b/CMakeLists.txt index 09f62bb3c..611ca1de7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,15 +211,15 @@ if(NOT (UNIX AND APPLE)) endif() if(UNIX AND APPLE) - set(BINARY_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS") - set(LIBRARY_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS") - set(PLUGIN_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS") - set(FRAMEWORK_DEST_DIR "${Launcher_DisplayName}.app/Contents/Frameworks") - set(RESOURCES_DEST_DIR "${Launcher_DisplayName}.app/Contents/Resources") - set(JARS_DEST_DIR "${Launcher_DisplayName}.app/Contents/MacOS/jars") + set(BINARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") + set(LIBRARY_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") + set(PLUGIN_DEST_DIR "${Launcher_Name}.app/Contents/MacOS") + set(FRAMEWORK_DEST_DIR "${Launcher_Name}.app/Contents/Frameworks") + set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources") + set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars") # Apps to bundle - set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_DisplayName}.app") + set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") # Mac bundle settings set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_DisplayName}") @@ -229,7 +229,7 @@ if(UNIX AND APPLE) set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) - set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}") + set(MACOSX_BUNDLE_COPYRIGHT "© 2021-2022 ${Launcher_Copyright_Mac}") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml") diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index f6e2ea848..61949e137 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -15,6 +15,7 @@ set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_DisplayName}" PARENT_SCOPE) set(Launcher_Copyright "Prism Launcher Contributors\\n© 2021-2022 PolyMC Contributors \\n© 2012-2021 MultiMC Contributors") +set(Launcher_Copyright_Mac "Prism Launcher Contributors, © 2021-2022 PolyMC Contributors and © 2012-2021 MultiMC Contributors" PARENT_SCOPE) set(Launcher_Copyright "${Launcher_Copyright}" PARENT_SCOPE) set(Launcher_Domain "prismlauncher.org" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_SCOPE) From 482d3a1d76e1c66e351b400c40d772be624cd9b9 Mon Sep 17 00:00:00 2001 From: tobimori Date: Tue, 25 Oct 2022 15:24:41 +0200 Subject: [PATCH 025/341] update macos copyright year Signed-off-by: tobimori --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 611ca1de7..d2111fe44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,7 +229,7 @@ if(UNIX AND APPLE) set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) - set(MACOSX_BUNDLE_COPYRIGHT "© 2021-2022 ${Launcher_Copyright_Mac}") + set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml") From 610b9711178d164a41f76106968c3fb511570fd8 Mon Sep 17 00:00:00 2001 From: Adrien <66513643+AshtakaOOf@users.noreply.github.com> Date: Wed, 26 Oct 2022 02:09:45 +0200 Subject: [PATCH 026/341] Improve a bit README.md (#305) Add Open Collective and fix some stuff. --- README.md | 44 ++++++++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d7df8e264..dc6cdbc72 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -

+

Prism Launcher logo Prism Launcher logo

+ Prism Launcher is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. + ## Installation - All downloads and instructions for Prism Launcher can be found [on our website](https://prismlauncher.org/download/). @@ -16,25 +18,24 @@ This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. There are development builds available [here](https://github.com/PrismLauncher/PrismLauncher/actions). These have debug information in the binaries, so their file sizes are relatively larger. -Portable builds are provided for on Linux, Windows, and macOS. +Portable builds are provided for Linux, Windows, and macOS. + +For Arch, Debian and Gentoo, respectively, you can use these packages to get compiled development versions: + +[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-blue?style=flat-square&logo=appveyor)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-blue?style=flat-square&logo=appveyor)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-orange?style=flat-square&logo=appveyor)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-purple?style=flat-square&logo=appveyor)](https://packages.gentoo.org/packages/games-action/prismlauncher) -For Debian and Arch, you can use these packages for the latest development versions: -[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-blue)](https://aur.archlinux.org/packages/prismlauncher-git/) -[![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-orange)](https://mpr.makedeb.org/packages/prismlauncher-git) ## Help & Support -Feel free to create an issue if you need help. However, you might find it easier to ask in the Discord server. +Feel free to create an issue if you need help. +#### Join our Discord server: [![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/prismlauncher) -We will also soon be opening up our Matrix channels. -You can already join our Matrix space: +#### Join our Matrix space: +[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&logo=appveyor)](https://matrix.to/#/#prismlauncher:matrix.org) -[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?label=PrismLauncher%20space)](https://matrix.to/#/#prismlauncher:matrix.org) - -We also have a subreddit you can post your issues and suggestions on: - -[r/PrismLauncher](https://www.reddit.com/r/PrismLauncher/) +#### Join our SubReddit: +[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=appveyor)](https://www.reddit.com/r/PrismLauncher/) ## Building @@ -60,13 +61,11 @@ Be aware that if you build this software without removing the provided API keys If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`). -## License +## Sponsors & Partners -All launcher code is available under the GPL-3.0-only license. - -The logo and related assets are under the CC BY-SA 4.0 license. +We thank all the wonderful backers over at Open Collective! Support Prism Launcher by [becoming a backer](https://opencollective.com/prismlauncher). -## Sponsors +[![OpenCollective Backers](https://opencollective.com/prismlauncher/backers.svg?width=890&limit=1000)](https://opencollective.com/prismlauncher#backers) Thanks to JetBrains for providing us a few licenses for all their products, as part of their [Open Source program](https://www.jetbrains.com/opensource/). @@ -85,3 +84,12 @@ Thanks to Netlify for providing us their excellent web services, as part of thei Thanks to the awesome people over at [MacStadium](https://www.macstadium.com/), for providing M1-Macs for development purposes! Powered by MacStadium + + +## License + +All launcher code is available under the GPL-3.0-only license. + +![https://github.com/PrismLauncher/PrismLauncher/blob/develop/LICENSE](https://img.shields.io/github/license/PrismLauncher/PrismLauncher?style=for-the-badge) + +The logo and related assets are under the CC BY-SA 4.0 license. From 63b6c6685ce53e3fac1902e0ee7a6998c5d341d0 Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez Date: Wed, 26 Oct 2022 00:20:36 -0400 Subject: [PATCH 027/341] Abstract away InstanceCopyPrefs' internals through new getSelectedFiltersAsRegex() function + fix typo in comment + remove unused import + add [[nodiscard]] to methods Signed-off-by: Marcelo Hernandez --- launcher/InstanceCopyPrefs.cpp | 33 ++++++++++++ launcher/InstanceCopyPrefs.h | 5 +- launcher/InstanceCopyTask.cpp | 60 +++------------------- launcher/InstanceCopyTask.h | 4 -- launcher/ui/dialogs/CopyInstanceDialog.cpp | 3 +- 5 files changed, 45 insertions(+), 60 deletions(-) diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index fad55d1e5..6432a5351 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -14,3 +14,36 @@ bool InstanceCopyPrefs::allTrue() const copyServers && copyMods; } + +// Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat") +QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const +{ + QStringList filters; + + if(!copySaves) + filters << "saves"; + + if(!copyGameOptions) + filters << "options.txt"; + + if(!copyResourcePacks) + filters << "resourcepacks" << "texturepacks"; + + if(!copyShaderPacks) + filters << "shaderpacks"; + + if(!copyServers) + filters << "servers.dat" << "servers.dat_old" << "server-resource-packs"; + + if(!copyMods) + filters << "coremods" << "mods" << "config"; + + // If we have any filters to add, join them as a single regex string to return: + if (!filters.isEmpty()) { + const QString MC_ROOT = "[.]?minecraft/"; + // Ensure first filter starts with root, then join other filters with OR regex before root (ex: ".minecraft/saves|.minecraft/mods"): + return MC_ROOT + filters.join("|" + MC_ROOT); + } + + return {}; +} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index c5c1a7ae2..432d67c40 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -5,6 +5,8 @@ #ifndef LAUNCHER_INSTANCECOPYPREFS_H #define LAUNCHER_INSTANCECOPYPREFS_H +#include + struct InstanceCopyPrefs { bool copySaves = true; bool keepPlaytime = true; @@ -14,7 +16,8 @@ struct InstanceCopyPrefs { bool copyServers = true; bool copyMods = true; - bool allTrue() const; + [[nodiscard]] bool allTrue() const; + [[nodiscard]] QString getSelectedFiltersAsRegex() const; }; #endif // LAUNCHER_INSTANCECOPYPREFS_H diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index e0f682244..7fbf86363 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -9,62 +9,16 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyP { m_origInstance = origInstance; m_keepPlaytime = prefs.keepPlaytime; - QString filter; - if(!prefs.copySaves) + QString filters = prefs.getSelectedFiltersAsRegex(); + if (!filters.isEmpty()) { - appendToFilter(filter, "saves"); + // Set regex filter: + // FIXME: get this from the original instance type... + auto matcherReal = new RegexpMatcher(filters); + matcherReal->caseSensitive(false); + m_matcher.reset(matcherReal); } - - if(!prefs.copyGameOptions) { - appendToFilter(filter, "options.txt"); - } - - if(!prefs.copyResourcePacks) - { - appendToFilter(filter, "resourcepacks"); - appendToFilter(filter, "texturepacks"); - } - - if(!prefs.copyShaderPacks) - { - appendToFilter(filter, "shaderpacks"); - } - - if(!prefs.copyServers) - { - appendToFilter(filter, "servers.dat"); - appendToFilter(filter, "servers.dat_old"); - appendToFilter(filter, "server-resource-packs"); - } - - if(!prefs.copyMods) - { - appendToFilter(filter, "coremods"); - appendToFilter(filter, "mods"); - appendToFilter(filter, "config"); - } - - if (!filter.isEmpty()) - { - resetFromMatcher(filter); - } -} - -void InstanceCopyTask::appendToFilter(QString& filter, const QString& append) -{ - if (!filter.isEmpty()) - filter.append('|'); // OR regex - - filter.append("[.]?minecraft/" + append); -} - -void InstanceCopyTask::resetFromMatcher(const QString& regexp) -{ - // FIXME: get this from the original instance type... - auto matcherReal = new RegexpMatcher(regexp); - matcherReal->caseSensitive(false); - m_matcher.reset(matcherReal); } void InstanceCopyTask::executeTask() diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 4abbf6e69..1f29b8545 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -24,10 +24,6 @@ protected: void copyAborted(); private: - // Helper functions to avoid repeating code - static void appendToFilter(QString &filter, const QString &append); - void resetFromMatcher(const QString ®exp); - /* data */ InstancePtr m_origInstance; QFuture m_copyFuture; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index 8445f0a91..e658f26d5 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -44,7 +44,6 @@ #include "BaseVersion.h" #include "icons/IconList.h" -#include "tasks/Task.h" #include "BaseInstance.h" #include "InstanceList.h" @@ -138,7 +137,7 @@ void CopyInstanceDialog::checkAllCheckboxes(const bool& b) ui->copyModsCheckbox->setChecked(b); } -// Check the "Select all" checkbox checked if all options are already checked: +// Check the "Select all" checkbox if all options are already selected: void CopyInstanceDialog::updateSelectAllCheckbox() { ui->selectAllCheckbox->blockSignals(true); From 2e0f8189057c2d9a3d76a9d88afe59f8daf41b08 Mon Sep 17 00:00:00 2001 From: Piper McCorkle Date: Mon, 24 Oct 2022 13:19:30 -0500 Subject: [PATCH 028/341] Add a snapcraft.yml for building Snaps The included snapcraft.yml can be used to build a Snap (the application format used by Canonical's modern package manager) out of Prism. If the project wants in the future, Prism can publish these Snaps to the Snap Store so the Prism Launcher can be installed through the Ubuntu Software app on vanilla Ubuntu. I haven't registered the Snap name, so it's currently free for anyone to reserve. I'd suggest that a Prism developer register the name prismlauncher at https://snapcraft.io/register-snap to ensure the name belongs to the project, even if there are no plans of setting up CI to publish snaps in the short term. I have also modified JavaUtils.cpp to be able to autodetect the Java versions included in the Snap, and added "*.snap" to the .gitignore so the compiled Snap isn't accidentally committed to the repository. Signed-off-by: Piper McCorkle --- .gitignore | 3 ++ launcher/java/JavaUtils.cpp | 25 +++++++++++------ snap/snapcraft.yaml | 55 +++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 snap/snapcraft.yaml diff --git a/.gitignore b/.gitignore index f5917a46f..b9a793d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ result/ # Flatpak .flatpak-builder flatbuild + +# Snap +*.snap \ No newline at end of file diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 6c0c60cd3..5efbc7a86 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -439,19 +439,28 @@ QList JavaUtils::FindJavaPaths() javas.append(FS::PathCombine(prefix, "bin/java")); } }; + // java installed in a snap is installed in the standard directory, but underneath $SNAP + auto snap = qEnvironmentVariable("SNAP"); + auto scanJavaDirs = [&](const QString & dirPath) + { + scanJavaDir(dirPath); + if (!snap.isNull()) { + scanJavaDir(snap + dirPath); + } + }; // oracle RPMs - scanJavaDir("/usr/java"); + scanJavaDirs("/usr/java"); // general locations used by distro packaging - scanJavaDir("/usr/lib/jvm"); - scanJavaDir("/usr/lib64/jvm"); - scanJavaDir("/usr/lib32/jvm"); + scanJavaDirs("/usr/lib/jvm"); + scanJavaDirs("/usr/lib64/jvm"); + scanJavaDirs("/usr/lib32/jvm"); // javas stored in Prism Launcher's folder - scanJavaDir("java"); + scanJavaDirs("java"); // manually installed JDKs in /opt - scanJavaDir("/opt/jdk"); - scanJavaDir("/opt/jdks"); + scanJavaDirs("/opt/jdk"); + scanJavaDirs("/opt/jdks"); // flatpak - scanJavaDir("/app/jdk"); + scanJavaDirs("/app/jdk"); javas = addJavasFromEnv(javas); javas.removeDuplicates(); return javas; diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 000000000..f64e5b0a5 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,55 @@ +name: prismlauncher +license: GPL-3.0-only +base: core20 +website: https://prismlauncher.org/ +source-code: https://github.com/PrismLauncher/PrismLauncher +issues: https://github.com/PrismLauncher/PrismLauncher/issues +donation: https://opencollective.com/prismlauncher +contact: https://discord.gg/prismlauncher +summary: A custom Minecraft launcher with modpack support +adopt-info: prismlauncher + +grade: devel +confinement: strict + +architectures: + - build-on: amd64 + - build-on: arm64 + +parts: + prismlauncher: + parse-info: + - usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml + plugin: cmake + build-packages: + - default-jdk-headless + stage-packages: + - openjdk-17-jre + - openjdk-8-jre + source: . + override-pull: | + snapcraftctl pull + # Fix the icon reference in the desktop file + sed -i.bak -e 's|Icon=org.prismlauncher.PrismLauncher|Icon=/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg|g' program_info/org.prismlauncher.PrismLauncher.desktop.in + # Remove the build directory so that local development doesn't interfere with Snap compilation + rm -rf build + cmake-generator: Ninja + cmake-parameters: + - "-DCMAKE_INSTALL_PREFIX=/usr" + - "-DCMAKE_BUILD_TYPE=RelWithDebInfo" + - "-DENABLE_LTO=ON" + - "-DLauncher_BUILD_PLATFORM=snap" + +apps: + prismlauncher: + common-id: org.prismlauncher.PrismLauncher + desktop: usr/share/applications/org.prismlauncher.PrismLauncher.desktop + command: usr/bin/prismlauncher + extensions: + - kde-neon + plugs: + - home + - opengl + - network + - network-bind + - audio-playback \ No newline at end of file From aacf7938ae6cf3447d02847a1a4f606bd0b2f2cf Mon Sep 17 00:00:00 2001 From: Piper McCorkle Date: Wed, 26 Oct 2022 06:11:19 -0500 Subject: [PATCH 029/341] Add CI build for Snap Signed-off-by: Piper McCorkle --- .github/workflows/build.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac701c1e0..31e4112a6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -412,5 +412,25 @@ jobs: with: name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - - + snap: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: 'true' + - name: Set short version + shell: bash + run: | + ver_short=`git rev-parse --short HEAD` + echo "VERSION=$ver_short" >> $GITHUB_ENV + - name: Package Snap (Linux) + id: snapcraft + if: runner.os == 'Linux' && matrix.qt_ver != 5 + uses: snapcore/action-build@v1 + - name: Upload Snap (Linux) + if: runner.os == 'Linux' && matrix.qt_ver != 5 + uses: actions/upload-artifact@v3 + with: + name: prismlauncher_${{ env.VERSION }}_amd64.snap + path: ${{ steps.snapcraft.outputs.snap }} From 990cfb7b3378aa5c4f0482c1f6ef4bfa8b073e81 Mon Sep 17 00:00:00 2001 From: Adrien <66513643+AshtakaOOf@users.noreply.github.com> Date: Thu, 27 Oct 2022 18:08:56 +0200 Subject: [PATCH 030/341] Remove appveyor I forgor :skull: But seriously I forgor to remove the &logo=appveyor thing AAAAAH!!! Signed-off-by: Adrien <66513643+AshtakaOOf@users.noreply.github.com> --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc6cdbc72..db0e2c623 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Portable builds are provided for Linux, Windows, and macOS. For Arch, Debian and Gentoo, respectively, you can use these packages to get compiled development versions: -[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-blue?style=flat-square&logo=appveyor)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-blue?style=flat-square&logo=appveyor)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-orange?style=flat-square&logo=appveyor)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-purple?style=flat-square&logo=appveyor)](https://packages.gentoo.org/packages/games-action/prismlauncher) +[![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--git-blue?style=flat-square)](https://aur.archlinux.org/packages/prismlauncher-qt5-git/) [![prismlauncher-git](https://img.shields.io/badge/aur-prismlauncher--qt5--git-blue?style=flat-square)](https://aur.archlinux.org/packages/prismlauncher-git/) [![prismlauncher-git](https://img.shields.io/badge/mpr-prismlauncher--git-orange?style=flat-square)](https://mpr.makedeb.org/packages/prismlauncher-git) [![prismlauncher-9999](https://img.shields.io/badge/gentoo-prismlauncher--9999-purple?style=flat-square)](https://packages.gentoo.org/packages/games-action/prismlauncher) ## Help & Support @@ -32,10 +32,10 @@ Feel free to create an issue if you need help. [![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/prismlauncher) #### Join our Matrix space: -[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&logo=appveyor)](https://matrix.to/#/#prismlauncher:matrix.org) +[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge)](https://matrix.to/#/#prismlauncher:matrix.org) #### Join our SubReddit: -[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=appveyor)](https://www.reddit.com/r/PrismLauncher/) +[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge)](https://www.reddit.com/r/PrismLauncher/) ## Building From b18466da8430ba782e59f50812f3724defa1f2a9 Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Thu, 27 Oct 2022 12:29:41 +0100 Subject: [PATCH 031/341] Fix: Don't specify x86 in manifest Signed-off-by: TheLastRar --- program_info/prismlauncher.manifest.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program_info/prismlauncher.manifest.in b/program_info/prismlauncher.manifest.in index 1d7644450..6f4467c7d 100644 --- a/program_info/prismlauncher.manifest.in +++ b/program_info/prismlauncher.manifest.in @@ -10,7 +10,7 @@ - + Custom Minecraft launcher for managing multiple installs. From d35c985d2ebca44903bedba87c99efb8db0f158d Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 23 Oct 2022 12:57:29 +0200 Subject: [PATCH 032/341] feat(actions): use clang32 for building on windows *way* faster Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6400791d..a7dd31d7e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,12 +30,12 @@ jobs: - os: windows-2022 name: "Windows-Legacy" - msystem: mingw32 + msystem: clang32 qt_ver: 5 - os: windows-2022 name: "Windows" - msystem: mingw32 + msystem: clang32 qt_ver: 6 - os: macos-12 @@ -89,6 +89,7 @@ jobs: update: true install: >- git + mingw-w64-x86_64-binutils pacboy: >- toolchain:p cmake:p @@ -99,7 +100,6 @@ jobs: qt${{ matrix.qt_ver }}-imageformats:p quazip-qt${{ matrix.qt_ver }}:p ccache:p - nsis:p ${{ matrix.qt_ver == 6 && 'qt6-5compat:p' || '' }} - name: Setup ccache @@ -194,7 +194,7 @@ jobs: if: runner.os == 'Windows' shell: msys2 {0} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -G Ninja - name: Configure CMake (Linux) if: runner.os == 'Linux' @@ -280,7 +280,7 @@ jobs: cd ${{ env.INSTALL_DIR }} if [ "${{ matrix.qt_ver }}" == "5" ]; then - cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./ + cp /clang32/bin/libcrypto-1_1.dll /clang32/bin/libssl-1_1.dll ./ fi - name: Package (Windows, portable) @@ -292,7 +292,6 @@ jobs: - name: Package (Windows, installer) if: runner.os == 'Windows' - shell: msys2 {0} run: | cd ${{ env.INSTALL_DIR }} makensis -NOCD "${{ github.workspace }}/${{ env.BUILD_DIR }}/program_info/win_install.nsi" From 2ba3de79d8df31d196f0cd249b1606e7233cf840 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 27 Oct 2022 21:14:30 +0200 Subject: [PATCH 033/341] chore: add comment about copy bug Signed-off-by: Sefa Eyeoglu --- launcher/FileSystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 39e68c207..76cfccb0b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -401,6 +401,7 @@ bool overrideFolder(QString overwritten_path, QString override_path) std::error_code err; fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; + // FIXME: hello traveller! Apparently std::copy does NOT overwrite existing files on GNU libstdc++ on Windows? fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); if (err) { From 93894f62ffcb8c33caf36e8801e163738cc2873d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 28 Oct 2022 00:05:11 +0200 Subject: [PATCH 034/341] fix: avoid segfault for unexpected API reponse Signed-off-by: Sefa Eyeoglu --- .../modrinth/ModrinthPackIndex.cpp | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 3e53becb4..ae45e0966 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -1,20 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* -* 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 . -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * 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 . + */ #include "ModrinthPackIndex.h" #include "ModrinthAPI.h" @@ -35,7 +35,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.provider = ModPlatform::Provider::MODRINTH; pack.name = Json::requireString(obj, "title"); - + pack.slug = Json::ensureString(obj, "slug", ""); if (!pack.slug.isEmpty()) pack.websiteUrl = "https://modrinth.com/mod/" + pack.slug; @@ -59,23 +59,23 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.extraData.issuesUrl = Json::ensureString(obj, "issues_url"); - if(pack.extraData.issuesUrl.endsWith('/')) + if (pack.extraData.issuesUrl.endsWith('/')) pack.extraData.issuesUrl.chop(1); pack.extraData.sourceUrl = Json::ensureString(obj, "source_url"); - if(pack.extraData.sourceUrl.endsWith('/')) + if (pack.extraData.sourceUrl.endsWith('/')) pack.extraData.sourceUrl.chop(1); pack.extraData.wikiUrl = Json::ensureString(obj, "wiki_url"); - if(pack.extraData.wikiUrl.endsWith('/')) + if (pack.extraData.wikiUrl.endsWith('/')) pack.extraData.wikiUrl.chop(1); pack.extraData.discordUrl = Json::ensureString(obj, "discord_url"); - if(pack.extraData.discordUrl.endsWith('/')) + if (pack.extraData.discordUrl.endsWith('/')) pack.extraData.discordUrl.chop(1); auto donate_arr = Json::ensureArray(obj, "donation_urls"); - for(auto d : donate_arr){ + for (auto d : donate_arr) { auto d_obj = Json::requireObject(d); ModPlatform::DonationData donate; @@ -104,7 +104,7 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, 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()) // Heuristic to check if the returned value is valid unsortedVersions.append(file); } auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { @@ -116,7 +116,8 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, pack.versionsLoaded = true; } -auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_type, QString preferred_file_name) -> ModPlatform::IndexedVersion +auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_type, QString preferred_file_name) + -> ModPlatform::IndexedVersion { ModPlatform::IndexedVersion file; @@ -141,6 +142,12 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t auto files = Json::requireArray(obj, "files"); int i = 0; + if (files.empty()) { + // This should not happen normally, but check just in case + qWarning() << "Modrinth returned an unexpected empty list of files:" << obj; + return {}; + } + // Find correct file (needed in cases where one version may have multiple files) // Will default to the last one if there's no primary (though I think Modrinth requires that // at least one file is primary, idk) @@ -167,7 +174,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject &obj, QString preferred_hash_t file.fileName = Json::requireString(parent, "filename"); file.is_preferred = Json::requireBoolean(parent, "primary") || (files.count() == 1); auto hash_list = Json::requireObject(parent, "hashes"); - + if (hash_list.contains(preferred_hash_type)) { file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash_type = preferred_hash_type; From a5be974f1a53a8e56527d639beb4c16f3a7e18c6 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Thu, 27 Oct 2022 21:05:32 +0200 Subject: [PATCH 035/341] feat: use clang64 on qt6 builds Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca82d3d8f..dd2c05994 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: - os: windows-2022 name: "Windows" - msystem: clang32 + msystem: clang64 qt_ver: 6 - os: macos-12 From 03b48554b1dcffc662b4275af6defbf3516eada2 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sat, 29 Oct 2022 00:01:22 +0800 Subject: [PATCH 036/341] fix(style): convert tabs to spaces Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- cmake/MacOSXBundleInfo.plist.in | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index 597beaa61..400e482fe 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -48,21 +48,21 @@ CFBundleTypeExtensions - - zip + + zip mrpack - - CFBundleTypeName - Prism Launcher instance - CFBundleTypeOSTypes - - TEXT - utxt - TUTX - **** - - CFBundleTypeRole - Viewer + + CFBundleTypeName + Prism Launcher instance + CFBundleTypeOSTypes + + TEXT + utxt + TUTX + **** + + CFBundleTypeRole + Viewer LSHandlerRank Alternate From e6e92f2b0e9ef16bdbae371ccb284339a70cff02 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 28 Oct 2022 13:31:21 -0300 Subject: [PATCH 037/341] fix: only allow workarounds for blocked mods from MR when 100% safe If a version on Modrinth has more than a single mod loader associated, it means that it's possible we might get the wrong file for download, since individual files don't really have this kind of metadata in the API response. So, in such cases, it's best to let the user take care of it instead. Signed-off-by: flow --- .../modplatform/flame/FileResolvingTask.cpp | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index c50abb8f2..25b56fbd1 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -3,6 +3,8 @@ #include "Json.h" #include "net/Upload.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" + Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest& toProcess) : m_network(network), m_toProcess(toProcess) {} @@ -84,18 +86,21 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { delete bytes; continue; } + QJsonDocument doc = QJsonDocument::fromJson(*bytes); auto obj = doc.object(); - auto array = Json::requireArray(obj,"files"); - for (auto file: array) { - auto fileObj = Json::requireObject(file); - auto primary = Json::requireBoolean(fileObj,"primary"); - if (primary) { - out->url = Json::requireUrl(fileObj,"url"); - qDebug() << "Found alternative on modrinth " << out->fileName; - break; - } + auto file = Modrinth::loadIndexedPackVersion(obj); + + // 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) { + out->url = file.downloadUrl; + qDebug() << "Found alternative on modrinth " << out->fileName; + } else { + out->resolved = false; } + delete bytes; } //copy to an output list and filter out projects found on modrinth From 76146e0f17dc56a68128edb13a73f4852af86e84 Mon Sep 17 00:00:00 2001 From: leo78913 Date: Fri, 28 Oct 2022 15:51:20 -0300 Subject: [PATCH 038/341] add a flat white version of the launch icon Signed-off-by: leo78913 --- launcher/resources/flat_white/flat_white.qrc | 1 + .../resources/flat_white/scalable/launch.svg | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 launcher/resources/flat_white/scalable/launch.svg diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc index 9df690600..e11d63165 100644 --- a/launcher/resources/flat_white/flat_white.qrc +++ b/launcher/resources/flat_white/flat_white.qrc @@ -46,5 +46,6 @@ scalable/export.svg scalable/rename.svg scalable/tag.svg + scalable/launch.svg diff --git a/launcher/resources/flat_white/scalable/launch.svg b/launcher/resources/flat_white/scalable/launch.svg new file mode 100644 index 000000000..9e7594318 --- /dev/null +++ b/launcher/resources/flat_white/scalable/launch.svg @@ -0,0 +1,16 @@ + + + + + From be3c9abcfeb68ad76914848e7135b2595490beda Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 28 Oct 2022 20:11:46 -0300 Subject: [PATCH 039/341] fix: copy single files too in FS::copy eek Signed-off-by: flow --- launcher/FileSystem.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 76cfccb0b..9bca847e7 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -182,6 +182,21 @@ bool copy::operator()(const QString& offset) if (!m_followSymlinks) opt |= copy_opts::copy_symlinks; + // Function that'll do the actual copying + auto copy_file = [&](QString src_path, QString relative_dst_path) { + if (m_blacklist && m_blacklist->matches(relative_dst_path)) + return; + + auto dst_path = PathCombine(dst, relative_dst_path); + ensureFilePathExists(dst_path); + + fs::copy(toStdString(src_path), toStdString(dst_path), opt, err); + if (err) { + qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; + } + }; // We can't use copy_opts::recursive because we need to take into account the // blacklisted paths, so we iterate over the source directory, and if there's no blacklist @@ -193,20 +208,13 @@ bool copy::operator()(const QString& offset) auto src_path = source_it.next(); auto relative_path = src_dir.relativeFilePath(src_path); - if (m_blacklist && m_blacklist->matches(relative_path)) - continue; - - auto dst_path = PathCombine(dst, relative_path); - ensureFilePathExists(dst_path); - - fs::copy(toStdString(src_path), toStdString(dst_path), opt, err); - if (err) { - qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); - qDebug() << "Source file:" << src_path; - qDebug() << "Destination file:" << dst_path; - } + copy_file(src_path, relative_path); } + // If the root src is not a directory, the previous iterator won't run. + if (!fs::is_directory(toStdString(src))) + copy_file(src, ""); + return err.value() == 0; } From 81333515e0ecfa0ad0880032808a220a3f463e30 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 28 Oct 2022 20:55:25 -0300 Subject: [PATCH 040/341] feat(tests): add test for FS::copy with files Signed-off-by: flow --- tests/FileSystem_test.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 47a963b0c..21270f6f6 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -183,6 +183,32 @@ slots: f(); } + void test_copy_single_file() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + + { + QString file = QFINDTESTDATA("testdata/FileSystem/test_folder/pack.mcmeta"); + + qDebug() << "From:" << file << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::copy c(file, target_dir.filePath("pack.mcmeta")); + c(); + + auto filter = QDir::Filter::Files; + + for (auto entry: target_dir.entryList(filter)) { + qDebug() << entry; + } + + QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta")); + } + } + void test_getDesktop() { QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); From c00f96c7ca49a624ea8e9c4774ea11e954bbdc4b Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez Date: Sat, 29 Oct 2022 00:55:33 -0400 Subject: [PATCH 041/341] create getters and setters for InstanceCopyPrefs + use pragma once like other .h files in this directory Signed-off-by: Marcelo Hernandez --- launcher/InstanceCopyPrefs.cpp | 72 ++++++++++++++++++++++ launcher/InstanceCopyPrefs.h | 29 ++++++--- launcher/InstanceCopyTask.cpp | 2 +- launcher/ui/dialogs/CopyInstanceDialog.cpp | 28 ++++----- 4 files changed, 109 insertions(+), 22 deletions(-) diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index 6432a5351..ae30bb824 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -47,3 +47,75 @@ QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const return {}; } + +// ======= Getters ======= +bool InstanceCopyPrefs::isCopySavesEnabled() const +{ + return copySaves; +} + +bool InstanceCopyPrefs::isKeepPlaytimeEnabled() const +{ + return keepPlaytime; +} + +bool InstanceCopyPrefs::isCopyGameOptionsEnabled() const +{ + return copyGameOptions; +} + +bool InstanceCopyPrefs::isCopyResourcePacksEnabled() const +{ + return copyResourcePacks; +} + +bool InstanceCopyPrefs::isCopyShaderPacksEnabled() const +{ + return copyShaderPacks; +} + +bool InstanceCopyPrefs::isCopyServersEnabled() const +{ + return copyServers; +} + +bool InstanceCopyPrefs::isCopyModsEnabled() const +{ + return copyMods; +} + +// ======= Setters ======= +void InstanceCopyPrefs::enableCopySaves(bool b) +{ + copySaves = b; +} + +void InstanceCopyPrefs::enableKeepPlaytime(bool b) +{ + keepPlaytime = b; +} + +void InstanceCopyPrefs::enableCopyGameOptions(bool b) +{ + copyGameOptions = b; +} + +void InstanceCopyPrefs::enableCopyResourcePacks(bool b) +{ + copyResourcePacks = b; +} + +void InstanceCopyPrefs::enableCopyShaderPacks(bool b) +{ + copyShaderPacks = b; +} + +void InstanceCopyPrefs::enableCopyServers(bool b) +{ + copyServers = b; +} + +void InstanceCopyPrefs::enableCopyMods(bool b) +{ + copyMods = b; +} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 432d67c40..3855965de 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -2,12 +2,32 @@ // Created by marcelohdez on 10/22/22. // -#ifndef LAUNCHER_INSTANCECOPYPREFS_H -#define LAUNCHER_INSTANCECOPYPREFS_H +#pragma once #include struct InstanceCopyPrefs { + public: + [[nodiscard]] bool allTrue() const; + [[nodiscard]] QString getSelectedFiltersAsRegex() const; + // Getters + [[nodiscard]] bool isCopySavesEnabled() const; + [[nodiscard]] bool isKeepPlaytimeEnabled() const; + [[nodiscard]] bool isCopyGameOptionsEnabled() const; + [[nodiscard]] bool isCopyResourcePacksEnabled() const; + [[nodiscard]] bool isCopyShaderPacksEnabled() const; + [[nodiscard]] bool isCopyServersEnabled() const; + [[nodiscard]] bool isCopyModsEnabled() const; + // Setters + void enableCopySaves(bool b); + void enableKeepPlaytime(bool b); + void enableCopyGameOptions(bool b); + void enableCopyResourcePacks(bool b); + void enableCopyShaderPacks(bool b); + void enableCopyServers(bool b); + void enableCopyMods(bool b); + + protected: // data bool copySaves = true; bool keepPlaytime = true; bool copyGameOptions = true; @@ -15,9 +35,4 @@ struct InstanceCopyPrefs { bool copyShaderPacks = true; bool copyServers = true; bool copyMods = true; - - [[nodiscard]] bool allTrue() const; - [[nodiscard]] QString getSelectedFiltersAsRegex() const; }; - -#endif // LAUNCHER_INSTANCECOPYPREFS_H diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index 7fbf86363..a4ea947d2 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -8,7 +8,7 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs) { m_origInstance = origInstance; - m_keepPlaytime = prefs.keepPlaytime; + m_keepPlaytime = prefs.isKeepPlaytimeEnabled(); QString filters = prefs.getSelectedFiltersAsRegex(); if (!filters.isEmpty()) diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index e658f26d5..f76b509ea 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -77,13 +77,13 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) } ui->groupBox->setCurrentIndex(index); ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); - ui->copySavesCheckbox->setChecked(m_selectedOptions.copySaves); - ui->keepPlaytimeCheckbox->setChecked(m_selectedOptions.keepPlaytime); - ui->copyGameOptionsCheckbox->setChecked(m_selectedOptions.copyGameOptions); - ui->copyResPacksCheckbox->setChecked(m_selectedOptions.copyResourcePacks); - ui->copyShaderPacksCheckbox->setChecked(m_selectedOptions.copyShaderPacks); - ui->copyServersCheckbox->setChecked(m_selectedOptions.copyServers); - ui->copyModsCheckbox->setChecked(m_selectedOptions.copyMods); + ui->copySavesCheckbox->setChecked(m_selectedOptions.isCopySavesEnabled()); + ui->keepPlaytimeCheckbox->setChecked(m_selectedOptions.isKeepPlaytimeEnabled()); + ui->copyGameOptionsCheckbox->setChecked(m_selectedOptions.isCopyGameOptionsEnabled()); + ui->copyResPacksCheckbox->setChecked(m_selectedOptions.isCopyResourcePacksEnabled()); + ui->copyShaderPacksCheckbox->setChecked(m_selectedOptions.isCopyShaderPacksEnabled()); + ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled()); + ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); } CopyInstanceDialog::~CopyInstanceDialog() @@ -172,43 +172,43 @@ void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state) void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) { - m_selectedOptions.copySaves = (state == Qt::Checked); + m_selectedOptions.enableCopySaves(state == Qt::Checked); updateSelectAllCheckbox(); } void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) { - m_selectedOptions.keepPlaytime = (state == Qt::Checked); + m_selectedOptions.enableKeepPlaytime(state == Qt::Checked); updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyGameOptionsCheckbox_stateChanged(int state) { - m_selectedOptions.copyGameOptions = (state == Qt::Checked); + m_selectedOptions.enableCopyGameOptions(state == Qt::Checked); updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyResPacksCheckbox_stateChanged(int state) { - m_selectedOptions.copyResourcePacks = (state == Qt::Checked); + m_selectedOptions.enableCopyResourcePacks(state == Qt::Checked); updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyShaderPacksCheckbox_stateChanged(int state) { - m_selectedOptions.copyShaderPacks = (state == Qt::Checked); + m_selectedOptions.enableCopyShaderPacks(state == Qt::Checked); updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyServersCheckbox_stateChanged(int state) { - m_selectedOptions.copyServers = (state == Qt::Checked); + m_selectedOptions.enableCopyServers(state == Qt::Checked); updateSelectAllCheckbox(); } void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state) { - m_selectedOptions.copyMods = (state == Qt::Checked); + m_selectedOptions.enableCopyMods(state == Qt::Checked); updateSelectAllCheckbox(); } From 5ed7eb9d537558da603683d517f5537f0e4537a1 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 29 Oct 2022 13:12:51 +0200 Subject: [PATCH 042/341] chore: update credits Signed-off-by: Sefa Eyeoglu --- launcher/ui/dialogs/AboutDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index cecda1df5..52d6baef4 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -97,7 +97,7 @@ QString getCreditsHtml() stream << "
\n"; stream << "

" << QObject::tr("With thanks to", "About Credits") << "

\n"; - stream << QString("

Boba %1

\n") .arg(getWebsite("https://cmdplusv.neocities.org/")); + stream << QString("

Boba %1

\n") .arg(getWebsite("https://bobaonline.neocities.org/")); stream << QString("

Davi Rafael %1

\n") .arg(getWebsite("https://auti.one/")); stream << QString("

Fulmine %1

\n") .arg(getWebsite("https://www.fulmine.xyz/")); stream << QString("

ely %1

\n") .arg(getGitHub("elyrodso")); From 7f991ed897d0f344166511daddde4aa4994a67b0 Mon Sep 17 00:00:00 2001 From: txtsd Date: Sat, 29 Oct 2022 19:24:12 +0530 Subject: [PATCH 043/341] feat: Add Repology badge to README Signed-off-by: txtsd --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index db0e2c623..57f1e30a3 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ This is a **fork** of the MultiMC Launcher and not endorsed by MultiMC. ## Installation + + Packaging status + + - All downloads and instructions for Prism Launcher can be found [on our website](https://prismlauncher.org/download/). - Last build status can be found [here](https://github.com/PrismLauncher/PrismLauncher/actions). From b142a6da5b2ae433ce8164c7be0a3c3870131414 Mon Sep 17 00:00:00 2001 From: fn2006 Date: Sat, 29 Oct 2022 16:10:18 +0100 Subject: [PATCH 044/341] fix hardcoded svg Signed-off-by: fn2006 --- buildconfig/BuildConfig.cpp.in | 1 + buildconfig/BuildConfig.h | 1 + launcher/Application.cpp | 2 +- program_info/CMakeLists.txt | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index b8fa51339..1262ce8e4 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -49,6 +49,7 @@ Config::Config() LAUNCHER_CONFIGFILE = "@Launcher_ConfigFile@"; LAUNCHER_GIT = "@Launcher_Git@"; LAUNCHER_DESKTOPFILENAME = "@Launcher_DesktopFileName@"; + LAUNCHER_SVGFILENAME = "@Launcher_SVGFileName@"; USER_AGENT = "@Launcher_UserAgent@"; USER_AGENT_UNCACHED = USER_AGENT + " (Uncached)"; diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 13ccdaa18..4a3090736 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -51,6 +51,7 @@ class Config { QString LAUNCHER_CONFIGFILE; QString LAUNCHER_GIT; QString LAUNCHER_DESKTOPFILENAME; + QString LAUNCHER_SVGFILENAME; /// The major version number. int VERSION_MAJOR; diff --git a/launcher/Application.cpp b/launcher/Application.cpp index f6b418502..c6814abf5 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1177,7 +1177,7 @@ void Application::setIconTheme(const QString& name) QIcon Application::getThemedIcon(const QString& name) { if(name == "logo") { - return QIcon(":/org.prismlauncher.PrismLauncher.svg"); // FIXME: Make this a BuildConfig variable + return QIcon(":/" + BuildConfig.LAUNCHER_SVGFILENAME); } return QIcon::fromTheme(name); } diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 61949e137..b0507816b 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -22,6 +22,7 @@ set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_ set(Launcher_ConfigFile "prismlauncher.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PrismLauncher/PrismLauncher" PARENT_SCOPE) set(Launcher_DesktopFileName "org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) +set(Launcher_SVGFileName "org.prismlauncher.PrismLauncher.svg" PARENT_SCOPE) set(Launcher_Desktop "program_info/org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/org.prismlauncher.PrismLauncher.metainfo.xml" PARENT_SCOPE) From 98891a036dcd96dc7d9bf6b8f11c2a5f2ddd054e Mon Sep 17 00:00:00 2001 From: fn2006 Date: Sat, 29 Oct 2022 17:35:37 +0100 Subject: [PATCH 045/341] use existing variables Signed-off-by: fn2006 --- program_info/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index b0507816b..f064e0984 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -21,12 +21,12 @@ set(Launcher_Domain "prismlauncher.org" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "prismlauncher.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PrismLauncher/PrismLauncher" PARENT_SCOPE) -set(Launcher_DesktopFileName "org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) -set(Launcher_SVGFileName "org.prismlauncher.PrismLauncher.svg" PARENT_SCOPE) +set(Launcher_DesktopFileName "org.prismlauncher.PrismLauncher.desktop") +set(Launcher_SVGFileName "org.prismlauncher.PrismLauncher.svg") -set(Launcher_Desktop "program_info/org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) +set(Launcher_Desktop "program_info/${Launcher_DesktopFileName}" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/org.prismlauncher.PrismLauncher.metainfo.xml" PARENT_SCOPE) -set(Launcher_SVG "program_info/org.prismlauncher.PrismLauncher.svg" PARENT_SCOPE) +set(Launcher_SVG "program_info/${Launcher_SVGFileName}" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/prismlauncher.icns" PARENT_SCOPE) set(Launcher_Branding_ICO "program_info/prismlauncher.ico") set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE) From 5d1aac3c53904f7c843dc5cfdbdd33086eb4b6d6 Mon Sep 17 00:00:00 2001 From: Marcelo Hernandez Date: Sat, 29 Oct 2022 22:27:31 -0400 Subject: [PATCH 046/341] added option to not copy screenshots + moved select all checkbox to top row, centered. Signed-off-by: Marcelo Hernandez --- launcher/InstanceCopyPrefs.cpp | 16 +++- launcher/InstanceCopyPrefs.h | 3 + launcher/ui/dialogs/CopyInstanceDialog.cpp | 8 ++ launcher/ui/dialogs/CopyInstanceDialog.h | 1 + launcher/ui/dialogs/CopyInstanceDialog.ui | 85 ++++++++++++++-------- 5 files changed, 81 insertions(+), 32 deletions(-) diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp index ae30bb824..7b93a5164 100644 --- a/launcher/InstanceCopyPrefs.cpp +++ b/launcher/InstanceCopyPrefs.cpp @@ -12,7 +12,8 @@ bool InstanceCopyPrefs::allTrue() const copyResourcePacks && copyShaderPacks && copyServers && - copyMods; + copyMods && + copyScreenshots; } // Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat") @@ -38,6 +39,9 @@ QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const if(!copyMods) filters << "coremods" << "mods" << "config"; + if(!copyScreenshots) + filters << "screenshots"; + // If we have any filters to add, join them as a single regex string to return: if (!filters.isEmpty()) { const QString MC_ROOT = "[.]?minecraft/"; @@ -84,6 +88,11 @@ bool InstanceCopyPrefs::isCopyModsEnabled() const return copyMods; } +bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const +{ + return copyScreenshots; +} + // ======= Setters ======= void InstanceCopyPrefs::enableCopySaves(bool b) { @@ -119,3 +128,8 @@ void InstanceCopyPrefs::enableCopyMods(bool b) { copyMods = b; } + +void InstanceCopyPrefs::enableCopyScreenshots(bool b) +{ + copyScreenshots = b; +} diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h index 3855965de..6988b2df3 100644 --- a/launcher/InstanceCopyPrefs.h +++ b/launcher/InstanceCopyPrefs.h @@ -18,6 +18,7 @@ struct InstanceCopyPrefs { [[nodiscard]] bool isCopyShaderPacksEnabled() const; [[nodiscard]] bool isCopyServersEnabled() const; [[nodiscard]] bool isCopyModsEnabled() const; + [[nodiscard]] bool isCopyScreenshotsEnabled() const; // Setters void enableCopySaves(bool b); void enableKeepPlaytime(bool b); @@ -26,6 +27,7 @@ struct InstanceCopyPrefs { void enableCopyShaderPacks(bool b); void enableCopyServers(bool b); void enableCopyMods(bool b); + void enableCopyScreenshots(bool b); protected: // data bool copySaves = true; @@ -35,4 +37,5 @@ struct InstanceCopyPrefs { bool copyShaderPacks = true; bool copyServers = true; bool copyMods = true; + bool copyScreenshots = true; }; diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index f76b509ea..3f5122f66 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -84,6 +84,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->copyShaderPacksCheckbox->setChecked(m_selectedOptions.isCopyShaderPacksEnabled()); ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled()); ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled()); + ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled()); } CopyInstanceDialog::~CopyInstanceDialog() @@ -135,6 +136,7 @@ void CopyInstanceDialog::checkAllCheckboxes(const bool& b) ui->copyShaderPacksCheckbox->setChecked(b); ui->copyServersCheckbox->setChecked(b); ui->copyModsCheckbox->setChecked(b); + ui->copyScreenshotsCheckbox->setChecked(b); } // Check the "Select all" checkbox if all options are already selected: @@ -212,3 +214,9 @@ void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state) m_selectedOptions.enableCopyMods(state == Qt::Checked); updateSelectAllCheckbox(); } + +void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state) +{ + m_selectedOptions.enableCopyScreenshots(state == Qt::Checked); + updateSelectAllCheckbox(); +} diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h index 94015334e..884501d14 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.h +++ b/launcher/ui/dialogs/CopyInstanceDialog.h @@ -54,6 +54,7 @@ slots: void on_copyShaderPacksCheckbox_stateChanged(int state); void on_copyServersCheckbox_stateChanged(int state); void on_copyModsCheckbox_stateChanged(int state); + void on_copyScreenshotsCheckbox_stateChanged(int state); private: void checkAllCheckboxes(const bool& b); diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui index 822ed7977..b7828fe31 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.ui +++ b/launcher/ui/dialogs/CopyInstanceDialog.ui @@ -10,7 +10,7 @@ 0 0 341 - 385 + 399 @@ -112,25 +112,31 @@
+ + + + + + + 0 + 0 + + + + Qt::LeftToRight + + + Select all + + + false + + + + + - - - - Copy saves - - - - - - - true - - - Copy resource packs - - - @@ -151,17 +157,10 @@ - - + + - Copy servers - - - - - - - Keep play time + Copy saves @@ -172,10 +171,34 @@ - - + + - Select all + Copy servers + + + + + + + true + + + Copy resource packs + + + + + + + Keep play time + + + + + + + Copy screenshots From 19a8d51bf3567950db77d6bc05b16373fd1a0a1d Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 30 Oct 2022 11:52:16 +0800 Subject: [PATCH 047/341] fix: more user friendly Java warning text Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- launcher/minecraft/launch/VerifyJavaInstall.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 99809f828..43f118f44 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -71,5 +71,7 @@ void VerifyJavaInstall::executeTask() { { emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); } + emit logLine(tr("If you are sure your version will work, go to instance settings and check \"Skip Java compatibility checks\"."), MessageLevel::Error); + emitFailed(QString("Incompatible Java major version")); } From b4be28b9bb29609212b398a5f46ee4dbc400cf2d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 30 Oct 2022 18:37:03 +0100 Subject: [PATCH 048/341] fix: fix disabled trash undo action Signed-off-by: Sefa Eyeoglu --- launcher/ui/MainWindow.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 559ebc316..55f3e4f85 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1134,11 +1134,6 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup())); actions.append(actionDeleteGroup); } - - QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this); - connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance())); - actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); - actions.append(actionUndoTrashInstance); } QMenu myMenu; myMenu.addActions(actions); @@ -1834,6 +1829,7 @@ void MainWindow::deleteGroup() void MainWindow::undoTrashInstance() { APPLICATION->instances()->undoTrashInstance(); + ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); } void MainWindow::on_actionViewInstanceFolder_triggered() @@ -1940,6 +1936,7 @@ void MainWindow::on_actionDeleteInstance_triggered() auto id = m_selectedInstance->id(); if (APPLICATION->instances()->trashInstance(id)) { + ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); return; } From fd099166c6a69bf77491cef8aa42cbdf06c78e35 Mon Sep 17 00:00:00 2001 From: TheLastRar Date: Sun, 30 Oct 2022 20:27:37 +0000 Subject: [PATCH 049/341] Fix: Check if Windows 10 before calling setDarkWinTitlebar() Signed-off-by: TheLastRar --- launcher/Application.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c6814abf5..38613280a 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -62,6 +62,7 @@ #ifdef Q_OS_WIN #include "ui/WinDarkmode.h" +#include #endif #include "ui/setupwizard/SetupWizard.h" @@ -1154,7 +1155,7 @@ void Application::setApplicationTheme(const QString& name, bool initial) auto & theme = (*themeIter).second; theme->apply(initial); #ifdef Q_OS_WIN - if (m_mainWindow) { + if (m_mainWindow && IsWindows10OrGreater()) { if (QString::compare(theme->id(), "dark") == 0) { WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); } else { @@ -1395,10 +1396,13 @@ MainWindow* Application::showMainWindow(bool minimized) m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray())); m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray())); #ifdef Q_OS_WIN - if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); - } else { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); + if (IsWindows10OrGreater()) + { + if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); + } else { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); + } } #endif if(minimized) From b7d5fee9956a2fe7801f009eb7905eeae4f09c34 Mon Sep 17 00:00:00 2001 From: Fayne Aldan Date: Sun, 30 Oct 2022 15:55:15 -0600 Subject: [PATCH 050/341] Change Profiles to Accounts for consistency Signed-off-by: Fayne Aldan --- launcher/ui/MainWindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 559ebc316..84a789415 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -527,7 +527,7 @@ public: menuBar->addMenu(foldersMenu); - profileMenu = menuBar->addMenu(tr("&Profiles")); + profileMenu = menuBar->addMenu(tr("&Accounts")); profileMenu->setSeparatorsCollapsible(false); profileMenu->addAction(actionManageAccounts); @@ -1052,7 +1052,7 @@ void MainWindow::retranslateUi() accountMenuButton->setText(profileLabel); } else { - accountMenuButton->setText(tr("Profiles")); + accountMenuButton->setText(tr("Accounts")); } if (m_selectedInstance) { @@ -1395,7 +1395,7 @@ void MainWindow::defaultAccountChanged() // Set the icon to the "no account" icon. accountMenuButton->setIcon(APPLICATION->getThemedIcon("noaccount")); - accountMenuButton->setText(tr("Profiles")); + accountMenuButton->setText(tr("Accounts")); } bool MainWindow::eventFilter(QObject *obj, QEvent *ev) From 42350e689d9dba0c9385f15e7d4c4da0a71f200c Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 30 Oct 2022 23:50:56 +0100 Subject: [PATCH 051/341] chore: improve display names of certain languages Signed-off-by: Sefa Eyeoglu --- launcher/translations/TranslationsModel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 2f57de3a9..20aa6d040 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -83,6 +83,12 @@ struct Language else if(key == "es_UY") { result = u8"español de Latinoamérica"; } + else if(key == "en@pirate") { + result = u8"Tongue of the High Seas"; + } + else if(key == "en@uwu") { + result = u8"Cute Engwish"; + } else { result = locale.nativeLanguageName(); } From 27dfe30fe61055de5591d60c43351c7b2ca22668 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:44:11 +0800 Subject: [PATCH 052/341] Update VerifyJavaInstall.cpp Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- launcher/minecraft/launch/VerifyJavaInstall.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 43f118f44..89ece6e59 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -71,7 +71,7 @@ void VerifyJavaInstall::executeTask() { { emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); } - emit logLine(tr("If you are sure your version will work, go to instance settings and check \"Skip Java compatibility checks\"."), MessageLevel::Error); + emit logLine(tr(""Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what you're doing."), MessageLevel::Error); emitFailed(QString("Incompatible Java major version")); } From 8467daa0ec71544b86fadae5bbaca9818f21254e Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Mon, 31 Oct 2022 14:58:38 +0800 Subject: [PATCH 053/341] oop Signed-off-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com> --- launcher/minecraft/launch/VerifyJavaInstall.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/minecraft/launch/VerifyJavaInstall.cpp b/launcher/minecraft/launch/VerifyJavaInstall.cpp index 89ece6e59..6ae666b47 100644 --- a/launcher/minecraft/launch/VerifyJavaInstall.cpp +++ b/launcher/minecraft/launch/VerifyJavaInstall.cpp @@ -71,7 +71,7 @@ void VerifyJavaInstall::executeTask() { { emit logLine(tr("Java version %1").arg(major), MessageLevel::Error); } - emit logLine(tr(""Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what you're doing."), MessageLevel::Error); + emit logLine(tr("Go to instance Java settings to change your Java version or disable the Java compatibility check if you know what you're doing."), MessageLevel::Error); emitFailed(QString("Incompatible Java major version")); } From 29d362a6e08f28ac48e737510a17ae7b3b717850 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 30 Oct 2022 18:54:52 +0100 Subject: [PATCH 054/341] fix: do not trash instances in Flatpak Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 9 --------- launcher/Application.h | 2 -- launcher/DesktopServices.cpp | 19 ++++++++++++++----- launcher/DesktopServices.h | 2 ++ launcher/FileSystem.cpp | 4 ++++ launcher/ui/pages/global/LauncherPage.cpp | 3 ++- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c6814abf5..0c370b42b 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1136,15 +1136,6 @@ std::vector Application::getValidApplicationThemes() return ret; } -bool Application::isFlatpak() -{ - #ifdef Q_OS_LINUX - return QFile::exists("/.flatpak-info"); - #else - return false; - #endif -} - void Application::setApplicationTheme(const QString& name, bool initial) { auto systemPalette = qApp->palette(); diff --git a/launcher/Application.h b/launcher/Application.h index c453cc28b..8fa0ab10e 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -116,8 +116,6 @@ public: QIcon getThemedIcon(const QString& name); - bool isFlatpak(); - void setIconTheme(const QString& name); std::vector getValidApplicationThemes(); diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp index c29cbe7d0..302eaf967 100644 --- a/launcher/DesktopServices.cpp +++ b/launcher/DesktopServices.cpp @@ -119,7 +119,7 @@ bool openDirectory(const QString &path, bool ensureExists) return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen(f); } @@ -140,7 +140,7 @@ bool openFile(const QString &path) return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen(f); } @@ -158,7 +158,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo qDebug() << "Opening file" << path << "using" << application; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen([&]() { @@ -178,7 +178,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor { qDebug() << "Running" << application << "with args" << args.join(' '); #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave return IndirectOpen([&]() @@ -203,7 +203,7 @@ bool openUrl(const QUrl &url) return QDesktopServices::openUrl(url); }; #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - if(!APPLICATION->isFlatpak()) + if(!isFlatpak()) { return IndirectOpen(f); } @@ -216,4 +216,13 @@ bool openUrl(const QUrl &url) #endif } +bool isFlatpak() +{ +#ifdef Q_OS_LINUX + return QFile::exists("/.flatpak-info"); +#else + return false; +#endif +} + } diff --git a/launcher/DesktopServices.h b/launcher/DesktopServices.h index 1c081da41..21c9cae0b 100644 --- a/launcher/DesktopServices.h +++ b/launcher/DesktopServices.h @@ -33,4 +33,6 @@ namespace DesktopServices * Open the URL, most likely in a browser. Maybe. */ bool openUrl(const QUrl &url); + + bool isFlatpak(); } diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 76cfccb0b..aaa75e6f1 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -44,6 +44,7 @@ #include #include #include +#include "DesktopServices.h" #if defined Q_OS_WIN32 #include @@ -228,6 +229,9 @@ bool trash(QString path, QString *pathInTrash = nullptr) #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) return false; #else + // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal + if (DesktopServices::isFlatpak()) + return false; return QFile::moveToTrash(path, pathInTrash); #endif } diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 536ab22e1..4ae7509cf 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -49,6 +49,7 @@ #include #include "Application.h" #include "BuildConfig.h" +#include "DesktopServices.h" #include "ui/themes/ITheme.h" #include @@ -143,7 +144,7 @@ void LauncherPage::on_instDirBrowseBtn_clicked() ui->instDirTextBox->setText(cooked_dir); } } - else if(APPLICATION->isFlatpak() && raw_dir.startsWith("/run/user")) + else if(DesktopServices::isFlatpak() && raw_dir.startsWith("/run/user")) { QMessageBox warning; warning.setText(tr("You're trying to specify an instance folder " From f2d445d27b1052a038cd94a5c514cee807abd894 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 30 Oct 2022 19:08:02 +0100 Subject: [PATCH 055/341] fix: fix undo key sequence Signed-off-by: Sefa Eyeoglu --- launcher/ui/MainWindow.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 55f3e4f85..3b1779b7a 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -335,11 +335,10 @@ public: all_actions.append(&actionSettings); actionUndoTrashInstance = TranslatedAction(MainWindow); - connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), MainWindow, SLOT(undoTrashInstance())); actionUndoTrashInstance->setObjectName(QStringLiteral("actionUndoTrashInstance")); actionUndoTrashInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Undo Last Instance Deletion")); actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); - actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z")); + actionUndoTrashInstance->setShortcut(QKeySequence::Undo); all_actions.append(&actionUndoTrashInstance); actionClearMetadata = TranslatedAction(MainWindow); @@ -1023,6 +1022,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow } } + connect(ui->actionUndoTrashInstance.operator->(), &QAction::triggered, this, &MainWindow::undoTrashInstance); + setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); // removing this looks stupid From dfdf739282792ca646bb284b3d83b2d2265a9d55 Mon Sep 17 00:00:00 2001 From: Chrono Date: Mon, 31 Oct 2022 19:44:55 -0400 Subject: [PATCH 056/341] remove one space Signed-off-by: Chrono --- launcher/ui/pages/global/APIPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 1ae788c7f..d56a9ef68 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -179,7 +179,7 @@ - Enter a custom client ID for Microsoft Authentication here. + Enter a custom client ID for Microsoft Authentication here. Qt::RichText From 028e086960402f685e07163def36d6b5eee1b796 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Mon, 24 Oct 2022 04:08:38 -0700 Subject: [PATCH 057/341] send blocked mod info to dialog & prototype UI Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .../flame/FlameInstanceCreationTask.cpp | 18 +++++--- .../modpacksch/FTBPackInstallTask.cpp | 22 ++++++---- launcher/ui/dialogs/BlockedModsDialog.cpp | 42 +++++++++++++++++-- launcher/ui/dialogs/BlockedModsDialog.h | 16 ++++++- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 48ac02e06..15e660a9d 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -372,13 +372,20 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) auto results = m_mod_id_resolver->getResults(); // first check for blocked mods - QString text; - QList urls; + QList blocked_mods; auto anyBlocked = false; for (const auto& result : results.files.values()) { if (!result.resolved || result.url.isEmpty()) { - text += QString("%1: %2
").arg(result.fileName, result.websiteUrl); - urls.append(QUrl(result.websiteUrl)); + + BlockedMod blocked_mod; + blocked_mod.name = result.fileName; + blocked_mod.websiteUrl = result.websiteUrl; + blocked_mod.hash = result.hash; + blocked_mod.matched = false; + blocked_mod.localPath = ""; + + blocked_mods.append(blocked_mod); + anyBlocked = true; } } @@ -388,8 +395,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"), tr("The following mods were blocked on third party launchers.
" "You will need to manually download them and add them to the modpack"), - text, - urls); + blocked_mods); message_dialog->setModal(true); if (message_dialog->exec()) { diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 7b112d8f9..75fda2086 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -176,8 +176,7 @@ void PackInstallTask::resolveMods() void PackInstallTask::onResolveModsSucceeded() { - QString text; - QList urls; + QList blocked_mods; auto anyBlocked = false; Flame::Manifest results = m_mod_id_resolver_task->getResults(); @@ -191,11 +190,19 @@ void PackInstallTask::onResolveModsSucceeded() // First check for blocked mods if (!results_file.resolved || results_file.url.isEmpty()) { - QString type(local_file.type); + // QString type(local_file.type); + + // type[0] = type[0].toUpper(); + + BlockedMod blocked_mod; + blocked_mod.name = local_file.name; + blocked_mod.websiteUrl = results_file.websiteUrl; + blocked_mod.hash = results_file.hash; + blocked_mod.matched = false; + blocked_mod.localPath = ""; + + blocked_mods.append(blocked_mod); - type[0] = type[0].toUpper(); - text += QString("%1: %2 - %3
").arg(type, local_file.name, results_file.websiteUrl); - urls.append(QUrl(results_file.websiteUrl)); anyBlocked = true; } else { local_file.url = results_file.url.toString(); @@ -210,8 +217,7 @@ void PackInstallTask::onResolveModsSucceeded() auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"), tr("The following files are not available for download in third party launchers.
" "You will need to manually download them and add them to the instance."), - text, - urls); + blocked_mods); if (message_dialog->exec() == QDialog::Accepted) createInstance(); diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index fe87b517c..e29f8eb3e 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -4,17 +4,22 @@ #include #include +#include -BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList &urls) : - QDialog(parent), ui(new Ui::BlockedModsDialog), urls(urls) { + +BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QList &mods) : + QDialog(parent), ui(new Ui::BlockedModsDialog), mods(mods) { ui->setupUi(this); auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole); connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll); + qDebug() << "Mods List: " << mods; + this->setWindowTitle(title); ui->label->setText(text); ui->textBrowser->setText(body); + update(); } BlockedModsDialog::~BlockedModsDialog() { @@ -22,7 +27,36 @@ BlockedModsDialog::~BlockedModsDialog() { } void BlockedModsDialog::openAll() { - for(auto &url : urls) { - QDesktopServices::openUrl(url); + for(auto &mod : mods) { + QDesktopServices::openUrl(mod.websiteUrl); } } + +void BlockedModsDialog::update() { + QString text; + QString span; + + for (auto &mod : mods) { + if (mod.matched) { + // ✔ -> html for HEAVY CHECK MARK : ✔ + span = QString(" ✔ Found at %1 ").arg(mod.localPath); + } else { + // ✘ -> html for HEAVY BALLOT X : ✘ + span = QString(" ✘ Not Found "); + } + text += QString("%1: %2

Hash: %3 %4


").arg(mod.name, mod.websiteUrl, mod.hash, span); + } + + ui->textBrowser->setText(text); +} + + +QDebug operator<<(QDebug debug, const BlockedMod &m) { + QDebugStateSaver saver(debug); + + debug.nospace() << "{ name: " << m.name << ", websiteUrl: " << m.websiteUrl + << ", hash: " << m.hash << ", matched: " << m.matched + << ", localPath: " << m.localPath <<"}"; + + return debug; +} \ No newline at end of file diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index 5f5bd61b7..4be020eca 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -3,6 +3,15 @@ #include +struct BlockedMod { + QString name; + QString websiteUrl; + QString hash; + bool matched; + QString localPath; + +}; + QT_BEGIN_NAMESPACE namespace Ui { class BlockedModsDialog; } QT_END_NAMESPACE @@ -11,12 +20,15 @@ class BlockedModsDialog : public QDialog { Q_OBJECT public: - BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList &urls); + BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QList &mods); ~BlockedModsDialog() override; + private: Ui::BlockedModsDialog *ui; - const QList &urls; + const QList &mods; void openAll(); + void update(); }; + From 1598d6582473f1bb6aa02fd9b4dabc8210771e56 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 25 Oct 2022 01:19:19 -0700 Subject: [PATCH 058/341] watch filesystem, compute and match hashes Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/modplatform/helpers/HashUtils.cpp | 58 ++++++++++ launcher/modplatform/helpers/HashUtils.h | 15 +++ launcher/ui/dialogs/BlockedModsDialog.cpp | 120 ++++++++++++++++++++- launcher/ui/dialogs/BlockedModsDialog.h | 23 +++- 4 files changed, 212 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index a7bbaba50..bf53aa0e1 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -35,6 +35,18 @@ Hasher::Ptr createFlameHasher(QString file_path) return new FlameHasher(file_path); } +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider) +{ + return new BlockedModHasher(file_path, provider); +} + +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type) +{ + auto hasher = new BlockedModHasher(file_path, provider); + hasher->useHashType(type); + return hasher; +} + void ModrinthHasher::executeTask() { QFile file(m_path); @@ -78,4 +90,50 @@ void FlameHasher::executeTask() } } + +BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider) + : Hasher(file_path), provider(provider) { + setObjectName(QString("BlockedModHasher: %1").arg(file_path)); + hash_type = ProviderCaps.hashType(provider).first(); +} + +void BlockedModHasher::executeTask() +{ + QFile file(m_path); + + try { + file.open(QFile::ReadOnly); + } catch (FS::FileSystemException& e) { + qCritical() << QString("Failed to open JAR file in %1").arg(m_path); + qCritical() << QString("Reason: ") << e.cause(); + + emitFailed("Failed to open file for hashing."); + return; + } + + m_hash = ProviderCaps.hash(provider, &file, hash_type); + + file.close(); + + if (m_hash.isEmpty()) { + emitFailed("Empty hash!"); + } else { + emitSucceeded(); + } +} + +QStringList BlockedModHasher::getHashTypes() { + return ProviderCaps.hashType(provider); +} + +bool BlockedModHasher::useHashType(QString type) { + auto types = ProviderCaps.hashType(provider); + if (types.contains(type)) { + hash_type = type; + return true; + } + qDebug() << "Bad hash type " << type << " for provider"; + return false; +} + } // namespace Hashing diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h index 38fddf039..fa3244f6b 100644 --- a/launcher/modplatform/helpers/HashUtils.h +++ b/launcher/modplatform/helpers/HashUtils.h @@ -40,8 +40,23 @@ class ModrinthHasher : public Hasher { void executeTask() override; }; +class BlockedModHasher : public Hasher { + public: + BlockedModHasher(QString file_path, ModPlatform::Provider provider); + + void executeTask() override; + + QStringList getHashTypes(); + bool useHashType(QString type); + private: + ModPlatform::Provider provider; + QString hash_type; +}; + Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider); Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createModrinthHasher(QString file_path); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type); } // namespace Hashing diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index e29f8eb3e..9ba033d79 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -1,3 +1,6 @@ +#include +#include +#include "Application.h" #include "BlockedModsDialog.h" #include "ui_BlockedModsDialog.h" #include @@ -5,20 +8,29 @@ #include #include +#include -BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QList &mods) : + + +BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, QList &mods) : QDialog(parent), ui(new Ui::BlockedModsDialog), mods(mods) { ui->setupUi(this); auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole); connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll); + connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &BlockedModsDialog::directoryChanged); + + hashing_task = shared_qobject_ptr(new ConcurrentTask(this, "MakeHashesTask", 10)); + qDebug() << "Mods List: " << mods; + setupWatch(); + scanPaths(true); + this->setWindowTitle(title); ui->label->setText(text); - ui->textBrowser->setText(body); update(); } @@ -50,6 +62,110 @@ void BlockedModsDialog::update() { ui->textBrowser->setText(text); } +void BlockedModsDialog::directoryChanged(QString path) { + qDebug() << "Directory changed: " << path; + scanPath(path, false); +} + + +void BlockedModsDialog::setupWatch() { + const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString(); + watcher.addPath(downloadsFolder); + watcher.addPath(modsFolder); +} + +void BlockedModsDialog::scanPaths(bool init) { + for (auto &dir : watcher.directories()) { + scanPath(dir, init); + } +} + +void BlockedModsDialog::scanPath(QString path, bool init) { + + QDir scan_dir(path); + QDirIterator scan_it(path, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::NoIteratorFlags); + while (scan_it.hasNext()) { + QString file = scan_it.next(); + + if (checked_paths.contains(file)){ + continue; + } + + if (!checkValidPath(file)) { + continue; + } + + auto hash_task = Hashing::createBlockedModHasher(file, ModPlatform::Provider::FLAME, "sha1"); + + qDebug() << "Creating Hash task for path: " << file; + + connect(hash_task.get(), &Task::succeeded, [this, hash_task, file] { + checkMatchHash(hash_task->getResult(), file); + }); + connect(hash_task.get(), &Task::failed, [this, hash_task, file] { + qDebug() << "Failed to hash path: " << file; + }); + + if (init) { + checked_paths.insert(file); + } + + hashing_task->addTask(hash_task); + } + + hashing_task->start(); + +} + +void BlockedModsDialog::checkMatchHash(QString hash, QString path) { + bool match = false; + + qDebug() << "Checking for match on hash: " << hash << " | From path:" << path; + + for (auto &mod : mods) { + if (mod.matched) { + continue; + } + if (mod.hash.compare(hash, Qt::CaseInsensitive) == 0) { + mod.matched = true; + mod.localPath = path; + match = true; + + qDebug() << "Hash match found: " << mod.name << " " << hash << " | From path:" << path; + + break; + } + } + + if (match) { + update(); + } +} + +bool BlockedModsDialog::checkValidPath(QString path) { + + QFileInfo file = QFileInfo(path); + QString filename = file.fileName(); + + for (auto &mod : mods) { + if (mod.name.compare(filename, Qt::CaseInsensitive) == 0) { + qDebug() << "Name match found: " << mod.name << " | From path:" << path; + return true; + } + } + + return false; +} + +bool BlockedModsDialog::allModsMatched() { + for (auto &mod : mods) { + if (!mod.matched) + return false; + } + return true; +} + QDebug operator<<(QDebug debug, const BlockedMod &m) { QDebugStateSaver saver(debug); diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index 4be020eca..f1ea99ca0 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -1,7 +1,14 @@ #pragma once #include +#include +#include +#include + +#include "modplatform/helpers/HashUtils.h" + +#include "tasks/ConcurrentTask.h" struct BlockedMod { QString name; @@ -20,15 +27,27 @@ class BlockedModsDialog : public QDialog { Q_OBJECT public: - BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QList &mods); + BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, QList &mods); ~BlockedModsDialog() override; private: Ui::BlockedModsDialog *ui; - const QList &mods; + QList &mods; + QFileSystemWatcher watcher; + shared_qobject_ptr hashing_task; + QSet checked_paths; + void openAll(); void update(); + void directoryChanged(QString path); + void setupWatch(); + void scanPaths(bool init); + void scanPath(QString path, bool init); + void checkMatchHash(QString hash, QString path); + + bool checkValidPath(QString path); + bool allModsMatched(); }; From 13c7efa0584caf34950a6e6efa4b8e3bee16d764 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 25 Oct 2022 10:59:37 -0700 Subject: [PATCH 059/341] copy found mods to instance (FTB and Flame) Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 22 +++++++++ launcher/FileSystem.h | 2 + .../flame/FlameInstanceCreationTask.cpp | 35 +++++++++++++- .../flame/FlameInstanceCreationTask.h | 3 ++ .../modpacksch/FTBPackInstallTask.cpp | 46 +++++++++++++++++-- .../modpacksch/FTBPackInstallTask.h | 3 ++ launcher/ui/dialogs/BlockedModsDialog.cpp | 7 +++ launcher/ui/dialogs/BlockedModsDialog.h | 1 + launcher/ui/dialogs/BlockedModsDialog.ui | 37 +++++++++------ 9 files changed, 137 insertions(+), 19 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 4026d6c16..4285fa876 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -163,6 +163,28 @@ bool ensureFolderPathExists(QString foldernamepath) return success; } +bool copyFile(QString &src, QString &dst) { + using copy_opts = fs::copy_options; + + std::error_code err; + + fs::copy_options opt = copy_opts::none; + // The default behavior is to follow symlinks + opt |= copy_opts::copy_symlinks; + + ensureFilePathExists(dst); + + fs::copy(toStdString(src), toStdString(dst), opt, err); + if (err) { + qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src; + qDebug() << "Destination file:" << dst; + } + + return err.value() == 0; + +} + bool copy::operator()(const QString& offset) { using copy_opts = fs::copy_options; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index b46f32812..68f6bc4c8 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -75,6 +75,8 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); +bool copyFile(QString &src, QString &dst); + class copy { public: copy(const QString& src, const QString& dst) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 15e660a9d..fbc4ecf30 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -399,6 +399,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) message_dialog->setModal(true); if (message_dialog->exec()) { + copyBlockedMods(blocked_mods); setupDownloadJob(loop); } else { m_mod_id_resolver.reset(); @@ -410,6 +411,36 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } } +void FlameCreationTask::copyBlockedMods(QList blocked_mods) { + + setStatus(tr("Copying Blocked Mods...")); + setAbortable(false); + int i = 0; + int total = blocked_mods.length(); + setProgress(i, total); + for (auto mod = blocked_mods.begin(); mod != blocked_mods.end(); mod++, i++) { + + if (!mod->matched) { + qDebug() << mod->name << "was not matched to a local file, skipping copy"; + continue; + } + + auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod->name); + + setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); + + qDebug() << "Will try to copy" << mod->localPath << "to" << dest_path; + + if (!FS::copyFile(mod->localPath, dest_path)) { + qDebug() << "Copy of" << mod->localPath << "to" << dest_path << "Failed"; + } + + setProgress(i+1, total); + } + + setAbortable(true); +} + void FlameCreationTask::setupDownloadJob(QEventLoop& loop) { m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); @@ -455,7 +486,9 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_files_job.reset(); setError(reason); }); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { + setProgress(current, total); + }); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); setStatus(tr("Downloading mods...")); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index ded0e2ceb..69a8f1ab4 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -10,6 +10,8 @@ #include "net/NetJob.h" +#include "ui/dialogs/BlockedModsDialog.h" + class FlameCreationTask final : public InstanceCreationTask { Q_OBJECT @@ -29,6 +31,7 @@ class FlameCreationTask final : public InstanceCreationTask { private slots: void idResolverSucceeded(QEventLoop&); void setupDownloadJob(QEventLoop&); + void copyBlockedMods(QList blocked_mods); private: QWidget* m_parent = nullptr; diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 75fda2086..f6bf24880 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -176,7 +176,6 @@ void PackInstallTask::resolveMods() void PackInstallTask::onResolveModsSucceeded() { - QList blocked_mods; auto anyBlocked = false; Flame::Manifest results = m_mod_id_resolver_task->getResults(); @@ -201,7 +200,7 @@ void PackInstallTask::onResolveModsSucceeded() blocked_mod.matched = false; blocked_mod.localPath = ""; - blocked_mods.append(blocked_mod); + m_blocked_mods.append(blocked_mod); anyBlocked = true; } else { @@ -217,12 +216,16 @@ void PackInstallTask::onResolveModsSucceeded() auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"), tr("The following files are not available for download in third party launchers.
" "You will need to manually download them and add them to the instance."), - blocked_mods); + m_blocked_mods); - if (message_dialog->exec() == QDialog::Accepted) + if (message_dialog->exec() == QDialog::Accepted) { + qDebug() << "Post dialog mods list: " << m_blocked_mods; createInstance(); - else + } + else { abort(); + } + } else { createInstance(); } @@ -326,6 +329,9 @@ void PackInstallTask::downloadPack() void PackInstallTask::onModDownloadSucceeded() { m_net_job.reset(); + if (m_blocked_mods.length() > 0) { + copyBlockedMods(); + } emitSucceeded(); } @@ -349,4 +355,34 @@ void PackInstallTask::onModDownloadFailed(QString reason) emitFailed(reason); } +void PackInstallTask::copyBlockedMods() { + + setStatus(tr("Copying Blocked Mods...")); + setAbortable(false); + int i = 0; + int total = m_blocked_mods.length(); + setProgress(i, total); + for (auto mod = m_blocked_mods.begin(); mod != m_blocked_mods.end(); mod++, i++) { + + if (!mod->matched) { + qDebug() << mod->name << "was not matched to a local file, skipping copy"; + continue; + } + + auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod->name); + + setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); + + qDebug() << "Will try to copy" << mod->localPath << "to" << dest_path; + + if (!FS::copyFile(mod->localPath, dest_path)) { + qDebug() << "Copy of" << mod->localPath << "to" << dest_path << "Failed"; + } + + setProgress(i+1, total); + } + + setAbortable(true); +} + } // namespace ModpacksCH diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h index 7c6fbeb93..2cd4d7296 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h @@ -43,6 +43,7 @@ #include "QObjectPtr.h" #include "modplatform/flame/FileResolvingTask.h" #include "net/NetJob.h" +#include "ui/dialogs/BlockedModsDialog.h" #include @@ -76,6 +77,7 @@ private: void resolveMods(); void createInstance(); void downloadPack(); + void copyBlockedMods(); private: NetJob::Ptr m_net_job = nullptr; @@ -90,6 +92,7 @@ private: Version m_version; QMap m_files_to_copy; + QList m_blocked_mods; //FIXME: nuke QWidget* m_parent; diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 9ba033d79..542d06815 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -31,6 +31,7 @@ BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, cons this->setWindowTitle(title); ui->label->setText(text); + ui->labelModsFound->setText("Please download the missing mods."); update(); } @@ -60,6 +61,12 @@ void BlockedModsDialog::update() { } ui->textBrowser->setText(text); + + if (allModsMatched()) { + ui->labelModsFound->setText("All mods found ✔"); + } else { + ui->labelModsFound->setText("Please download the missing mods."); + } } void BlockedModsDialog::directoryChanged(QString path) { diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index f1ea99ca0..93b9f46a6 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -51,3 +51,4 @@ private: bool allModsMatched(); }; +QDebug operator<<(QDebug debug, const BlockedMod &m); \ No newline at end of file diff --git a/launcher/ui/dialogs/BlockedModsDialog.ui b/launcher/ui/dialogs/BlockedModsDialog.ui index f4ae95b69..371549cf4 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.ui +++ b/launcher/ui/dialogs/BlockedModsDialog.ui @@ -13,8 +13,8 @@ BlockedModsDialog - - + + @@ -24,17 +24,7 @@ - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + true @@ -44,6 +34,27 @@ + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + +
From e9d4793b1e98944dad910b3952c117bb2d3369de Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 25 Oct 2022 20:18:14 -0700 Subject: [PATCH 060/341] minor clean up and add some docs Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 7 ++++ launcher/FileSystem.h | 1 + .../flame/FlameInstanceCreationTask.cpp | 2 + .../modpacksch/FTBPackInstallTask.cpp | 4 +- launcher/ui/dialogs/BlockedModsDialog.cpp | 42 +++++++++++-------- launcher/ui/dialogs/BlockedModsDialog.h | 5 +-- 6 files changed, 38 insertions(+), 23 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 4285fa876..8fe441b31 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -163,6 +163,10 @@ bool ensureFolderPathExists(QString foldernamepath) return success; } +/// @brief Copy file at src to dest, ensures the full filepath exsists +/// @param src srouce file path +/// @param dst destination file path +/// @return boolean: was there an error during the filecopy? bool copyFile(QString &src, QString &dst) { using copy_opts = fs::copy_options; @@ -185,6 +189,9 @@ bool copyFile(QString &src, QString &dst) { } +/// @brief Copies a directory and it's contents from src to dest +/// @param offset subdirectory form src to copy to dest +/// @return if there was an error during the filecopy bool copy::operator()(const QString& offset) { using copy_opts = fs::copy_options; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 68f6bc4c8..ab006d48a 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -77,6 +77,7 @@ bool ensureFolderPathExists(QString filenamepath); bool copyFile(QString &src, QString &dst); +/// @brief Copies a directory and it's contents from src to dest class copy { public: copy(const QString& src, const QString& dst) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index fbc4ecf30..edacb8197 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -411,6 +411,8 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } } +/// @brief copy the matched blocked mods to the instance staging area +/// @param blocked_mods list of the blocked mods and their matched paths void FlameCreationTask::copyBlockedMods(QList blocked_mods) { setStatus(tr("Copying Blocked Mods...")); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index f6bf24880..49fbafd67 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -189,9 +189,6 @@ void PackInstallTask::onResolveModsSucceeded() // First check for blocked mods if (!results_file.resolved || results_file.url.isEmpty()) { - // QString type(local_file.type); - - // type[0] = type[0].toUpper(); BlockedMod blocked_mod; blocked_mod.name = local_file.name; @@ -355,6 +352,7 @@ void PackInstallTask::onModDownloadFailed(QString reason) emitFailed(reason); } +/// @brief copy the matched blocked mods to the instance staging area void PackInstallTask::copyBlockedMods() { setStatus(tr("Copying Blocked Mods...")); diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 542d06815..f5bc7f735 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -1,5 +1,3 @@ -#include -#include #include "Application.h" #include "BlockedModsDialog.h" #include "ui_BlockedModsDialog.h" @@ -27,7 +25,7 @@ BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, cons qDebug() << "Mods List: " << mods; setupWatch(); - scanPaths(true); + scanPaths(); this->setWindowTitle(title); ui->label->setText(text); @@ -45,6 +43,7 @@ void BlockedModsDialog::openAll() { } } +/// @brief update UI with current status of the blocked mod detection void BlockedModsDialog::update() { QString text; QString span; @@ -69,12 +68,15 @@ void BlockedModsDialog::update() { } } +/// @brief Signal fired when a watched direcotry has changed +/// @param path the path to the changed directory void BlockedModsDialog::directoryChanged(QString path) { qDebug() << "Directory changed: " << path; - scanPath(path, false); + scanPath(path); } +/// @brief add the user downloads folder and the global mods folder to the filesystem watcher void BlockedModsDialog::setupWatch() { const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString(); @@ -82,23 +84,24 @@ void BlockedModsDialog::setupWatch() { watcher.addPath(modsFolder); } -void BlockedModsDialog::scanPaths(bool init) { + +/// @brief scan all watched folder +void BlockedModsDialog::scanPaths() { for (auto &dir : watcher.directories()) { - scanPath(dir, init); + scanPath(dir); } } -void BlockedModsDialog::scanPath(QString path, bool init) { +/// @brief Scan the directory at path, skip paths that do not contain a file name +/// of a blocked mod we are looking for +/// @param path the directory to scan +void BlockedModsDialog::scanPath(QString path) { QDir scan_dir(path); QDirIterator scan_it(path, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::NoIteratorFlags); while (scan_it.hasNext()) { QString file = scan_it.next(); - if (checked_paths.contains(file)){ - continue; - } - if (!checkValidPath(file)) { continue; } @@ -113,10 +116,6 @@ void BlockedModsDialog::scanPath(QString path, bool init) { connect(hash_task.get(), &Task::failed, [this, hash_task, file] { qDebug() << "Failed to hash path: " << file; }); - - if (init) { - checked_paths.insert(file); - } hashing_task->addTask(hash_task); } @@ -125,6 +124,10 @@ void BlockedModsDialog::scanPath(QString path, bool init) { } +/// @brief check if the conputed hash for the provided path matches a blocked +/// mod we are looking for +/// @param hash the computed hash for the provided path +/// @param path the path to the local file being compared void BlockedModsDialog::checkMatchHash(QString hash, QString path) { bool match = false; @@ -150,6 +153,9 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path) { } } +/// @brief Check if the name of the file at path matches the naem of a blocked mod we are searching for +/// @param path the path to check +/// @return boolean: did the path match the name of a blocked mod? bool BlockedModsDialog::checkValidPath(QString path) { QFileInfo file = QFileInfo(path); @@ -165,6 +171,8 @@ bool BlockedModsDialog::checkValidPath(QString path) { return false; } +/// @brief have we found all the mods we're lookign for? +/// @return boolean bool BlockedModsDialog::allModsMatched() { for (auto &mod : mods) { if (!mod.matched) @@ -173,7 +181,7 @@ bool BlockedModsDialog::allModsMatched() { return true; } - +/// qDebug print support for the BlockedMod struct QDebug operator<<(QDebug debug, const BlockedMod &m) { QDebugStateSaver saver(debug); @@ -182,4 +190,4 @@ QDebug operator<<(QDebug debug, const BlockedMod &m) { << ", localPath: " << m.localPath <<"}"; return debug; -} \ No newline at end of file +} diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index 93b9f46a6..cf1d3b3d5 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -37,14 +37,13 @@ private: QList &mods; QFileSystemWatcher watcher; shared_qobject_ptr hashing_task; - QSet checked_paths; void openAll(); void update(); void directoryChanged(QString path); void setupWatch(); - void scanPaths(bool init); - void scanPath(QString path, bool init); + void scanPaths(); + void scanPath(QString path); void checkMatchHash(QString hash, QString path); bool checkValidPath(QString path); From d2f3dbaa2984b70a71e5fb1b246a31987a6fdf10 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 30 Oct 2022 22:39:12 -0700 Subject: [PATCH 061/341] fix mispellings and wrap strings for translation Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 18 ++++++++---------- launcher/ui/dialogs/BlockedModsDialog.h | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index f5bc7f735..136a7371b 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -29,7 +29,7 @@ BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, cons this->setWindowTitle(title); ui->label->setText(text); - ui->labelModsFound->setText("Please download the missing mods."); + ui->labelModsFound->setText(tr("Please download the missing mods.")); update(); } @@ -51,20 +51,20 @@ void BlockedModsDialog::update() { for (auto &mod : mods) { if (mod.matched) { // ✔ -> html for HEAVY CHECK MARK : ✔ - span = QString(" ✔ Found at %1 ").arg(mod.localPath); + span = QString(tr(" ✔ Found at %1 ")).arg(mod.localPath); } else { // ✘ -> html for HEAVY BALLOT X : ✘ - span = QString(" ✘ Not Found "); + span = QString(tr(" ✘ Not Found ")); } - text += QString("%1: %2

Hash: %3 %4


").arg(mod.name, mod.websiteUrl, mod.hash, span); + text += QString(tr("%1: %2

Hash: %3 %4


")).arg(mod.name, mod.websiteUrl, mod.hash, span); } ui->textBrowser->setText(text); if (allModsMatched()) { - ui->labelModsFound->setText("All mods found ✔"); + ui->labelModsFound->setText(tr("All mods found ✔")); } else { - ui->labelModsFound->setText("Please download the missing mods."); + ui->labelModsFound->setText(tr("Please download the missing mods.")); } } @@ -124,7 +124,7 @@ void BlockedModsDialog::scanPath(QString path) { } -/// @brief check if the conputed hash for the provided path matches a blocked +/// @brief check if the computed hash for the provided path matches a blocked /// mod we are looking for /// @param hash the computed hash for the provided path /// @param path the path to the local file being compared @@ -153,7 +153,7 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path) { } } -/// @brief Check if the name of the file at path matches the naem of a blocked mod we are searching for +/// @brief Check if the name of the file at path matches the name of a blocked mod we are searching for /// @param path the path to check /// @return boolean: did the path match the name of a blocked mod? bool BlockedModsDialog::checkValidPath(QString path) { @@ -171,8 +171,6 @@ bool BlockedModsDialog::checkValidPath(QString path) { return false; } -/// @brief have we found all the mods we're lookign for? -/// @return boolean bool BlockedModsDialog::allModsMatched() { for (auto &mod : mods) { if (!mod.matched) diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h index cf1d3b3d5..0a5c90db0 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.h +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -50,4 +50,4 @@ private: bool allModsMatched(); }; -QDebug operator<<(QDebug debug, const BlockedMod &m); \ No newline at end of file +QDebug operator<<(QDebug debug, const BlockedMod &m); From fda2c116bef33df2ca49d77ff4b016e724f47549 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 30 Oct 2022 22:42:35 -0700 Subject: [PATCH 062/341] code quality cleanup Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 2 +- launcher/FileSystem.h | 2 +- .../flame/FlameInstanceCreationTask.cpp | 23 +++++++++---------- .../flame/FlameInstanceCreationTask.h | 2 +- .../modpacksch/FTBPackInstallTask.cpp | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8fe441b31..508da08d4 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -167,7 +167,7 @@ bool ensureFolderPathExists(QString foldernamepath) /// @param src srouce file path /// @param dst destination file path /// @return boolean: was there an error during the filecopy? -bool copyFile(QString &src, QString &dst) { +bool copyFile(QString const& src, QString const& dst) { using copy_opts = fs::copy_options; std::error_code err; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index ab006d48a..771bda605 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -75,7 +75,7 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); -bool copyFile(QString &src, QString &dst); +bool copyFile(QString const& src, QString const& dst); /// @brief Copies a directory and it's contents from src to dest class copy { diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index edacb8197..30438a1a5 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -413,31 +413,32 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) /// @brief copy the matched blocked mods to the instance staging area /// @param blocked_mods list of the blocked mods and their matched paths -void FlameCreationTask::copyBlockedMods(QList blocked_mods) { +void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) { setStatus(tr("Copying Blocked Mods...")); setAbortable(false); int i = 0; int total = blocked_mods.length(); setProgress(i, total); - for (auto mod = blocked_mods.begin(); mod != blocked_mods.end(); mod++, i++) { + for (auto &mod : blocked_mods) { - if (!mod->matched) { - qDebug() << mod->name << "was not matched to a local file, skipping copy"; + if (!mod.matched) { + qDebug() << mod.name << "was not matched to a local file, skipping copy"; continue; } - auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod->name); + auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod.name); setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); - qDebug() << "Will try to copy" << mod->localPath << "to" << dest_path; + qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - if (!FS::copyFile(mod->localPath, dest_path)) { - qDebug() << "Copy of" << mod->localPath << "to" << dest_path << "Failed"; + if (!FS::copyFile(mod.localPath, dest_path)) { // FIXME: use FS::copy once #333 is merged + qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; } - setProgress(i+1, total); + i++; + setProgress(i, total); } setAbortable(true); @@ -488,9 +489,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_files_job.reset(); setError(reason); }); - connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { - setProgress(current, total); - }); + connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress); connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); setStatus(tr("Downloading mods...")); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 69a8f1ab4..fbc7d5bf7 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -31,7 +31,7 @@ class FlameCreationTask final : public InstanceCreationTask { private slots: void idResolverSucceeded(QEventLoop&); void setupDownloadJob(QEventLoop&); - void copyBlockedMods(QList blocked_mods); + void copyBlockedMods(QList const& blocked_mods); private: QWidget* m_parent = nullptr; diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 49fbafd67..5091db0ca 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -326,7 +326,7 @@ void PackInstallTask::downloadPack() void PackInstallTask::onModDownloadSucceeded() { m_net_job.reset(); - if (m_blocked_mods.length() > 0) { + if (!m_blocked_mods.isEmpty()) { copyBlockedMods(); } emitSucceeded(); From a7a331a26e43df3dbafdbb29a59d38ba807ffa7d Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 30 Oct 2022 22:49:54 -0700 Subject: [PATCH 063/341] ensure FS::copyFile is marked for removal Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.h | 1 + launcher/modplatform/modpacksch/FTBPackInstallTask.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 771bda605..11981f68f 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -75,6 +75,7 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); +// TODO: remove in favor of FS::copy once #333 is merged bool copyFile(QString const& src, QString const& dst); /// @brief Copies a directory and it's contents from src to dest diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 5091db0ca..06ef1deb6 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -373,7 +373,7 @@ void PackInstallTask::copyBlockedMods() { qDebug() << "Will try to copy" << mod->localPath << "to" << dest_path; - if (!FS::copyFile(mod->localPath, dest_path)) { + if (!FS::copyFile(mod->localPath, dest_path)) { // FIXME: use FS::copy once #333 is merged qDebug() << "Copy of" << mod->localPath << "to" << dest_path << "Failed"; } From 6010ce0dc587527caa05bdc9b4cecdb9bd811375 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 1 Nov 2022 04:28:57 -0700 Subject: [PATCH 064/341] chore(remove FS::copyFile): Now that #333 is merged and FS::copy works on non directory copyFile can be removed. Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/FileSystem.cpp | 26 ------------------- launcher/FileSystem.h | 3 --- .../flame/FlameInstanceCreationTask.cpp | 2 +- .../modpacksch/FTBPackInstallTask.cpp | 2 +- 4 files changed, 2 insertions(+), 31 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 508da08d4..bf0849ec3 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -163,32 +163,6 @@ bool ensureFolderPathExists(QString foldernamepath) return success; } -/// @brief Copy file at src to dest, ensures the full filepath exsists -/// @param src srouce file path -/// @param dst destination file path -/// @return boolean: was there an error during the filecopy? -bool copyFile(QString const& src, QString const& dst) { - using copy_opts = fs::copy_options; - - std::error_code err; - - fs::copy_options opt = copy_opts::none; - // The default behavior is to follow symlinks - opt |= copy_opts::copy_symlinks; - - ensureFilePathExists(dst); - - fs::copy(toStdString(src), toStdString(dst), opt, err); - if (err) { - qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); - qDebug() << "Source file:" << src; - qDebug() << "Destination file:" << dst; - } - - return err.value() == 0; - -} - /// @brief Copies a directory and it's contents from src to dest /// @param offset subdirectory form src to copy to dest /// @return if there was an error during the filecopy diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index 11981f68f..b7e175fdf 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -75,9 +75,6 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); -// TODO: remove in favor of FS::copy once #333 is merged -bool copyFile(QString const& src, QString const& dst); - /// @brief Copies a directory and it's contents from src to dest class copy { public: diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 30438a1a5..5d4dc689f 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -433,7 +433,7 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) { qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - if (!FS::copyFile(mod.localPath, dest_path)) { // FIXME: use FS::copy once #333 is merged + if (!FS::copy(mod.localPath, dest_path)()) { qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; } diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 06ef1deb6..1e4bbe19b 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -373,7 +373,7 @@ void PackInstallTask::copyBlockedMods() { qDebug() << "Will try to copy" << mod->localPath << "to" << dest_path; - if (!FS::copyFile(mod->localPath, dest_path)) { // FIXME: use FS::copy once #333 is merged + if (!FS::copy(mod->localPath, dest_path)()) { qDebug() << "Copy of" << mod->localPath << "to" << dest_path << "Failed"; } From b142e2a7a75171da4c5bad9be17aa1bc52676b67 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 1 Nov 2022 13:59:11 +0100 Subject: [PATCH 065/341] fix: fix logo name Signed-off-by: Sefa Eyeoglu --- program_info/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index f064e0984..b0507816b 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -21,12 +21,12 @@ set(Launcher_Domain "prismlauncher.org" PARENT_SCOPE) set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "prismlauncher.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PrismLauncher/PrismLauncher" PARENT_SCOPE) -set(Launcher_DesktopFileName "org.prismlauncher.PrismLauncher.desktop") -set(Launcher_SVGFileName "org.prismlauncher.PrismLauncher.svg") +set(Launcher_DesktopFileName "org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) +set(Launcher_SVGFileName "org.prismlauncher.PrismLauncher.svg" PARENT_SCOPE) -set(Launcher_Desktop "program_info/${Launcher_DesktopFileName}" PARENT_SCOPE) +set(Launcher_Desktop "program_info/org.prismlauncher.PrismLauncher.desktop" PARENT_SCOPE) set(Launcher_MetaInfo "program_info/org.prismlauncher.PrismLauncher.metainfo.xml" PARENT_SCOPE) -set(Launcher_SVG "program_info/${Launcher_SVGFileName}" PARENT_SCOPE) +set(Launcher_SVG "program_info/org.prismlauncher.PrismLauncher.svg" PARENT_SCOPE) set(Launcher_Branding_ICNS "program_info/prismlauncher.icns" PARENT_SCOPE) set(Launcher_Branding_ICO "program_info/prismlauncher.ico") set(Launcher_Branding_ICO "${Launcher_Branding_ICO}" PARENT_SCOPE) From 209a1650e489e21417ce2e1a29862703d51a2cd0 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 1 Nov 2022 07:06:36 -0700 Subject: [PATCH 066/341] clang_format and code cleanup Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- .../flame/FlameInstanceCreationTask.cpp | 11 +-- .../modpacksch/FTBPackInstallTask.cpp | 24 ++--- launcher/ui/dialogs/BlockedModsDialog.cpp | 95 +++++++++---------- 3 files changed, 62 insertions(+), 68 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 5d4dc689f..f86e9335d 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -413,15 +413,14 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) /// @brief copy the matched blocked mods to the instance staging area /// @param blocked_mods list of the blocked mods and their matched paths -void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) { - +void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) +{ setStatus(tr("Copying Blocked Mods...")); setAbortable(false); int i = 0; int total = blocked_mods.length(); setProgress(i, total); - for (auto &mod : blocked_mods) { - + for (auto const& mod : blocked_mods) { if (!mod.matched) { qDebug() << mod.name << "was not matched to a local file, skipping copy"; continue; @@ -433,9 +432,9 @@ void FlameCreationTask::copyBlockedMods(QList const& blocked_mods) { qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - if (!FS::copy(mod.localPath, dest_path)()) { + if (!FS::copy(mod.localPath, dest_path)()) { qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; - } + } i++; setProgress(i, total); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 1e4bbe19b..70ef75716 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -353,31 +353,31 @@ void PackInstallTask::onModDownloadFailed(QString reason) } /// @brief copy the matched blocked mods to the instance staging area -void PackInstallTask::copyBlockedMods() { - +void PackInstallTask::copyBlockedMods() +{ setStatus(tr("Copying Blocked Mods...")); setAbortable(false); int i = 0; int total = m_blocked_mods.length(); setProgress(i, total); - for (auto mod = m_blocked_mods.begin(); mod != m_blocked_mods.end(); mod++, i++) { - - if (!mod->matched) { - qDebug() << mod->name << "was not matched to a local file, skipping copy"; + for (auto const& mod : m_blocked_mods) { + if (!mod.matched) { + qDebug() << mod.name << "was not matched to a local file, skipping copy"; continue; } - auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod->name); + auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod.name); setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total))); - qDebug() << "Will try to copy" << mod->localPath << "to" << dest_path; + qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path; - if (!FS::copy(mod->localPath, dest_path)()) { - qDebug() << "Copy of" << mod->localPath << "to" << dest_path << "Failed"; - } + if (!FS::copy(mod.localPath, dest_path)()) { + qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed"; + } - setProgress(i+1, total); + i++; + setProgress(i, total); } setAbortable(true); diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 136a7371b..2cf942500 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -1,18 +1,16 @@ -#include "Application.h" #include "BlockedModsDialog.h" -#include "ui_BlockedModsDialog.h" -#include -#include #include +#include +#include +#include "Application.h" +#include "ui_BlockedModsDialog.h" #include #include - - - -BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, QList &mods) : - QDialog(parent), ui(new Ui::BlockedModsDialog), mods(mods) { +BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList& mods) + : QDialog(parent), ui(new Ui::BlockedModsDialog), mods(mods) +{ ui->setupUi(this); auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole); @@ -21,7 +19,7 @@ BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, cons connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &BlockedModsDialog::directoryChanged); hashing_task = shared_qobject_ptr(new ConcurrentTask(this, "MakeHashesTask", 10)); - + qDebug() << "Mods List: " << mods; setupWatch(); @@ -33,22 +31,25 @@ BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, cons update(); } -BlockedModsDialog::~BlockedModsDialog() { +BlockedModsDialog::~BlockedModsDialog() +{ delete ui; } -void BlockedModsDialog::openAll() { - for(auto &mod : mods) { +void BlockedModsDialog::openAll() +{ + for (auto& mod : mods) { QDesktopServices::openUrl(mod.websiteUrl); } } /// @brief update UI with current status of the blocked mod detection -void BlockedModsDialog::update() { +void BlockedModsDialog::update() +{ QString text; QString span; - for (auto &mod : mods) { + for (auto& mod : mods) { if (mod.matched) { // ✔ -> html for HEAVY CHECK MARK : ✔ span = QString(tr(" ✔ Found at %1 ")).arg(mod.localPath); @@ -70,33 +71,34 @@ void BlockedModsDialog::update() { /// @brief Signal fired when a watched direcotry has changed /// @param path the path to the changed directory -void BlockedModsDialog::directoryChanged(QString path) { +void BlockedModsDialog::directoryChanged(QString path) +{ qDebug() << "Directory changed: " << path; scanPath(path); } - /// @brief add the user downloads folder and the global mods folder to the filesystem watcher -void BlockedModsDialog::setupWatch() { +void BlockedModsDialog::setupWatch() +{ const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString(); watcher.addPath(downloadsFolder); watcher.addPath(modsFolder); } - /// @brief scan all watched folder -void BlockedModsDialog::scanPaths() { - for (auto &dir : watcher.directories()) { +void BlockedModsDialog::scanPaths() +{ + for (auto& dir : watcher.directories()) { scanPath(dir); } } -/// @brief Scan the directory at path, skip paths that do not contain a file name +/// @brief Scan the directory at path, skip paths that do not contain a file name /// of a blocked mod we are looking for /// @param path the directory to scan -void BlockedModsDialog::scanPath(QString path) { - +void BlockedModsDialog::scanPath(QString path) +{ QDir scan_dir(path); QDirIterator scan_it(path, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::NoIteratorFlags); while (scan_it.hasNext()) { @@ -110,30 +112,26 @@ void BlockedModsDialog::scanPath(QString path) { qDebug() << "Creating Hash task for path: " << file; - connect(hash_task.get(), &Task::succeeded, [this, hash_task, file] { - checkMatchHash(hash_task->getResult(), file); - }); - connect(hash_task.get(), &Task::failed, [this, hash_task, file] { - qDebug() << "Failed to hash path: " << file; - }); - + connect(hash_task.get(), &Task::succeeded, [this, hash_task, file] { checkMatchHash(hash_task->getResult(), file); }); + connect(hash_task.get(), &Task::failed, [file] { qDebug() << "Failed to hash path: " << file; }); + hashing_task->addTask(hash_task); } hashing_task->start(); - } /// @brief check if the computed hash for the provided path matches a blocked /// mod we are looking for /// @param hash the computed hash for the provided path /// @param path the path to the local file being compared -void BlockedModsDialog::checkMatchHash(QString hash, QString path) { +void BlockedModsDialog::checkMatchHash(QString hash, QString path) +{ bool match = false; - qDebug() << "Checking for match on hash: " << hash << " | From path:" << path; + qDebug() << "Checking for match on hash: " << hash << "| From path:" << path; - for (auto &mod : mods) { + for (auto& mod : mods) { if (mod.matched) { continue; } @@ -142,7 +140,7 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path) { mod.localPath = path; match = true; - qDebug() << "Hash match found: " << mod.name << " " << hash << " | From path:" << path; + qDebug() << "Hash match found:" << mod.name << hash << "| From path:" << path; break; } @@ -156,14 +154,14 @@ void BlockedModsDialog::checkMatchHash(QString hash, QString path) { /// @brief Check if the name of the file at path matches the name of a blocked mod we are searching for /// @param path the path to check /// @return boolean: did the path match the name of a blocked mod? -bool BlockedModsDialog::checkValidPath(QString path) { - +bool BlockedModsDialog::checkValidPath(QString path) +{ QFileInfo file = QFileInfo(path); QString filename = file.fileName(); - for (auto &mod : mods) { + for (auto& mod : mods) { if (mod.name.compare(filename, Qt::CaseInsensitive) == 0) { - qDebug() << "Name match found: " << mod.name << " | From path:" << path; + qDebug() << "Name match found:" << mod.name << "| From path:" << path; return true; } } @@ -171,21 +169,18 @@ bool BlockedModsDialog::checkValidPath(QString path) { return false; } -bool BlockedModsDialog::allModsMatched() { - for (auto &mod : mods) { - if (!mod.matched) - return false; - } - return true; +bool BlockedModsDialog::allModsMatched() +{ + return std::all_of(mods.begin(), mods.end(), [](auto const& mod) { return mod.matched; }); } /// qDebug print support for the BlockedMod struct -QDebug operator<<(QDebug debug, const BlockedMod &m) { +QDebug operator<<(QDebug debug, const BlockedMod& m) +{ QDebugStateSaver saver(debug); - debug.nospace() << "{ name: " << m.name << ", websiteUrl: " << m.websiteUrl - << ", hash: " << m.hash << ", matched: " << m.matched - << ", localPath: " << m.localPath <<"}"; + debug.nospace() << "{ name: " << m.name << ", websiteUrl: " << m.websiteUrl << ", hash: " << m.hash << ", matched: " << m.matched + << ", localPath: " << m.localPath << "}"; return debug; } From 7f32c6464d84181fc8947f632da340a863dc53d6 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 1 Nov 2022 16:58:22 +0000 Subject: [PATCH 067/341] Initial better mod browser link implementation Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ModDownloadDialog.cpp | 5 ++ launcher/ui/dialogs/ModDownloadDialog.h | 2 + launcher/ui/pages/modplatform/ModPage.cpp | 63 +++++++++++++++++-- launcher/ui/pages/modplatform/ModPage.h | 1 + launcher/ui/pages/modplatform/ModPage.ui | 4 +- .../pages/modplatform/flame/FlameModPage.cpp | 19 +++++- .../ui/pages/modplatform/flame/FlameModPage.h | 2 + .../modplatform/modrinth/ModrinthModPage.cpp | 2 +- 8 files changed, 87 insertions(+), 11 deletions(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index d740c8cbb..38b4ffcf9 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -187,3 +187,8 @@ void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* select // Same effect as having a global search bar selected_page->setSearchTerm(prev_page->getSearchTerm()); } + +bool ModDownloadDialog::selectPage(QString pageId) +{ + return m_container->selectPage(pageId); +} \ No newline at end of file diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 18a5f0f36..125cb776a 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -53,6 +53,8 @@ public: const QList getTasks(); const std::shared_ptr &mods; + bool selectPage(QString pageId); + public slots: void confirm(); void accept() override; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index f2c1746fc..7f62fff1e 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad * * 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 "Application.h" #include "ui_ModPage.h" +#include #include #include @@ -80,6 +82,8 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packView->installEventFilter(this); + + connect(ui->packDescription, &QTextBrowser::anchorClicked, this, &ModPage::openUrl); } ModPage::~ModPage() @@ -158,8 +162,8 @@ void ModPage::triggerSearch() { auto changed = m_filter_widget->changed(); m_filter = m_filter_widget->getFilter(); - - if(changed){ + + if (changed) { ui->packView->clearSelection(); ui->packDescription->clear(); ui->versionSelectionBox->clear(); @@ -241,6 +245,54 @@ void ModPage::onModSelected() ui->packView->adjustSize(); } +void ModPage::openUrl(const QUrl& url) +{ + // do not allow other url schemes for security reasons + if (!(url.scheme() == "http" || url.scheme() == "https")) { + qWarning() << "Unsupported scheme" << url.scheme(); + return; + } + + // detect mod URLs and search instead + int prefixLength; + const char* page; + + if ((url.host() == "modrinth.com" || url.host() == "www.modrinth.com") + && url.path().startsWith("/mod/")) { + prefixLength = 5; + page = "modrinth"; + } + else if (APPLICATION->capabilities() & Application::SupportsFlame + && url.host() == "www.curseforge.com" + && url.path().toLower().startsWith("/minecraft/mc-mods/")) { + prefixLength = 19; + page = "curseforge"; + } + else + prefixLength = 0; + + if (prefixLength != 0) { + QString slug = url.path().mid(prefixLength); + + // remove trailing slash(es) + while (slug.endsWith('/')) + slug.remove(slug.length() - 1, 1); + + // ensure that the path doesn't contain any further slashes, + // and the user isn't opening the same mod; they probably + // intended to view in their web browser + if (!slug.isEmpty() && !slug.contains('/') && slug != current.slug) { + ui->searchEdit->setText(slug); + dialog->selectPage(page); + triggerSearch(); + return; + } + } + + // open in the user's web browser + QDesktopServices::openUrl(url); +} + /******** Make changes to the UI ********/ @@ -270,8 +322,8 @@ void ModPage::updateModVersions(int prev_count) if ((valid || m_filter->versions.empty()) && !optedOut(version)) ui->versionSelectionBox->addItem(version.version, QVariant(i)); } - if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { - ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); + if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { + ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); } @@ -317,8 +369,7 @@ void ModPage::updateUi() text += "
" + tr(" by ") + authorStrs.join(", "); } - - if(current.extraDataLoaded) { + if (current.extraDataLoaded) { if (!current.extraData.donate.isEmpty()) { text += "

" + tr("Donate information: "); auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index ae3d7e77e..c9ccbaf20 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -82,6 +82,7 @@ class ModPage : public QWidget, public BasePage { void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(QString data); void onModSelected(); + virtual void openUrl(const QUrl& url); protected: Ui::ModPage* ui = nullptr; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui index 943f02aa2..94365aa5f 100644 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ b/launcher/ui/pages/modplatform/ModPage.ui @@ -16,10 +16,10 @@ - true + false - true + false diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index fd6e32ff7..a4b7b5a14 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -39,7 +39,7 @@ #include "FlameModModel.h" #include "ui/dialogs/ModDownloadDialog.h" -FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) +FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) : ModPage(dialog, instance, new FlameAPI()) { listModel = new FlameMod::ListModel(this); @@ -53,7 +53,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) ui->sortByBox->addItem(tr("Sort by Author")); ui->sortByBox->addItem(tr("Sort by Downloads")); - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // 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 contructor... connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); @@ -78,3 +78,18 @@ bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... auto FlameModPage::shouldDisplay() const -> bool { return true; } + +void FlameModPage::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); + ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary + return; + } + } + + ModPage::openUrl(url); +} \ No newline at end of file diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 50dedd6f4..aef9c6987 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -64,4 +64,6 @@ class FlameModPage : public ModPage { bool optedOut(ModPlatform::IndexedVersion& ver) const override; auto shouldDisplay() const -> bool override; + + void openUrl(const QUrl& url) override; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp index 62e417c8a..c531ea904 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -53,7 +53,7 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan ui->sortByBox->addItem(tr("Sort by Last Updated")); ui->sortByBox->addItem(tr("Sort by Newest")); - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // 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(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); From b9547adce7c7222a2d3c8dc455e7619f2be7a221 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 1 Nov 2022 17:02:24 +0000 Subject: [PATCH 068/341] Add more license headers Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ModDownloadDialog.cpp | 1 + launcher/ui/dialogs/ModDownloadDialog.h | 1 + launcher/ui/pages/modplatform/flame/FlameModPage.cpp | 1 + launcher/ui/pages/modplatform/flame/FlameModPage.h | 1 + 4 files changed, 4 insertions(+) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 38b4ffcf9..7f6f450ce 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad * * 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 diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 125cb776a..6227b58e5 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad * * 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 diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index a4b7b5a14..faf12cea1 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad * * 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 diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index aef9c6987..da4fcdffb 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad * * 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 From d03ae34b61b6c61d0afd4a5ba0d27347c87b0726 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 1 Nov 2022 17:19:54 +0000 Subject: [PATCH 069/341] Auto-select first result Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModPage.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 7f62fff1e..780750a82 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -261,8 +261,7 @@ void ModPage::openUrl(const QUrl& url) && url.path().startsWith("/mod/")) { prefixLength = 5; page = "modrinth"; - } - else if (APPLICATION->capabilities() & Application::SupportsFlame + } else if (APPLICATION->capabilities() & Application::SupportsFlame && url.host() == "www.curseforge.com" && url.path().toLower().startsWith("/minecraft/mc-mods/")) { prefixLength = 19; @@ -282,9 +281,14 @@ void ModPage::openUrl(const QUrl& url) // and the user isn't opening the same mod; they probably // intended to view in their web browser if (!slug.isEmpty() && !slug.contains('/') && slug != current.slug) { - ui->searchEdit->setText(slug); dialog->selectPage(page); + ui->searchEdit->setText(slug); + triggerSearch(); + connect(listModel->activeJob(), &Task::finished, [this] { + ui->packView->setCurrentIndex(listModel->index(0)); + }); + return; } } From 6c7d04043984c0c2c25d2cd646be223786defdc3 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 1 Nov 2022 18:12:27 +0000 Subject: [PATCH 070/341] Hacky tweaks Signed-off-by: TheKodeToad --- launcher/ui/dialogs/ModDownloadDialog.cpp | 13 ++++++++++--- launcher/ui/dialogs/ModDownloadDialog.h | 4 ++++ launcher/ui/pages/modplatform/ModPage.cpp | 10 ++++++---- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 7f6f450ce..876f015a3 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -132,6 +132,8 @@ QList ModDownloadDialog::getPages() if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(FlameModPage::create(this, m_instance)); + m_selected_page = dynamic_cast(pages[0]); + return pages; } @@ -179,17 +181,22 @@ void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* select return; } - auto* selected_page = dynamic_cast(selected); - if (!selected_page) { + m_selected_page = dynamic_cast(selected); + if (!m_selected_page) { qCritical() << "Page '" << selected->displayName() << "' in ModDownloadDialog is not a ModPage!"; return; } // Same effect as having a global search bar - selected_page->setSearchTerm(prev_page->getSearchTerm()); + m_selected_page->setSearchTerm(prev_page->getSearchTerm()); } bool ModDownloadDialog::selectPage(QString pageId) { return m_container->selectPage(pageId); +} + +ModPage* ModDownloadDialog::getSelectedPage() +{ + return m_selected_page; } \ No newline at end of file diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 6227b58e5..c637a70ac 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -25,6 +25,7 @@ #include "ModDownloadTask.h" #include "minecraft/mod/ModFolderModel.h" #include "ui/pages/BasePageProvider.h" +#include "ui/pages/modplatform/ModPage.h" namespace Ui { @@ -56,6 +57,8 @@ public: bool selectPage(QString pageId); + ModPage* getSelectedPage(); + public slots: void confirm(); void accept() override; @@ -69,6 +72,7 @@ private: PageContainer * m_container = nullptr; QDialogButtonBox * m_buttons = nullptr; QVBoxLayout *m_verticalLayout = nullptr; + ModPage *m_selected_page = nullptr; QHash modTask; BaseInstance *m_instance; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 780750a82..6a53e25e5 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -282,11 +282,13 @@ void ModPage::openUrl(const QUrl& url) // intended to view in their web browser if (!slug.isEmpty() && !slug.contains('/') && slug != current.slug) { dialog->selectPage(page); - ui->searchEdit->setText(slug); - triggerSearch(); - connect(listModel->activeJob(), &Task::finished, [this] { - ui->packView->setCurrentIndex(listModel->index(0)); + ModPage* newPage = dialog->getSelectedPage(); + newPage->ui->searchEdit->setText(slug); + newPage->triggerSearch(); + + connect(newPage->listModel->activeJob(), &Task::finished, [newPage] { + newPage->ui->packView->setCurrentIndex(newPage->listModel->index(0)); }); return; From c890aa18f7b24bbb0429456d8c4a9cbb1c8d2bb1 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 1 Nov 2022 18:25:54 +0000 Subject: [PATCH 071/341] Only select correct mod Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModPage.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 6a53e25e5..231e98f67 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -262,7 +262,7 @@ void ModPage::openUrl(const QUrl& url) prefixLength = 5; page = "modrinth"; } else if (APPLICATION->capabilities() & Application::SupportsFlame - && url.host() == "www.curseforge.com" + && (url.host() == "curseforge.com" || url.host() == "www.curseforge.com") && url.path().toLower().startsWith("/minecraft/mc-mods/")) { prefixLength = 19; page = "curseforge"; @@ -287,8 +287,15 @@ void ModPage::openUrl(const QUrl& url) newPage->ui->searchEdit->setText(slug); newPage->triggerSearch(); - connect(newPage->listModel->activeJob(), &Task::finished, [newPage] { - newPage->ui->packView->setCurrentIndex(newPage->listModel->index(0)); + connect(newPage->listModel->activeJob(), &Task::finished, [slug, newPage] { + for (int row = 0; row < newPage->listModel->rowCount({}); row++) { + QModelIndex index = newPage->listModel->index(row); + auto pack = newPage->listModel->data(index, Qt::UserRole).value(); + if (pack.slug == slug) { + newPage->ui->packView->setCurrentIndex(index); + break; + } + } }); return; From d1626d20bd4fdeeb1e9cf0f00d862fc75ddaa663 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 1 Nov 2022 18:30:11 +0000 Subject: [PATCH 072/341] Slight cleanup Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModPage.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 231e98f67..9bb56290f 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -287,12 +287,15 @@ void ModPage::openUrl(const QUrl& url) newPage->ui->searchEdit->setText(slug); newPage->triggerSearch(); - connect(newPage->listModel->activeJob(), &Task::finished, [slug, newPage] { - for (int row = 0; row < newPage->listModel->rowCount({}); row++) { - QModelIndex index = newPage->listModel->index(row); - auto pack = newPage->listModel->data(index, Qt::UserRole).value(); + ModPlatform::ListModel* model = newPage->listModel; + QListView* view = newPage->ui->packView; + + connect(model->activeJob(), &Task::finished, [slug, model, view] { + for (int row = 0; row < model->rowCount({}); row++) { + QModelIndex index = model->index(row); + ModPlatform::IndexedPack pack = model->data(index, Qt::UserRole).value(); if (pack.slug == slug) { - newPage->ui->packView->setCurrentIndex(index); + view->setCurrentIndex(index); break; } } From 576867605dc09b1d117598908d41482e168fbc95 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Tue, 1 Nov 2022 18:40:12 +0000 Subject: [PATCH 073/341] Add another fallback Signed-off-by: TheKodeToad --- launcher/ui/pages/modplatform/ModPage.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 9bb56290f..f269fc72c 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -290,15 +290,18 @@ void ModPage::openUrl(const QUrl& url) ModPlatform::ListModel* model = newPage->listModel; QListView* view = newPage->ui->packView; - connect(model->activeJob(), &Task::finished, [slug, model, view] { + connect(model->activeJob(), &Task::finished, [url, slug, model, view] { for (int row = 0; row < model->rowCount({}); row++) { QModelIndex index = model->index(row); ModPlatform::IndexedPack pack = model->data(index, Qt::UserRole).value(); if (pack.slug == slug) { view->setCurrentIndex(index); - break; + return; } } + + // The final fallback. + QDesktopServices::openUrl(url); }); return; From fef60a9da0216bdcb266d935bbfea03aba8905b4 Mon Sep 17 00:00:00 2001 From: Tayou Date: Sat, 29 Oct 2022 19:27:20 +0200 Subject: [PATCH 074/341] add support for multiple custom themes also moved theme related code from Application.cpp to new ui/themes/ThemeManager.cpp, this class should cleanly isolate theme related functions and help avoid code duplication in future theme related additions. Themes can now be just qss or css files, they won't have color pallette information with them in that case Signed-off-by: Tayou --- launcher/Application.cpp | 70 ++-------- launcher/Application.h | 9 +- launcher/CMakeLists.txt | 2 + launcher/ui/themes/CustomTheme.cpp | 202 +++++++++++++++++++--------- launcher/ui/themes/CustomTheme.h | 43 +++++- launcher/ui/themes/SystemTheme.cpp | 49 ++++++- launcher/ui/themes/ThemeManager.cpp | 152 +++++++++++++++++++++ launcher/ui/themes/ThemeManager.h | 44 ++++++ 8 files changed, 445 insertions(+), 126 deletions(-) create mode 100644 launcher/ui/themes/ThemeManager.cpp create mode 100644 launcher/ui/themes/ThemeManager.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2da8ac560..7b33361c7 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1,7 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Tayou + * + * PolyMC - Minecraft Launcher * Copyright (C) 2022 Lenny McLennington * * This program is free software: you can redistribute it and/or modify @@ -54,12 +57,6 @@ #include "ui/pages/global/APIPage.h" #include "ui/pages/global/CustomCommandsPage.h" -#include "ui/themes/ITheme.h" -#include "ui/themes/SystemTheme.h" -#include "ui/themes/DarkTheme.h" -#include "ui/themes/BrightTheme.h" -#include "ui/themes/CustomTheme.h" - #ifdef Q_OS_WIN #include "ui/WinDarkmode.h" #include @@ -749,28 +746,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Instance icons intialized."; } - // Icon themes + // 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); - qDebug() << "<> Icon themes initialized."; - } + m_themeManager = new ThemeManager(m_mainWindow); - // Initialize widget themes - { - auto insertTheme = [this](ITheme * theme) - { - m_themes.insert(std::make_pair(theme->id(), std::unique_ptr(theme))); - }; - auto darkTheme = new DarkTheme(); - insertTheme(new SystemTheme()); - insertTheme(darkTheme); - insertTheme(new BrightTheme()); - insertTheme(new CustomTheme(darkTheme, "custom")); - qDebug() << "<> Widget themes initialized."; + m_themeManager->InitializeThemes(); } // initialize and load all instances @@ -880,6 +860,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) performMainStartupAction(); } +ThemeManager* Application::getThemeManager() { + return Application::m_themeManager; +} + bool Application::createSetupWizard() { bool javaRequired = [&]() @@ -1127,43 +1111,17 @@ std::shared_ptr Application::javalist() std::vector Application::getValidApplicationThemes() { - std::vector ret; - auto iter = m_themes.cbegin(); - while (iter != m_themes.cend()) - { - ret.push_back((*iter).second.get()); - iter++; - } - return ret; + return m_themeManager->getValidApplicationThemes(); } void Application::setApplicationTheme(const QString& name, bool initial) { - auto systemPalette = qApp->palette(); - auto themeIter = m_themes.find(name); - if(themeIter != m_themes.end()) - { - auto & theme = (*themeIter).second; - theme->apply(initial); -#ifdef Q_OS_WIN - if (m_mainWindow && IsWindows10OrGreater()) { - if (QString::compare(theme->id(), "dark") == 0) { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); - } else { - WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); - } - } -#endif - } - else - { - qWarning() << "Tried to set invalid theme:" << name; - } + m_themeManager->setApplicationTheme(name, initial); } void Application::setIconTheme(const QString& name) { - QIcon::setThemeName(name); + m_themeManager->setIconTheme(name); } QIcon Application::getThemedIcon(const QString& name) diff --git a/launcher/Application.h b/launcher/Application.h index 8fa0ab10e..33d8db29a 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-only /* - * PolyMC - Minecraft Launcher + * Prism Launcher * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 Tayou * * 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,6 +45,8 @@ #include #include +#include "ui/themes/ThemeManager.h" + #include #include "minecraft/launch/MinecraftServerTarget.h" @@ -198,6 +201,8 @@ public: void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); + ThemeManager* getThemeManager(); + signals: void updateAllowedChanged(bool status); void globalSettingsAboutToOpen(); @@ -255,9 +260,9 @@ private: std::shared_ptr m_javalist; std::shared_ptr m_translations; std::shared_ptr m_globalSettingsProvider; - std::map> m_themes; std::unique_ptr m_mcedit; QSet m_features; + ThemeManager* m_themeManager; QMap> m_profilers; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0dae47df8..5b0d9282a 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -650,6 +650,8 @@ SET(LAUNCHER_SOURCES ui/themes/ITheme.h ui/themes/SystemTheme.cpp ui/themes/SystemTheme.h + ui/themes/ThemeManager.cpp + ui/themes/ThemeManager.h # Processes LaunchController.h diff --git a/launcher/ui/themes/CustomTheme.cpp b/launcher/ui/themes/CustomTheme.cpp index 3e3e27de3..3c35bf37c 100644 --- a/launcher/ui/themes/CustomTheme.cpp +++ b/launcher/ui/themes/CustomTheme.cpp @@ -1,12 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher + * Copyright (C) 2022 Tayou + * + * 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 . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Authors: Andrew Okin + * Peterix + * Orochimarufan + * + * 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 "CustomTheme.h" -#include #include #include +#include "ThemeManager.h" const char * themeFile = "theme.json"; -const char * styleFile = "themeStyle.css"; -static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAmount, QColor &fadeColor, QString &name, QString &widgets) +static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAmount, QColor &fadeColor, QString &name, QString &widgets, QString &qssFilePath, bool &dataIncomplete) { QFileInfo pathInfo(path); if(pathInfo.exists() && pathInfo.isFile()) @@ -15,8 +52,11 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm { auto doc = Json::requireDocument(path, "Theme JSON file"); const QJsonObject root = doc.object(); + dataIncomplete = !root.contains("qssFilePath"); name = Json::requireString(root, "name", "Theme name"); widgets = Json::requireString(root, "widgets", "Qt widget theme"); + qssFilePath = Json::ensureString(root, "qssFilePath", "themeStyle.css"); + //auto colorFileList = Json::ensureArray(root, "colorFiles"); auto colorsRoot = Json::requireObject(root, "colors", "colors object"); auto readColor = [&](QString colorName) -> QColor { @@ -26,7 +66,7 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm QColor color(colorValue); if(!color.isValid()) { - qWarning() << "Color value" << colorValue << "for" << colorName << "was not recognized."; + themeWarningLog << "Color value" << colorValue << "for" << colorName << "was not recognized."; return QColor(); } return color; @@ -42,7 +82,7 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm } else { - qDebug() << "Color value for" << colorName << "was not present."; + themeDebugLog << "Color value for" << colorName << "was not present."; } }; @@ -68,23 +108,24 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm } catch (const Exception &e) { - qWarning() << "Couldn't load theme json: " << e.cause(); + themeWarningLog << "Couldn't load theme json: " << e.cause(); return false; } } else { - qDebug() << "No theme json present."; + themeDebugLog << "No theme json present."; return false; } return true; } -static bool writeThemeJson(const QString &path, const QPalette &palette, double fadeAmount, QColor fadeColor, QString name, QString widgets) +static bool writeThemeJson(const QString &path, const QPalette &palette, double fadeAmount, QColor fadeColor, QString name, QString widgets, QString qssFilePath) { QJsonObject rootObj; rootObj.insert("name", name); rootObj.insert("widgets", widgets); + rootObj.insert("qssFilePath", qssFilePath); QJsonObject colorsObj; auto insertColor = [&](QPalette::ColorRole role, QString colorName) @@ -119,75 +160,112 @@ static bool writeThemeJson(const QString &path, const QPalette &palette, double } catch (const Exception &e) { - qWarning() << "Failed to write theme json to" << path; + themeWarningLog << "Failed to write theme json to" << path; return false; } } -CustomTheme::CustomTheme(ITheme* baseTheme, QString folder) +/// @brief +/// @param baseTheme Base Theme +/// @param fileInfo FileInfo object for file to load +/// @param isManifest whether to load a theme manifest or a qss file +CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest) { - m_id = folder; - QString path = FS::PathCombine("themes", m_id); - QString pathResources = FS::PathCombine("themes", m_id, "resources"); + if (isManifest) { + m_id = fileInfo.dir().dirName(); - qDebug() << "Loading theme" << m_id; + QString path = FS::PathCombine("themes", m_id); + QString pathResources = FS::PathCombine("themes", m_id, "resources"); - if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) - { - qWarning() << "couldn't create folder for theme!"; - m_palette = baseTheme->colorScheme(); - m_styleSheet = baseTheme->appStyleSheet(); - return; - } - - auto themeFilePath = FS::PathCombine(path, themeFile); - - m_palette = baseTheme->colorScheme(); - if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets)) - { - m_name = "Custom"; - m_palette = baseTheme->colorScheme(); - m_fadeColor = baseTheme->fadeColor(); - m_fadeAmount = baseTheme->fadeAmount(); - m_widgets = baseTheme->qtTheme(); - - QFileInfo info(themeFilePath); - if(!info.exists()) + if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) { - writeThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, "Custom", m_widgets); - } - } - else - { - m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); - } - - auto cssFilePath = FS::PathCombine(path, styleFile); - QFileInfo info (cssFilePath); - if(info.isFile()) - { - try - { - // TODO: validate css? - m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); - } - catch (const Exception &e) - { - qWarning() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; + themeWarningLog << "X couldn't create folder for theme!"; + m_palette = baseTheme->colorScheme(); m_styleSheet = baseTheme->appStyleSheet(); + return; } - } - else - { - qDebug() << "No theme css present."; - m_styleSheet = baseTheme->appStyleSheet(); + + auto themeFilePath = FS::PathCombine(path, themeFile); + + bool jsonDataIncomplete = false; + + m_palette = baseTheme->colorScheme(); + if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath, jsonDataIncomplete)) + { + themeDebugLog << "Did not read theme json file correctly, writing new one to: " << themeFilePath; + m_name = "Custom"; + m_palette = baseTheme->colorScheme(); + m_fadeColor = baseTheme->fadeColor(); + m_fadeAmount = baseTheme->fadeAmount(); + m_widgets = baseTheme->qtTheme(); + m_qssFilePath = "themeStyle.css"; + + QFileInfo info(themeFilePath); + } + else + { + m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); + } + + if(jsonDataIncomplete) + { + writeThemeJson(fileInfo.absoluteFilePath(), m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath); + } + + auto cssFilePath = FS::PathCombine(path, m_qssFilePath); + QFileInfo info (cssFilePath); + if(info.isFile()) + { + try + { + // TODO: validate css? + m_styleSheet = QString::fromUtf8(FS::read(cssFilePath)); + } + catch (const Exception &e) + { + themeWarningLog << "X Couldn't load css:" << e.cause() << "from" << cssFilePath; + m_styleSheet = baseTheme->appStyleSheet(); + } + } + else + { + themeDebugLog << "X No theme css present."; + m_styleSheet = baseTheme->appStyleSheet(); + try + { + FS::write(cssFilePath, m_styleSheet.toUtf8()); + } + catch (const Exception &e) + { + themeWarningLog << "X Couldn't write css:" << e.cause() << "to" << cssFilePath; + } + } + } else { + m_id = fileInfo.fileName(); + m_name = fileInfo.baseName(); + QString path = fileInfo.filePath(); + //themeDebugLog << "Theme ID: " << m_id; + //themeDebugLog << "Theme Name: " << m_name; + //themeDebugLog << "Theme Path: " << path; + + if(!FS::ensureFilePathExists(path)) + { + themeWarningLog << m_name << " Theme file path doesn't exist!"; + m_palette = baseTheme->colorScheme(); + m_styleSheet = baseTheme->appStyleSheet(); + return; + } + + m_palette = baseTheme->colorScheme(); try { - FS::write(cssFilePath, m_styleSheet.toUtf8()); + // TODO: validate qss? + m_styleSheet = QString::fromUtf8(FS::read(path)); } catch (const Exception &e) { - qWarning() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; + themeWarningLog << "Couldn't load qss:" << e.cause() << "from" << path; + m_styleSheet = baseTheme->appStyleSheet(); } } } diff --git a/launcher/ui/themes/CustomTheme.h b/launcher/ui/themes/CustomTheme.h index d216895de..f4a7b7378 100644 --- a/launcher/ui/themes/CustomTheme.h +++ b/launcher/ui/themes/CustomTheme.h @@ -1,11 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher + * Copyright (C) 2022 Tayou + * + * 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 . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Authors: Andrew Okin + * Peterix + * Orochimarufan + * + * 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 "ITheme.h" +#include +#include class CustomTheme: public ITheme { public: - CustomTheme(ITheme * baseTheme, QString folder); + CustomTheme(ITheme * baseTheme, QFileInfo& file, bool isManifest); virtual ~CustomTheme() {} QString id() override; @@ -27,5 +67,6 @@ private: /* data */ QString m_name; QString m_id; QString m_widgets; + QString m_qssFilePath; }; diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index 49b1afaa1..b8eb2a3c9 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -1,30 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher + * Copyright (C) 2022 Tayou + * + * 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 . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Authors: Andrew Okin + * Peterix + * Orochimarufan + * + * 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 "SystemTheme.h" #include #include #include #include +#include "ThemeManager.h" SystemTheme::SystemTheme() { - qDebug() << "Determining System Theme..."; + themeDebugLog << "Determining System Theme..."; const auto & style = QApplication::style(); systemPalette = style->standardPalette(); QString lowerThemeName = style->objectName(); - qDebug() << "System theme seems to be:" << lowerThemeName; + themeDebugLog << "System theme seems to be:" << lowerThemeName; QStringList styles = QStyleFactory::keys(); for(auto &st: styles) { - qDebug() << "Considering theme from theme factory:" << st.toLower(); + themeDebugLog << "Considering theme from theme factory:" << st.toLower(); if(st.toLower() == lowerThemeName) { systemTheme = st; - qDebug() << "System theme has been determined to be:" << systemTheme; + themeDebugLog << "System theme has been determined to be:" << systemTheme; return; } } // fall back to fusion if we can't find the current theme. systemTheme = "Fusion"; - qDebug() << "System theme not found, defaulted to Fusion"; + themeDebugLog << "System theme not found, defaulted to Fusion"; } void SystemTheme::apply(bool initial) diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp new file mode 100644 index 000000000..6e1566d2f --- /dev/null +++ b/launcher/ui/themes/ThemeManager.cpp @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher + * Copyright (C) 2022 Tayou + * + * 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 . + */ +#include "ThemeManager.h" + +#include "ui/themes/SystemTheme.h" +#include "ui/themes/DarkTheme.h" +#include "ui/themes/BrightTheme.h" +#include "ui/themes/CustomTheme.h" +#include +#include +#include +#include + +#include "Application.h" + +#ifdef Q_OS_WIN +#include +#include "ui/WinDarkmode.h" +#endif + +ThemeManager::ThemeManager(MainWindow* mainWindow) { + m_mainWindow = mainWindow; +} + +/// @brief Adds the Theme to the list of themes +/// @param theme The Theme to add +/// @return Theme ID +QString ThemeManager::AddTheme(ITheme * theme) { + m_themes.insert(std::make_pair(theme->id(), std::unique_ptr(theme))); + return theme->id(); +} + +/// @brief Gets the Theme from the List via ID +/// @param themeId Theme ID of theme to fetch +/// @return Theme at themeId +ITheme* ThemeManager::GetTheme(QString themeId) { + return m_themes[themeId].get(); +} + +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."; + } + + // Initialize widget themes + { + themeDebugLog << "<> Initializing Widget Themes"; + themeDebugLog "✓ Loading Built-in Theme:" << AddTheme(new SystemTheme()); + auto darkThemeId = AddTheme(new DarkTheme()); + themeDebugLog "✓ Loading Built-in Theme:" << darkThemeId; + themeDebugLog "✓ Loading Built-in Theme:" << AddTheme(new BrightTheme()); + + // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in dropdown? Dunno how to do that though) + QString themeFolder = (new QDir("./themes/"))->absoluteFilePath(""); + themeDebugLog << "Theme Folder Path: " << themeFolder; + + QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + 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(); + CustomTheme* theme = new CustomTheme(GetTheme(darkThemeId), themeJson, true); + AddTheme(theme); + } 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(); + CustomTheme* theme = new CustomTheme(GetTheme(darkThemeId), customThemeFileInfo, false); + AddTheme(theme); + } + } + } + + themeDebugLog << "<> Widget themes initialized."; + } +} + +std::vector ThemeManager::getValidApplicationThemes() +{ + std::vector ret; + auto iter = m_themes.cbegin(); + while (iter != m_themes.cend()) + { + ret.push_back((*iter).second.get()); + iter++; + } + return ret; +} + +void ThemeManager::setIconTheme(const QString& name) +{ + QIcon::setThemeName(name); +} + +void ThemeManager::applyCurrentlySelectedTheme() { + setIconTheme(APPLICATION->settings()->get("IconTheme").toString()); + themeDebugLog() << "<> Icon theme set."; + setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString(), true); + themeDebugLog() << "<> Application theme set."; +} + +void ThemeManager::setApplicationTheme(const QString& name, bool initial) +{ + auto systemPalette = qApp->palette(); + auto themeIter = m_themes.find(name); + if(themeIter != m_themes.end()) + { + auto & theme = (*themeIter).second; + theme->apply(initial); +#ifdef Q_OS_WIN + if (m_mainWindow && IsWindows10OrGreater()) { + if (QString::compare(theme->id(), "dark") == 0) { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); + } else { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); + } + } +#endif + } + else + { + qWarning() << "Tried to set invalid theme:" << name; + } +} diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h new file mode 100644 index 000000000..fa6ea2369 --- /dev/null +++ b/launcher/ui/themes/ThemeManager.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher + * Copyright (C) 2022 Tayou + * + * 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 . + */ + +#include + +#include "ui/themes/ITheme.h" +#include "ui/MainWindow.h" + +#define themeDebugLog qDebug() << "[Themes]" +#define themeWarningLog qWarning() << "[Themes]" + +class ThemeManager { +public: + ThemeManager(MainWindow* mainWindow); + void InitializeThemes(); + + std::vector getValidApplicationThemes(); + void setIconTheme(const QString& name); + void applyCurrentlySelectedTheme(); + void setApplicationTheme(const QString& name, bool initial); + +private: + std::map> m_themes; + MainWindow* m_mainWindow; + + QString AddTheme(ITheme * theme); + ITheme* GetTheme(QString themeId); +}; + From a3f5ea359827abb7949070cdcd8d56be50ab3360 Mon Sep 17 00:00:00 2001 From: Tayou Date: Tue, 1 Nov 2022 15:41:08 +0100 Subject: [PATCH 075/341] added suggested changes Signed-off-by: Tayou --- launcher/Application.cpp | 18 +++------ launcher/Application.h | 9 ++--- launcher/ui/themes/CustomTheme.cpp | 24 ++++++------ launcher/ui/themes/CustomTheme.h | 3 +- launcher/ui/themes/SystemTheme.cpp | 10 ++--- launcher/ui/themes/ThemeManager.cpp | 60 +++++++++++++++-------------- launcher/ui/themes/ThemeManager.h | 15 ++++++-- 7 files changed, 68 insertions(+), 71 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 7b33361c7..fa2023121 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -2,10 +2,8 @@ /* * Prism Launcher * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 Tayou - * - * PolyMC - Minecraft Launcher * Copyright (C) 2022 Lenny McLennington + * Copyright (C) 2022 Tayou * * 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 @@ -71,6 +69,8 @@ #include "ui/pagedialog/PageDialog.h" +#include "ui/themes/ThemeManager.h" + #include "ApplicationMessage.h" #include @@ -747,11 +747,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) } // Themes - { - m_themeManager = new ThemeManager(m_mainWindow); - - m_themeManager->InitializeThemes(); - } + m_themeManager = std::make_unique(m_mainWindow); // initialize and load all instances { @@ -860,10 +856,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) performMainStartupAction(); } -ThemeManager* Application::getThemeManager() { - return Application::m_themeManager; -} - bool Application::createSetupWizard() { bool javaRequired = [&]() @@ -1109,7 +1101,7 @@ std::shared_ptr Application::javalist() return m_javalist; } -std::vector Application::getValidApplicationThemes() +QList Application::getValidApplicationThemes() { return m_themeManager->getValidApplicationThemes(); } diff --git a/launcher/Application.h b/launcher/Application.h index 33d8db29a..ec434c6cc 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -45,8 +45,6 @@ #include #include -#include "ui/themes/ThemeManager.h" - #include #include "minecraft/launch/MinecraftServerTarget.h" @@ -71,6 +69,7 @@ class BaseDetachedToolFactory; class TranslationsModel; class ITheme; class MCEditTool; +class ThemeManager; namespace Meta { class Index; @@ -121,7 +120,7 @@ public: void setIconTheme(const QString& name); - std::vector getValidApplicationThemes(); + QList getValidApplicationThemes(); void setApplicationTheme(const QString& name, bool initial); @@ -201,8 +200,6 @@ public: void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); - ThemeManager* getThemeManager(); - signals: void updateAllowedChanged(bool status); void globalSettingsAboutToOpen(); @@ -262,7 +259,7 @@ private: std::shared_ptr m_globalSettingsProvider; std::unique_ptr m_mcedit; QSet m_features; - ThemeManager* m_themeManager; + std::unique_ptr m_themeManager; QMap> m_profilers; diff --git a/launcher/ui/themes/CustomTheme.cpp b/launcher/ui/themes/CustomTheme.cpp index 3c35bf37c..c346d42f3 100644 --- a/launcher/ui/themes/CustomTheme.cpp +++ b/launcher/ui/themes/CustomTheme.cpp @@ -66,7 +66,7 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm QColor color(colorValue); if(!color.isValid()) { - themeWarningLog << "Color value" << colorValue << "for" << colorName << "was not recognized."; + themeWarningLog() << "Color value" << colorValue << "for" << colorName << "was not recognized."; return QColor(); } return color; @@ -82,7 +82,7 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm } else { - themeDebugLog << "Color value for" << colorName << "was not present."; + themeDebugLog() << "Color value for" << colorName << "was not present."; } }; @@ -108,13 +108,13 @@ static bool readThemeJson(const QString &path, QPalette &palette, double &fadeAm } catch (const Exception &e) { - themeWarningLog << "Couldn't load theme json: " << e.cause(); + themeWarningLog() << "Couldn't load theme json: " << e.cause(); return false; } } else { - themeDebugLog << "No theme json present."; + themeDebugLog() << "No theme json present."; return false; } return true; @@ -160,7 +160,7 @@ static bool writeThemeJson(const QString &path, const QPalette &palette, double } catch (const Exception &e) { - themeWarningLog << "Failed to write theme json to" << path; + themeWarningLog() << "Failed to write theme json to" << path; return false; } } @@ -179,7 +179,7 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest if(!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) { - themeWarningLog << "X couldn't create folder for theme!"; + themeWarningLog() << "couldn't create folder for theme!"; m_palette = baseTheme->colorScheme(); m_styleSheet = baseTheme->appStyleSheet(); return; @@ -192,7 +192,7 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest m_palette = baseTheme->colorScheme(); if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath, jsonDataIncomplete)) { - themeDebugLog << "Did not read theme json file correctly, writing new one to: " << themeFilePath; + themeDebugLog() << "Did not read theme json file correctly, writing new one to: " << themeFilePath; m_name = "Custom"; m_palette = baseTheme->colorScheme(); m_fadeColor = baseTheme->fadeColor(); @@ -223,13 +223,13 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest } catch (const Exception &e) { - themeWarningLog << "X Couldn't load css:" << e.cause() << "from" << cssFilePath; + themeWarningLog() << "Couldn't load css:" << e.cause() << "from" << cssFilePath; m_styleSheet = baseTheme->appStyleSheet(); } } else { - themeDebugLog << "X No theme css present."; + themeDebugLog() << "No theme css present."; m_styleSheet = baseTheme->appStyleSheet(); try { @@ -237,7 +237,7 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest } catch (const Exception &e) { - themeWarningLog << "X Couldn't write css:" << e.cause() << "to" << cssFilePath; + themeWarningLog() << "Couldn't write css:" << e.cause() << "to" << cssFilePath; } } } else { @@ -250,7 +250,7 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest if(!FS::ensureFilePathExists(path)) { - themeWarningLog << m_name << " Theme file path doesn't exist!"; + themeWarningLog() << m_name << " Theme file path doesn't exist!"; m_palette = baseTheme->colorScheme(); m_styleSheet = baseTheme->appStyleSheet(); return; @@ -264,7 +264,7 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest } catch (const Exception &e) { - themeWarningLog << "Couldn't load qss:" << e.cause() << "from" << path; + themeWarningLog() << "Couldn't load qss:" << e.cause() << "from" << path; m_styleSheet = baseTheme->appStyleSheet(); } } diff --git a/launcher/ui/themes/CustomTheme.h b/launcher/ui/themes/CustomTheme.h index f4a7b7378..6d1f46b4b 100644 --- a/launcher/ui/themes/CustomTheme.h +++ b/launcher/ui/themes/CustomTheme.h @@ -39,8 +39,7 @@ #pragma once #include "ITheme.h" -#include -#include +#include class CustomTheme: public ITheme { diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index b8eb2a3c9..ec24c6d76 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -45,25 +45,25 @@ SystemTheme::SystemTheme() { - themeDebugLog << "Determining System Theme..."; + themeDebugLog() << "Determining System Theme..."; const auto & style = QApplication::style(); systemPalette = style->standardPalette(); QString lowerThemeName = style->objectName(); - themeDebugLog << "System theme seems to be:" << lowerThemeName; + themeDebugLog() << "System theme seems to be:" << lowerThemeName; QStringList styles = QStyleFactory::keys(); for(auto &st: styles) { - themeDebugLog << "Considering theme from theme factory:" << st.toLower(); + themeDebugLog() << "Considering theme from theme factory:" << st.toLower(); if(st.toLower() == lowerThemeName) { systemTheme = st; - themeDebugLog << "System theme has been determined to be:" << systemTheme; + themeDebugLog() << "System theme has been determined to be:" << systemTheme; return; } } // fall back to fusion if we can't find the current theme. systemTheme = "Fusion"; - themeDebugLog << "System theme not found, defaulted to Fusion"; + themeDebugLog() << "System theme not found, defaulted to Fusion"; } void SystemTheme::apply(bool initial) diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 6e1566d2f..3c338fd67 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -29,20 +29,25 @@ #include "Application.h" #ifdef Q_OS_WIN -#include +#include +// this is needed for versionhelpers.h, it is also included in WinDarkmode, but we can't rely on that. +// Ultimately this should be included in versionhelpers, but that is outside of the project. #include "ui/WinDarkmode.h" +#include #endif ThemeManager::ThemeManager(MainWindow* mainWindow) { m_mainWindow = mainWindow; + InitializeThemes(); } /// @brief Adds the Theme to the list of themes /// @param theme The Theme to add /// @return Theme ID -QString ThemeManager::AddTheme(ITheme * theme) { - m_themes.insert(std::make_pair(theme->id(), std::unique_ptr(theme))); - return theme->id(); +QString ThemeManager::AddTheme(std::unique_ptr theme) { + QString id = theme->id(); + m_themes.emplace(id, std::move(theme)); + return id; } /// @brief Gets the Theme from the List via ID @@ -62,20 +67,20 @@ void ThemeManager::InitializeThemes() { auto searchPaths = QIcon::themeSearchPaths(); searchPaths.append("iconthemes"); QIcon::setThemeSearchPaths(searchPaths); - themeDebugLog << "<> Icon themes initialized."; + themeDebugLog() << "<> Icon themes initialized."; } // Initialize widget themes { - themeDebugLog << "<> Initializing Widget Themes"; - themeDebugLog "✓ Loading Built-in Theme:" << AddTheme(new SystemTheme()); - auto darkThemeId = AddTheme(new DarkTheme()); - themeDebugLog "✓ Loading Built-in Theme:" << darkThemeId; - themeDebugLog "✓ Loading Built-in Theme:" << AddTheme(new BrightTheme()); + themeDebugLog() << "<> Initializing Widget Themes"; + themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique()); + auto darkThemeId = AddTheme(std::make_unique()); + themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; + themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique()); - // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in dropdown? Dunno how to do that though) - QString themeFolder = (new QDir("./themes/"))->absoluteFilePath(""); - themeDebugLog << "Theme Folder Path: " << themeFolder; + // 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, QDirIterator::Subdirectories); while (directoryIterator.hasNext()) { @@ -83,34 +88,30 @@ void ThemeManager::InitializeThemes() { QFileInfo themeJson(dir.absoluteFilePath("theme.json")); if (themeJson.exists()) { // Load "theme.json" based themes - themeDebugLog "✓ Loading JSON Theme from:" << themeJson.absoluteFilePath(); - CustomTheme* theme = new CustomTheme(GetTheme(darkThemeId), themeJson, true); - AddTheme(theme); + themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath(); + AddTheme(std::make_unique(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(); - CustomTheme* theme = new CustomTheme(GetTheme(darkThemeId), customThemeFileInfo, false); - AddTheme(theme); + themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath(); + AddTheme(std::make_unique(GetTheme(darkThemeId), customThemeFileInfo, false)); } } } - themeDebugLog << "<> Widget themes initialized."; + themeDebugLog() << "<> Widget themes initialized."; } } -std::vector ThemeManager::getValidApplicationThemes() +QList ThemeManager::getValidApplicationThemes() { - std::vector ret; - auto iter = m_themes.cbegin(); - while (iter != m_themes.cend()) - { - ret.push_back((*iter).second.get()); - iter++; + QList ret; + ret.reserve(m_themes.size()); + for (auto&& [id, theme] : m_themes) { + ret.append(theme.get()); } return ret; } @@ -133,7 +134,8 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial) auto themeIter = m_themes.find(name); if(themeIter != m_themes.end()) { - auto & theme = (*themeIter).second; + auto & theme = themeIter->second; + themeDebugLog() << "applying theme" << theme->name(); theme->apply(initial); #ifdef Q_OS_WIN if (m_mainWindow && IsWindows10OrGreater()) { @@ -147,6 +149,6 @@ void ThemeManager::setApplicationTheme(const QString& name, bool initial) } else { - qWarning() << "Tried to set invalid theme:" << name; + themeWarningLog() << "Tried to set invalid theme:" << name; } } diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index fa6ea2369..871e6fe2f 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -15,21 +15,28 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#pragma once #include #include "ui/themes/ITheme.h" #include "ui/MainWindow.h" -#define themeDebugLog qDebug() << "[Themes]" -#define themeWarningLog qWarning() << "[Themes]" +inline auto themeDebugLog() { + return qDebug() << "[Theme]"; +} +inline auto themeWarningLog() { + return qWarning() << "[Theme]"; +} class ThemeManager { public: ThemeManager(MainWindow* mainWindow); + + // maybe make private? Or put in ctor? void InitializeThemes(); - std::vector getValidApplicationThemes(); + QList getValidApplicationThemes(); void setIconTheme(const QString& name); void applyCurrentlySelectedTheme(); void setApplicationTheme(const QString& name, bool initial); @@ -38,7 +45,7 @@ private: std::map> m_themes; MainWindow* m_mainWindow; - QString AddTheme(ITheme * theme); + QString AddTheme(std::unique_ptr theme); ITheme* GetTheme(QString themeId); }; From afcdf4b9cf5c65d67657bf6a682eb43d61b21ee6 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 31 Oct 2022 13:00:41 +0100 Subject: [PATCH 076/341] condensed icon theme code Signed-off-by: Tayou --- launcher/ui/pages/global/LauncherPage.cpp | 42 ++--------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 4ae7509cf..a1cbbc339 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -384,46 +384,8 @@ void LauncherPage::loadSettings() m_currentUpdateChannel = s->get("UpdateChannel").toString(); //FIXME: make generic auto theme = s->get("IconTheme").toString(); - if (theme == "pe_colored") - { - ui->themeComboBox->setCurrentIndex(0); - } - else if (theme == "pe_light") - { - ui->themeComboBox->setCurrentIndex(1); - } - else if (theme == "pe_dark") - { - ui->themeComboBox->setCurrentIndex(2); - } - else if (theme == "pe_blue") - { - ui->themeComboBox->setCurrentIndex(3); - } - else if (theme == "OSX") - { - ui->themeComboBox->setCurrentIndex(4); - } - else if (theme == "iOS") - { - ui->themeComboBox->setCurrentIndex(5); - } - else if (theme == "flat") - { - ui->themeComboBox->setCurrentIndex(6); - } - else if (theme == "flat_white") - { - ui->themeComboBox->setCurrentIndex(7); - } - else if (theme == "multimc") - { - ui->themeComboBox->setCurrentIndex(8); - } - else if (theme == "custom") - { - ui->themeComboBox->setCurrentIndex(9); - } + QStringList iconThemeOptions{"pe_colored", "pe_light", "pe_dark", "pe_blue", "OSX", "iOS", "flat", "flat_white", "multimc", "custom"}; + ui->themeComboBox->setCurrentIndex(iconThemeOptions.indexOf(theme)); { auto currentTheme = s->get("ApplicationTheme").toString(); From 718a9a35599a109578bbf36925548f60e9a0167d Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 2 Nov 2022 01:20:58 +0530 Subject: [PATCH 077/341] change: Update credits Replaces txtsd's GitHub link with their website Moves DioEgizio and flowln from contributors to dev team Signed-off-by: txtsd --- launcher/ui/dialogs/AboutDialog.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 52d6baef4..a36e4a3dd 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -73,17 +73,12 @@ QString getCreditsHtml() stream << "

" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "

\n"; stream << QString("

Sefa Eyeoglu (Scrumplex) %1

\n") .arg(getWebsite("https://scrumplex.net")); stream << QString("

dada513 %1

\n") .arg(getGitHub("dada513")); - stream << QString("

txtsd %1

\n") .arg(getGitHub("txtsd")); + stream << QString("

txtsd %1

\n") .arg(getWebsite("https://ihavea.quest")); stream << QString("

timoreo %1

\n") .arg(getGitHub("timoreo22")); stream << QString("

Ezekiel Smith (ZekeSmith) %1

\n") .arg(getGitHub("ZekeSmith")); stream << QString("

cozyGalvinism %1

\n") .arg(getGitHub("cozyGalvinism")); - stream << "
\n"; - - //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Contributors" - stream << "

" << QObject::tr("%1 Contributors", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "

\n"; stream << QString("

DioEgizio %1

\n") .arg(getGitHub("DioEgizio")); stream << QString("

flowln %1

\n") .arg(getGitHub("flowln")); - stream << QString("

swirl %1

\n") .arg(getWebsite("https://swurl.xyz/")); stream << "
\n"; // TODO: possibly retrieve from git history at build time? From 0a0ce74105b1c01bb312175a823e715d6abb310c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santiago=20C=C3=A9zar?= Date: Tue, 1 Nov 2022 19:06:27 -0300 Subject: [PATCH 078/341] add rory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Santiago Cézar Co-authored-by: Ashtaka <66513643+AshtakaOOf@users.noreply.github.com> --- launcher/Application.cpp | 1 + .../resources/backgrounds/backgrounds.qrc | 9 +++-- launcher/resources/backgrounds/generic.jpg | Bin 0 -> 335269 bytes .../{cattiversary.png => kitteh-bday.png} | Bin .../{catmas.png => kitteh-xmas.png} | Bin .../backgrounds/{catbgrnd2.png => kitteh.png} | Bin launcher/resources/backgrounds/rory-bday.png | Bin 0 -> 89472 bytes launcher/resources/backgrounds/rory-xmas.png | Bin 0 -> 90158 bytes launcher/resources/backgrounds/rory.png | Bin 0 -> 90624 bytes launcher/ui/MainWindow.cpp | 17 ++++----- launcher/ui/pages/global/LauncherPage.cpp | 16 +++++++++ launcher/ui/pages/global/LauncherPage.ui | 33 ++++++++++++++++++ 12 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 launcher/resources/backgrounds/generic.jpg rename launcher/resources/backgrounds/{cattiversary.png => kitteh-bday.png} (100%) rename launcher/resources/backgrounds/{catmas.png => kitteh-xmas.png} (100%) rename launcher/resources/backgrounds/{catbgrnd2.png => kitteh.png} (100%) create mode 100644 launcher/resources/backgrounds/rory-bday.png create mode 100644 launcher/resources/backgrounds/rory-xmas.png create mode 100644 launcher/resources/backgrounds/rory.png diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2da8ac560..5772d7cad 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -501,6 +501,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Theming m_settings->registerSetting("IconTheme", QString("pe_colored")); m_settings->registerSetting("ApplicationTheme", QString("system")); + m_settings->registerSetting("BackgroundCat", QString("kitteh")); // Remembered state m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); diff --git a/launcher/resources/backgrounds/backgrounds.qrc b/launcher/resources/backgrounds/backgrounds.qrc index 52921512a..db48af8bb 100644 --- a/launcher/resources/backgrounds/backgrounds.qrc +++ b/launcher/resources/backgrounds/backgrounds.qrc @@ -1,8 +1,11 @@ - catbgrnd2.png - catmas.png - cattiversary.png + kitteh.png + kitteh-xmas.png + kitteh-bday.png + rory.png + rory-xmas.png + rory-bday.png diff --git a/launcher/resources/backgrounds/generic.jpg b/launcher/resources/backgrounds/generic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c4832b0a9a2d8f83f899e811376077ab7c7dffc9 GIT binary patch literal 335269 zcmb4q^;Z;L(EbLnbV)3*Q7{~$ukpaPE!2d=8FaQ7q2L1!! z{{Y4Ug0KNN5Zr%KJyHM&1O$Vyf!L6L+dvRC5CkRzV39KmC}2Nf(YN=b5RAzz$AK%h z4SzbPG}ygxU={L*P`y;j5;m+@WD^M(p*C_-Hg=4St8D*v`R_#9|F!kM_&;<1`~(7H zVdMOh36cHV2K_$(K{!Ax@IMcN$iU3x0$2)9^jQS$Dg3bEiZPjOTbG*HhQ>{NeIy;}geThMRG%gGBrEXg(6ro{tv^5N zQ!ZhO*`wWl=#ci{J40oyA`&E3XM7)Kj=;Gyx;be#cbE;m28rQ0Ux_+hE8%c zl5uxT$N-=Q)Go`4R>LGf-uxLJfEbnVHqV)I(2iUr|Y-`pI#y*)0t=lHf%+tpadOB`Q{X zCj=bsD=O)v^427Y)b$TP^s<6w8YJIhVxWy6%}41EstuAj{oq}WIf;vL$+RsdUsazv zzKQJ#8fZhdX9?+7q#Kkc0g*f3?CwpU1kt=&Y8G?C*fn*&Z~cKOd5>cdWTK~LMd+_T z^pT^}BD68zhL}zV?Y3Mx@E<@L`FTnjR)IXT{1IW|K^0I!`0j}V`hc*$Jc&P@&YwSTay;M`Wq$q5ia^8$yx4$HNWeQ6k}|z3s1*D z_SZgVvO-fplc|+tt-i65a#XT5VE#jqMzzV+}|vmF8W~)x2+U8yC*e4{kcmyk9chIES~vUy-K1zRmH^ zqsCU;W452N9ldc3Hv|8&FMsYANvf(fv)?{hiFE%D@bLq#VUY6V9t4H-wJ2JZr=Jjy zvQkJdkSrwzmfO!iXn(+0^+Kc-{8?H~o1o$<{~;-{5rsa2436I&`3LRp$*q+lwYNv zz*NWG#!1}Peov-k$Zxw7JxYPD-DR@et@nRe1J5#pqpZzF0n+^9N4avL_yf}~eczS& z8IQY%wM{0v;q>n8XBLlFfivxHk5eR!GU8=)!PWM8+cD^kFRd9^@}vHHmpUc{4ek7U zMJLPqE?@9WXV!5cbnc?TFQZ=zjDtvx4CxxNnRC=;E`H)pEE&)W2LFEFXSWxRf3ZQ~ zTCFB;9UO4v%^xA|g#9AS>UPk-?MPWuiF`Crg6zDzhpDv0AkQ%E#Wi0tK(i;->J<@=VOV&8y+>0yJK)F2NW`~ej z!|bS%PMx^4JZj!SGlAZaL#2Uwlkw{Mqr9?TeJRjo9XsOcaoi!azFt~~FQ(&ag|gAd z-W+llC@VMD0~7hq|P~CcB8d3i?O3U zTcP(@qOBmkwv%*TcG!1*u8URswRY}_Qqi<}7b3yrKY-f=Yv%WI%0f}uUdt>^4GPYd zhym9t7FMntA5?Cds;HORyjutEBoO7rD3UZqKCb>2Z$14mm1i3JyC;LWgb^YuJU-oT zo=kZDQ)gP#yk>)9zqwchslQufWwih$w(kBmvtdRES%3L-#h330lXOr!<)=+cznjU5 zDaPF6hM{7P>b!>sI~zsXnU}JHbK)|Rk4``J06(z#eBJ`&nTlxMa8rfGWoi@RbNLbHbBDW+_XL$%s5&M3EWt~?Kjd)Lj(tqM zvF3J56TLm;N&dz#b<|X=y;Y@Q=k9K#iR^MUR$h$#W8`ZM1D>1dPVpA+U#u+xA*BVx z<;I!->wx&B)Wt>FMeLa04(VL+TqIU}@r*|4>*)F)QvU(oKvhwwO!B6MlFnDW1vn%F z8L~&k%*mbDCF}94jka@z90;R}Ij^=DH)imcG91X8;;avD@MeGa$_&z3Fv-EIx)*%z z6LU8*{McNeqhP<~%sldm36Xh}jdQAKz$Y^jU`f@s~50i*e9b51vJYq18({X*xt- zBCZmxgs#vmox#W6{{XTDVSx>!MV@IZrg>NecP-FFU+f8iG`GuGb%?$4pxeW3pb0 zQ=8)w{=`Z}pCCgsHM8#2%KAP$HUW|TwKaW@`}T^TPFAXTg{;cU zQR5fV2{|8#+OSiz^Ow+&Gbcd*+Cl3GH$!~}{=?usYc?>$+znUcr$ozfF$-;-jrCxj zJEO=rzIj>(#eOB_4U`&N^l~8GK82Eq`Eofkaz){<38n3FFw5Co~)M#`2@{s#yRH(^<5 z^l$HV!SYL2iZ;jv*b*Q|mCo9ZCaxKGXAfDo;S82d5sn7#WKN+ApUNWx$JUh@ui`+s ziFHLypJA+vVfw4W5q1UHv(GUY#O?em=E2aBWRTf5+uEKpl%uq!p)_npn@NAAxXKCN zrQu{A7&q<7A$uSW($$kK5JI>sSe@gLPN++4j>gE}46YK6W)6cjOyOz#3MJ4yn3#$LoEye*~SsVN7cuka9vlj(c zMxK09Q@QG8JP}}C9Ci}VJg8hf68jrseOxwNVS$p*=lC_y@J{vtV(h17=v}=<9 z`;8?=N~O}Cn*4GX*@l*~4{%ESeidJ?`H*~CwU2E5dkH2l-pl*@N+v(+F=F`$_!F_i z<#c)A;nksH=8Rb9_4Mqxs0(`o6iZc%L1XrC&uxzh{4tWCl{U4}yq36Z|DGPNrJ1W8YHLrW z=3QWRgMXr|KfpDuWk3o&kxBG?rydd{z0-*WFK4|9GvsvRV%xHXg40aX)Le!J!Umj< zz9iOW-%KlVi4ti)&rsFPi1`4tg_04ppAt$IO_sU?^5CVVkNa<**$SxDMVMNdDPIL3 znCz&`pRvJ9Rf5oQ%LrD{U7P#B^qCCs^qW471E_Gr6>`9UGCgSZ_fC;PBzh*~W-^dP z_o+qBt48iB53=e#;vU%ZuVslt6I|wlx0s80ECzrL@Hoq9Q@w3T+&Q;$@ROJ2poF-@ zK5xIm-VK@(nCx=4!Ad?C|qH&9yQWMAc6I2)x|iZAdP2-uxnHR!`aiuN?27tsUW-7#8ly+ZQjys zC9wXCafNada9q4t*u46wZEJ>;DZ zT6c+zuLh+}ueW3862PEI&i=`HXfd|0ebk-9n-&Y}v zEJ!aptXkQo%P*ujU|vt{?>CLJu-Pf-Nhm2;qH8C@N&0#%i!GBIC_9Rz8mrK*KyX1;bFOx*mz5PJp-!v5`O@7<3vXox!i)Ak(^~G$_K5 z!)R1mc@m%RuByhUg@rWQ>+2#5d0$pEFn;)P zK0q-Z&t9n+5;01Gjik}-;z{0DkFzo7TjN`^l1TdXW zkMCA_OOhe^omFM?9{Jmjjlylt-eZ{R4o!1hKlioBSphVdLpF;^ihENx0cYK-CQ6E* zuH>6-B*4FQA)s%#%K=kUifYw~nC#_!b1!);8(^;PAT1@A4x7I!uDxBz0IS8DXZG0n zp5)exVn6)M@!i?iH4M97%3Q>;`0CHSnvCMo2mJ2m|99V7hx&`gsxncn2CDkWC+YWK zy}=5lVA%;EOFKtCQ_T^{pv#=(OuNcjsEptt`|c(GX&s9{;_mkj6M%EDdz{7j$Mn&A z0PyJ7i~>g$xrB^AnWuH^iK9drW2iy*MC4Mz-b*PHf|&z9>bv2#Pv2|bE%A>PO!4_`m;5xepJJH3k=OKf7?hJ-qwRRoji&Je^ ztNZJ6-_jG%G|3A$2iG&48NW%i_Leg=#j#2g-|l7}lk_m-hoc~lK2HIi!!vb6W4?k( zGfc;MqSPJ;IOsRc?X>YPlfzI^_~2qBf4R=C(Z5IuNdoD5Vjx}FaV;a@ zHJYIyEYtebkeq6Wc7})_zN{{b!OcQYXxkg-ycys-Mq;U|)f}v10DA9ASbpp3K&Uf1 z_p>jM5+=j`%WB;;3lrZ<=7$Fb$8ze7v6C?v{Jube7B-8%l%1VtZuTxj9cG7fFLsm%u_0OS;mk#4ubf$H#{ z)Pua-90f)vJ9p;9`S!} z^F5%*k~|mi<}IcXNBlE%oL@aJCTFPV!nen@MfUJDelXGg-p_L1t^adLVZHf0dzd@l zTAb-6k>j(ymSVHTt9VmHm5lz~wD40>I_P8UoZaoa!L5yPZN40v+xJ`GlLTL5FGc`7 zNVby=FkRAr=m|dCCg@xqyc}uVn{1Q_u-m8Aq*DM8zSGbsfNY;PH_GBK4sqMK8 z^cf4#Cwc(F4n$y=w|OW40w9ZnV-^G63`F0D#~UN~w%r=rkF2cxRW}lq{w0^nU-t%A z2O4_Hw&r)^&GxbcrO{Lm#J)AysC5RPK~U8*8qxB^p2^F-T%WgoX#nWfii03+CDyi1 zDB4I&Pn&Fonw5qCdZHH9omPNZUZTT_?ooh(a(t!i)+m+|JvNju64x~%+cZ+|)0r^# z%DNL5 zbMaas_DKArWjsg^yoQJY@vaRk_yE-?&`9n@7WnC%e6Ru_@gtOPA9tN?(TyE9;`)+hAo-(1Wsp6IZ1tjd>u{} ztZjy_E6iV(X#9Mh%Q_MD_ipJ?B3J)si|6VmNpxl@52w$EzX{|iwlld~@J(o@5{462 zHvQKQ_-9Zc0k=JOL$f9x)?7|ney~gwdlHAQj>+t|bv${Mz9pA;qiiO@R^pa+8g3g| zb(ODIgiEi+crZxmCjWTBX^d&uv$b+_T1BjR|H3O>{SVL$w$wO6EXykl-%TIsQMHbW z04F+~yOOTuPnj0$POSd}G_yP#tn?`uL#)gLcQb=Y#e<~xqZjrMsfzaYb4^69kpgB@ z`TBjwZteWg;>l&R&%|YBG-U5+2~|H$4(phaKJ(UJ4jjo-v7r2AC&d2tlg1JKBosG@ zohbxloKCqxEyRDgbU?FIF;U5-;Ks`OxC3M4&%#{`4Jo*0X>?%$zedfA_;RAqO zS0>>Ylb+V2f@FAJ`^7w$MqMQOS4XCXaQ{LRG0%SXtobLlGF=^?U~D?Awr9O}(2pKJ z{H(Rv+v+%70dl}X$yCKpsy3mcsU2g*`b_$d{MDr08>g~(H~ZJm)7fn(WeJL6F>xE0u34u-c`W_l#h^dm-yT{+BHOq- zkldIJ>M)|Tr_q8IQI*v;WE*?ya0n`I1KJ#Ve~ctFj%7#`Z_OO3Z4ZC6H;u6fyMI7l z-_3;@g<&%77+&z5B)e50o^2t7v4iGhq?yq&1OYD?Z{jN2=vXsiG&{P_a$SUSjaxhC z@EhCRKGk4yVOL9!2Lz+OQwWPKmp)&~7lyxf$<>q8pg5V{gRw#!&s7C|_AXVP>(*zg zy*>->ReGW^nm*w>V6RNrrslp0t3M(TsmS|-gC7Q1k3DljWI-EVZiS(ft=A1kVqm6& zb1P{UfN+vbv*n3RNQZ`#udOg$5_UX=sO~&Tg5g zcAH?EiA%KlSi*NX5i^KZh}fPdV)Uoi8%)Qa8n4n zn|)f3ZE!AAU8Z}jnbsNQ*IR`jqi1S|1M*XM*d~gdJq^ar|J&7zPEEL2Z-xKS$`o@}^`*k%?B!L{1 zZyL{4{)%g64(1ibw7>}AX7%7QW9Y+h5|)-U4b%HR9rCbs2pgfnS=!)wGIuoiCXz5C zH=O8)40jhKY;RgYnoxpur3*(}LLglBoU!!j!{^Gj#HC`KeTFx0B@{H@~XUo5-3F@Pvw`P7u%Qkct#G`?LAJ>?lCyWs!PMp}52e^d`wEItJxuV*p4k1Qg5!6ci_>g{7t+YTze=|d$LgRl z8^Zt_8mqToHSeK)gI`fb zyI5IS_?ai;mcYkJ+t~Y^b#XLrD*7j}5@~2A;y*yIe*)QOhH-VS=Gm~rct{OiRDx~% zU+!Ns$~|%k;AysA3=I}cGTzV0rL`{*jbx`Llnfc+&RHf^uiPdPedE7=H=4wyDaR<$ zq}ffCa2|H@GK(^pr?a2hEx2Y(45hdNwg2c?-062RV}YYRn!2T`*N*_C6|Bks@%79@ zT+gpM^F8Alv}%6ufg#MqVhrEd#;R)24=~;?mi;1gp z1R?O{r#6$lTiN@s_C!u(xop+h4sYs4h@=pr=xn6fKBP$G>eW1j?61k@$GdrcaV+lx zu0A)-HRs>pn81QkDU1LMu_q(Sa2nD57?tL)Zj5%Rj43CJ^m4@ z?i2F`Q`yo5yE;3*o$LfQ<1yk%_n?U{ewy5(96<{~7=>M3-`Lmyqw25PDP5bx&kl}G6;N2yQX|dHpas-a3Ole4zu62<)ag#R#&L09=(5eX zxl`Y^t3zr2vp>EmjqhGl;-AQ_guRhn&d1b0x=gp|8W&Hj@SUZcGt&^K#SK_9)LL2Xwtfg6%~{`2pB5Jo&Pye+G%;vng|Sb^)nA(VZE zMb8w`asYBslMLsxoMj{I75vpEuG|b)Ha^!#(%yp;Pt0mPgW8u?nniV1q(Z zNx7unrbQ>>i{;=}02wB@O0&B!r<1=5J&d{EH4Bct-p7$u@g;$h8)ZekKNqN+fZ7$s z+`=#*`rG-t|fSJIguMobBy0tR9YJh+#g zYslns!A}vd7VrN6RwVgc>^qIJ5>VK1$0fk4QihnKRT`;%4F;7Qg`Gw@ z`6CfheQAac@@(f}KWOSdKt-@HZnN47UWdjZ)>f=z7he;(8W_U#mTEhVtOT&dOzi2` z^=n{3?m>&Y8(PNQ)jnckf*(gEi;0PUqKU^lJbk77;*C`+gVks+E163AAhF!09Fv#h z6jbAn^!5-G5|Zvqt=8$xrkX2b#GRQ!U*VG}PS|9o%)Rv}yh zL(cgXkNQsG_*GwyuXPN#!R!0^N7=6$tx*Z2&pi&>hU;q$LI^m@hIz|ha~h@`>2RSA z7mNM_kcQhWT%ohFyI)G|d}ASP1g{0jaHBpYch!7vn&|wts7W~dl7R${?{83U!rqL` zyhmXbi6W%}r*&vnr}sdT*uw;_Up9Xy!a-VDIP39dchkxsEd^mqo@UTbOj<>IHqlIw zEeFWOA8IcBctDf_Tqg)(y^7Ipd?-GZlOUf2W$-tgxG}nSf0SscQ6=LR(=3nvAowUN zwEymN_J&AJ@M_yQUz*1To?;fCNVoj1r@s_oY?Y#D;nS5Me`3=JHy9`UVh1)5KKi_Z zyHsL;DZ_v$eVMlJICq&cv~+Bt72*lYVsV#eA79_O$QtxanXp&6-^q-L5*m+P*6`j> z6t8Jp&~P7V?wME@&bdtS`Gq{V8s%7`$VJ(es-SoX&$4rrjF%5^jdMfy@Y@m<>3JBN zjeOb-m!)Em0&n%IUs!<^;N)*l0|yVFE@6q)+ChVBy5dZ$Xfxq3wC&`on!%7v`=V;K z$2Re4Dvh15az1#*5>xbpziPx>bThEAgDjI7!Q0a)rHa2qtRTJTc0?z806Z@b+{L@0 zoiH$4wmC(vMEhCG7TXpHux18E5Ozog2NAwq3I@`LoyAEb$IDDv1ELSQuczG^DqKTC zD$fPq8p&|d<_8`}@f`>yW#}xM2FA5vYDmplP<1xlFNNA$t^3o%#fpuxz+b-ZgiHZV zPiv`9;_*ecC4!G2j5LB%^Z!`a*{P>ZoZb@i%J}^yP3aNnRz0eMZL~0sNy%y)l}sz| zM|j+;H;3tm-!@34vvebg7a?97dZ&jkPDZ9kFQ>uE)-Xp+&7+%NB95AV=6X2aNi#`0!aE9-4Fz7`qY zA~{ajGK#6UBeAqbc@>l5e_!LVt?Hem9HP?$A~EhZ{%|FEf88#8o+4W1?<1FLo?i5b zfJ0a@p@o^FQg5n$yRV<}{9qa?bbz@RxJ)gb^f}6Qg94v%Xh&5z&JE*QE#tX*s$)XL zS*Y{Wkumf?fEKgab>rm;3YmMv(dDWJR;zJF<*=s696#*c+Xr!x8X&O=9h8hJan{b? zC1kt5W`($kI})&*1$gU<#4SCApX>-_5eGLLr!jSZ90V;PGI9S3EIjTwF6GuZdc_h? zF$ZIT&k|@0=B#5c`Rfbfbeadi%fV0+zjrAI+?+U4kS@S<1MS+o7>!)Qm&29IG4Vny zDyC=(!IZJhQ$1{7fiMTn4$8B6a6Lo7dm`Wx0U;q9AKYfUk6(j7MWiKsMz;E8uFcz_ zNSwmJ^tzMKceCy4tP~Xfg2`2GfG{&;*hU(Wu#~!VvD4{HE z>p7GybSZf^k`m&+$L#v_ck)z0ONl%_z}em;(bJPt*D$iUBSyph?l{iRXu}Erc2s>m z_@xFtcb8Cq3aoHO4WydqR&c{=ubHZrJ1iq}ZaJGpP5EVBbfKI}lyL zo%9x4{T3FvPan&jagoL*R$Cfj zCnDQm>+Rbrp2LNdeAxATHI}zbuDkHu8(uW)wj4F4;Sk}A1L#+ObOJbuY{1k(NWUMq zR`S6Kk?n^wt>@pf{MO|coIy{ zdw|ZE9hI03COA+rni3Ld8ridh7_Z6LlS;$X9p=$5&Yi8U1N?vm zbGVx8btV2~`>t2l(5_U73{pFQ{a!yqH`@Q3okdb3&vX+#S;!LLw*^(B5B_U)3SZA> zSx+wJjXi*4yT9?9rR?xj5T}A5Z1(>BVf@c$89zn8f-Wn)udgL=$h!J>^|qt^KW_u@ z#Sdfs68^IAd#NV&qWATv`4$_pV-Yh87bq4lv^_gk%>v*Ne|F!(g53^AZS`_Go|R~ZYwBtv8pkt7K;JIFm-@yGbUwAFhe9?@9*P6=G*bMEP>_UKx*zrq!1PqAkQ1pBS(` zFjUefkC9Tv2R35O(R_Pwz82z%Yq)6y%>eRbw;>zsVqfFU^#9E`OPR$^qIGos%vhx` z?c&dY#+PM}0~yu=-Z~IzYQNy&T5da$QFu%L@01x#+st4 zX1!1e8i{w3+Q3YoI8RZt+{_)UI7dh-tKzA~>sl*&@ zT0+ACpy4}#Nll9^zsB2j?YI4C(xF{?B-01i@4e03 zR7^fxDH<{jF^PIsrV7@+87C-GAZ%^$7k;kmBxqQsoiR2;?Zsm zi?A{bXHcN0ll9N8yuG=8Uj6)2-q&4uiar|;cGUC0rUH_fT;k~wWy>M^6?W(jEBQv! zPc_D7x3`r%m;;AkP|Lm8CBDmp$`aiL!+_*$1aLKp*BFlBf#jcc& z3Chv4TI42`dnK;T>KN~!Q9dD#twFi6xu< zWge#7X2Ynte>-jlC9%5xz}An_2~gDgdNOM7R_4|CRQCdybz>U(q`E+f)6rSI8KU)K zme^ZRf?=%$ON#?ECZ_kVI4TjS_F*yMab>9DgK{FNL`_-df^NmtST8*Zv9bY!mBXhv zM(!3W%umwqY^|8%ng&|mZE*6qaf0GYSCY7===Fxu3ci+X*@1sH5WbxyK2mRf zQ3|Vn%EhFA-I%IcMk-J7;r@0i(WDA1EBAOPk|18O>=L3mpq=kQnKpUc5j?)Qx^Nif zf5GZ^ijS1oD?I>`2`Ov8lt*`@3YR(%>vp+GCX{(xj8N!*2)G&fIY65W_Ar4 zG}-J*HWnF>&0gf6pIm!;J1xxLRjBw(xQGbnTdJMQQ=ak=hT@{bL{(fK+j)I`k6gO? zH0)wL(bMWi&V5Ig80&~)Fq6spWBPv1t0+oB*XJ5TzE>ob+qj>;ioOKCyGW6Cn$%gc zmEfP9#MSQ!1{Zf1ZX7AwNAcKEu!K2GP#Pog=OVzQQW*9AM7hAN$Xy^?aE$R7Ar`F~ z_kP?{hu_Xd9d_irza4j)F!)8c}_K9e{;3oFF37h;s zf@ylzfbNs{h4K(yqVpGgS15vZJo-DF^)MU1uopph+9ZyJg7uOo4q#$c#JY*}>~MN3 zS~s9E57%3eElVC3j(R>xCI(&zC-FScw+j3lihGJ!306OlaJq6fZpJ$hXGxH1#FnLt$<{J4&KGxlznc=RP$V1MXg3eH~U%Yk-WpgQi%6*(6 zOk_P|HnOY;|4XyzR+g(>b`Nj1{W--NVNt-KQ9m~2dLqVodylSDU2OHPmBP5)MX~<} zr~#kg?Vn^=pPPcQkmO?F{53NJ!N;>ve+AT*({#&N+Blb8oUeEARAs9C8 zbKw@oSirfj%Adw@cJ2F4|NbJ7%?%M!1$h5moBV=*iGN}4^|R(j=_uJeuy~5u$$%u1 zI71ZEU3i>XBlca0*vOY;&}zDd7lK*548$AskLoLiL>WeX5ixQNf*w!pSJt1)ujAmp z;m{sn!a3~^!jNYhB3SsTRv#Yisjgt+s1wNcHbPW+l)629ZIbNN?$on|i4fdYXxEko z+$%<8^@IbX0BQaN#DN)N#8Gnem$b(2#&iYeIM3AO zg?VI$Ux;_3J3@anr5FGPg?~>&=AF^_ON%UyZ{JVN*?@UU~P>o~*A?g>2bR);O8q;wZ&RE&!;O z<1mJ&Fr0~x9*2XyJ8vE6LizmP3^QxTXf?<33Clu8=}MIUZ)9MfYsE7x5p3I*tC>SH zw2c^3drV&X-I8!(E{yZ<^C!+Liy6`>cRWr{PW>;fvazu0Sf*}iyd0A-Sg+WTzxT#b z{jVpQ5;>YkJeeYB)XfUkr0s(E`@+8#mFA|8O1S)}9J(3}5~2czXN5-);!kEy;ceY{ zq>TO@*96fgt`x(0#s%{a!6|3?DPAL8Z!&zV<)X_Pb2}bv#a%1p8$6z`-j4is!r~Vv z)p`Q`sKX6fr2Bpkg#>atz8$20uZkQhHZ`RB%%;1G9Mw3GVeAt~Vp9Lk)@vsrPS4X; z^+G4Q*XhZ9+O8)IXFmtP4Y+#Ol2_*KXpTsQe{IF8TCcmXS+gB~wX@*2oqB8B zjT9!kDX`Mf`y2LcYw4bVgBPlbd&0s*DWXFa3hiO#x)w9SUwBMqfTOV27E^nHGb;V5 z-;pCft|kU~cWy9U#3CUnGqx*|+6s8jCS{JPwG~kLVjT~5!gvNMc8r0G_x261I5v1~ z=VVJUPwg3y$H;*Ue%U+Y*@dTj<~z)3dOp}$DAArcc-}%u zVQr4++o^K4B*(ep!f$Iy!-;@PNhj%;K9bRWSoyOeu{alb8SaTC98}!`x&Q3!wfYE8 z*-Dj@VCIx}RDz)&j7ly+zwl$g4f=@n;yGU@dM{-<8TwoQ1#@D$=Gknj(gv3|75@A+ z_O1!ebNspJo#1gg-L6%Ur_Q_AO%>$2k&Wvv6zW;^4JVhG#gFMTR+~sUcyO|uFlD)7T+4{wb`Rp~ANtgQ zx|NZFm44Fprml=KmnNw(%f;LkCfo8>JzHoRAF4R zOb-BazT>(ZVU90EpW^RXUnRI1;lsM{;~ zc0qoo<2**?+>ycPj^y4D;xcFbZl)QYeuX|X=gq=lJhh##uC6DpqFDpu2U^~K9y^U1 zqWc|?GGddH`yy8(xKre>_>{97=pi7J)qP79bPyorwG(j=7U6n#mapIW<7S2|Mz1?g zHZx|^w06W$J@gQ%7K0pI9YUy!sWrQCN9^p!)9bSc zfVONdJd^(0(dwe-t$51BlDi$#7E^~&{5q(LBn(kC(@U8&Z-E>^{ru$;nRju=T~m)j zal=-86803{A(VgoTc6-@>}s3n96DI|DSbV?$f%e7(b$)Q0qAo`HNxzO|KRXe&PdZS zid*0IfD50_(h%_<9LEYtr4~h5-+usUe*uZ_nL9D9_1B|?!)*ng7Q8wM3tG&@uJkc@ zMWY~9PPdR=IHK0zh%mOPXeG?1k!UgY7t|@3;%oXtxim;l)LcagjNd-UxJ|>Az8;~> z5GLs%(YzZL-kG-w^zQlja+vZ3!>@OTaC{mCm_L7Qpd0o%?jB6jJjy5(j`EjC#^SGs z=2f6#evoYg?23r2O!(h5=|j$^(mgnINIc8qEi4SsA(ip;&Z#{_znxdj23Ok_$?mMu zYX{D@-x5as8-^Uv68&8SpKT9rDry+`;i%&ketkfr-|32AR6ycp=WhZzy1OiYUStHP z$QkB)*n z-#awM9<`7B-tI;?i8eae)dChhQbVOiA6Of$y4DXJo52R+#&@G%G{{0Iy()htjqv{2 zVB2}PmD38MPV2!-mR7;|L{fF8mKJje{JP`$EAI~<(EZT zSDr(wK6tr?CKAi9!%xFGeFL+j*FMo-{;DNVG4(!m>gF*1-ybo+=ZKwxbP0J_(E!UV z#kI;~dmTBsc?F~4yoIW<*opl9>F~gt)BIRgbAHLYH{TR!v_k+F?W)#&F5XuaR zXp6xfaTi4^TdDLV#HT6g>Wo@|sW?&Bx|rGlb+#sra*A$m^F84^_N^T+$QgKr#x2ed zATKI@fJ?zi{?Y*66U4(;+>ZTp@?Qs!B0M8)!i`H;SZeoiG^0+(7h4?lG_A7I4l21s zI<2uIDqh!_r@EIuX%EZtDJGn94l$5^*AbESTHb<*$9#?r?8C!vFrGzQ{?UqeI;={Y zVX5P^@**xIBu8}*+Z!{;$6la1F}sN_6*zArv#X;QN9U-hkLd51aul1>SAO=?Nf=`& zFc^zI20#XRpJ@(%c5U%Pj*e?j&w>Ho`OTEU1*gAc__-0euCy}h!^}5v9xV5O9g%^P z4HH>@H^P*>VVgd0>GTO6Q6wnk(T92Kvo@vNuWPh4iMo+KycS;keVOH%(^@@~LtjK@ z-gEYQIyK7tWn?YXOl$*pu)-zC#F&fkl}L_xRa21;CYvy8>MgP2ldJ9G6UnRAbM!X- zIK{WVG3r{si|r>5^4MsGJj4544csqPWWT;GsgCA9Q!rBVWDZfu`3-Fdk)yE(d9BOa zM~xd-h8||Wd4bv$PSUv+mMk1;P9n|SOIvf%-b=yxbD4?=R!y>9L2)Tw5@ezoW@4=6 z{+1|FF!57;4x>$uofOTgsC~p_lBnyC5#PW0CXiV#|6X*Q+nkIA#@Oi(adUj9t&)`X{MWUL^ zJoVeceo{DKz9=z^Z3CA62hBh-zXC?d-+YNt=#Wk?x`+wa`Qb#ImjObX z?V9=}@*torY&`t-%y>h|I1DO>1Lrs4jx~Y!;Xw!6rytJ=OEhw^GFQ!DzS+$%7C#Ec z7I7HbLm&3!L}Pg9kV)(Hzyz`*!dXq8lrUsGfNfg&IA+r+vk*{yvJsSm4-7c01&UI# zk$m%g@{uo>DJnVm!^(w^r1CIOk|%B46_$1Sv=S|B6!h> zW&ja^A7_lZsEiOj`5&%x>0=4N1LLj+Z9}ZzyA8Iv<1ZBca{vjhq~HZ(lu9F!)d3%P z9O>b0%M5L0dEDOk)~Z0FhFM(0p)QmPN7b;@=DmRjxe%n*m_AM zbnSyirRT3q8!9RQNFWZ_86}mXAgCV$whAKT*6&BY4J$U9kC+K5)PaZ4MYK6Zp>>v) zU)@k$!=;QKmRDGch5&!M@Eo2*E9C+RQ_t7i6Mc#X)3%RWzzXwdIW@~LJ%0?AV5|xC zdtbfEgU9R}|%DX7K;QX7)yQ(cB z@IErSXt&H6y59o|AV&_Q^Jk#znh~o+hDPdWSGGbY?^Jtq{{TPEnBpjsK)qEu-*bf7 zWrek57A0a)TzAXOr$lsE*zRtl*kR0y5TaFf7)>;EOwtQ(gWy&ZV8V|a%FjSGf$(yy zs-zWb7lzfi^5aV6^1(%##3S9hgEOV`eZM>llxdx8`a7QI2P_C~fgZqi!?wy5+ox<4 z^C~XnU{A+5P+Ll%FqfvyvS0Nbi;{9+o9r6bA#j!D?kH`$fJ3-@)xJ1?N0DGKi5-8S!3LhAORGM;OT16kO5W{M=p-$CbQseKWc8fp{^1|$-b8PgH&vApEOJaHqE}|9&)02xB z3!rCjz+gG2i_Bovee*%jmk|S&IJ5?ZjAV`Ewb=)ct`T_D#|o()qsx79T~46J+iSKk zB?24=1BQ}w<)GR-oOEeoh5`uW^(U#o!BD3pUwkr#o$dHe!vfHx zVliF$I5`bWg7`O+FaDg-vZtG@S(ZuMJY)dA_yp9kBWMQJp54LY<`9=J2!Gc52~I;4o%Kj1s&P)c4DX zrWRLk*S-n&q2&p0&oie@#3Y1rK_vG%W{|g573?rU?}Na|XB9wJ0hL#)a6WUNkw`xr z2q&!)a!GpR*W-b8J6#U=GJbH$3%@^fdz@)Dp%ulFeX_z9VQuMLxj_?+wpig; ze;d&JWzt%{ngFAL$?uI7+9-ZG5wM{{Eas@5r?yrDkwK51r=Z3nT8(H|11PaO4a{iv z9@%?n$G^4%C@6Z4Evt}p8GDu2gg1cl{{VP5j!5r-D5MH0xXaqNLJmfv8)Ya!Ju155 z%(B97ZT&DV!}qoEf^rS07INn0lriGROu0lYF% z-wHIKf-jt~a5|sMD_P#*oMZvI&T|n}eQ*%pUXjTeV2oDW9(rM( zJ(BMWcg9I1pKK|1k8i0-1p14$!*1L@7r)A}hWflOw z_@hF=@r|R~5ma!6K^%-rqfFOVu6ldqth*yAM*7}7+I`%DFb7OQJao&|_RYs*q@H|4 z_vv^j+@$0ZD0ILA`gZj6J#(D95k|n|9OvQm=i}a*O|``lco`vdj4(Tt_V&P}taghO zMIVfAIob5U1-`iB$5kffB+qNLzYEDxyaC*1gK47F#Gs0BI*eXjDT0`E9rLZ4beU`x z(|T{d0-83y!yNNLzH*LGsTrAC&?ZtqtR~^GHR7kx)!QzzP$2XiEh%H~n{#l~o8$xZ!$&%|$K#Y; zG-O#`a*d=HXytw{A_=G}s5%jSvKDYxg(vBP+R4KkKKOWc(HvjC1yv?t>##x^B@_x8%k1I29_wZ1{^ zg$i)!+t65VY*s?$z$;b8X%z+nyo3C)DH*nl>9HE~GJ{ykhJ@knK`WQUJW>R$8&@J+KWdG`r*KI{h=Fm@?BIaBVEM z6f0lGRbz7eji;^@;)Zj_3-6a`V<7QgOjRdkU1=J;wtOr^RfaHi7KqH>V+afs&uYbn?nzG7OqigMaWJDw_LHGjCz6}gmQQScI zysR+(?TRxeCvRN3Y0`@^EX4N}Sz-IX{u}(TC`O?W*;Der^MqJg)H69RTw#zceeS&F zWN{cIaz`Tw5u3^-WVIlEcn0%lV(38&*ztJCDX9a@QPTc!yFlO#pbEfES>$b!dRTQB z;DuQNl~M<|ysWYBP*2_+4n%VAPB|X<6qYa;G%5vk6+r%&AnmDbs=G&Xcw*c!Ekll& zJv>sboHTAb@K0PQqeD%@*(ZWKemGY{sEK3^&>v5^{{XxaOQ;TtQ)%2g-+XbHluJIV zDt>D4jI!o$G;w37`5b>t%oiyj177*BsvcQaq@cl3y(CU@zM6 zsX88_vbM5FqpFU17*^9n>ODO@xWKH;ssX=Km>tfjJPt$$7EzLO~?9Sp+F;KU=KD!bIuS+WsXQnspO6T%-E%5kUfFLdRH{Q z&cAt5nur7N%o$58j1{~6-;6A3olow`uvGU0AvI=2DyTT`c%%hFQuNBmS~-=}?OPo7 ztO5e@uof@drZEeUcRh2{oCB+}*nPxvmRS&~z76y^6RL3P2Fg8;xyVS_fs=N^c;H~3 zQpi+tq*f9y{R6JN7Z1`G?DPkX?2LYpX1zhQ!a4elKKZQOc{63*$t z=iA>PNM&LVqyu8dI1iGlZ4Dm(069jPkM7E=llo%l^&wv|s<^{O{#wxGe_RXc+sOha zG&vxXoh_pVCcTbziBhV@0mU#`7?m7-Fdab}fM}MkNjN=4`3+1m;g`#>I*!8>b#3iH z*Y&`LX-cRoz#`~fj9@adUs3eSCf&TRBL-q=BqB8a!vmTSM(F{08(W(FfHuHYQ^Hz3LW{i!%k_gP-3PNRf3fzka-j7Uw$r6_?-n*mlaFijYMfI(c zeeiO|7uEm=zsSfbsP!tf?LWfDILjecNfMji0*)@Y#Y#1^EgpZSIwIT#_9u=j1mG_n z2lBuMmY+~ddP4J?5Kd%`fERtban~~;j7d0K?l0+@zz5$y{{UhD3Ll^I!BDEDxUtv( za2kbV9y>c;!`OH_c>)Q0UGq_VyS;Vc8p-BCfCxA(leu4WDlLry$%Y(E8_X zU7#8x;|k96RH*=T{4%;gB-SnU9OdWJ5;vjaxE|Sk6lx|V7q#<%{E{?iil)n*)_PZ8 z>x!A4c5th}Y#tQrV;TlsU zOrZ((Vbd+9QZ&F0-u>{QvGXSju%YTt`>@+gYe3QOTwsGUOH1iK_+c9MEq^Bn4B$Lu zsP%#OjtEMv1Z7BO9YFeHJI9%`k8(y=$X7Om2D|5>!ni{65?z9iV1J%95|%)RrBN4v zhCsuf7;tdH0>H?mt5pj585d4x=rqLUocCKCU`_Flgc# z4gJzEqf69!iRYU)V=v|kq*F(|V&dGFB8Rc>m6Cd3pcX#E0JhTlH*k3Ck4m9JBvIPN zDA4}^J@63et)l21jg$Ce({0kC(I%NH%ppt1+&ni326;}cb6j2gS{Fsj5FM%?@X+ut6B6;v}3 z>L;l^uo_5~PG7m8G3J#Rm{oTV;v=>Y%uxvt04OoD-;5!FOrawMKoo1Z%EhQ&8`xR; z;o(vv5446OaX%ffiC7~O5EL(#yn)=C{{WxU1wN@jXqTrujbmH9pb6+j*L%#?irJXC z^%Hm%lUL_67?6-SzMqCs1IDgh*Pd&(R64+cN}3%FWhHMuMN#)}zkDi)#pNY~ljONB%hZl-LdX~ZNV8aR2x)|E(yuJ$*t`5t_|9TvW|d7?q4dmK+qOP|^4KY&N*@0B z>n_bWR0Df)$F>RI%?OC31?}&bfkIGb^_Bp9bCIT)P>@M0jz}$j-q{iA0>Y=Y_>(`GFxt0WNbQ0T2R2;v z;{EWfM&NGGOkQcWbfNXiMqX6h%s}`UQ!}`YH+S~D66$w@WME|g6+m|n_TM>Gh+ghM z_81OFZVMCF6)~WnZ4XiHg$>7~Rd**AqVrbkanAzl14*f4+P!_Ulj_n1D13W^4~%WVUIC=S@Pfwa`cy7%_TU0K~ksUG-IUXo41 ztnhCki7m!PqD3gIyqoFYDYjXSk4pRA0Jb?U(Vg7n7m?U5(l3riFpIPm9YOr^#SaF# z!Lpzj&eMMwyv%n1pqlm=?5eV+zd7-;?n@znC`Ba zL_-aFh~)IfK=l4O)~ol8XBp5Ba(WCPOu-~P`&Jqh^@oznb{&QVYs}tg9BCy}@^CIi z{AHp*&APZ8bA#L%?d{(Vvqmf}3?k0;=>rqguo5W)mCH1YUuxSq&UgewD0^en))cp! zzEDV^+VHGHE%9E*CE(t))nmRcS%9Nlb-;-uF`eV?=Mtqs?U!j2$|1jqRbtVecDF%= zM3IwhFX@zo2K(l|F^O0O;{>1)z~=$Q04?7b2bcH2-vDH!Y~ThoJ+Y*PdX@a}fC*Kr zu5&RYS+8I)vIM_Edb?1<#Q;Uz)>w{*wgRFiV?zG8SfEvu9^?HPc%zq5uyIR)Y20n#^PH?6AbZv?BL`|rC%?8$GB_cveXjtF1B&h6EWiOvz{>>z zdXAY<_b&ipO_M^fGev)Qw>U+b_{%6PN$r5VF$1-j*#{aX%?uoIk2r(JH;mGbm>Kp5 z2d-Q-bC;48BXab>Y&BV)tFfr$Q_1U>^T9Zt*6ee)Fzb^>&Y3A}WutAv`ix#C7tce+ z8BnP-MP#ZZkgTcfeen&ZBS#-c?RnCo%06)^rg>0a`yRQepaaPx=MZ=qIT-GRt4`KA z-I|PN=ve`UulN}LS<*-X({c_;kt7WpV*dbTlpeM-PGFc;P{ua(Dd2nJ@~fc%xW8<0 zz|eZptW~f%-cX;H=zENsYGhN%-hcyxF%oEFZKskJw6|Yw;{?rbydSP-xalmVUN%FW z5haau?~S{uTlmGJAUjulvZh{z2tl=KCoW@gOb+5Y^OB4@kPaMe;GR9RPHIOjnj(n8 z83E~8NlLX{is^$Kn%aJ+2yI9!Rf3iiAFU6h4sc54TYyb`WHzD6s$e=+0|_{8xK#}8 z#|wz6G30Ue!=&UM0io}J@I?zppPT`N2{$c~&4ZHDmRh=Q`Bj}zfx5}SERcnddUIc> z%SAYl07V-7^P-gz3ZBP-iyb*-^CF?m)&o-Uv4>;&WJt>BSEwS60d)dM7!yvSh&^(5 zsf|wR1mFTmSXzG=;{C=dWuilI&wOWg$=#1Dz$e=Wt5uaj;{v;st%G<)wqiO8>x;t} zELV3*$@?uN<4gGX)06I2J`EuKISuQuHGJFAy@gB#?IB z2C#!sxj+}Y0I#PK8?p~>aB;$l-_PUilXz!Y^2Q_ad;OVQl%n+_+^n(+tu~T z!!pR8q3X*$D+BX^(jsgjx%u|RYz7JmE!w!o2r?<7Lg?XR5u*ABGyy*KfTFu2S1dc= zpHt?VplhccG3Eo4K%VBZ=&Tlw(eS-6ZJ!x2jpu>e8cju^;0`#;yhvA!PF*~!>Q>{g zOau9yOEBZxD6Un*qS!q^88Jf(jm+i#F0jNYV^#;aI19-$d5X@vSDObKR}IDfIcnH0xD#I^CADBiQ-VmNcnmCx=m_KA zJ7|#X1CFPhNf?H)VS17Ajzg(l7P$Apr%4OOhheeoPdQlQGf`)-I*cPrZP+ip;ERQm z%t2!LtO%AFV&))9_oG#m66zJNY=@7oUR^kbR|2vGYcSdi zSHEl_g%m#j03)2F?!s}j6Bsr~?ZtmQ1dB8)ce#Jv(;7rA9Fac;`LCP=9GPNtZ{hV3 z@pt^QIb%@8K?m!a`i4|c)zv8f0702(16vNh*ii%^j=MTa;@5;ySm<1Ct3fF7FqoDPN3!#`3u#vQ>sM?UzY zz{=#uMD;&8(MI!+Q*EQjzHkhVBao?YLFw_4<|Ollt;z-RGX6>}C?J9U;dw8n4eGFj zbYa^Fq?$-1Ti8Ab;|61BNMZ?5&g_!zsuh3+@QbKW2!4ZtI^GmW+%ggmUfCsFk+pzs z*pQGchswRA^vCq_#lQ#Fd*K{4E{qk_h9csyQ8wafBhdtLktQL|Xf! zcYl1NI9J=af&8(jj7ohBRP-cemrSapee3Ie@D_}?J5{ZH^QBohEJ;r1zBI9jn6D*! zWt>^yUl;&_Z!FTauRt#H=2>jJM%zA_Xi&yW`F2YlMV|ROnbp#$f(_qH0!RDaSEcj) zu*vBC*c3X9QVEBdumVZ$zt=4!&l`h5NBfQgEXt0dxW3-`c9Aw3#mBhhgA#2fk)WAvGo1{{SOo z;D9s^{-R2r*%8H74_r8%3FKGfdD0bD2J9bfDQS>ZjtCer5|y@V#)`YCJ9dwrxmhAs z=DT$m8eR~Thbj5(*dML5N`z93Q!MU^N-6kBo2}@8tdB(lT#!| zYw{09_sK0BmJJYkbB3FA)f$0)ueJtFD=k!(M|!{sEd+%e+MZP9T?BxVJvnirV?@6G z{@A;hio+5~?^soI>Hh$!Avv+d`;LF6Y_b*82!R0CtG#o7(=zl3e$vJ51rhW0#`Pu4 zl8D&u>Bme6Ko)ONP*2^VSC^J`L$vkyz`%;i8ToO+-Pq+dM6`<^zZZac+DTzFyLs=< zE}m)h*f;z@;iXa{3bHVvU9+NeQ~-int7yQpIaH-h-%e`-rFJERYAEsv?fx7vE&L|F zy>g-<31(LznAxtF6=ZnjRP#wJ+|_aU;Tj{=jjEd>sANWBX#Bu8H*?uyp63HO zX(UjDsK2HZ$ogeeBXx=gD2!xnq!8en1YlK=5~~_n`09SS*EA%MgxBTxV=<_Wfdi5# zp801gq!+LW?S$DdlB|W@2t7z0@u91@sf`}z3SAj!3%m>ImN`~nszIPb4lpydGArsN zVYrS5>554v1r}f<^Fsu+&GMyE$SHwiXDRdN5LV4^TaZ z0HQHaI-Uk==_A;T?nV&IN=Xi&5Cwtn^#1@mQCYR3>3`r z%f34RUeZ?Y{Qj6iZxpK|#~Y2kMVE#F!90kcTb~|kJ{{XBNDWWqec`aGVGzVja`#?|Jm1kmoN8DjrV!O%_=@cyY zIh@Su;e5eF^{#M;)Xh8U1+uOBRRx@*2ku35BxJis427(gJ^JM(F5sQJP<8pjqy+}W zmiN0S7n#{63_T|d(-XvfJ08OomNtG%0)2-}3vDFtmF)Hxfs#gd5yW{t&R9$5IE@#t zQgGu8}5prz%`{Ph)(7Q7&w(c*W<0^r+>=nMi1Lqlz7YAf#uTtGG zgy%qYvKBwPTIt-@QZy|!SlXj;9^V*EKrp?v79B|GijCf36n8aeI`m`e3mXJ1#MSaL z%W};MuN#QxD5K13*~dfd0LBp_hIB>hSW)@M+TTq$!p1=Az)&Y5GCbyXCwmQ}p4k#8 zR#XR;2Lq|rM-0lAH}$H{k1D<+AVgGBcN2VORHXTSkVyvY<5Eq%w>_~q2_;73^sB>B z%K`uiJKDJ{dgn_hgaA7B7%$%TY5?>-a)<=8dX16KI2VpKmYPz2nbPf5rMvCP%F8m2 z6}if-e;pr-{&)pfjT9}C-rae~X?G9kaAsxbqvvqN>i^6sP|HO=c;~Q|13u3?jn`3>FSLRekER|}QsTk) zCbF-$ZWUDDD^n5$V^p!P7&zi)^^e036fW&@aI4T@bXrv;GlInP&ullAv9Jr%4xkQB z3VL?|Tc=vW2&v~dQDFyCd*cB>pa7xzWn)wT72pooMP2L^bnk>rT4W=8p1#;AV@OJ} zanDR@X9|E0{{W^Y#LLl0kFoc_nWP?CQTyhyt)_{iqN``AtQphrO1G#R0F1iIR2|-v z(yIzw!Q}8SZ@w})8;5+l6_Iwe5sIrAU86kn#sMF4kic0PMxTNW-ZF_}dE}m-7zscP z<|wav?|{DMjPE0Z@s|^+U>aPS^_(Nk^Z29B2cY7)cJtzkazvAhXj7DSBe`l#*?4x$B0HMv8ipeesZ2+Xd#)_xKnV`yvoPYCib0sg25m-;6YqGXRl- zS-T7*8Z%*}KCZRxfC0Tm+8pzFUG7u`UZ<{Tm=sqx^Mx>qvZLJK*buTJ?T(qdw@j?a z+shNqQd*6zdXrpxWe%%!g&YuT0$GmAJupK~N^WCB^!UOe?&S2YnR_h1Oaz-sSb@H) zug+3fe6-`YH;)awxjf|@h9Q3}8)h?-DMi=mjA`RwfU%-`Wu%P9DIgD48W@>?CyW9_ z6n5tb#NBgQQlS`S(N6Nm@E8@1K#)8A?%d)2_6BA|UP!akEB1h^>8v1SXIUqU z!?b~qsNM~nSEjI`^*dE;o}~86ED8;tP7Ie*Vu1O}10;ikOYShMv&SC;udvF4ES&wp z`s9A@K1V0tzD2tqVT4?yYAlk+=LUAa291@|KgH?O@68FPC*n(%)e!lJ^eDzrxZ>|1_vs7Wz3?99OpL4v`oB?nIb~V*BLCeRf;&HxMrdlfw`vS z946|Lj{>+jqQB2LRho~gj!iDj1_V(V3a4qN9R$&XotRAmBon(J4m8zTN9NHU_bcMI2>$9!w35HF@p zH|4Q-+m;L{4*0BoU^{tZT`wC%r2{J|*crid(C|I+g1`zG7U)UpGGjKlMajNM2QL^P z9snO085khz&1DxsahiLcjU^ zlhZmxQUNttND3D6&r#n1j}pHm6J5qu2J(lLdXH~xD<3Fme8Q%tlNV07^*K4SG9 za)|_Ns!s==MsiKb2D!*q5lqTK3f!&)>{TwiK?Ha2iOmrV06vbx8Ea0Xg)DUn4 zBP;4oee!(Z7oHp2BhA>r0bt=&BevqA{xAbl%GRU+Ki!1f<-$3mh5#IvP{~K%i~>fq zpbE~FL#E5;+;+i7EfS#JanmnkwG06nB@AOA8|hxyWYVjk48$?#j26xMk4q3j{{UPi zPajHg?hi}}n?{8=BoXhE1Wdz|!naI>GRwmnU#?i8)7b*h^*7%Eg0w%T4l{B!bQOa- zm8%R%z%SZ-!yt^C?_gljjYI82w9{Y{RCo;Eu3kUiuxl0VjrnTI11Ksz#o#Qmi5yjd zjP6apcn_Qek(HBtAOX;Wz`<{AOKk#&b9ff>5heuDR`}_RGNA^re_~KCaU}jYA0|j# zB0mS9B-R1U^ zK<;_MF$%VLHHrPLmCrRt3T>f6q);DBR!AHMXwN}ampXv-6!Lo3GMhpGSba&s=$ue2 zil#|d3W4pEW<;BJ1QSHp>x^xTi(hObys*NFwLNTN?t&J$+tc3rVRs7K zefY^pRi?Q}APy7y;4UO(^<9sVmTh7LDn;%TMdL3VZ9`(v>0D(N1Zgb9riW9`0x0Kq zruO&erf5N^RvT-kOF)}V0KWiYb3-I(OmtVc_QCAYl#^{rzb7p0R!F7(xk_OpQ#d1R}C55{mw+D+i7?M!eDJib^Pee;u5 zxO$Fs9=Eoz-o$jqSyX|!LwjBXY1j};9;cx>59K7Gfm-iFt^@axpCw3OMcL~|8jnVd zs?5i57C`BMO|i0C9-}LJj?u{jz9!)5=8z`g&){*WW zr;V5a7&d;n%RFe!XKMDr&8Q*N#dX(?_y_weV)lTi;}w<(BT~`TcLyxGz1(tJ`D206 zBO8zDfV5h1B^IA??5c5Hd`Hya6}mOr_`mbx8j585M%wFCeU1-ZCiRnoKs9yKlZ9mz z^2PNBoA$x5G7+<}ykpD+(XFTB3ZWSxK?@KVQ5@yk5Jx!7iRr&?Mp3J1B>p%G z?R~*&$jxmT5y2Z-^!@7Z&LOZfG2|NH=BABQX;61JY&h?Pt2Ut?MFdiICyP8~onn@xP3A*h; z3ObA^{#kQq!~x#0Y$YsE(%(!vPjUU)ufqb&uEB2GuN|-k@ktQewri8|l^TaZxzO$R zDsay$v~*)3MQ$ex8zaUmrFEV7!e735C=o^i|S*TFi>r7&$a+yPath0fOudsbwye+ zwY@_9J^9H)BW~bWHP^r8ghWr`7RRX|U|1|~5IYy}8CaxoXrq6((C#C z$jF((Mq95P4}1*dssdl0oN;+n*xGR3Qyc0>Tq46(9dD2Cl4DsSC920a?SqCDVYsT{ z9aVu2NcP)+EiTyE-PjghfS#wQ!VO=UM4=AdsGJ-Td2Y_tfLh{?6-~xQ zAvpJI>GaCH2VfzIW#~!5O310hTXw9S$*>X$HPD)@1>~JxFanb2x6U3lXq5Ell0n6( zjD|?Zruk#QICqhD+c^fhU_#Cvh*T^`HgbY0n{C$y@qc6C}jC*cE-v3hV$DOKuJ}zvk~C)A1g@D|k(P}QfNv<a4OCtA{3Ert#6#viQYlA6pnZ` z!NBc~k~oN#0{;L5jk174r3C;`!$7I0jdycEyBp-3&fO;SwTM>!3a`tIC$Z%(BJ4rS zaq;-zUrDyVcu5}^e@aC_oz;JiQ&BP`WP%uqv3G!=(KgNH%e7y&D&k8q?IpVN(=-OG zN}{MS;)ZsqQ_Br}ud%|1;fPUfhphgYp{OM^o}L?lOYh$m(#r}$t$g0%I-C*KJcRF3 zIVDA37(nDQ7C;%18}AyzZAG~%&$;I)(mU9o0PfsiT>_=SX-3)rf$vp;V(;k&uZ*fQY8(xxZ@x5|U*7

d?1i*GrNZ4 z_)o?*2w0u?=|lYg06Jotm?DW3qY(E%Wp+O0-lxjCVm>>5I16ewzM+|gD$y6*cFV*< z#Uz){Ko5Mjxj%kO&`J~+q2m*0RE)-^=3~I@gXiZt-rkITM>e71O{7u%aBD2XQxt;F ztQx+*i&fllc&RH4DEkIjWGofO^8n`33(=*t4lGtWr zey1&rC`n9#l%17lBKJ)oRn`*q<%vckh!88uWYkCp=^Fw29e6I zJYu4mkErELj776?+KvoNBP0%azA&bPBuYY$t_kFMPSxY&^vH-b=G%n_r@kD=J8?nPHih+zP9!!V-B&d*^SyU(JoH)B*X!EK$h~4j11O8b)Di=V<5{VMLUE z?2ve@U71w{kx%%$lDZqp4d+~A|5&U!9^dOc_(N%#9CAm`t6A8eJ}tLdASw`xyEDCNx%*GoME=A z@sMX#Tiw$J$`dSHH(V;rYJsA9OC2Q=6Mj`b}-kr+IjVrmGB7$HUA z+7Bbt3EB;n?Swjr6Tg;3S z6#H|P5lRl{TCQ_Ki!kRd!DxazVRp|+h}~waReNL!109R)WSk`WVPU{MZL7Wz7}h$C zcqYieS&~h+yLEIIgh?%VuAM$`>Z(Q8PPnweJj1kddthH;$h1c|vN;xw)MWXLfr5g5 zm@8*u{{V@=DMD11{IOJBl9C0CIhdHpJbPfGJEST<6NNUjjhqW&Xk;W)?<@tD!$Acb z01uPb86YB>Jp17ZTU9Wt=sobGOzZ`P1HK$FyPq4t9Oo4}|t`&7^s;U7kf=FIx+*MJ3GIGv^ zBq|hf(BKoz4#AiZ0qI?Gvg+hMlnRIIXBT#sy<`FGG7PGK@NemXJ7q1nxF_c%7AKya z@}6e#&!lAev9ADgg(=?`ij zI0ZCMsjumfP0&zD%SRDzTbyBI+ARK936Sevp1C$^g7SUwy1)pTwLNR;foP)%>SM_m zVK5v6XD95*{6wFOJaI+^D)EFYcgStN?ha4g9-JI{R0^Kl;)JZc5x_VPx@@TwaolCn zLc@cW47@2IgNn+J3lf}zfqhX%j>+{5RkY2tNg3QLk(V*aD5J&XpqEZ|vf7^G8d+>B zCiOH$1DA~fOl6}JwGotU9I*?G?j38sXHCqZcGLNHdBaT1EOU6lmvR(gM0PpQF+)e8 z>6zVZp^cN8HH_%Nebi_UxJg|qk39Ct(J%`2-mfgI=jm|r#JfPTlc#OP%MNUeI-qVr zqwRo_7Dn?U1RmgXdAB5LG^+ljJ6q!bWRaut-i3F@)=Q51^cUP%VR4<`o(paHa=J+jFIRmp}itMyUsbMmc1fFqBOZ*9u#nPA@PfWwrE?+XCoipYHMoNYcs<jI}Z2>eAXZlx9Tye7`v;g@D1H#2`DNJU}ZSj_Xm6H zf^A892dbRcl*Uw<6ndg@Y*%ZK{)Z(s#eVSw9`%6Kzy=S{fzQ(r@PI}=4YNKkX z0|8y~IT6SLwR2~-G?1x4r9mBe=Oxe>==MB(Wh0b>w~HNp@H$u!0k8#e(`Xg=;_WlB z^7d2xags@H2TTM}mzqY7PI%