aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.yml2
-rw-r--r--.github/workflows/backport.yml19
-rw-r--r--.github/workflows/pr-comment.yml61
-rw-r--r--COPYING.md98
-rw-r--r--README.md10
-rw-r--r--launcher/Application.cpp3
-rw-r--r--launcher/CMakeLists.txt9
-rw-r--r--launcher/InstancePageProvider.h4
-rw-r--r--launcher/MMCZip.cpp2
-rw-r--r--launcher/launch/steps/CheckJava.cpp16
-rw-r--r--launcher/launch/steps/CheckJava.h2
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp10
-rw-r--r--launcher/minecraft/MojangDownloadInfo.h2
-rw-r--r--launcher/minecraft/launch/LauncherPartLaunch.cpp2
-rw-r--r--launcher/news/NewsEntry.cpp2
-rw-r--r--launcher/tasks/ConcurrentTask.cpp144
-rw-r--r--launcher/tasks/ConcurrentTask.h58
-rw-r--r--launcher/ui/MainWindow.cpp114
-rw-r--r--launcher/ui/MainWindow.h2
-rw-r--r--launcher/ui/dialogs/AboutDialog.ui27
-rw-r--r--launcher/ui/dialogs/NewsDialog.cpp49
-rw-r--r--launcher/ui/dialogs/NewsDialog.h30
-rw-r--r--launcher/ui/dialogs/NewsDialog.ui113
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp297
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.h73
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui (renamed from launcher/ui/pages/instance/ModFolderPage.ui)49
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp381
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h112
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.h20
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.h16
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.h18
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.ui2
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp11
-rw-r--r--lgtm.yml2
-rw-r--r--libraries/README.md2
-rw-r--r--libraries/javacheck/CMakeLists.txt2
-rw-r--r--libraries/javacheck/JavaCheck.java35
-rw-r--r--libraries/launcher/CMakeLists.txt20
l---------libraries/launcher/LICENSE1
-rw-r--r--libraries/launcher/org/polymc/EntryPoint.java (renamed from libraries/launcher/org/multimc/EntryPoint.java)23
-rw-r--r--libraries/launcher/org/polymc/Launcher.java (renamed from libraries/launcher/org/multimc/Launcher.java)2
-rw-r--r--libraries/launcher/org/polymc/LauncherFactory.java (renamed from libraries/launcher/org/multimc/LauncherFactory.java)23
-rw-r--r--libraries/launcher/org/polymc/applet/LegacyFrame.java (renamed from libraries/launcher/org/multimc/applet/LegacyFrame.java)2
-rw-r--r--libraries/launcher/org/polymc/exception/ParameterNotFoundException.java (renamed from libraries/launcher/org/multimc/exception/ParameterNotFoundException.java)2
-rw-r--r--libraries/launcher/org/polymc/exception/ParseException.java (renamed from libraries/launcher/org/multimc/exception/ParseException.java)2
-rw-r--r--libraries/launcher/org/polymc/impl/OneSixLauncher.java (renamed from libraries/launcher/org/multimc/impl/OneSixLauncher.java)10
-rw-r--r--libraries/launcher/org/polymc/utils/Parameters.java (renamed from libraries/launcher/org/multimc/utils/Parameters.java)4
-rw-r--r--libraries/launcher/org/polymc/utils/Utils.java (renamed from libraries/launcher/org/multimc/utils/Utils.java)2
-rw-r--r--program_info/CMakeLists.txt3
-rw-r--r--program_info/win_install.nsi.in14
51 files changed, 1197 insertions, 716 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index b387f46a..bac73932 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -27,7 +27,7 @@ body:
attributes:
label: Version of PolyMC
description: The version of PolyMC used in the bug report.
- placeholder: PolyMC 1.2.2
+ placeholder: PolyMC 1.3.2
validations:
required: true
- type: textarea
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
deleted file mode 100644
index fa287a2c..00000000
--- a/.github/workflows/backport.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-name: Backport PR to stable
-on:
- pull_request:
- branches: [ develop ]
- types: [ closed ]
-jobs:
- release_pull_request:
- if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'backport')
- runs-on: ubuntu-latest
- steps:
- - name: checkout
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Backport PR by cherry-pick-ing
- uses: Nathanmalnoury/gh-backport-action@master
- with:
- pr_branch: 'stable'
- github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml
deleted file mode 100644
index f0f5b8cc..00000000
--- a/.github/workflows/pr-comment.yml
+++ /dev/null
@@ -1,61 +0,0 @@
-name: Comment on pull request
-on:
- workflow_run:
- workflows: ['Build Application']
- types: [completed]
-jobs:
- pr_comment:
- if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
- runs-on: ubuntu-latest
- steps:
- - uses: actions/github-script@v5
- with:
- # This snippet is public-domain, taken from
- # https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
- script: |
- async function upsertComment(owner, repo, issue_number, purpose, body) {
- const {data: comments} = await github.rest.issues.listComments(
- {owner, repo, issue_number});
-
- const marker = `<!-- bot: ${purpose} -->`;
- body = marker + "\n" + body;
-
- const existing = comments.filter((c) => c.body.includes(marker));
- if (existing.length > 0) {
- const last = existing[existing.length - 1];
- core.info(`Updating comment ${last.id}`);
- await github.rest.issues.updateComment({
- owner, repo,
- body,
- comment_id: last.id,
- });
- } else {
- core.info(`Creating a comment in issue / PR #${issue_number}`);
- await github.rest.issues.createComment({issue_number, body, owner, repo});
- }
- }
-
- const {owner, repo} = context.repo;
- const run_id = ${{github.event.workflow_run.id}};
-
- const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
- if (!pull_requests.length) {
- return core.error("This workflow doesn't match any pull requests!");
- }
-
- const artifacts = await github.paginate(
- github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
- if (!artifacts.length) {
- return core.error(`No artifacts found`);
- }
- let body = `Download the artifacts for this pull request:\n`;
- for (const art of artifacts) {
- body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
- }
-
- core.info("Review thread message body:", body);
-
- for (const pr of pull_requests) {
- await upsertComment(owner, repo, pr.number,
- "nightly-link", body);
- }
diff --git a/COPYING.md b/COPYING.md
index 273a5b3a..191ea785 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -1,33 +1,36 @@
# PolyMC
- Copyright (C) 2012-2021 MultiMC Contributors
- Copyright (C) 2021-2022 PolyMC Contributors
+ PolyMC - Minecraft Launcher
+ Copyright (C) 2021-2022 PolyMC Contributors
- 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 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.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+ This file incorporates work covered by the following copyright and
+ permission notice:
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
+ Copyright 2013-2021 MultiMC Contributors
-# Launcher (https://github.com/MultiMC/Launcher)
- Copyright 2012-2021 MultiMC Contributors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
+ 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
+ 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.
+ 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.
# MinGW runtime (Windows)
@@ -213,6 +216,57 @@
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# launcher (`libraries/launcher`)
+
+ PolyMC - Minecraft Launcher
+ Copyright (C) 2021-2022 PolyMC Contributors
+
+ 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.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library. Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give
+ you permission to link this library with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also meet,
+ for each linked independent module, the terms and conditions of the
+ license of that module. An independent module is a module which is
+ not derived from or based on this library. If you modify this
+ library, you may extend this exception to your version of the
+ library, but you are not obliged to do so. If you do not wish to do
+ so, delete this exception statement from your version.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+ This file incorporates work covered by the following copyright and
+ permission notice:
+
+ Copyright 2013-2021 MultiMC Contributors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
# lionshead
Code has been taken from https://github.com/natefoo/lionshead and loosely
diff --git a/README.md b/README.md
index 1e4e5caf..c1fabc44 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
-<p align="center">
- <img src="./program_info/polymc-header-black.svg#gh-light-mode-only" alt="PolyMC logo"/>
- <img src="./program_info/polymc-header.svg#gh-dark-mode-only" alt="PolyMC logo"/>
+ <p align="center">
+<img src="./program_info/polymc-header-black.svg#gh-light-mode-only" alt="PolyMC logo" width="50%"/>
+<img src="./program_info/polymc-header.svg#gh-dark-mode-only" alt="PolyMC logo" width="50%"/>
</p>
-<br>
PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.
@@ -80,8 +79,7 @@ To modify download information or change packaging information send a pull reque
## Forking/Redistributing/Custom builds policy
-We don't care what you do with your fork/custom build as long as you do the following as a basic courtesy:
-- Follow the terms of the [license](LICENSE) (not just a courtesy, but also a legal responsibility)
+We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy:
- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org).
- Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled).
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index ab3110a3..bafb928b 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -226,7 +226,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setOrganizationName(BuildConfig.LAUNCHER_NAME);
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME);
- setApplicationDisplayName(BuildConfig.LAUNCHER_DISPLAYNAME);
+ setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
setApplicationVersion(BuildConfig.printableVersionString());
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime();
@@ -626,6 +626,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("JavaPath", "");
m_settings->registerSetting("JavaTimestamp", 0);
m_settings->registerSetting("JavaArchitecture", "");
+ m_settings->registerSetting("JavaRealArchitecture", "");
m_settings->registerSetting("JavaVersion", "");
m_settings->registerSetting("JavaVendor", "");
m_settings->registerSetting("LastHostname", "");
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index b8db803b..26bcfe09 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -418,6 +418,8 @@ set(TASKS_SOURCES
# Tasks
tasks/Task.h
tasks/Task.cpp
+ tasks/ConcurrentTask.h
+ tasks/ConcurrentTask.cpp
tasks/SequentialTask.h
tasks/SequentialTask.cpp
)
@@ -717,6 +719,8 @@ SET(LAUNCHER_SOURCES
ui/pages/BasePageProvider.h
# GUI - instance pages
+ ui/pages/instance/ExternalResourcesPage.cpp
+ ui/pages/instance/ExternalResourcesPage.h
ui/pages/instance/GameOptionsPage.cpp
ui/pages/instance/GameOptionsPage.h
ui/pages/instance/VersionPage.cpp
@@ -845,6 +849,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/NewComponentDialog.h
ui/dialogs/NewInstanceDialog.cpp
ui/dialogs/NewInstanceDialog.h
+ ui/dialogs/NewsDialog.cpp
+ ui/dialogs/NewsDialog.h
ui/pagedialog/PageDialog.cpp
ui/pagedialog/PageDialog.h
ui/dialogs/ProgressDialog.cpp
@@ -924,7 +930,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/pages/global/ProxyPage.ui
ui/pages/global/MinecraftPage.ui
ui/pages/global/ExternalToolsPage.ui
- ui/pages/instance/ModFolderPage.ui
+ ui/pages/instance/ExternalResourcesPage.ui
ui/pages/instance/NotesPage.ui
ui/pages/instance/LogPage.ui
ui/pages/instance/ServersPage.ui
@@ -954,6 +960,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/dialogs/NewInstanceDialog.ui
ui/dialogs/UpdateDialog.ui
ui/dialogs/NewComponentDialog.ui
+ ui/dialogs/NewsDialog.ui
ui/dialogs/ProfileSelectDialog.ui
ui/dialogs/SkinUploadDialog.ui
ui/dialogs/ExportInstanceDialog.ui
diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h
index 357157d0..78fb7016 100644
--- a/launcher/InstancePageProvider.h
+++ b/launcher/InstancePageProvider.h
@@ -33,10 +33,10 @@ public:
values.append(new LogPage(inst));
std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
values.append(new VersionPage(onesix.get()));
- auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Mods"), "Loader-mods");
+ auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList());
modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
values.append(modsPage);
- values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods"));
+ values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList()));
values.append(new ResourcePackPage(onesix.get()));
values.append(new TexturePackPage(onesix.get()));
values.append(new ShaderPackPage(onesix.get()));
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index 627ceaf1..d7ad4428 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -305,7 +305,7 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
QString path;
if(name.contains('/') && !name.endsWith('/')){
path = name.section('/', 0, -2) + "/";
- FS::ensureFolderPathExists(path);
+ FS::ensureFolderPathExists(FS::PathCombine(target, path));
name = name.split('/').last();
}
diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp
index 3226fae7..ef5db2c9 100644
--- a/launcher/launch/steps/CheckJava.cpp
+++ b/launcher/launch/steps/CheckJava.cpp
@@ -75,11 +75,14 @@ void CheckJava::executeTask()
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
+ auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
auto storedVendor = settings->get("JavaVendor").toString();
m_javaUnixTime = javaUnixTime;
// if timestamps are not the same, or something is missing, check!
- if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedVendor.size() == 0)
+ if (javaUnixTime != storedUnixTime || storedVersion.size() == 0
+ || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
+ || storedVendor.size() == 0)
{
m_JavaChecker = new JavaChecker();
emit logLine(QString("Checking Java version..."), MessageLevel::Launcher);
@@ -92,8 +95,9 @@ void CheckJava::executeTask()
{
auto verString = instance->settings()->get("JavaVersion").toString();
auto archString = instance->settings()->get("JavaArchitecture").toString();
+ auto realArchString = settings->get("JavaRealArchitecture").toString();
auto vendorString = instance->settings()->get("JavaVendor").toString();
- printJavaInfo(verString, archString, vendorString);
+ printJavaInfo(verString, archString, realArchString, vendorString);
}
emitSucceeded();
}
@@ -124,10 +128,11 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
case JavaCheckResult::Validity::Valid:
{
auto instance = m_parent->instance();
- printJavaInfo(result.javaVersion.toString(), result.realPlatform, result.javaVendor);
+ printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor);
printSystemInfo(true, result.is_64bit);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
+ instance->settings()->set("JavaRealArchitecture", result.realPlatform);
instance->settings()->set("JavaVendor", result.javaVendor);
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded();
@@ -136,9 +141,10 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
}
}
-void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor)
+void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString & vendor)
{
- emit logLine(QString("Java is version %1, using %2 architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::Launcher);
+ emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n")
+ .arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher);
}
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h
index 68cd618b..d084b132 100644
--- a/launcher/launch/steps/CheckJava.h
+++ b/launcher/launch/steps/CheckJava.h
@@ -35,7 +35,7 @@ private slots:
void checkJavaFinished(JavaCheckResult result);
private:
- void printJavaInfo(const QString & version, const QString & architecture, const QString & vendor);
+ void printJavaInfo(const QString & version, const QString & architecture, const QString & realArchitecture, const QString & vendor);
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
private:
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 7e72601f..e0113a09 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -826,8 +826,16 @@ QString MinecraftInstance::getStatusbarDescription()
traits.append(tr("broken"));
}
+ QString mcVersion = m_components->getComponentVersion("net.minecraft");
+ if (mcVersion.isEmpty())
+ {
+ // Load component info if needed
+ m_components->reload(Net::Mode::Offline);
+ mcVersion = m_components->getComponentVersion("net.minecraft");
+ }
+
QString description;
- description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
+ description.append(tr("Minecraft %1").arg(mcVersion));
if(m_settings->get("ShowGameTime").toBool())
{
if (lastTimePlayed() > 0) {
diff --git a/launcher/minecraft/MojangDownloadInfo.h b/launcher/minecraft/MojangDownloadInfo.h
index 88f87287..13e27e15 100644
--- a/launcher/minecraft/MojangDownloadInfo.h
+++ b/launcher/minecraft/MojangDownloadInfo.h
@@ -65,7 +65,7 @@ struct MojangAssetIndexInfo : public MojangDownloadInfo
// https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/
if(id == "legacy")
{
- url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
+ url = "https://piston-meta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
}
// HACK
else
diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp
index 427bc32b..fe8a1b1b 100644
--- a/launcher/minecraft/launch/LauncherPartLaunch.cpp
+++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp
@@ -142,7 +142,7 @@ void LauncherPartLaunch::executeTask()
#else
args << classPath.join(':');
#endif
- args << "org.multimc.EntryPoint";
+ args << "org.polymc.EntryPoint";
qDebug() << args.join(' ');
diff --git a/launcher/news/NewsEntry.cpp b/launcher/news/NewsEntry.cpp
index 137703d1..cfe07e86 100644
--- a/launcher/news/NewsEntry.cpp
+++ b/launcher/news/NewsEntry.cpp
@@ -54,7 +54,7 @@ inline QString childValue(const QDomElement& element, const QString& childName,
bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg)
{
QString title = childValue(element, "title", tr("Untitled"));
- QString content = childValue(element, "description", tr("No content."));
+ QString content = childValue(element, "content", tr("No content."));
QString link = childValue(element, "id");
entry->title = title;
diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp
new file mode 100644
index 00000000..b88cfb13
--- /dev/null
+++ b/launcher/tasks/ConcurrentTask.cpp
@@ -0,0 +1,144 @@
+#include "ConcurrentTask.h"
+
+#include <QDebug>
+
+ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent)
+ : Task(parent), m_name(task_name), m_total_max_size(max_concurrent)
+{}
+
+ConcurrentTask::~ConcurrentTask()
+{
+ for (auto task : m_queue) {
+ if (task)
+ task->deleteLater();
+ }
+}
+
+auto ConcurrentTask::getStepProgress() const -> qint64
+{
+ return m_stepProgress;
+}
+
+auto ConcurrentTask::getStepTotalProgress() const -> qint64
+{
+ return m_stepTotalProgress;
+}
+
+void ConcurrentTask::addTask(Task::Ptr task)
+{
+ if (!isRunning())
+ m_queue.append(task);
+ else
+ qWarning() << "Tried to add a task to a running concurrent task!";
+}
+
+void ConcurrentTask::executeTask()
+{
+ m_total_size = m_queue.size();
+
+ for (int i = 0; i < m_total_max_size; i++)
+ startNext();
+}
+
+bool ConcurrentTask::abort()
+{
+ if (m_doing.isEmpty()) {
+ // Don't call emitAborted() here, we want to bypass the 'is the task running' check
+ emit aborted();
+ emit finished();
+
+ m_aborted = true;
+ return true;
+ }
+
+ m_queue.clear();
+
+ m_aborted = true;
+ for (auto task : m_doing)
+ m_aborted &= task->abort();
+
+ if (m_aborted)
+ emitAborted();
+
+ return m_aborted;
+}
+
+void ConcurrentTask::startNext()
+{
+ if (m_aborted || m_doing.count() > m_total_max_size)
+ return;
+
+ if (m_queue.isEmpty() && m_doing.isEmpty()) {
+ emitSucceeded();
+ return;
+ }
+
+ if (m_queue.isEmpty())
+ return;
+
+ Task::Ptr next = m_queue.dequeue();
+
+ connect(next.get(), &Task::succeeded, this, [this, next] { subTaskSucceeded(next); });
+ connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); });
+
+ connect(next.get(), &Task::status, this, &ConcurrentTask::subTaskStatus);
+ connect(next.get(), &Task::stepStatus, this, &ConcurrentTask::subTaskStatus);
+
+ connect(next.get(), &Task::progress, this, &ConcurrentTask::subTaskProgress);
+
+ m_doing.insert(next.get(), next);
+
+ setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus());
+ updateState();
+
+ next->start();
+}
+
+void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
+{
+ m_done.insert(task.get(), task);
+ m_doing.remove(task.get());
+
+ disconnect(task.get(), 0, this, 0);
+
+ updateState();
+
+ startNext();
+}
+
+void ConcurrentTask::subTaskFailed(Task::Ptr task, const QString& msg)
+{
+ m_done.insert(task.get(), task);
+ m_failed.insert(task.get(), task);
+
+ m_doing.remove(task.get());
+
+ disconnect(task.get(), 0, this, 0);
+
+ updateState();
+
+ startNext();
+}
+
+void ConcurrentTask::subTaskStatus(const QString& msg)
+{
+ setStepStatus(msg);
+}
+
+void ConcurrentTask::subTaskProgress(qint64 current, qint64 total)
+{
+ if (total == 0) {
+ setProgress(0, 100);
+ return;
+ }
+
+ m_stepProgress = current;
+ m_stepTotalProgress = total;
+}
+
+void ConcurrentTask::updateState()
+{
+ setProgress(m_done.count(), m_total_size);
+ setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)")
+ .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(m_total_size)));
+}
diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h
new file mode 100644
index 00000000..5898899d
--- /dev/null
+++ b/launcher/tasks/ConcurrentTask.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <QQueue>
+#include <QSet>
+
+#include "tasks/Task.h"
+
+class ConcurrentTask : public Task {
+ Q_OBJECT
+public:
+ explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6);
+ virtual ~ConcurrentTask();
+
+ inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; };
+ auto getStepProgress() const -> qint64 override;
+ auto getStepTotalProgress() const -> qint64 override;
+
+ inline auto getStepStatus() const -> QString override { return m_step_status; }
+
+ void addTask(Task::Ptr task);
+
+public slots:
+ bool abort() override;
+
+protected
+slots:
+ void executeTask() override;
+
+ virtual void startNext();
+
+ void subTaskSucceeded(Task::Ptr);
+ void subTaskFailed(Task::Ptr, const QString &msg);
+ void subTaskStatus(const QString &msg);
+ void subTaskProgress(qint64 current, qint64 total);
+
+protected:
+ void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); };
+
+ virtual void updateState();
+
+protected:
+ QString m_name;
+ QString m_step_status;
+
+ QQueue<Task::Ptr> m_queue;
+
+ QHash<Task*, Task::Ptr> m_doing;
+ QHash<Task*, Task::Ptr> m_done;
+ QHash<Task*, Task::Ptr> m_failed;
+
+ int m_total_max_size;
+ int m_total_size;
+
+ qint64 m_stepProgress = 0;
+ qint64 m_stepTotalProgress = 100;
+
+ bool m_aborted = false;
+};
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 210442df..f68cf61a 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -95,6 +95,7 @@
#include "ui/instanceview/InstanceDelegate.h"
#include "ui/widgets/LabeledToolButton.h"
#include "ui/dialogs/NewInstanceDialog.h"
+#include "ui/dialogs/NewsDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/VersionSelectDialog.h"
@@ -224,6 +225,7 @@ public:
TranslatedAction actionMoreNews;
TranslatedAction actionManageAccounts;
TranslatedAction actionLaunchInstance;
+ TranslatedAction actionKillInstance;
TranslatedAction actionRenameInstance;
TranslatedAction actionChangeInstGroup;
TranslatedAction actionChangeInstIcon;
@@ -282,27 +284,6 @@ public:
TranslatedToolbar instanceToolBar;
TranslatedToolbar newsToolBar;
QVector<TranslatedToolbar *> all_toolbars;
- bool m_kill = false;
-
- void updateLaunchAction()
- {
- if(m_kill)
- {
- actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill"));
- actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance"));
- }
- else
- {
- actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Launch"));
- actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance."));
- }
- actionLaunchInstance.retranslate();
- }
- void setLaunchAction(bool kill)
- {
- m_kill = kill;
- updateLaunchAction();
- }
void createMainToolbarActions(QMainWindow *MainWindow)
{
@@ -503,9 +484,12 @@ public:
menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool());
fileMenu = menuBar->addMenu(tr("&File"));
+ // Workaround for QTBUG-94802 (https://bugreports.qt.io/browse/QTBUG-94802); also present for other menus
+ fileMenu->setSeparatorsCollapsible(false);
fileMenu->addAction(actionAddInstance);
fileMenu->addAction(actionLaunchInstance);
fileMenu->addAction(actionLaunchInstanceOffline);
+ fileMenu->addAction(actionKillInstance);
fileMenu->addAction(actionCloseWindow);
fileMenu->addSeparator();
fileMenu->addAction(actionEditInstance);
@@ -526,15 +510,18 @@ public:
fileMenu->addAction(actionSettings);
viewMenu = menuBar->addMenu(tr("&View"));
+ viewMenu->setSeparatorsCollapsible(false);
viewMenu->addAction(actionCAT);
viewMenu->addSeparator();
menuBar->addMenu(foldersMenu);
profileMenu = menuBar->addMenu(tr("&Profiles"));
+ profileMenu->setSeparatorsCollapsible(false);
profileMenu->addAction(actionManageAccounts);
helpMenu = menuBar->addMenu(tr("&Help"));
+ helpMenu->setSeparatorsCollapsible(false);
helpMenu->addAction(actionAbout);
helpMenu->addAction(actionOpenWiki);
helpMenu->addAction(actionNewsMenuBar);
@@ -580,10 +567,9 @@ public:
}
// "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)
+ // Actions that also require other conditions (e.g. a running instance) won't be changed.
void setInstanceActionsEnabled(bool enabled)
{
- actionLaunchInstance->setEnabled(enabled);
- actionLaunchInstanceOffline->setEnabled(enabled);
actionEditInstance->setEnabled(enabled);
actionEditInstNotes->setEnabled(enabled);
actionMods->setEnabled(enabled);
@@ -670,6 +656,14 @@ public:
actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode."));
all_actions.append(&actionLaunchInstanceOffline);
+ actionKillInstance = TranslatedAction(MainWindow);
+ actionKillInstance->setObjectName(QStringLiteral("actionKillInstance"));
+ actionKillInstance->setDisabled(true);
+ actionKillInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill"));
+ actionKillInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance"));
+ actionKillInstance->setShortcut(QKeySequence(tr("Ctrl+K")));
+ all_actions.append(&actionKillInstance);
+
actionEditInstance = TranslatedAction(MainWindow);
actionEditInstance->setObjectName(QStringLiteral("actionEditInstance"));
actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Inst&ance..."));
@@ -785,6 +779,7 @@ public:
instanceToolBar->addAction(actionLaunchInstance);
instanceToolBar->addAction(actionLaunchInstanceOffline);
+ instanceToolBar->addAction(actionKillInstance);
instanceToolBar->addSeparator();
@@ -822,7 +817,7 @@ public:
}
MainWindow->resize(800, 600);
MainWindow->setWindowIcon(APPLICATION->getThemedIcon("logo"));
- MainWindow->setWindowTitle(BuildConfig.LAUNCHER_DISPLAYNAME);
+ MainWindow->setWindowTitle(APPLICATION->applicationDisplayName());
#ifndef QT_NO_ACCESSIBILITY
MainWindow->setAccessibleName(BuildConfig.LAUNCHER_NAME);
#endif
@@ -857,8 +852,6 @@ public:
void retranslateUi(MainWindow *MainWindow)
{
- QString winTitle = tr("%1 - Version %2", "Launcher - Version X").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString());
- MainWindow->setWindowTitle(winTitle);
// all the actions
for(auto * item: all_actions)
{
@@ -1184,14 +1177,10 @@ void MainWindow::updateToolsMenu()
QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance));
QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline));
- if(m_selectedInstance && m_selectedInstance->isRunning())
- {
- ui->actionLaunchInstance->setMenu(nullptr);
- ui->actionLaunchInstanceOffline->setMenu(nullptr);
- launchButton->setPopupMode(QToolButton::InstantPopup);
- launchOfflineButton->setPopupMode(QToolButton::InstantPopup);
- return;
- }
+ bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning();
+
+ ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning);
+ ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning);
QMenu *launchMenu = ui->actionLaunchInstance->menu();
QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu();
@@ -1219,6 +1208,9 @@ void MainWindow::updateToolsMenu()
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
if (m_selectedInstance)
{
+ normalLaunch->setEnabled(m_selectedInstance->canLaunch());
+ normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch());
+
connect(normalLaunch, &QAction::triggered, [this]() {
APPLICATION->launch(m_selectedInstance, true);
});
@@ -1249,6 +1241,9 @@ void MainWindow::updateToolsMenu()
}
else if (m_selectedInstance)
{
+ profilerAction->setEnabled(m_selectedInstance->canLaunch());
+ profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch());
+
connect(profilerAction, &QAction::triggered, [this, profiler]()
{
APPLICATION->launch(m_selectedInstance, true, profiler.get());
@@ -1952,20 +1947,17 @@ void MainWindow::on_actionOpenWiki_triggered()
void MainWindow::on_actionMoreNews_triggered()
{
- DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
+ auto entries = m_newsChecker->getNewsEntries();
+ NewsDialog news_dialog(entries, this);
+ news_dialog.exec();
}
void MainWindow::newsButtonClicked()
{
- QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries();
- if (entries.count() > 0)
- {
- DesktopServices::openUrl(QUrl(entries[0]->link));
- }
- else
- {
- DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
- }
+ auto entries = m_newsChecker->getNewsEntries();
+ NewsDialog news_dialog(entries, this);
+ news_dialog.toggleArticleList();
+ news_dialog.exec();
}
void MainWindow::on_actionAbout_triggered()
@@ -2081,15 +2073,7 @@ void MainWindow::instanceActivated(QModelIndex index)
void MainWindow::on_actionLaunchInstance_triggered()
{
- if (!m_selectedInstance)
- {
- return;
- }
- if(m_selectedInstance->isRunning())
- {
- APPLICATION->kill(m_selectedInstance);
- }
- else
+ if(m_selectedInstance && !m_selectedInstance->isRunning())
{
APPLICATION->launch(m_selectedInstance);
}
@@ -2108,6 +2092,14 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered()
}
}
+void MainWindow::on_actionKillInstance_triggered()
+{
+ if(m_selectedInstance && m_selectedInstance->isRunning())
+ {
+ APPLICATION->kill(m_selectedInstance);
+ }
+}
+
void MainWindow::taskEnd()
{
QObject *sender = QObject::sender();
@@ -2141,17 +2133,9 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
{
ui->instanceToolBar->setEnabled(true);
ui->setInstanceActionsEnabled(true);
- if(m_selectedInstance->isRunning())
- {
- ui->actionLaunchInstance->setEnabled(true);
- ui->setLaunchAction(true);
- }
- else
- {
- ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
- ui->setLaunchAction(false);
- }
+ ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch());
+ ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning());
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
ui->renameButton->setText(m_selectedInstance->name());
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
@@ -2168,6 +2152,9 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
{
ui->instanceToolBar->setEnabled(false);
ui->setInstanceActionsEnabled(false);
+ ui->actionLaunchInstance->setEnabled(false);
+ ui->actionLaunchInstanceOffline->setEnabled(false);
+ ui->actionKillInstance->setEnabled(false);
APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad();
return;
@@ -2197,6 +2184,7 @@ void MainWindow::selectionBad()
statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false);
ui->setInstanceActionsEnabled(false);
+ updateToolsMenu();
ui->renameButton->setText(tr("Rename Instance"));
updateInstanceToolIcon("grass");
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 6c64756f..4615975e 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -148,6 +148,8 @@ private slots:
void on_actionLaunchInstanceOffline_triggered();
+ void on_actionKillInstance_triggered();
+
void on_actionDeleteInstance_triggered();
void deleteGroup();
diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui
index 70c5009d..6323992b 100644
--- a/launcher/ui/dialogs/AboutDialog.ui
+++ b/launcher/ui/dialogs/AboutDialog.ui
@@ -89,9 +89,15 @@
</item>
<item>
<widget class="QLabel" name="versionLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
@@ -133,6 +139,9 @@
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
+ </property>
</widget>
</item>
<item>
@@ -160,32 +169,50 @@
</item>
<item>
<widget class="QLabel" name="platformLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="text">
<string>Platform:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
<widget class="QLabel" name="buildNumLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="text">
<string>Build Number:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
<property name="text">
<string>Channel:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
</widget>
</item>
<item>
diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp
new file mode 100644
index 00000000..df620464
--- /dev/null
+++ b/launcher/ui/dialogs/NewsDialog.cpp
@@ -0,0 +1,49 @@
+#include "NewsDialog.h"
+#include "ui_NewsDialog.h"
+
+NewsDialog::NewsDialog(QList<NewsEntryPtr> entries, QWidget* parent) : QDialog(parent), ui(new Ui::NewsDialog())
+{
+ ui->setupUi(this);
+
+ for (auto entry : entries) {
+ ui->articleListWidget->addItem(entry->title);
+ m_entries.insert(entry->title, entry);
+ }
+
+ connect(ui->articleListWidget, &QListWidget::currentTextChanged, this, &NewsDialog::selectedArticleChanged);
+ connect(ui->toggleListButton, &QPushButton::clicked, this, &NewsDialog::toggleArticleList);
+
+ m_article_list_hidden = ui->articleListWidget->isHidden();
+
+ auto first_item = ui->articleListWidget->item(0);
+ ui->articleListWidget->setItemSelected(first_item, true);
+
+ auto article_entry = m_entries.constFind(first_item->text()).value();
+ ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, first_item->text()));
+ ui->currentArticleContentBrowser->setText(article_entry->content);
+}
+
+NewsDialog::~NewsDialog()
+{
+ delete ui;
+}
+
+void NewsDialog::selectedArticleChanged(const QString& new_title)
+{
+ auto const& article_entry = m_entries.constFind(new_title).value();
+
+ ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title));
+ ui->currentArticleContentBrowser->setText(article_entry->content);
+}
+
+void NewsDialog::toggleArticleList()
+{
+ m_article_list_hidden = !m_article_list_hidden;
+
+ ui->articleListWidget->setHidden(m_article_list_hidden);
+
+ if (m_article_list_hidden)
+ ui->toggleListButton->setText(tr("Show article list"));
+ else
+ ui->toggleListButton->setText(tr("Hide article list"));
+}
diff --git a/launcher/ui/dialogs/NewsDialog.h b/launcher/ui/dialogs/NewsDialog.h
new file mode 100644
index 00000000..add6b8dd
--- /dev/null
+++ b/launcher/ui/dialogs/NewsDialog.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <QDialog>
+#include <QHash>
+
+#include "news/NewsEntry.h"
+
+namespace Ui {
+class NewsDialog;
+}
+
+class NewsDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ NewsDialog(QList<NewsEntryPtr> entries, QWidget* parent = nullptr);
+ ~NewsDialog();
+
+ public slots:
+ void toggleArticleList();
+
+ private slots:
+ void selectedArticleChanged(const QString& new_title);
+
+ private:
+ Ui::NewsDialog* ui;
+
+ QHash<QString, NewsEntryPtr> m_entries;
+ bool m_article_list_hidden = false;
+};
diff --git a/launcher/ui/dialogs/NewsDialog.ui b/launcher/ui/dialogs/NewsDialog.ui
new file mode 100644
index 00000000..2aaa08f1
--- /dev/null
+++ b/launcher/ui/dialogs/NewsDialog.ui
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewsDialog</class>
+ <widget class="QDialog" name="NewsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>500</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>News</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="leftVerticalLayout">
+ <item>
+ <widget class="QListWidget" name="articleListWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="rightVerticalLayout">
+ <item>
+ <widget class="QLabel" name="articleTitleLabel">
+ <property name="text">
+ <string notr="true">Placeholder</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="currentArticleContentBrowser">
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QPushButton" name="closeButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>10</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QPushButton" name="toggleListButton">
+ <property name="text">
+ <string>Hide article list</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>closeButton</sender>
+ <signal>clicked()</signal>
+ <receiver>NewsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>277</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>149</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
new file mode 100644
index 00000000..02eeae3d
--- /dev/null
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -0,0 +1,297 @@
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
+
+#include "DesktopServices.h"
+#include "Version.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "ui/GuiUtil.h"
+
+#include <QKeyEvent>
+#include <QMenu>
+
+namespace {
+// FIXME: wasteful
+void RemoveThePrefix(QString& string)
+{
+ QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
+ string.remove(regex);
+ string = string.trimmed();
+}
+} // namespace
+
+class SortProxy : public QSortFilterProxyModel {
+ public:
+ explicit SortProxy(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {}
+
+ protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
+ {
+ ModFolderModel* model = qobject_cast<ModFolderModel*>(sourceModel());
+ if (!model)
+ return false;
+
+ const auto& mod = model->at(source_row);
+
+ if (mod.name().contains(filterRegExp()))
+ return true;
+ if (mod.description().contains(filterRegExp()))
+ return true;
+
+ for (auto& author : mod.authors()) {
+ if (author.contains(filterRegExp())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override
+ {
+ ModFolderModel* model = qobject_cast<ModFolderModel*>(sourceModel());
+ if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) {
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+ }
+
+ // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and
+ // proceed.
+
+ auto column = (ModFolderModel::Columns) source_left.column();
+ bool invert = false;
+ switch (column) {
+ // GH-2550 - sort by enabled/disabled
+ case ModFolderModel::ActiveColumn: {
+ auto dataL = source_left.data(Qt::CheckStateRole).toBool();
+ auto dataR = source_right.data(Qt::CheckStateRole).toBool();
+ if (dataL != dataR)
+ return dataL > dataR;
+
+ // fallthrough
+ invert = sortOrder() == Qt::DescendingOrder;
+ }
+ // GH-2722 - sort mod names in a way that discards "The" prefixes
+ case ModFolderModel::NameColumn: {
+ auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString();
+ RemoveThePrefix(dataL);
+ auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString();
+ RemoveThePrefix(dataR);
+
+ auto less = dataL.compare(dataR, sortCaseSensitivity());
+ if (less != 0)
+ return invert ? (less > 0) : (less < 0);
+
+ // fallthrough
+ invert = sortOrder() == Qt::DescendingOrder;
+ }
+ // GH-2762 - sort versions by parsing them as versions
+ case ModFolderModel::VersionColumn: {
+ auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString());
+ auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString());
+ return invert ? (dataL > dataR) : (dataL < dataR);
+ }
+ default: {
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+ }
+ }
+ }
+};
+
+ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr<ModFolderModel> model, QWidget* parent)
+ : QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model)
+{
+ ui->setupUi(this);
+
+ runningStateChanged(m_instance && m_instance->isRunning());
+
+ ui->actionsToolbar->insertSpacer(ui->actionViewConfigs);
+
+ m_filterModel = new SortProxy(this);
+ m_filterModel->setDynamicSortFilter(true);
+ m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
+ m_filterModel->setSourceModel(m_model.get());
+ m_filterModel->setFilterKeyColumn(-1);
+ ui->treeView->setModel(m_filterModel);
+
+ ui->treeView->installEventFilter(this);
+ ui->treeView->sortByColumn(1, Qt::AscendingOrder);
+ ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ // The default function names by Qt are pretty ugly, so let's just connect the actions manually,
+ // to make it easier to read :)
+ connect(ui->actionAddItem, &QAction::triggered, this, &ExternalResourcesPage::addItem);
+ connect(ui->actionRemoveItem, &QAction::triggered, this, &ExternalResourcesPage::removeItem);
+ connect(ui->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem);
+ connect(ui->actionDisableItem, &QAction::triggered, this, &ExternalResourcesPage::disableItem);
+ connect(ui->actionViewConfigs, &QAction::triggered, this, &ExternalResourcesPage::viewConfigs);
+ connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder);
+
+ connect(ui->treeView, &ModListView::customContextMenuRequested, this, &ExternalResourcesPage::ShowContextMenu);
+ connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated);
+
+ auto selection_model = ui->treeView->selectionModel();
+ connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
+ connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
+ connect(m_instance, &BaseInstance::runningStatusChanged, this, &ExternalResourcesPage::runningStateChanged);
+}
+
+ExternalResourcesPage::~ExternalResourcesPage()
+{
+ m_model->stopWatching();
+ delete ui;
+}
+
+void ExternalResourcesPage::itemActivated(const QModelIndex&)
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setModStatus(selection.indexes(), ModFolderModel::Toggle);
+}
+
+QMenu* ExternalResourcesPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction());
+ return filteredMenu;
+}
+
+void ExternalResourcesPage::ShowContextMenu(const QPoint& pos)
+{
+ auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->treeView->mapToGlobal(pos));
+ delete menu;
+}
+
+void ExternalResourcesPage::openedImpl()
+{
+ m_model->startWatching();
+}
+
+void ExternalResourcesPage::closedImpl()
+{
+ m_model->stopWatching();
+}
+
+void ExternalResourcesPage::retranslate()
+{
+ ui->retranslateUi(this);
+}
+
+void ExternalResourcesPage::filterTextChanged(const QString& newContents)
+{
+ m_viewFilter = newContents;
+ m_filterModel->setFilterFixedString(m_viewFilter);
+}
+
+void ExternalResourcesPage::runningStateChanged(bool running)
+{
+ if (m_controlsEnabled == !running)
+ return;
+
+ m_controlsEnabled = !running;
+ ui->actionAddItem->setEnabled(m_controlsEnabled);
+ ui->actionDisableItem->setEnabled(m_controlsEnabled);
+ ui->actionEnableItem->setEnabled(m_controlsEnabled);
+ ui->actionRemoveItem->setEnabled(m_controlsEnabled);
+}
+
+bool ExternalResourcesPage::shouldDisplay() const
+{
+ return true;
+}
+
+bool ExternalResourcesPage::listFilter(QKeyEvent* keyEvent)
+{
+ switch (keyEvent->key()) {
+ case Qt::Key_Delete:
+ removeItem();
+ return true;
+ case Qt::Key_Plus:
+ addItem();
+ return true;
+ default:
+ break;
+ }
+ return QWidget::eventFilter(ui->treeView, keyEvent);
+}
+
+bool ExternalResourcesPage::eventFilter(QObject* obj, QEvent* ev)
+{
+ if (ev->type() != QEvent::KeyPress)
+ return QWidget::eventFilter(obj, ev);
+
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
+ if (obj == ui->treeView)
+ return listFilter(keyEvent);
+
+ return QWidget::eventFilter(obj, ev);
+}
+
+void ExternalResourcesPage::addItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+
+ auto list = GuiUtil::BrowseForFiles(
+ helpPage(), tr("Select %1", "Select whatever type of files the page contains. Example: 'Loader Mods'").arg(displayName()),
+ m_fileSelectionFilter.arg(displayName()), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
+
+ if (!list.isEmpty()) {
+ for (auto filename : list) {
+ m_model->installMod(filename);
+ }
+ }
+}
+
+void ExternalResourcesPage::removeItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->deleteMods(selection.indexes());
+}
+
+void ExternalResourcesPage::enableItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setModStatus(selection.indexes(), ModFolderModel::Enable);
+}
+
+void ExternalResourcesPage::disableItem()
+{
+ if (!m_controlsEnabled)
+ return;
+
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setModStatus(selection.indexes(), ModFolderModel::Disable);
+}
+
+void ExternalResourcesPage::viewConfigs()
+{
+ DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true);
+}
+
+void ExternalResourcesPage::viewFolder()
+{
+ DesktopServices::openDirectory(m_model->dir().absolutePath(), true);
+}
+
+void ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous)
+{
+ if (!current.isValid()) {
+ ui->frame->clear();
+ return;
+ }
+
+ auto sourceCurrent = m_filterModel->mapToSource(current);
+ int row = sourceCurrent.row();
+ Mod& m = m_model->operator[](row);
+ ui->frame->updateWithMod(m);
+}
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h
new file mode 100644
index 00000000..41237139
--- /dev/null
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <QMainWindow>
+#include <QSortFilterProxyModel>
+
+#include "Application.h"
+#include "minecraft/MinecraftInstance.h"
+#include "ui/pages/BasePage.h"
+
+class ModFolderModel;
+
+namespace Ui {
+class ExternalResourcesPage;
+}
+
+/* This page is used as a base for pages in which the user can manage external resources
+ * related to the game, such as mods, shaders or resource packs. */
+class ExternalResourcesPage : public QMainWindow, public BasePage {
+ Q_OBJECT
+
+ public:
+ // FIXME: Switch to different model (or change the name of this one)
+ explicit ExternalResourcesPage(BaseInstance* instance, std::shared_ptr<ModFolderModel> model, QWidget* parent = nullptr);
+ virtual ~ExternalResourcesPage();
+
+ virtual QString displayName() const override = 0;
+ virtual QIcon icon() const override = 0;
+ virtual QString id() const override = 0;
+ virtual QString helpPage() const override = 0;
+
+ virtual bool shouldDisplay() const override = 0;
+
+ void openedImpl() override;
+ void closedImpl() override;
+
+ void retranslate() override;
+
+ protected:
+ bool eventFilter(QObject* obj, QEvent* ev) override;
+ bool listFilter(QKeyEvent* ev);
+ QMenu* createPopupMenu() override;
+
+ public slots:
+ void current(const QModelIndex& current, const QModelIndex& previous);
+
+ protected slots:
+ void itemActivated(const QModelIndex& index);
+ void filterTextChanged(const QString& newContents);
+ void runningStateChanged(bool running);
+
+ virtual void addItem();
+ virtual void removeItem();
+
+ virtual void enableItem();
+ virtual void disableItem();
+
+ virtual void viewFolder();
+ virtual void viewConfigs();
+
+ void ShowContextMenu(const QPoint& pos);
+
+ protected:
+ BaseInstance* m_instance = nullptr;
+
+ Ui::ExternalResourcesPage* ui = nullptr;
+ std::shared_ptr<ModFolderModel> m_model;
+ QSortFilterProxyModel* m_filterModel = nullptr;
+
+ QString m_fileSelectionFilter;
+ QString m_viewFilter;
+
+ bool m_controlsEnabled = true;
+};
diff --git a/launcher/ui/pages/instance/ModFolderPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index ab59b0df..17bf455a 100644
--- a/launcher/ui/pages/instance/ModFolderPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>ModFolderPage</class>
- <widget class="QMainWindow" name="ModFolderPage">
+ <class>ExternalResourcesPage</class>
+ <widget class="QMainWindow" name="ExternalResourcesPage">
<property name="geometry">
<rect>
<x>0</x>
@@ -53,7 +53,7 @@
</widget>
</item>
<item row="1" column="1" colspan="3">
- <widget class="ModListView" name="modTreeView">
+ <widget class="ModListView" name="treeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@@ -83,15 +83,15 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
- <addaction name="actionAdd"/>
+ <addaction name="actionAddItem"/>
<addaction name="separator"/>
- <addaction name="actionRemove"/>
- <addaction name="actionEnable"/>
- <addaction name="actionDisable"/>
- <addaction name="actionView_configs"/>
- <addaction name="actionView_Folder"/>
+ <addaction name="actionRemoveItem"/>
+ <addaction name="actionEnableItem"/>
+ <addaction name="actionDisableItem"/>
+ <addaction name="actionViewConfigs"/>
+ <addaction name="actionViewFolder"/>
</widget>
- <action name="actionAdd">
+ <action name="actionAddItem">
<property name="text">
<string>&amp;Add</string>
</property>
@@ -99,31 +99,31 @@
<string>Add</string>
</property>
</action>
- <action name="actionRemove">
+ <action name="actionRemoveItem">
<property name="text">
<string>&amp;Remove</string>
</property>
<property name="toolTip">
- <string>Remove selected mods</string>
+ <string>Remove selected item</string>
</property>
</action>
- <action name="actionEnable">
+ <action name="actionEnableItem">
<property name="text">
<string>&amp;Enable</string>
</property>
<property name="toolTip">
- <string>Enable selected mods</string>
+ <string>Enable selected item</string>
</property>
</action>
- <action name="actionDisable">
+ <action name="actionDisableItem">
<property name="text">
<string>&amp;Disable</string>
</property>
<property name="toolTip">
- <string>Disable selected mods</string>
+ <string>Disable selected item</string>
</property>
</action>
- <action name="actionView_configs">
+ <action name="actionViewConfigs">
<property name="text">
<string>View &amp;Configs</string>
</property>
@@ -131,11 +131,22 @@
<string>Open the 'config' folder in the system file manager.</string>
</property>
</action>
- <action name="actionView_Folder">
+ <action name="actionViewFolder">
<property name="text">
<string>View &amp;Folder</string>
</property>
</action>
+ <action name="actionDownloadItem">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&amp;Download</string>
+ </property>
+ <property name="toolTip">
+ <string>Download a new resource</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
@@ -156,7 +167,7 @@
</customwidget>
</customwidgets>
<tabstops>
- <tabstop>modTreeView</tabstop>
+ <tabstop>treeView</tabstop>
<tabstop>filterEdit</tabstop>
</tabstops>
<resources/>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index d929a0ea..4432ccc8 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -35,370 +35,99 @@
*/
#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ui_ExternalResourcesPage.h"
-#include <QMessageBox>
+#include <QAbstractItemModel>
#include <QEvent>
#include <QKeyEvent>
-#include <QAbstractItemModel>
#include <QMenu>
+#include <QMessageBox>
#include <QSortFilterProxyModel>
#include "Application.h"
+#include "ui/GuiUtil.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ModDownloadDialog.h"
-#include "ui/GuiUtil.h"
#include "DesktopServices.h"
-#include "minecraft/mod/ModFolderModel.h"
-#include "minecraft/mod/Mod.h"
-#include "minecraft/VersionFilterData.h"
#include "minecraft/PackProfile.h"
+#include "minecraft/VersionFilterData.h"
+#include "minecraft/mod/Mod.h"
+#include "minecraft/mod/ModFolderModel.h"
#include "modplatform/ModAPI.h"
#include "Version.h"
+#include "tasks/ConcurrentTask.h"
#include "ui/dialogs/ProgressDialog.h"
-#include "tasks/SequentialTask.h"
-
-namespace {
- // FIXME: wasteful
- void RemoveThePrefix(QString & string) {
- QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +"));
- string.remove(regex);
- string = string.trimmed();
- }
-}
-
-class ModSortProxy : public QSortFilterProxyModel
-{
-public:
- explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent)
- {
- }
-
-protected:
- bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override {
- ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
- if(!model) {
- return false;
- }
- const auto &mod = model->at(source_row);
- if(mod.name().contains(filterRegExp())) {
- return true;
- }
- if(mod.description().contains(filterRegExp())) {
- return true;
- }
- for(auto & author: mod.authors()) {
- if (author.contains(filterRegExp())) {
- return true;
- }
- }
- return false;
- }
-
- bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override
- {
- ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
- if(
- !model ||
- !source_left.isValid() ||
- !source_right.isValid() ||
- source_left.column() != source_right.column()
- ) {
- return QSortFilterProxyModel::lessThan(source_left, source_right);
- }
-
- // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed.
- auto column = (ModFolderModel::Columns) source_left.column();
- bool invert = false;
- switch(column) {
- // GH-2550 - sort by enabled/disabled
- case ModFolderModel::ActiveColumn: {
- auto dataL = source_left.data(Qt::CheckStateRole).toBool();
- auto dataR = source_right.data(Qt::CheckStateRole).toBool();
- if(dataL != dataR) {
- return dataL > dataR;
- }
- // fallthrough
- invert = sortOrder() == Qt::DescendingOrder;
- }
- // GH-2722 - sort mod names in a way that discards "The" prefixes
- case ModFolderModel::NameColumn: {
- auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString();
- RemoveThePrefix(dataL);
- auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString();
- RemoveThePrefix(dataR);
-
- auto less = dataL.compare(dataR, sortCaseSensitivity());
- if(less != 0) {
- return invert ? (less > 0) : (less < 0);
- }
- // fallthrough
- invert = sortOrder() == Qt::DescendingOrder;
- }
- // GH-2762 - sort versions by parsing them as versions
- case ModFolderModel::VersionColumn: {
- auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString());
- auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString());
- return invert ? (dataL > dataR) : (dataL < dataR);
- }
- default: {
- return QSortFilterProxyModel::lessThan(source_left, source_right);
- }
- }
- }
-};
-
-ModFolderPage::ModFolderPage(
- BaseInstance *inst,
- std::shared_ptr<ModFolderModel> mods,
- QString id,
- QString iconName,
- QString displayName,
- QString helpPage,
- QWidget *parent
-) :
- QMainWindow(parent),
- ui(new Ui::ModFolderPage)
+ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
+ : ExternalResourcesPage(inst, mods, parent)
{
- ui->setupUi(this);
-
// This is structured like that so that these changes
- // do not affect the Resouce pack and Shader pack tabs
- if(id == "mods") {
- auto act = new QAction(tr("Download mods"), this);
- act->setToolTip(tr("Download mods from online mod platforms"));
- ui->actionsToolbar->insertActionBefore(ui->actionAdd, act);
- connect(act, &QAction::triggered, this, &ModFolderPage::on_actionInstall_mods_triggered);
-
- ui->actionAdd->setText(tr("Add .jar"));
- ui->actionAdd->setToolTip(tr("Add mods via local file"));
- }
-
- ui->actionsToolbar->insertSpacer(ui->actionView_configs);
-
- m_inst = inst;
- on_RunningState_changed(m_inst && m_inst->isRunning());
- m_mods = mods;
- m_id = id;
- m_displayName = displayName;
- m_iconName = iconName;
- m_helpName = helpPage;
- m_fileSelectionFilter = "%1 (*.zip *.jar)";
- m_filterModel = new ModSortProxy(this);
- m_filterModel->setDynamicSortFilter(true);
- m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
- m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
- m_filterModel->setSourceModel(m_mods.get());
- m_filterModel->setFilterKeyColumn(-1);
- ui->modTreeView->setModel(m_filterModel);
- ui->modTreeView->installEventFilter(this);
- ui->modTreeView->sortByColumn(1, Qt::AscendingOrder);
- ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu);
- connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated);
+ // do not affect the Resource pack and Shader pack tabs
+ {
+ ui->actionDownloadItem->setText(tr("Download mods"));
+ ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms"));
+ ui->actionDownloadItem->setEnabled(true);
+ ui->actionAddItem->setText(tr("Add file"));
+ ui->actionAddItem->setToolTip(tr("Add a locally downloaded file"));
- auto smodel = ui->modTreeView->selectionModel();
- connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent);
- connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged);
- connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed);
-}
+ ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
-void ModFolderPage::modItemActivated(const QModelIndex&)
-{
- if(!m_controlsEnabled) {
- return;
+ connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods);
}
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle);
-}
-
-QMenu * ModFolderPage::createPopupMenu()
-{
- QMenu* filteredMenu = QMainWindow::createPopupMenu();
- filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() );
- return filteredMenu;
}
-void ModFolderPage::ShowContextMenu(const QPoint& pos)
-{
- auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu"));
- menu->exec(ui->modTreeView->mapToGlobal(pos));
- delete menu;
-}
-
-void ModFolderPage::openedImpl()
-{
- m_mods->startWatching();
-}
-
-void ModFolderPage::closedImpl()
-{
- m_mods->stopWatching();
-}
-
-void ModFolderPage::on_filterTextChanged(const QString& newContents)
-{
- m_viewFilter = newContents;
- m_filterModel->setFilterFixedString(m_viewFilter);
-}
-
-
-CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods,
- QString id, QString iconName, QString displayName,
- QString helpPage, QWidget *parent)
- : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent)
-{
-}
-
-ModFolderPage::~ModFolderPage()
-{
- m_mods->stopWatching();
- delete ui;
-}
-
-void ModFolderPage::on_RunningState_changed(bool running)
-{
- if(m_controlsEnabled == !running) {
- return;
- }
- m_controlsEnabled = !running;
- ui->actionsToolbar->setEnabled(m_controlsEnabled);
-}
+CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
+ : ModFolderPage(inst, mods, parent)
+{}
bool ModFolderPage::shouldDisplay() const
{
return true;
}
-void ModFolderPage::retranslate()
-{
- ui->retranslateUi(this);
-}
-
bool CoreModFolderPage::shouldDisplay() const
{
- if (ModFolderPage::shouldDisplay())
- {
- auto inst = dynamic_cast<MinecraftInstance *>(m_inst);
+ if (ModFolderPage::shouldDisplay()) {
+ auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
if (!inst)
return true;
+
auto version = inst->getPackProfile();
+
if (!version)
return true;
- if(!version->getComponent("net.minecraftforge"))
- {
+ if (!version->getComponent("net.minecraftforge"))
return false;
- }
- if(!version->getComponent("net.minecraft"))
- {
+ if (!version->getComponent("net.minecraft"))
return false;
- }
- if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
- {
+ if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
return true;
- }
+
}
return false;
}
-bool ModFolderPage::modListFilter(QKeyEvent *keyEvent)
+void ModFolderPage::installMods()
{
- switch (keyEvent->key())
- {
- case Qt::Key_Delete:
- on_actionRemove_triggered();
- return true;
- case Qt::Key_Plus:
- on_actionAdd_triggered();
- return true;
- default:
- break;
- }
- return QWidget::eventFilter(ui->modTreeView, keyEvent);
-}
-
-bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev)
-{
- if (ev->type() != QEvent::KeyPress)
- {
- return QWidget::eventFilter(obj, ev);
- }
- QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
- if (obj == ui->modTreeView)
- return modListFilter(keyEvent);
- return QWidget::eventFilter(obj, ev);
-}
-
-void ModFolderPage::on_actionAdd_triggered()
-{
- if(!m_controlsEnabled) {
+ if (!m_controlsEnabled)
return;
- }
- auto list = GuiUtil::BrowseForFiles(
- m_helpName,
- tr("Select %1",
- "Select whatever type of files the page contains. Example: 'Loader Mods'")
- .arg(m_displayName),
- m_fileSelectionFilter.arg(m_displayName), APPLICATION->settings()->get("CentralModsDir").toString(),
- this->parentWidget());
- if (!list.empty())
- {
- for (auto filename : list)
- {
- m_mods->installMod(filename);
- }
- }
-}
-
-void ModFolderPage::on_actionEnable_triggered()
-{
- if(!m_controlsEnabled) {
- return;
- }
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable);
-}
-
-void ModFolderPage::on_actionDisable_triggered()
-{
- if(!m_controlsEnabled) {
- return;
- }
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable);
-}
-
-void ModFolderPage::on_actionRemove_triggered()
-{
- if(!m_controlsEnabled) {
- return;
- }
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->deleteMods(selection.indexes());
-}
-
-void ModFolderPage::on_actionInstall_mods_triggered()
-{
- if(!m_controlsEnabled) {
- return;
- }
- if(m_inst->typeName() != "Minecraft"){
- return; //this is a null instance or a legacy instance
- }
- auto profile = ((MinecraftInstance *)m_inst)->getPackProfile();
+ if (m_instance->typeName() != "Minecraft")
+ return; // this is a null instance or a legacy instance
+
+ auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (profile->getModLoaders() == ModAPI::Unspecified) {
- QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!"));
+ QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
return;
}
- ModDownloadDialog mdownload(m_mods, this, m_inst);
+
+ ModDownloadDialog mdownload(m_model, this, m_instance);
if (mdownload.exec()) {
- SequentialTask* tasks = new SequentialTask(this);
+ ConcurrentTask* tasks = new ConcurrentTask(this);
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@@ -409,40 +138,20 @@ void ModFolderPage::on_actionInstall_mods_triggered()
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
- if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); }
+ if (warnings.count())
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
+
tasks->deleteLater();
});
- for (auto task : mdownload.getTasks()) {
+ for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
- m_mods->update();
- }
-}
-void ModFolderPage::on_actionView_configs_triggered()
-{
- DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true);
-}
-
-void ModFolderPage::on_actionView_Folder_triggered()
-{
- DesktopServices::openDirectory(m_mods->dir().absolutePath(), true);
-}
-
-void ModFolderPage::modCurrent(const QModelIndex &current, const QModelIndex &previous)
-{
- if (!current.isValid())
- {
- ui->frame->clear();
- return;
+ m_model->update();
}
- auto sourceCurrent = m_filterModel->mapToSource(current);
- int row = sourceCurrent.row();
- Mod &m = m_mods->operator[](row);
- ui->frame->updateWithMod(m);
}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index 2dd44e85..1a9ed7db 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -35,109 +35,31 @@
#pragma once
-#include <QMainWindow>
+#include "ExternalResourcesPage.h"
-#include "minecraft/MinecraftInstance.h"
-#include "ui/pages/BasePage.h"
-
-#include <Application.h>
-#include <QSortFilterProxyModel>
-
-class ModFolderModel;
-namespace Ui
-{
-class ModFolderPage;
-}
-
-class ModFolderPage : public QMainWindow, public BasePage
-{
+class ModFolderPage : public ExternalResourcesPage {
Q_OBJECT
-public:
- explicit ModFolderPage(
- BaseInstance *inst,
- std::shared_ptr<ModFolderModel> mods,
- QString id,
- QString iconName,
- QString displayName,
- QString helpPage = "",
- QWidget *parent = 0
- );
- virtual ~ModFolderPage();
-
- void setFilter(const QString & filter)
- {
- m_fileSelectionFilter = filter;
- }
+ public:
+ explicit ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = nullptr);
+ virtual ~ModFolderPage() = default;
- virtual QString displayName() const override
- {
- return m_displayName;
- }
- virtual QIcon icon() const override
- {
- return APPLICATION->getThemedIcon(m_iconName);
- }
- virtual QString id() const override
- {
- return m_id;
- }
- virtual QString helpPage() const override
- {
- return m_helpName;
- }
- virtual bool shouldDisplay() const override;
- void retranslate() override;
-
- virtual void openedImpl() override;
- virtual void closedImpl() override;
-protected:
- bool eventFilter(QObject *obj, QEvent *ev) override;
- bool modListFilter(QKeyEvent *ev);
- QMenu * createPopupMenu() override;
+ void setFilter(const QString& filter) { m_fileSelectionFilter = filter; }
-protected:
- BaseInstance *m_inst = nullptr;
+ virtual QString displayName() const override { return tr("Mods"); }
+ virtual QIcon icon() const override { return APPLICATION->getThemedIcon("loadermods"); }
+ virtual QString id() const override { return "mods"; }
+ virtual QString helpPage() const override { return "Loader-mods"; }
-protected:
- Ui::ModFolderPage *ui = nullptr;
- std::shared_ptr<ModFolderModel> m_mods;
- QSortFilterProxyModel *m_filterModel = nullptr;
- QString m_iconName;
- QString m_id;
- QString m_displayName;
- QString m_helpName;
- QString m_fileSelectionFilter;
- QString m_viewFilter;
- bool m_controlsEnabled = true;
-
-public
-slots:
- void modCurrent(const QModelIndex &current, const QModelIndex &previous);
+ virtual bool shouldDisplay() const override;
-private
-slots:
- void modItemActivated(const QModelIndex &index);
- void on_filterTextChanged(const QString & newContents);
- void on_RunningState_changed(bool running);
- void on_actionAdd_triggered();
- void on_actionRemove_triggered();
- void on_actionEnable_triggered();
- void on_actionDisable_triggered();
- void on_actionInstall_mods_triggered();
- void on_actionView_Folder_triggered();
- void on_actionView_configs_triggered();
- void ShowContextMenu(const QPoint &pos);
+ private slots:
+ void installMods();
};
-class CoreModFolderPage : public ModFolderPage
-{
-public:
- explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id,
- QString iconName, QString displayName, QString helpPage = "",
- QWidget *parent = 0);
- virtual ~CoreModFolderPage()
- {
- }
+class CoreModFolderPage : public ModFolderPage {
+ public:
+ explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
+ virtual ~CoreModFolderPage() = default;
virtual bool shouldDisplay() const;
};
diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h
index 8054926c..a6c9fdd3 100644
--- a/launcher/ui/pages/instance/ResourcePackPage.h
+++ b/launcher/ui/pages/instance/ResourcePackPage.h
@@ -35,24 +35,28 @@
#pragma once
-#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
-class ResourcePackPage : public ModFolderPage
+class ResourcePackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0)
- : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks",
- "resourcepacks", tr("Resource packs"), "Resource-packs", parent)
+ : ExternalResourcesPage(instance, instance->resourcePackList(), parent)
{
- ui->actionView_configs->setVisible(false);
+ ui->actionViewConfigs->setVisible(false);
}
virtual ~ResourcePackPage() {}
+ QString displayName() const override { return tr("Resource packs"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); }
+ QString id() const override { return "resourcepacks"; }
+ QString helpPage() const override { return "Resource-packs"; }
+
virtual bool shouldDisplay() const override
{
- return !m_inst->traits().contains("no-texturepacks") &&
- !m_inst->traits().contains("texturepacks");
+ return !m_instance->traits().contains("no-texturepacks") &&
+ !m_instance->traits().contains("texturepacks");
}
};
diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h
index 7d4f5074..2cc056c8 100644
--- a/launcher/ui/pages/instance/ShaderPackPage.h
+++ b/launcher/ui/pages/instance/ShaderPackPage.h
@@ -35,21 +35,25 @@
#pragma once
-#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
-class ShaderPackPage : public ModFolderPage
+class ShaderPackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
explicit ShaderPackPage(MinecraftInstance *instance, QWidget *parent = 0)
- : ModFolderPage(instance, instance->shaderPackList(), "shaderpacks",
- "shaderpacks", tr("Shader packs"), "Resource-packs", parent)
+ : ExternalResourcesPage(instance, instance->shaderPackList(), parent)
{
- ui->actionView_configs->setVisible(false);
+ ui->actionViewConfigs->setVisible(false);
}
virtual ~ShaderPackPage() {}
+ QString displayName() const override { return tr("Shader packs"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); }
+ QString id() const override { return "shaderpacks"; }
+ QString helpPage() const override { return "Resource-packs"; }
+
virtual bool shouldDisplay() const override
{
return true;
diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h
index e8cefe6e..f550a5bc 100644
--- a/launcher/ui/pages/instance/TexturePackPage.h
+++ b/launcher/ui/pages/instance/TexturePackPage.h
@@ -35,23 +35,27 @@
#pragma once
-#include "ModFolderPage.h"
-#include "ui_ModFolderPage.h"
+#include "ExternalResourcesPage.h"
+#include "ui_ExternalResourcesPage.h"
-class TexturePackPage : public ModFolderPage
+class TexturePackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0)
- : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks",
- tr("Texture packs"), "Texture-packs", parent)
+ : ExternalResourcesPage(instance, instance->texturePackList(), parent)
{
- ui->actionView_configs->setVisible(false);
+ ui->actionViewConfigs->setVisible(false);
}
virtual ~TexturePackPage() {}
+ QString displayName() const override { return tr("Texture packs"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); }
+ QString id() const override { return "texturepacks"; }
+ QString helpPage() const override { return "Texture-packs"; }
+
virtual bool shouldDisplay() const override
{
- return m_inst->traits().contains("texturepacks");
+ return m_instance->traits().contains("texturepacks");
}
};
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index 98eec31c..4917b890 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -53,7 +53,11 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
}
case Qt::DecorationRole: {
if (m_logoMap.contains(pack.logoName)) {
- return (m_logoMap.value(pack.logoName));
+ auto icon = m_logoMap.value(pack.logoName);
+ // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;(
+ auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
+
+ return icon_scaled;
}
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
// un-const-ify this
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui
index 9fab9773..aab16421 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui
@@ -19,7 +19,7 @@
</font>
</property>
<property name="text">
- <string>Note: CurseForge's API is very unreliable. CurseForge and some mod authors have disallowed downloading mods in third-party applications like PolyMC. As such, you may need to manually download some mods to be able to install a modpack.</string>
+ <string>Note: CurseForge allows creators to block access to third-party tools like PolyMC. As such, you may need to manually download some mods to be able to install a modpack.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index 07d1687c..39b935a6 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -87,6 +87,7 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
} else if (role == Qt::DecorationRole) {
if (m_logoMap.contains(pack.iconName)) {
auto icon = m_logoMap.value(pack.iconName);
+ // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;(
auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
return icon_scaled;
@@ -160,15 +161,15 @@ static auto sortFromIndex(int index) -> QString
{
switch(index){
default:
- case 1:
+ case 0:
return "relevance";
- case 2:
+ case 1:
return "downloads";
- case 3:
+ case 2:
return "follows";
- case 4:
+ case 3:
return "newest";
- case 5:
+ case 4:
return "updated";
}
diff --git a/lgtm.yml b/lgtm.yml
new file mode 100644
index 00000000..39cd3036
--- /dev/null
+++ b/lgtm.yml
@@ -0,0 +1,2 @@
+queries:
+ - exclude: "cpp/fixme-comment" # We like to use FIXME
diff --git a/libraries/README.md b/libraries/README.md
index 7e7e740d..49879a26 100644
--- a/libraries/README.md
+++ b/libraries/README.md
@@ -125,7 +125,7 @@ cp /home/peterix/minecraft/FTB/versions/1.7.10/1.7.10.jar
launcher onesix
```
-Available under the Apache 2.0 license.
+Available under `GPL-3.0-only` (with classpath exception), sublicensed from its original `Apache-2.0` codebase
## libnbtplusplus
libnbt++ is a free C++ library for Minecraft's file format Named Binary Tag (NBT). It can read and write compressed and uncompressed NBT files and provides a code interface for working with NBT data.
diff --git a/libraries/javacheck/CMakeLists.txt b/libraries/javacheck/CMakeLists.txt
index 735de443..fd545d2b 100644
--- a/libraries/javacheck/CMakeLists.txt
+++ b/libraries/javacheck/CMakeLists.txt
@@ -4,7 +4,7 @@ find_package(Java 1.7 REQUIRED COMPONENTS Development)
include(UseJava)
set(CMAKE_JAVA_JAR_ENTRY_POINT JavaCheck)
-set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unchecked)
+set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked)
set(SRC
JavaCheck.java
diff --git a/libraries/javacheck/JavaCheck.java b/libraries/javacheck/JavaCheck.java
index 560abbc0..4bf43a54 100644
--- a/libraries/javacheck/JavaCheck.java
+++ b/libraries/javacheck/JavaCheck.java
@@ -1,24 +1,25 @@
-import java.lang.Integer;
+public final class JavaCheck {
-public class JavaCheck
-{
- private static final String[] keys = {"os.arch", "java.version", "java.vendor"};
- public static void main (String [] args)
- {
- int ret = 0;
- for(String key : keys)
- {
+ private static final String[] CHECKED_PROPERTIES = new String[] {
+ "os.arch",
+ "java.version",
+ "java.vendor"
+ };
+
+ public static void main(String[] args) {
+ int returnCode = 0;
+
+ for (String key : CHECKED_PROPERTIES) {
String property = System.getProperty(key);
- if(property != null)
- {
+
+ if (property != null) {
System.out.println(key + "=" + property);
- }
- else
- {
- ret = 1;
+ } else {
+ returnCode = 1;
}
}
-
- System.exit(ret);
+
+ System.exit(returnCode);
}
+
}
diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt
index 2c859499..c4dfa5b7 100644
--- a/libraries/launcher/CMakeLists.txt
+++ b/libraries/launcher/CMakeLists.txt
@@ -3,19 +3,19 @@ project(launcher Java)
find_package(Java 1.7 REQUIRED COMPONENTS Development)
include(UseJava)
-set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint)
+set(CMAKE_JAVA_JAR_ENTRY_POINT org.polymc.EntryPoint)
set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked)
set(SRC
- org/multimc/EntryPoint.java
- org/multimc/Launcher.java
- org/multimc/LauncherFactory.java
- org/multimc/impl/OneSixLauncher.java
- org/multimc/applet/LegacyFrame.java
- org/multimc/exception/ParameterNotFoundException.java
- org/multimc/exception/ParseException.java
- org/multimc/utils/Parameters.java
- org/multimc/utils/Utils.java
+ org/polymc/EntryPoint.java
+ org/polymc/Launcher.java
+ org/polymc/LauncherFactory.java
+ org/polymc/impl/OneSixLauncher.java
+ org/polymc/applet/LegacyFrame.java
+ org/polymc/exception/ParameterNotFoundException.java
+ org/polymc/exception/ParseException.java
+ org/polymc/utils/Parameters.java
+ org/polymc/utils/Utils.java
net/minecraft/Launcher.java
)
add_jar(NewLaunch ${SRC})
diff --git a/libraries/launcher/LICENSE b/libraries/launcher/LICENSE
new file mode 120000
index 00000000..30cff740
--- /dev/null
+++ b/libraries/launcher/LICENSE
@@ -0,0 +1 @@
+../../LICENSE \ No newline at end of file
diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/polymc/EntryPoint.java
index c0500bbe..20f418eb 100644
--- a/libraries/launcher/org/multimc/EntryPoint.java
+++ b/libraries/launcher/org/polymc/EntryPoint.java
@@ -12,6 +12,23 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
@@ -33,10 +50,10 @@
* limitations under the License.
*/
-package org.multimc;
+package org.polymc;
-import org.multimc.exception.ParseException;
-import org.multimc.utils.Parameters;
+import org.polymc.exception.ParseException;
+import org.polymc.utils.Parameters;
import java.io.BufferedReader;
import java.io.IOException;
diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/polymc/Launcher.java
index bc0b525e..5bff123e 100644
--- a/libraries/launcher/org/multimc/Launcher.java
+++ b/libraries/launcher/org/polymc/Launcher.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.multimc;
+package org.polymc;
public interface Launcher {
diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/polymc/LauncherFactory.java
index a2af8581..86862929 100644
--- a/libraries/launcher/org/multimc/LauncherFactory.java
+++ b/libraries/launcher/org/polymc/LauncherFactory.java
@@ -12,14 +12,31 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
+ * Linking this library statically or dynamically with other modules is
+ * making a combined work based on this library. Thus, the terms and
+ * conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * As a special exception, the copyright holders of this library give
+ * you permission to link this library with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also meet,
+ * for each linked independent module, the terms and conditions of the
+ * license of that module. An independent module is a module which is
+ * not derived from or based on this library. If you modify this
+ * library, you may extend this exception to your version of the
+ * library, but you are not obliged to do so. If you do not wish to do
+ * so, delete this exception statement from your version.
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-package org.multimc;
+package org.polymc;
-import org.multimc.impl.OneSixLauncher;
-import org.multimc.utils.Parameters;
+import org.polymc.impl.OneSixLauncher;
+import org.polymc.utils.Parameters;
import java.util.HashMap;
import java.util.Map;
diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/polymc/applet/LegacyFrame.java
index caec079c..2cdd17d7 100644
--- a/libraries/launcher/org/multimc/applet/LegacyFrame.java
+++ b/libraries/launcher/org/polymc/applet/LegacyFrame.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.multimc.applet;
+package org.polymc.applet;
import net.minecraft.Launcher;
diff --git a/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java b/libraries/launcher/org/polymc/exception/ParameterNotFoundException.java
index 9edbb826..2044814e 100644
--- a/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java
+++ b/libraries/launcher/org/polymc/exception/ParameterNotFoundException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.multimc.exception;
+package org.polymc.exception;
public final class ParameterNotFoundException extends IllegalArgumentException {
diff --git a/libraries/launcher/org/multimc/exception/ParseException.java b/libraries/launcher/org/polymc/exception/ParseException.java
index 848b395d..2f2f8294 100644
--- a/libraries/launcher/org/multimc/exception/ParseException.java
+++ b/libraries/launcher/org/polymc/exception/ParseException.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.multimc.exception;
+package org.polymc.exception;
public final class ParseException extends IllegalArgumentException {
diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java
index b981e4ff..362ff8d6 100644
--- a/libraries/launcher/org/multimc/impl/OneSixLauncher.java
+++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java
@@ -13,12 +13,12 @@
* limitations under the License.
*/
-package org.multimc.impl;
+package org.polymc.impl;
-import org.multimc.Launcher;
-import org.multimc.applet.LegacyFrame;
-import org.multimc.utils.Parameters;
-import org.multimc.utils.Utils;
+import org.polymc.Launcher;
+import org.polymc.applet.LegacyFrame;
+import org.polymc.utils.Parameters;
+import org.polymc.utils.Utils;
import java.applet.Applet;
import java.io.File;
diff --git a/libraries/launcher/org/multimc/utils/Parameters.java b/libraries/launcher/org/polymc/utils/Parameters.java
index 7be790c2..864d3cd2 100644
--- a/libraries/launcher/org/multimc/utils/Parameters.java
+++ b/libraries/launcher/org/polymc/utils/Parameters.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.multimc.utils;
+package org.polymc.utils;
-import org.multimc.exception.ParameterNotFoundException;
+import org.polymc.exception.ParameterNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/libraries/launcher/org/multimc/utils/Utils.java b/libraries/launcher/org/polymc/utils/Utils.java
index 416eff26..12d6e1aa 100644
--- a/libraries/launcher/org/multimc/utils/Utils.java
+++ b/libraries/launcher/org/polymc/utils/Utils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.multimc.utils;
+package org.polymc.utils;
import java.io.File;
import java.lang.reflect.Field;
diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt
index 1000be23..8d835322 100644
--- a/program_info/CMakeLists.txt
+++ b/program_info/CMakeLists.txt
@@ -1,6 +1,7 @@
set(Launcher_CommonName "PolyMC")
-set(Launcher_Copyright "PolyMC Contributors\\n© 2012-2021 MultiMC Contributors" PARENT_SCOPE)
+set(Launcher_Copyright "PolyMC Contributors\\n© 2012-2021 MultiMC Contributors")
+set(Launcher_Copyright "${Launcher_Copyright}" PARENT_SCOPE)
set(Launcher_Domain "polymc.org" PARENT_SCOPE)
set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE)
set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE)
diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in
index e5687de7..987798b6 100644
--- a/program_info/win_install.nsi.in
+++ b/program_info/win_install.nsi.in
@@ -104,7 +104,11 @@ OutFile "../@Launcher_CommonName@-Setup.exe"
; Version info
VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@"
VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@"
-VIAddVersionKey "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@"
+VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "@Launcher_CommonName@"
+VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "@Launcher_CommonName@ Installer"
+VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "@Launcher_Copyright@"
+VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@"
+VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"
;--------------------------------
@@ -140,7 +144,10 @@ Section "@Launcher_CommonName@"
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors"
- WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@"
+ WriteRegStr HKCU "${UNINST_KEY}" "Version" "@Launcher_RELEASE_VERSION_NAME4@"
+ WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "@Launcher_RELEASE_VERSION_NAME@"
+ WriteRegStr HKCU "${UNINST_KEY}" "VersionMajor" "@Launcher_VERSION_MAJOR@"
+ WriteRegStr HKCU "${UNINST_KEY}" "VersionMinor" "@Launcher_VERSION_MINOR@"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
@@ -157,7 +164,7 @@ Section "Start Menu Shortcut" SM_SHORTCUTS
SectionEnd
-Section "Desktop Shortcut" DESKTOP_SHORTCUTS
+Section /o "Desktop Shortcut" DESKTOP_SHORTCUTS
CreateShortcut "$DESKTOP\@Launcher_CommonName@.lnk" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "" "$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" 0
@@ -245,6 +252,7 @@ Function .onInit
${GetParameters} $R0
${GetOptions} $R0 "/NoShortcuts" $R1
${IfNot} ${Errors}
+${OrIf} ${FileExists} "$InstDir\@Launcher_APP_BINARY_NAME@.exe"
!insertmacro UnselectSection ${SM_SHORTCUTS}
!insertmacro UnselectSection ${DESKTOP_SHORTCUTS}
${EndIf}