diff options
Diffstat (limited to 'application')
171 files changed, 3913 insertions, 601 deletions
diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 1d5b8e04..ab2b9960 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -124,15 +124,38 @@ SET(MULTIMC_SOURCES # GUI - platform pages pages/modplatform/VanillaPage.cpp pages/modplatform/VanillaPage.h + + pages/modplatform/atlauncher/AtlFilterModel.cpp + pages/modplatform/atlauncher/AtlFilterModel.h + pages/modplatform/atlauncher/AtlListModel.cpp + pages/modplatform/atlauncher/AtlListModel.h + pages/modplatform/atlauncher/AtlOptionalModDialog.cpp + pages/modplatform/atlauncher/AtlOptionalModDialog.h + pages/modplatform/atlauncher/AtlPage.cpp + pages/modplatform/atlauncher/AtlPage.h + + pages/modplatform/ftb/FtbFilterModel.cpp + pages/modplatform/ftb/FtbFilterModel.h + pages/modplatform/ftb/FtbListModel.cpp + pages/modplatform/ftb/FtbListModel.h + pages/modplatform/ftb/FtbPage.cpp + pages/modplatform/ftb/FtbPage.h + pages/modplatform/legacy_ftb/Page.cpp pages/modplatform/legacy_ftb/Page.h pages/modplatform/legacy_ftb/ListModel.h pages/modplatform/legacy_ftb/ListModel.cpp - pages/modplatform/twitch/TwitchData.h - pages/modplatform/twitch/TwitchModel.cpp - pages/modplatform/twitch/TwitchModel.h - pages/modplatform/twitch/TwitchPage.cpp - pages/modplatform/twitch/TwitchPage.h + + pages/modplatform/flame/FlameModel.cpp + pages/modplatform/flame/FlameModel.h + pages/modplatform/flame/FlamePage.cpp + pages/modplatform/flame/FlamePage.h + + pages/modplatform/technic/TechnicModel.cpp + pages/modplatform/technic/TechnicModel.h + pages/modplatform/technic/TechnicPage.cpp + pages/modplatform/technic/TechnicPage.h + pages/modplatform/ImportPage.cpp pages/modplatform/ImportPage.h @@ -250,10 +273,16 @@ SET(MULTIMC_UIS # Platform pages pages/modplatform/VanillaPage.ui + pages/modplatform/atlauncher/AtlPage.ui + pages/modplatform/ftb/FtbPage.ui pages/modplatform/legacy_ftb/Page.ui - pages/modplatform/twitch/TwitchPage.ui + pages/modplatform/flame/FlamePage.ui + pages/modplatform/technic/TechnicPage.ui pages/modplatform/ImportPage.ui + # Platform Dialogs + pages/modplatform/atlauncher/AtlOptionalModDialog.ui + # Dialogs dialogs/CopyInstanceDialog.ui dialogs/NewComponentDialog.ui diff --git a/application/HoeDown.h b/application/HoeDown.h index c94a312f..b9e06ffb 100644 --- a/application/HoeDown.h +++ b/application/HoeDown.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/InstancePageProvider.h b/application/InstancePageProvider.h index dde36aef..3cb723c4 100644 --- a/application/InstancePageProvider.h +++ b/application/InstancePageProvider.h @@ -46,7 +46,7 @@ public: values.append(new TexturePackPage(onesix.get())); values.append(new NotesPage(onesix.get())); values.append(new WorldListPage(onesix.get(), onesix->worldList())); - values.append(new ServersPage(onesix.get())); + values.append(new ServersPage(onesix)); // values.append(new GameOptionsPage(onesix.get())); values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots"))); values.append(new InstanceSettingsPage(onesix.get())); diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp index 02c1bb23..015ffe1c 100644 --- a/application/InstanceWindow.cpp +++ b/application/InstanceWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -123,12 +123,14 @@ void InstanceWindow::updateLaunchButtons() { m_launchOfflineButton->setEnabled(false); m_killButton->setText(tr("Kill")); + m_killButton->setObjectName("killButton"); m_killButton->setToolTip(tr("Kill the running instance")); } else if(!m_instance->canLaunch()) { m_launchOfflineButton->setEnabled(false); m_killButton->setText(tr("Launch")); + m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); m_killButton->setEnabled(false); } @@ -136,8 +138,12 @@ void InstanceWindow::updateLaunchButtons() { m_launchOfflineButton->setEnabled(true); m_killButton->setText(tr("Launch")); + m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); } + // NOTE: this is a hack to force the button to recalculate its style + m_killButton->setStyleSheet("/* */"); + m_killButton->setStyleSheet(QString()); } void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h index e5bd4464..cd7d2494 100644 --- a/application/InstanceWindow.h +++ b/application/InstanceWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/JavaCommon.cpp b/application/JavaCommon.cpp index 563dfb35..92a058f0 100644 --- a/application/JavaCommon.cpp +++ b/application/JavaCommon.cpp @@ -24,7 +24,8 @@ void JavaCommon::javaWasOk(QWidget *parent, JavaCheckResult result) { QString text; text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version " - "reported: %2<br />").arg(result.realPlatform, result.javaVersion.toString()); + "reported: %2<br />Java vendor " + "reported: %3<br />").arg(result.realPlatform, result.javaVersion.toString(), result.javaVendor); if (result.errorLog.size()) { auto htmlError = result.errorLog; diff --git a/application/KonamiCode.cpp b/application/KonamiCode.cpp index 4c5af837..46a2a0b2 100644 --- a/application/KonamiCode.cpp +++ b/application/KonamiCode.cpp @@ -35,7 +35,7 @@ void KonamiCode::input(QEvent* event) { m_progress = 0; } - if(m_progress == konamiCode.size()) + if(m_progress == static_cast<int>(konamiCode.size())) { m_progress = 0; emit triggered(); diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp index bebc3db1..ee764082 100644 --- a/application/LaunchController.cpp +++ b/application/LaunchController.cpp @@ -15,6 +15,9 @@ #include <minecraft/auth/YggdrasilTask.h> #include <launch/steps/TextPrint.h> #include <QStringList> +#include <QHostInfo> +#include <QList> +#include <QHostAddress> LaunchController::LaunchController(QObject *parent) : Task(parent) { @@ -197,7 +200,7 @@ void LaunchController::launchInstance() return; } - m_launcher = m_instance->createLaunchTask(m_session); + m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin); if (!m_launcher) { emitFailed(tr("Couldn't instantiate a launcher.")); @@ -215,7 +218,46 @@ void LaunchController::launchInstance() connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed); connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested); + // Prepend Online and Auth Status + QString online_mode; + if(m_session->wants_online) { + online_mode = "online"; + // Prepend Server Status + QStringList servers = {"authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com"}; + QString resolved_servers = ""; + QHostInfo host_info; + + for(QString server : servers) { + host_info = QHostInfo::fromName(server); + resolved_servers = resolved_servers + server + " resolves to:\n ["; + if(!host_info.addresses().isEmpty()) { + for(QHostAddress address : host_info.addresses()) { + resolved_servers = resolved_servers + address.toString(); + if(!host_info.addresses().endsWith(address)) { + resolved_servers = resolved_servers + ", "; + } + } + } else { + resolved_servers = resolved_servers + "N/A"; + } + resolved_servers = resolved_servers + "]\n\n"; + } + m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::MultiMC)); + } else { + online_mode = "offline"; + } + + QString auth_server_status; + if(m_session->auth_server_online) { + auth_server_status = "online"; + } else { + auth_server_status = "offline"; + } + + m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\nAuthentication server is " + auth_server_status + "\n", MessageLevel::MultiMC)); + + // Prepend Version m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); m_launcher->start(); } diff --git a/application/LaunchController.h b/application/LaunchController.h index 1d879028..5f177e00 100644 --- a/application/LaunchController.h +++ b/application/LaunchController.h @@ -3,6 +3,8 @@ #include <BaseInstance.h> #include <tools/BaseProfiler.h> +#include "minecraft/launch/MinecraftServerTarget.h" + class InstanceWindow; class LaunchController: public Task { @@ -33,6 +35,10 @@ public: { m_parentWidget = widget; } + void setServerToJoin(MinecraftServerTargetPtr serverToJoin) + { + m_serverToJoin = std::move(serverToJoin); + } QString id() { return m_instance->id(); @@ -58,4 +64,5 @@ private: InstanceWindow *m_console = nullptr; AuthSessionPtr m_session; shared_qobject_ptr<LaunchTask> m_launcher; + MinecraftServerTargetPtr m_serverToJoin; }; diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index 00c37084..13a7c7ae 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* Copyright 2013-2021 MultiMC Contributors * * Authors: Andrew Okin * Peterix @@ -306,29 +306,35 @@ public: helpMenu = new QMenu(MainWindow); helpMenu->setToolTipsVisible(true); - actionReportBug = TranslatedAction(MainWindow); - actionReportBug->setObjectName(QStringLiteral("actionReportBug")); - actionReportBug->setIcon(MMC->getThemedIcon("bug")); - actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug")); - actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC.")); - all_actions.append(&actionReportBug); - helpMenu->addAction(actionReportBug); - - actionDISCORD = TranslatedAction(MainWindow); - actionDISCORD->setObjectName(QStringLiteral("actionDISCORD")); - actionDISCORD->setIcon(MMC->getThemedIcon("discord")); - actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord")); - actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat.")); - all_actions.append(&actionDISCORD); - helpMenu->addAction(actionDISCORD); - - actionREDDIT = TranslatedAction(MainWindow); - actionREDDIT->setObjectName(QStringLiteral("actionREDDIT")); - actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien")); - actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit")); - actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit.")); - all_actions.append(&actionREDDIT); - helpMenu->addAction(actionREDDIT); + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { + actionReportBug = TranslatedAction(MainWindow); + actionReportBug->setObjectName(QStringLiteral("actionReportBug")); + actionReportBug->setIcon(MMC->getThemedIcon("bug")); + actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug")); + actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC.")); + all_actions.append(&actionReportBug); + helpMenu->addAction(actionReportBug); + } + + if (!BuildConfig.DISCORD_URL.isEmpty()) { + actionDISCORD = TranslatedAction(MainWindow); + actionDISCORD->setObjectName(QStringLiteral("actionDISCORD")); + actionDISCORD->setIcon(MMC->getThemedIcon("discord")); + actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord")); + actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat.")); + all_actions.append(&actionDISCORD); + helpMenu->addAction(actionDISCORD); + } + + if (!BuildConfig.SUBREDDIT_URL.isEmpty()) { + actionREDDIT = TranslatedAction(MainWindow); + actionREDDIT->setObjectName(QStringLiteral("actionREDDIT")); + actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien")); + actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit")); + actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit.")); + all_actions.append(&actionREDDIT); + helpMenu->addAction(actionREDDIT); + } actionAbout = TranslatedAction(MainWindow); actionAbout->setObjectName(QStringLiteral("actionAbout")); @@ -732,7 +738,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow repopulateAccountsMenu(); accountMenuButton = new QToolButton(this); - accountMenuButton->setText(tr("Profiles")); accountMenuButton->setMenu(accountMenu); accountMenuButton->setPopupMode(QToolButton::InstantPopup); accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); @@ -831,6 +836,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow // removing this looks stupid view->setFocus(); + + retranslateUi(); +} + +void MainWindow::retranslateUi() +{ + accountMenuButton->setText(tr("Profiles")); + + if (m_selectedInstance) { + m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); + } else { + m_statusLeft->setText(tr("No instance selected")); + } + + ui->retranslateUi(this); } MainWindow::~MainWindow() @@ -1455,12 +1475,12 @@ void MainWindow::droppedURLs(QList<QUrl> urls) void MainWindow::on_actionREDDIT_triggered() { - DesktopServices::openUrl(QUrl("https://www.reddit.com/r/MultiMC/")); + DesktopServices::openUrl(QUrl(BuildConfig.SUBREDDIT_URL)); } void MainWindow::on_actionDISCORD_triggered() { - DesktopServices::openUrl(QUrl("https://discord.gg/0k2zsXGNHs0fE4Wm")); + DesktopServices::openUrl(QUrl(BuildConfig.DISCORD_URL)); } void MainWindow::on_actionChangeInstIcon_triggered() @@ -1638,7 +1658,7 @@ void MainWindow::on_actionManageAccounts_triggered() void MainWindow::on_actionReportBug_triggered() { - DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/issues")); + DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL)); } void MainWindow::on_actionPatreon_triggered() @@ -1745,7 +1765,7 @@ void MainWindow::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { - ui->retranslateUi(this); + retranslateUi(); } QMainWindow::changeEvent(event); } diff --git a/application/MainWindow.h b/application/MainWindow.h index 00b8e043..08c6b969 100644 --- a/application/MainWindow.h +++ b/application/MainWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -187,6 +187,8 @@ private slots: void globalSettingsClosed(); private: + void retranslateUi(); + void addInstance(QString url = QString()); void activateInstance(InstancePtr instance); void setCatBackground(bool enabled); diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index eeab500e..2dd4f83f 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -191,6 +191,11 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) parser.addOption("launch"); parser.addShortOpt("launch", 'l'); parser.addDocumentation("launch", "Launch the specified instance (by instance ID)"); + // --server + parser.addOption("server"); + parser.addShortOpt("server", 's'); + parser.addDocumentation("server", "Join the specified server on launch " + "(only valid in combination with --launch)"); // --alive parser.addSwitch("alive"); parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts"); @@ -207,8 +212,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) catch (const ParsingError &e) { std::cerr << "CommandLineError: " << e.what() << std::endl; - std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." - << std::endl; + if(argc > 0) + std::cerr << "Try '" << argv[0] << " -h' to get help on MultiMC's command line parameters." + << std::endl; m_status = MultiMC::Failed; return; } @@ -231,6 +237,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) } } m_instanceIdToLaunch = args["launch"].toString(); + m_serverToJoin = args["server"].toString(); m_liveCheck = args["alive"].toBool(); m_zipToImport = args["import"].toUrl(); @@ -292,6 +299,13 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) return; } + if(m_instanceIdToLaunch.isEmpty() && !m_serverToJoin.isEmpty()) + { + std::cerr << "--server can only be used in combination with --launch!" << std::endl; + m_status = MultiMC::Failed; + return; + } + /* * Establish the mechanism for communication with an already running MultiMC that uses the same data path. * If there is one, tell it what the user actually wanted to do and exit. @@ -317,7 +331,15 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) } else { - m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout); + if(!m_serverToJoin.isEmpty()) + { + m_peerInstance->sendMessage( + "launch-with-server " + m_instanceIdToLaunch + " " + m_serverToJoin, timeout); + } + else + { + m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout); + } } m_status = MultiMC::Succeeded; return; @@ -378,7 +400,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) ); #endif - qDebug() << "MultiMC 5, (c) 2013-2019 MultiMC Contributors"; + qDebug() << "MultiMC 5, (c) 2013-2021 MultiMC Contributors"; qDebug() << "Version : " << BuildConfig.printableVersionString(); qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; @@ -398,6 +420,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch; } + if(!m_serverToJoin.isEmpty()) + { + qDebug() << "Address of server to join :" << m_serverToJoin; + } qDebug() << "<> Paths set."; } @@ -505,9 +531,18 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("JavaTimestamp", 0); m_settings->registerSetting("JavaArchitecture", ""); m_settings->registerSetting("JavaVersion", ""); + m_settings->registerSetting("JavaVendor", ""); m_settings->registerSetting("LastHostname", ""); m_settings->registerSetting("JvmArgs", ""); + // Native library workarounds + m_settings->registerSetting("UseNativeOpenAL", false); + m_settings->registerSetting("UseNativeGLFW", false); + + // Game time + m_settings->registerSetting("ShowGameTime", true); + m_settings->registerSetting("RecordGameTime", true); + // Minecraft launch method m_settings->registerSetting("MCLaunchMethod", "LauncherPart"); @@ -834,8 +869,19 @@ void MultiMC::performMainStartupAction() auto inst = instances()->getInstanceById(m_instanceIdToLaunch); if(inst) { - qDebug() << "<> Instance launching:" << m_instanceIdToLaunch; - launch(inst, true, nullptr); + MinecraftServerTargetPtr serverToJoin = nullptr; + + if(!m_serverToJoin.isEmpty()) + { + serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin))); + qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching with server" << m_serverToJoin; + } + else + { + qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching"; + } + + launch(inst, true, nullptr, serverToJoin); return; } } @@ -917,6 +963,31 @@ void MultiMC::messageReceived(const QString& message) launch(inst, true, nullptr); } } + else if(command == "launch-with-server") + { + QString instanceID = message.section(' ', 1, 1); + QString serverToJoin = message.section(' ', 2, 2); + if(instanceID.isEmpty()) + { + qWarning() << "Received" << command << "message without an instance ID."; + return; + } + if(serverToJoin.isEmpty()) + { + qWarning() << "Received" << command << "message without a server to join."; + return; + } + auto inst = instances()->getInstanceById(instanceID); + if(inst) + { + launch( + inst, + true, + nullptr, + std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(serverToJoin)) + ); + } + } else { qWarning() << "Received invalid message" << message; @@ -1004,8 +1075,12 @@ bool MultiMC::openJsonEditor(const QString &filename) } } -bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler) -{ +bool MultiMC::launch( + InstancePtr instance, + bool online, + BaseProfilerFactory *profiler, + MinecraftServerTargetPtr serverToJoin +) { if(m_updateRunning) { qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; @@ -1026,6 +1101,7 @@ bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *pro controller->setInstance(instance); controller->setOnline(online); controller->setProfiler(profiler); + controller->setServerToJoin(serverToJoin); if(window) { controller->setParentWidget(window); diff --git a/application/MultiMC.h b/application/MultiMC.h index e6588a14..af2b41c1 100644 --- a/application/MultiMC.h +++ b/application/MultiMC.h @@ -11,6 +11,8 @@ #include <BaseInstance.h> +#include "minecraft/launch/MinecraftServerTarget.h" + class LaunchController; class LocalPeer; class InstanceWindow; @@ -150,7 +152,12 @@ signals: void globalSettingsClosed(); public slots: - bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr); + bool launch( + InstancePtr instance, + bool online = true, + BaseProfilerFactory *profiler = nullptr, + MinecraftServerTargetPtr serverToJoin = nullptr + ); bool kill(InstancePtr instance); private slots: @@ -221,6 +228,7 @@ private: SetupWizard * m_setupWizard = nullptr; public: QString m_instanceIdToLaunch; + QString m_serverToJoin; bool m_liveCheck = false; QUrl m_zipToImport; std::unique_ptr<QFile> logFile; diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp index c4e4ee24..c97c471e 100644 --- a/application/dialogs/AboutDialog.cpp +++ b/application/dialogs/AboutDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/AboutDialog.h b/application/dialogs/AboutDialog.h index 21001c07..c7621c37 100644 --- a/application/dialogs/AboutDialog.h +++ b/application/dialogs/AboutDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/AboutDialog.ui b/application/dialogs/AboutDialog.ui index c4096b32..c6de9ebb 100644 --- a/application/dialogs/AboutDialog.ui +++ b/application/dialogs/AboutDialog.ui @@ -165,7 +165,7 @@ </font> </property> <property name="text"> - <string>© 2012-2019 MultiMC Contributors</string> + <string>© 2012-2021 MultiMC Contributors</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp index ab76e737..5fe90334 100644 --- a/application/dialogs/CopyInstanceDialog.cpp +++ b/application/dialogs/CopyInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/CopyInstanceDialog.h b/application/dialogs/CopyInstanceDialog.h index 2b8475b7..bf3cd920 100644 --- a/application/dialogs/CopyInstanceDialog.h +++ b/application/dialogs/CopyInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/CustomMessageBox.cpp b/application/dialogs/CustomMessageBox.cpp index db60b7f3..19029f68 100644 --- a/application/dialogs/CustomMessageBox.cpp +++ b/application/dialogs/CustomMessageBox.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/CustomMessageBox.h b/application/dialogs/CustomMessageBox.h index 92291e17..712c6518 100644 --- a/application/dialogs/CustomMessageBox.h +++ b/application/dialogs/CustomMessageBox.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/EditAccountDialog.cpp b/application/dialogs/EditAccountDialog.cpp index 9ae0c745..002c064b 100644 --- a/application/dialogs/EditAccountDialog.cpp +++ b/application/dialogs/EditAccountDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/EditAccountDialog.h b/application/dialogs/EditAccountDialog.h index 4f80284a..6b5eb4aa 100644 --- a/application/dialogs/EditAccountDialog.h +++ b/application/dialogs/EditAccountDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/ExportInstanceDialog.cpp b/application/dialogs/ExportInstanceDialog.cpp index 49c082e9..a42779d4 100644 --- a/application/dialogs/ExportInstanceDialog.cpp +++ b/application/dialogs/ExportInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/ExportInstanceDialog.h b/application/dialogs/ExportInstanceDialog.h index aec4cbfb..dea02d1b 100644 --- a/application/dialogs/ExportInstanceDialog.h +++ b/application/dialogs/ExportInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/IconPickerDialog.cpp b/application/dialogs/IconPickerDialog.cpp index 7f930717..90436554 100644 --- a/application/dialogs/IconPickerDialog.cpp +++ b/application/dialogs/IconPickerDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/IconPickerDialog.h b/application/dialogs/IconPickerDialog.h index cb870f44..9af6a678 100644 --- a/application/dialogs/IconPickerDialog.h +++ b/application/dialogs/IconPickerDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/LoginDialog.cpp b/application/dialogs/LoginDialog.cpp index 484cb8e2..32f8a48f 100644 --- a/application/dialogs/LoginDialog.cpp +++ b/application/dialogs/LoginDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/LoginDialog.h b/application/dialogs/LoginDialog.h index 30fcb760..16bdddfb 100644 --- a/application/dialogs/LoginDialog.h +++ b/application/dialogs/LoginDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/LoginDialog.ui b/application/dialogs/LoginDialog.ui index d92fbae3..dbdb3b93 100644 --- a/application/dialogs/LoginDialog.ui +++ b/application/dialogs/LoginDialog.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>400</width> - <height>162</height> + <width>421</width> + <height>238</height> </rect> </property> <property name="sizePolicy"> @@ -21,6 +21,16 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> + <widget class="QLabel" name="microsoftAccountsNoticeLabel"> + <property name="text"> + <string>NOTICE: MultiMC does not currently support Microsoft accounts. This means that accounts created from December 2020 onwards cannot be used.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> <widget class="QLabel" name="label"> <property name="text"> <string notr="true">Message label placeholder.</string> diff --git a/application/dialogs/NewComponentDialog.cpp b/application/dialogs/NewComponentDialog.cpp index d0d1d24b..f4d6274f 100644 --- a/application/dialogs/NewComponentDialog.cpp +++ b/application/dialogs/NewComponentDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/NewComponentDialog.h b/application/dialogs/NewComponentDialog.h index 518f342d..8c790beb 100644 --- a/application/dialogs/NewComponentDialog.h +++ b/application/dialogs/NewComponentDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp index 511f991e..86963149 100644 --- a/application/dialogs/NewInstanceDialog.cpp +++ b/application/dialogs/NewInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -34,9 +34,13 @@ #include "widgets/PageContainer.h" #include <pages/modplatform/VanillaPage.h> +#include <pages/modplatform/atlauncher/AtlPage.h> +#include <pages/modplatform/ftb/FtbPage.h> #include <pages/modplatform/legacy_ftb/Page.h> -#include <pages/modplatform/twitch/TwitchPage.h> +#include <pages/modplatform/flame/FlamePage.h> #include <pages/modplatform/ImportPage.h> +#include <pages/modplatform/technic/TechnicPage.h> + NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) @@ -120,13 +124,17 @@ void NewInstanceDialog::accept() QList<BasePage *> NewInstanceDialog::getPages() { importPage = new ImportPage(this); - twitchPage = new TwitchPage(this); + flamePage = new FlamePage(this); + auto technicPage = new TechnicPage(this); return { new VanillaPage(this), importPage, + new AtlPage(this), + flamePage, + new FtbPage(this), new LegacyFTB::Page(this), - twitchPage + technicPage }; } @@ -165,6 +173,14 @@ void NewInstanceDialog::setSuggestedIconFromFile(const QString &path, const QStr ui->iconButton->setIcon(QIcon(path)); } +void NewInstanceDialog::setSuggestedIcon(const QString &key) +{ + auto icon = MMC->icons()->getIcon(key); + importIcon = false; + + ui->iconButton->setIcon(icon); +} + InstanceTask * NewInstanceDialog::extractTask() { InstanceTask * extracted = creationTask.get(); diff --git a/application/dialogs/NewInstanceDialog.h b/application/dialogs/NewInstanceDialog.h index 0b8b2fb8..53abf8cf 100644 --- a/application/dialogs/NewInstanceDialog.h +++ b/application/dialogs/NewInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -29,7 +29,7 @@ class NewInstanceDialog; class PageContainer; class QDialogButtonBox; class ImportPage; -class TwitchPage; +class FlamePage; class NewInstanceDialog : public QDialog, public BasePageProvider { @@ -43,6 +43,7 @@ public: void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr); void setSuggestedIconFromFile(const QString &path, const QString &name); + void setSuggestedIcon(const QString &key); InstanceTask * extractTask(); @@ -68,7 +69,7 @@ private: QString InstIconKey; ImportPage *importPage = nullptr; - TwitchPage *twitchPage = nullptr; + FlamePage *flamePage = nullptr; std::unique_ptr<InstanceTask> creationTask; bool importIcon = false; diff --git a/application/dialogs/ProfileSelectDialog.cpp b/application/dialogs/ProfileSelectDialog.cpp index b64de01a..ae34709f 100644 --- a/application/dialogs/ProfileSelectDialog.cpp +++ b/application/dialogs/ProfileSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/ProfileSelectDialog.h b/application/dialogs/ProfileSelectDialog.h index 40fa0843..9f95830c 100644 --- a/application/dialogs/ProfileSelectDialog.h +++ b/application/dialogs/ProfileSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/ProgressDialog.cpp b/application/dialogs/ProgressDialog.cpp index 0e186fc2..4b092859 100644 --- a/application/dialogs/ProgressDialog.cpp +++ b/application/dialogs/ProgressDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/ProgressDialog.h b/application/dialogs/ProgressDialog.h index a3779e3f..b28ad4fa 100644 --- a/application/dialogs/ProgressDialog.h +++ b/application/dialogs/ProgressDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/SkinUploadDialog.cpp b/application/dialogs/SkinUploadDialog.cpp index 7d2ff829..56133529 100644 --- a/application/dialogs/SkinUploadDialog.cpp +++ b/application/dialogs/SkinUploadDialog.cpp @@ -1,7 +1,7 @@ #include <QFileInfo> #include <QFileDialog> #include <FileSystem.h> -#include <minecraft/SkinUpload.h> +#include <minecraft/services/SkinUpload.h> #include "SkinUploadDialog.h" #include "ui_SkinUploadDialog.h" #include "ProgressDialog.h" diff --git a/application/dialogs/UpdateDialog.h b/application/dialogs/UpdateDialog.h index 6a871b1c..ae1799c3 100644 --- a/application/dialogs/UpdateDialog.h +++ b/application/dialogs/UpdateDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/VersionSelectDialog.cpp b/application/dialogs/VersionSelectDialog.cpp index 0b6ba87e..ed1210ba 100644 --- a/application/dialogs/VersionSelectDialog.cpp +++ b/application/dialogs/VersionSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/dialogs/VersionSelectDialog.h b/application/dialogs/VersionSelectDialog.h index 14bc4d76..ed30d3f3 100644 --- a/application/dialogs/VersionSelectDialog.h +++ b/application/dialogs/VersionSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/GroupView.cpp b/application/groupview/GroupView.cpp index a1a28532..6bfc9381 100644 --- a/application/groupview/GroupView.cpp +++ b/application/groupview/GroupView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/GroupView.h b/application/groupview/GroupView.h index 13bfd234..cc5a58aa 100644 --- a/application/groupview/GroupView.h +++ b/application/groupview/GroupView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/GroupedProxyModel.cpp b/application/groupview/GroupedProxyModel.cpp index d5fbd93c..dc4212d5 100644 --- a/application/groupview/GroupedProxyModel.cpp +++ b/application/groupview/GroupedProxyModel.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/GroupedProxyModel.h b/application/groupview/GroupedProxyModel.h index 810657e7..fabf11c1 100644 --- a/application/groupview/GroupedProxyModel.h +++ b/application/groupview/GroupedProxyModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/InstanceDelegate.cpp b/application/groupview/InstanceDelegate.cpp index 39830d1a..fc959565 100644 --- a/application/groupview/InstanceDelegate.cpp +++ b/application/groupview/InstanceDelegate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/InstanceDelegate.h b/application/groupview/InstanceDelegate.h index be49f943..d95279f3 100644 --- a/application/groupview/InstanceDelegate.h +++ b/application/groupview/InstanceDelegate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/VisualGroup.cpp b/application/groupview/VisualGroup.cpp index f3e6751d..76bf8678 100644 --- a/application/groupview/VisualGroup.cpp +++ b/application/groupview/VisualGroup.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/groupview/VisualGroup.h b/application/groupview/VisualGroup.h index 356a6da2..239ee9d7 100644 --- a/application/groupview/VisualGroup.h +++ b/application/groupview/VisualGroup.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/package/rpm/MultiMC5.spec b/application/package/rpm/MultiMC5.spec new file mode 100644 index 00000000..78b9000e --- /dev/null +++ b/application/package/rpm/MultiMC5.spec @@ -0,0 +1,47 @@ +Name: MultiMC5 +Version: 1.4 +Release: 2%{?dist} +Summary: A local install wrapper for MultiMC + +License: ASL 2.0 +URL: https://multimc.org +BuildArch: x86_64 + +Requires: zenity qt5-qtbase wget xrandr +Provides: multimc MultiMC multimc5 + +%description +A local install wrapper for MultiMC + +%prep + + +%build + + +%install +mkdir -p %{buildroot}/opt/multimc +install -m 0644 ../ubuntu/multimc/opt/multimc/icon.svg %{buildroot}/opt/multimc/icon.svg +install -m 0755 ../ubuntu/multimc/opt/multimc/run.sh %{buildroot}/opt/multimc/run.sh +mkdir -p %{buildroot}/%{_datadir}/applications +install -m 0644 ../ubuntu/multimc/usr/share/applications/multimc.desktop %{buildroot}/%{_datadir}/applications/multimc.desktop +mkdir -p %{buildroot}/%{_metainfodir} +install -m 0644 ../ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml %{buildroot}/%{_metainfodir}/multimc.metainfo.xml + +%files +%dir /opt/multimc +/opt/multimc/icon.svg +/opt/multimc/run.sh +%{_datadir}/applications/multimc.desktop +%{_metainfodir}/multimc.metainfo.xml + + +%changelog +* Tue Jun 01 2021 kb1000 <fedora@kb1000.de> - 1.4-2 +- Add xrandr to the dependencies + +* Tue Dec 08 00:34:35 CET 2020 joshua-stone <joshua.gage.stone@gmail.com> +- Add metainfo.xml for improving package metadata + +* Wed Nov 25 22:53:59 CET 2020 kb1000 <fedora@kb1000.de> +- Initial version of the RPM package, based on the Ubuntu package diff --git a/application/package/rpm/README.md b/application/package/rpm/README.md new file mode 100644 index 00000000..0c2b1e49 --- /dev/null +++ b/application/package/rpm/README.md @@ -0,0 +1,12 @@ +# What is this? +A simple RPM package for MultiMC that contains a script that downloads and installs real MultiMC on Red Hat based systems. + +It contains a `.desktop` file, a `.metainfo.xml` file, an icon, and a simple script that does the heavy lifting. + +# How to build this? +You need the `rpm-build` package. Switch into this directory, then run: +``` +rpmbuild --build-in-place -bb MultiMC5.spec +``` + +Replace the version with whatever is appropriate. diff --git a/application/package/ubuntu/README.md b/application/package/ubuntu/README.md new file mode 100644 index 00000000..892abd12 --- /dev/null +++ b/application/package/ubuntu/README.md @@ -0,0 +1,14 @@ +# What is this? +A simple Ubuntu package for MultiMC that contains a script that downloads and installs real MultiMC on Ubuntu based systems. + +It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting. + +This is also the source for the files in the [RPM package](../rpm). If you rename, create or delete files here, you'll likely also have to update the RPM spec file there. + +# How to build this? +You need dpkg utils. Rename the `multimc` folder to `multimc_1.5-1` and then run: +``` +fakeroot dpkg-deb --build multimc_1.5-1 +``` + +Replace the version with whatever is appropriate. diff --git a/application/package/ubuntu/multimc/DEBIAN/control b/application/package/ubuntu/multimc/DEBIAN/control index 9386e945..3e0f570c 100644 --- a/application/package/ubuntu/multimc/DEBIAN/control +++ b/application/package/ubuntu/multimc/DEBIAN/control @@ -1,11 +1,11 @@ Package: multimc -Version: 1.4-1 +Version: 1.5-1 Architecture: all Maintainer: Petr Mrázek <peterix@gmail.com> Section: games Priority: optional Installed-Size: 75 -Depends: zenity, desktop-file-utils, qt5-default, wget +Depends: zenity, desktop-file-utils, libqt5widgets5, libqt5gui5, libqt5network5, libqt5core5a, libqt5xml5, libqt5concurrent5, wget Recommends: openjdk-8-jre Homepage: http://multimc.org Description: A local install wrapper for MultiMC diff --git a/application/package/ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml b/application/package/ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml new file mode 100644 index 00000000..4c6b7450 --- /dev/null +++ b/application/package/ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<component type="desktop"> + <id>multimc</id> + <launchable type="desktop-id">multimc.desktop</launchable> + <name>MultiMC</name> + <summary>Manage Minecraft instances with ease</summary> + <description> + <p>Overview</p> + <p>MultiMC is a free, open source launcher for Minecraft. It allows you to have multiple, cleanly separated instances of Minecraft (each with their own mods, texture packs, saves, etc) and helps you manage them and their associated options with a simple and powerful interface.</p> + <p>Features</p> + <ul> + <li>Manage multiple instances of Minecraft at once</li> + <li>Start Minecraft with a custom resolution</li> + <li>Change Java's runtime options (including memory options)</li> + <li>Shows Minecraft's console output in a colour coded window</li> + <li>Kill Minecraft easily if it crashes / freezes</li> + <li>Custom icons and groups for instances</li> + <li>Forge integration (automatic installation, version downloads, mod management)</li> + <li>Minecraft world management</li> + <li>Import and export Minecraft instances to share them with anyone</li> + <li>Supports every version of Minecraft that the vanilla launcher does</li> + </ul> + </description> + <screenshots> + <screenshot type="default"> + <image type="source" width="936" height="921">https://multimc.org/images/screenshots/main.png</image> + </screenshot> + <screenshot> + <image type="source" width="936" height="998">https://multimc.org/images/screenshots/editmods.png</image> + </screenshot> + <screenshot> + <image type="source" width="936" height="998">https://multimc.org/images/screenshots/version.png</image> + </screenshot> + <screenshot> + <image type="source" width="936" height="998">https://multimc.org/images/screenshots/console.png</image> + </screenshot> + <screenshot> + <image type="source" width="936" height="921">https://multimc.org/images/screenshots/settings.png</image> + </screenshot> + </screenshots> + <releases> + <release date="2021-01-07" version="5"/> + </releases> + <url type="homepage">https://multimc.org/</url> + <url type="help">https://discord.com/invite/0k2zsXGNHs0fE4Wm</url> + <url type="faq">https://github.com/MultiMC/MultiMC5/wiki/FAQ</url> + <url type="bugtracker">https://github.com/MultiMC/MultiMC5/issues</url> + <url type="translate">https://translate.multimc.org/</url> + <url type="donation">https://www.patreon.com/multimc</url> + <developer_name>The MultiMC Team</developer_name> + <metadata_license>CC0-1.0</metadata_license> + <project_license>Apache-2.0</project_license> + <update_contact>peterix_at_gmail.com</update_contact> +</component> diff --git a/application/package/ubuntu/readme.md b/application/package/ubuntu/readme.md deleted file mode 100644 index 5b0d6b27..00000000 --- a/application/package/ubuntu/readme.md +++ /dev/null @@ -1,12 +0,0 @@ -# What is this? -A simple ubuntu package for MultiMC that wraps the contains a script that downloads and installs real MultiMC on ubuntu based systems. - -It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting. - -# How to build this? -You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run: -``` -fakeroot dpkg-deb --build multimc_1.3-1 -``` - -Replace the version with whatever is appropriate. diff --git a/application/pagedialog/PageDialog.cpp b/application/pagedialog/PageDialog.cpp index a1a78d78..fd5d36d4 100644 --- a/application/pagedialog/PageDialog.cpp +++ b/application/pagedialog/PageDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pagedialog/PageDialog.h b/application/pagedialog/PageDialog.h index b92d90b5..1029bc30 100644 --- a/application/pagedialog/PageDialog.h +++ b/application/pagedialog/PageDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/BasePage.h b/application/pages/BasePage.h index 2ad56d71..408965d0 100644 --- a/application/pages/BasePage.h +++ b/application/pages/BasePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/BasePageProvider.h b/application/pages/BasePageProvider.h index ad29f163..7bfaaf3b 100644 --- a/application/pages/BasePageProvider.h +++ b/application/pages/BasePageProvider.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/AccountListPage.cpp b/application/pages/global/AccountListPage.cpp index 3c900fab..ff3736ed 100644 --- a/application/pages/global/AccountListPage.cpp +++ b/application/pages/global/AccountListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -30,6 +30,7 @@ #include "dialogs/SkinUploadDialog.h" #include "tasks/Task.h" #include "minecraft/auth/YggdrasilTask.h" +#include "minecraft/services/SkinDelete.h" #include "MultiMC.h" @@ -142,6 +143,7 @@ void AccountListPage::updateButtonStates() ui->actionRemove->setEnabled(selection.size() > 0); ui->actionSetDefault->setEnabled(selection.size() > 0); ui->actionUploadSkin->setEnabled(selection.size() > 0); + ui->actionDeleteSkin->setEnabled(selection.size() > 0); if(m_accounts->activeAccount().get() == nullptr) { ui->actionNoDefault->setEnabled(false); @@ -191,3 +193,25 @@ void AccountListPage::on_actionUploadSkin_triggered() dialog.exec(); } } + +void AccountListPage::on_actionDeleteSkin_triggered() +{ + QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.size() <= 0) + return; + + QModelIndex selected = selection.first(); + AuthSessionPtr session = std::make_shared<AuthSession>(); + MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>(); + auto login = account->login(session); + ProgressDialog prog(this); + if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted) { + CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to login!"), QMessageBox::Warning)->exec(); + return; + } + auto deleteSkinTask = std::make_shared<SkinDelete>(this, session); + if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) { + CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec(); + return; + } +} diff --git a/application/pages/global/AccountListPage.h b/application/pages/global/AccountListPage.h index 4c8bc00b..fba1833f 100644 --- a/application/pages/global/AccountListPage.h +++ b/application/pages/global/AccountListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -59,35 +59,26 @@ public: return "Getting-Started#adding-an-account"; } -private: - void changeEvent(QEvent * event) override; - QMenu * createPopupMenu() override; - -public -slots: +public slots: void on_actionAdd_triggered(); - void on_actionRemove_triggered(); - void on_actionSetDefault_triggered(); - void on_actionNoDefault_triggered(); - void on_actionUploadSkin_triggered(); + void on_actionDeleteSkin_triggered(); void listChanged(); //! Updates the states of the dialog's buttons. void updateButtonStates(); -protected: - std::shared_ptr<MojangAccountList> m_accounts; - -protected -slots: +protected slots: void ShowContextMenu(const QPoint &pos); void addAccount(const QString& errMsg=""); private: + void changeEvent(QEvent * event) override; + QMenu * createPopupMenu() override; + std::shared_ptr<MojangAccountList> m_accounts; Ui::AccountListPage *ui; }; diff --git a/application/pages/global/AccountListPage.ui b/application/pages/global/AccountListPage.ui index ba07445e..71647db3 100644 --- a/application/pages/global/AccountListPage.ui +++ b/application/pages/global/AccountListPage.ui @@ -40,7 +40,9 @@ <addaction name="actionRemove"/> <addaction name="actionSetDefault"/> <addaction name="actionNoDefault"/> + <addaction name="separator"/> <addaction name="actionUploadSkin"/> + <addaction name="actionDeleteSkin"/> </widget> <action name="actionAdd"> <property name="text"> @@ -70,6 +72,14 @@ <string>Upload Skin</string> </property> </action> + <action name="actionDeleteSkin"> + <property name="text"> + <string>Delete Skin</string> + </property> + <property name="toolTip"> + <string>Delete the currently active skin and go back to the default one</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/application/pages/global/CustomCommandsPage.h b/application/pages/global/CustomCommandsPage.h index a9047e63..414c3259 100644 --- a/application/pages/global/CustomCommandsPage.h +++ b/application/pages/global/CustomCommandsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2019 MultiMC Contributors +/* Copyright 2018-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. diff --git a/application/pages/global/ExternalToolsPage.cpp b/application/pages/global/ExternalToolsPage.cpp index 07670264..6a0a38be 100644 --- a/application/pages/global/ExternalToolsPage.cpp +++ b/application/pages/global/ExternalToolsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/ExternalToolsPage.h b/application/pages/global/ExternalToolsPage.h index 06e64273..0fc8ebe1 100644 --- a/application/pages/global/ExternalToolsPage.h +++ b/application/pages/global/ExternalToolsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/JavaPage.cpp b/application/pages/global/JavaPage.cpp index 71c5f803..cde0e035 100644 --- a/application/pages/global/JavaPage.cpp +++ b/application/pages/global/JavaPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -37,8 +37,8 @@ JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage) ui->setupUi(this); ui->tabWidget->tabBar()->hide(); - auto sysMB = Sys::getSystemRam() / Sys::megabyte; - ui->maxMemSpinBox->setMaximum(sysMB); + auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; + ui->maxMemSpinBox->setMaximum(sysMiB); loadSettings(); } diff --git a/application/pages/global/JavaPage.h b/application/pages/global/JavaPage.h index 3efebb78..832f460b 100644 --- a/application/pages/global/JavaPage.h +++ b/application/pages/global/JavaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/JavaPage.ui b/application/pages/global/JavaPage.ui index 201b310c..b67e9994 100644 --- a/application/pages/global/JavaPage.ui +++ b/application/pages/global/JavaPage.ui @@ -51,7 +51,7 @@ <string>The maximum amount of memory Minecraft is allowed to use.</string> </property> <property name="suffix"> - <string notr="true"> MB</string> + <string notr="true"> MiB</string> </property> <property name="minimum"> <number>128</number> @@ -87,7 +87,7 @@ <string>The amount of memory Minecraft is started with.</string> </property> <property name="suffix"> - <string notr="true"> MB</string> + <string notr="true"> MiB</string> </property> <property name="minimum"> <number>128</number> @@ -116,7 +116,7 @@ <string>The amount of memory available to store loaded Java classes.</string> </property> <property name="suffix"> - <string notr="true"> MB</string> + <string notr="true"> MiB</string> </property> <property name="minimum"> <number>64</number> diff --git a/application/pages/global/LanguagePage.h b/application/pages/global/LanguagePage.h index c4df2ea9..ca8eecc6 100644 --- a/application/pages/global/LanguagePage.h +++ b/application/pages/global/LanguagePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/MinecraftPage.cpp b/application/pages/global/MinecraftPage.cpp index 1d7042ad..6c9bd307 100644 --- a/application/pages/global/MinecraftPage.cpp +++ b/application/pages/global/MinecraftPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -63,6 +63,14 @@ void MinecraftPage::applySettings() s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked()); s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value()); s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); + + // Native library workarounds + s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); + s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); + + // Game time + s->set("ShowGameTime", ui->showGameTime->isChecked()); + s->set("RecordGameTime", ui->recordGameTime->isChecked()); } void MinecraftPage::loadSettings() @@ -73,4 +81,10 @@ void MinecraftPage::loadSettings() ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool()); ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); + + ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); + ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool()); + + ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); + ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); } diff --git a/application/pages/global/MinecraftPage.h b/application/pages/global/MinecraftPage.h index 39265fbe..5e781aed 100644 --- a/application/pages/global/MinecraftPage.h +++ b/application/pages/global/MinecraftPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/MinecraftPage.ui b/application/pages/global/MinecraftPage.ui index 9a18927a..2abd4bd4 100644 --- a/application/pages/global/MinecraftPage.ui +++ b/application/pages/global/MinecraftPage.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>545</width> - <height>195</height> + <width>936</width> + <height>1134</height> </rect> </property> <property name="sizePolicy"> @@ -112,6 +112,52 @@ </widget> </item> <item> + <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox"> + <property name="title"> + <string>Native library workarounds</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <widget class="QCheckBox" name="useNativeGLFWCheck"> + <property name="text"> + <string>Use system installation of GLFW</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="useNativeOpenALCheck"> + <property name="text"> + <string>Use system installation of OpenAL</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="gameTimeGroupBox"> + <property name="title"> + <string>Game time</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <widget class="QCheckBox" name="showGameTime"> + <property name="text"> + <string>Show time spent playing instances</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="recordGameTime"> + <property name="text"> + <string>Record time spent playing instances</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <spacer name="verticalSpacerMinecraft"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -135,6 +181,8 @@ <tabstop>maximizedCheckBox</tabstop> <tabstop>windowWidthSpinBox</tabstop> <tabstop>windowHeightSpinBox</tabstop> + <tabstop>useNativeGLFWCheck</tabstop> + <tabstop>useNativeOpenALCheck</tabstop> </tabstops> <resources/> <connections/> diff --git a/application/pages/global/MultiMCPage.cpp b/application/pages/global/MultiMCPage.cpp index 3a8c46e8..80d5c544 100644 --- a/application/pages/global/MultiMCPage.cpp +++ b/application/pages/global/MultiMCPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/MultiMCPage.h b/application/pages/global/MultiMCPage.h index 27a801be..e81832eb 100644 --- a/application/pages/global/MultiMCPage.h +++ b/application/pages/global/MultiMCPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/PasteEEPage.cpp b/application/pages/global/PasteEEPage.cpp index ba4885e8..f932dede 100644 --- a/application/pages/global/PasteEEPage.cpp +++ b/application/pages/global/PasteEEPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/PasteEEPage.h b/application/pages/global/PasteEEPage.h index 78cf41f4..001decdb 100644 --- a/application/pages/global/PasteEEPage.h +++ b/application/pages/global/PasteEEPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/ProxyPage.cpp b/application/pages/global/ProxyPage.cpp index 3f0e766b..809059ff 100644 --- a/application/pages/global/ProxyPage.cpp +++ b/application/pages/global/ProxyPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/global/ProxyPage.h b/application/pages/global/ProxyPage.h index d87bc120..ff94ec49 100644 --- a/application/pages/global/ProxyPage.h +++ b/application/pages/global/ProxyPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/GameOptionsPage.cpp b/application/pages/instance/GameOptionsPage.cpp index 5555fc79..782f2ab3 100644 --- a/application/pages/instance/GameOptionsPage.cpp +++ b/application/pages/instance/GameOptionsPage.cpp @@ -35,6 +35,3 @@ void GameOptionsPage::closedImpl() { // m_model->unobserve(); } - -#include "GameOptionsPage.moc" - diff --git a/application/pages/instance/GameOptionsPage.h b/application/pages/instance/GameOptionsPage.h index ae47747f..0fd2fbff 100644 --- a/application/pages/instance/GameOptionsPage.h +++ b/application/pages/instance/GameOptionsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/InstanceSettingsPage.cpp b/application/pages/instance/InstanceSettingsPage.cpp index b7b0a863..7bd424c0 100644 --- a/application/pages/instance/InstanceSettingsPage.cpp +++ b/application/pages/instance/InstanceSettingsPage.cpp @@ -19,7 +19,7 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) { m_settings = inst->settings(); ui->setupUi(this); - auto sysMB = Sys::getSystemRam() / Sys::megabyte; + auto sysMB = Sys::getSystemRam() / Sys::mebibyte; ui->maxMemSpinBox->setMaximum(sysMB); connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); connect(MMC, &MultiMC::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); @@ -163,6 +163,46 @@ void InstanceSettingsPage::applySettings() m_settings->reset("WrapperCommand"); m_settings->reset("PostExitCommand"); } + + // Workarounds + bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked(); + m_settings->set("OverrideNativeWorkarounds", workarounds); + if(workarounds) + { + m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); + m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); + } + else + { + m_settings->reset("UseNativeOpenAL"); + m_settings->reset("UseNativeGLFW"); + } + + // Game time + bool gameTime = ui->gameTimeGroupBox->isChecked(); + m_settings->set("OverrideGameTime", gameTime); + if (gameTime) + { + m_settings->set("ShowGameTime", ui->showGameTime->isChecked()); + m_settings->set("RecordGameTime", ui->recordGameTime->isChecked()); + } + else + { + m_settings->reset("ShowGameTime"); + m_settings->reset("RecordGameTime"); + } + + // Join server on launch + bool joinServerOnLaunch = ui->serverJoinGroupBox->isChecked(); + m_settings->set("JoinServerOnLaunch", joinServerOnLaunch); + if (joinServerOnLaunch) + { + m_settings->set("JoinServerOnLaunchAddress", ui->serverJoinAddress->text()); + } + else + { + m_settings->reset("JoinServerOnLaunchAddress"); + } } void InstanceSettingsPage::loadSettings() @@ -219,6 +259,19 @@ void InstanceSettingsPage::loadSettings() m_settings->get("WrapperCommand").toString(), m_settings->get("PostExitCommand").toString() ); + + // Workarounds + ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool()); + ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool()); + ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool()); + + // Miscellanous + ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool()); + ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool()); + ui->recordGameTime->setChecked(m_settings->get("RecordGameTime").toBool()); + + ui->serverJoinGroupBox->setChecked(m_settings->get("JoinServerOnLaunch").toBool()); + ui->serverJoinAddress->setText(m_settings->get("JoinServerOnLaunchAddress").toString()); } void InstanceSettingsPage::on_javaDetectBtn_clicked() diff --git a/application/pages/instance/InstanceSettingsPage.h b/application/pages/instance/InstanceSettingsPage.h index c3c78fd5..068213a8 100644 --- a/application/pages/instance/InstanceSettingsPage.h +++ b/application/pages/instance/InstanceSettingsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/InstanceSettingsPage.ui b/application/pages/instance/InstanceSettingsPage.ui index d6de53ee..e569ce56 100644 --- a/application/pages/instance/InstanceSettingsPage.ui +++ b/application/pages/instance/InstanceSettingsPage.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>738</width> - <height>804</height> + <width>691</width> + <height>581</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -116,7 +116,7 @@ <string>The maximum amount of memory Minecraft is allowed to use.</string> </property> <property name="suffix"> - <string notr="true"> MB</string> + <string notr="true"> MiB</string> </property> <property name="minimum"> <number>128</number> @@ -138,7 +138,7 @@ <string>The amount of memory Minecraft is started with.</string> </property> <property name="suffix"> - <string notr="true"> MB</string> + <string notr="true"> MiB</string> </property> <property name="minimum"> <number>128</number> @@ -160,7 +160,7 @@ <string>The amount of memory available to store loaded Java classes.</string> </property> <property name="suffix"> - <string notr="true"> MB</string> + <string notr="true"> MiB</string> </property> <property name="minimum"> <number>64</number> @@ -364,6 +364,145 @@ </item> </layout> </widget> + <widget class="QWidget" name="workaroundsPage"> + <attribute name="title"> + <string>Workarounds</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_8"> + <item> + <widget class="QGroupBox" name="nativeWorkaroundsGroupBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>Native libraries</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_7"> + <item> + <widget class="QCheckBox" name="useNativeGLFWCheck"> + <property name="text"> + <string>Use system installation of GLFW</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="useNativeOpenALCheck"> + <property name="text"> + <string>Use system installation of OpenAL</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QWidget" name="miscellanousPage"> + <attribute name="title"> + <string>Miscellanous</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_9"> + <item> + <widget class="QGroupBox" name="gameTimeGroupBox"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>Override global game time settings</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_10"> + <item> + <widget class="QCheckBox" name="showGameTime"> + <property name="text"> + <string>Show time spent playing this instance</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="recordGameTime"> + <property name="text"> + <string>Record time spent playing this instance</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="serverJoinGroupBox"> + <property name="title"> + <string>Set a server to join on launch</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_11"> + <item> + <layout class="QGridLayout" name="serverJoinLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="serverJoinAddressLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Server address:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="serverJoinAddress"/> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacerMiscellanous"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </widget> </item> </layout> @@ -398,6 +537,11 @@ <tabstop>showConsoleCheck</tabstop> <tabstop>autoCloseConsoleCheck</tabstop> <tabstop>showConsoleErrorCheck</tabstop> + <tabstop>nativeWorkaroundsGroupBox</tabstop> + <tabstop>useNativeGLFWCheck</tabstop> + <tabstop>useNativeOpenALCheck</tabstop> + <tabstop>showGameTime</tabstop> + <tabstop>recordGameTime</tabstop> </tabstops> <resources/> <connections/> diff --git a/application/pages/instance/LegacyUpgradePage.h b/application/pages/instance/LegacyUpgradePage.h index 4f86a721..df34e33a 100644 --- a/application/pages/instance/LegacyUpgradePage.h +++ b/application/pages/instance/LegacyUpgradePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/LogPage.cpp b/application/pages/instance/LogPage.cpp index 94ada424..3d2085c6 100644 --- a/application/pages/instance/LogPage.cpp +++ b/application/pages/instance/LogPage.cpp @@ -236,15 +236,15 @@ void LogPage::on_btnPaste_clicked() return; //FIXME: turn this into a proper task and move the upload logic out of GuiUtil! - m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); + m_model->append(MessageLevel::MultiMC, QString("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))); auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this); if(!url.isEmpty()) { - m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url)); + m_model->append(MessageLevel::MultiMC, QString("MultiMC: Log uploaded to: %1").arg(url)); } else { - m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!")); + m_model->append(MessageLevel::Error, "MultiMC: Log upload failed!"); } } diff --git a/application/pages/instance/LogPage.h b/application/pages/instance/LogPage.h index 9c3b56a9..b0b0e04b 100644 --- a/application/pages/instance/LogPage.h +++ b/application/pages/instance/LogPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp index 2981ea13..98f20e77 100644 --- a/application/pages/instance/ModFolderPage.cpp +++ b/application/pages/instance/ModFolderPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -163,7 +163,7 @@ ModFolderPage::ModFolderPage( auto smodel = ui->modTreeView->selectionModel(); connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged ); + connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged); connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed); } diff --git a/application/pages/instance/ModFolderPage.h b/application/pages/instance/ModFolderPage.h index d49d25c3..f653a8c0 100644 --- a/application/pages/instance/ModFolderPage.h +++ b/application/pages/instance/ModFolderPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/NotesPage.h b/application/pages/instance/NotesPage.h index f96e5374..d0c00ac1 100644 --- a/application/pages/instance/NotesPage.h +++ b/application/pages/instance/NotesPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/OtherLogsPage.cpp b/application/pages/instance/OtherLogsPage.cpp index 387db74f..b67b84bd 100644 --- a/application/pages/instance/OtherLogsPage.cpp +++ b/application/pages/instance/OtherLogsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/OtherLogsPage.h b/application/pages/instance/OtherLogsPage.h index 226f3af3..7f21c0fa 100644 --- a/application/pages/instance/OtherLogsPage.h +++ b/application/pages/instance/OtherLogsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/ResourcePackPage.h b/application/pages/instance/ResourcePackPage.h index e11c78a3..1486bf52 100644 --- a/application/pages/instance/ResourcePackPage.h +++ b/application/pages/instance/ResourcePackPage.h @@ -1,4 +1,5 @@ #pragma once + #include "ModFolderPage.h" #include "ui_ModFolderPage.h" @@ -12,8 +13,8 @@ public: { ui->actionView_configs->setVisible(false); } - virtual ~ResourcePackPage() {} + virtual bool shouldDisplay() const override { return !m_inst->traits().contains("no-texturepacks") && diff --git a/application/pages/instance/ScreenshotsPage.h b/application/pages/instance/ScreenshotsPage.h index 9adf79af..03a809de 100644 --- a/application/pages/instance/ScreenshotsPage.h +++ b/application/pages/instance/ScreenshotsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/instance/ServersPage.cpp b/application/pages/instance/ServersPage.cpp index 8b0c655c..d63c6e70 100644 --- a/application/pages/instance/ServersPage.cpp +++ b/application/pages/instance/ServersPage.cpp @@ -556,7 +556,7 @@ private: QTimer m_saveTimer; }; -ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent) +ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::ServersPage) { ui->setupUi(this); @@ -579,7 +579,7 @@ ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent) auto selectionModel = ui->serversView->selectionModel(); connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged); - connect(m_inst, &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed); + connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed); connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited); connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited); connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int))); @@ -695,6 +695,7 @@ void ServersPage::updateState() ui->actionMove_Down->setEnabled(serverEditEnabled); ui->actionMove_Up->setEnabled(serverEditEnabled); ui->actionRemove->setEnabled(serverEditEnabled); + ui->actionJoin->setEnabled(serverEditEnabled); if(server) { @@ -758,4 +759,10 @@ void ServersPage::on_actionMove_Down_triggered() } } +void ServersPage::on_actionJoin_triggered() +{ + const auto &address = m_model->at(currentServer)->m_address; + MMC->launch(m_inst, true, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address))); +} + #include "ServersPage.moc" diff --git a/application/pages/instance/ServersPage.h b/application/pages/instance/ServersPage.h index c81f47be..8c5b7eb8 100644 --- a/application/pages/instance/ServersPage.h +++ b/application/pages/instance/ServersPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -35,7 +35,7 @@ class ServersPage : public QMainWindow, public BasePage Q_OBJECT public: - explicit ServersPage(MinecraftInstance *inst, QWidget *parent = 0); + explicit ServersPage(InstancePtr inst, QWidget *parent = 0); virtual ~ServersPage(); void openedImpl() override; @@ -74,6 +74,7 @@ private slots: void on_actionRemove_triggered(); void on_actionMove_Up_triggered(); void on_actionMove_Down_triggered(); + void on_actionJoin_triggered(); void on_RunningState_changed(bool running); @@ -88,6 +89,6 @@ private: // data bool m_locked = true; Ui::ServersPage *ui = nullptr; ServersModel * m_model = nullptr; - MinecraftInstance * m_inst = nullptr; + InstancePtr m_inst = nullptr; }; diff --git a/application/pages/instance/ServersPage.ui b/application/pages/instance/ServersPage.ui index e9518e35..d89b7cba 100644 --- a/application/pages/instance/ServersPage.ui +++ b/application/pages/instance/ServersPage.ui @@ -148,6 +148,7 @@ <addaction name="actionRemove"/> <addaction name="actionMove_Up"/> <addaction name="actionMove_Down"/> + <addaction name="actionJoin"/> </widget> <action name="actionAdd"> <property name="text"> @@ -169,6 +170,11 @@ <string>Move Down</string> </property> </action> + <action name="actionJoin"> + <property name="text"> + <string>Join</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/application/pages/instance/TexturePackPage.h b/application/pages/instance/TexturePackPage.h index a792ba07..3f04997d 100644 --- a/application/pages/instance/TexturePackPage.h +++ b/application/pages/instance/TexturePackPage.h @@ -1,4 +1,5 @@ #pragma once + #include "ModFolderPage.h" #include "ui_ModFolderPage.h" @@ -13,6 +14,7 @@ public: ui->actionView_configs->setVisible(false); } virtual ~TexturePackPage() {} + virtual bool shouldDisplay() const override { return m_inst->traits().contains("texturepacks"); diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp index f2d19f25..eff12c9c 100644 --- a/application/pages/instance/VersionPage.cpp +++ b/application/pages/instance/VersionPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -120,7 +120,15 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) auto proxy = new IconProxy(ui->packageView); proxy->setSourceModel(m_profile.get()); - ui->packageView->setModel(proxy); + + m_filterModel = new QSortFilterProxyModel(); + m_filterModel->setDynamicSortFilter(true); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSourceModel(proxy); + m_filterModel->setFilterKeyColumn(-1); + + ui->packageView->setModel(m_filterModel); ui->packageView->installEventFilter(this); ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu); @@ -134,7 +142,8 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) updateVersionControls(); preselect(0); connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus); - connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::ShowContextMenu); + connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu); + connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged); } VersionPage::~VersionPage() @@ -142,7 +151,7 @@ VersionPage::~VersionPage() delete ui; } -void VersionPage::ShowContextMenu(const QPoint& pos) +void VersionPage::showContextMenu(const QPoint& pos) { auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); menu->exec(ui->packageView->mapToGlobal(pos)); @@ -203,11 +212,11 @@ void VersionPage::updateVersionControls() { // FIXME: this is a dirty hack auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft")); - bool newCraft = controlsEnabled && (minecraftVersion >= Version("1.14")); - bool oldCraft = controlsEnabled && (minecraftVersion <= Version("1.12.2")); - ui->actionInstall_Fabric->setEnabled(newCraft); - ui->actionInstall_Forge->setEnabled(true); - ui->actionInstall_LiteLoader->setEnabled(oldCraft); + bool newCraft = minecraftVersion >= Version("1.14"); + bool oldCraft = minecraftVersion <= Version("1.12.2"); + ui->actionInstall_Fabric->setEnabled(controlsEnabled && newCraft); + ui->actionInstall_Forge->setEnabled(controlsEnabled); + ui->actionInstall_LiteLoader->setEnabled(controlsEnabled && oldCraft); ui->actionReload->setEnabled(true); updateButtons(); } @@ -620,5 +629,10 @@ void VersionPage::on_actionRevert_triggered() m_container->refreshContainer(); } +void VersionPage::onFilterTextChanged(const QString &newContents) +{ + m_filterModel->setFilterFixedString(newContents); +} + #include "VersionPage.moc" diff --git a/application/pages/instance/VersionPage.h b/application/pages/instance/VersionPage.h index c95a0084..b5b4a6f5 100644 --- a/application/pages/instance/VersionPage.h +++ b/application/pages/instance/VersionPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -86,6 +86,7 @@ protected: private: Ui::VersionPage *ui; + QSortFilterProxyModel *m_filterModel; std::shared_ptr<PackProfile> m_profile; MinecraftInstance *m_inst; int currentIdx = 0; @@ -98,5 +99,6 @@ private slots: void updateRunningStatus(bool running); void onGameUpdateError(QString error); void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); - void ShowContextMenu(const QPoint &pos); + void showContextMenu(const QPoint &pos); + void onFilterTextChanged(const QString & newContents); }; diff --git a/application/pages/instance/VersionPage.ui b/application/pages/instance/VersionPage.ui index 718ad067..84d06e2e 100644 --- a/application/pages/instance/VersionPage.ui +++ b/application/pages/instance/VersionPage.ui @@ -46,6 +46,24 @@ </widget> </item> <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1"> + <widget class="QLineEdit" name="filterEdit"> + <property name="clearButtonEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="filterLabel"> + <property name="text"> + <string>Filter:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> <widget class="MCModInfoFrame" name="frame"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> diff --git a/application/pages/instance/WorldListPage.cpp b/application/pages/instance/WorldListPage.cpp index 8358a0f1..119cff3e 100644 --- a/application/pages/instance/WorldListPage.cpp +++ b/application/pages/instance/WorldListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2019 MultiMC Contributors +/* Copyright 2015-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. @@ -31,6 +31,33 @@ #include <QProcess> #include <FileSystem.h> +class WorldListProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + WorldListProxyModel(QObject *parent) : QSortFilterProxyModel(parent) {} + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + QModelIndex sourceIndex = mapToSource(index); + + if (index.column() == 0 && role == Qt::DecorationRole) + { + WorldList *worlds = qobject_cast<WorldList *>(sourceModel()); + auto iconFile = worlds->data(sourceIndex, WorldList::IconFileRole).toString(); + if(iconFile.isNull()) { + // NOTE: Minecraft uses the same placeholder for servers AND worlds + return MMC->getThemedIcon("unknown_server"); + } + return QIcon(iconFile); + } + + return sourceIndex.data(role); + } +}; + + WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QWidget *parent) : QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds) { @@ -38,13 +65,14 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worl ui->toolBar->insertSpacer(ui->actionRefresh); - QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this); + WorldListProxyModel * proxy = new WorldListProxyModel(this); proxy->setSortCaseSensitivity(Qt::CaseInsensitive); proxy->setSourceModel(m_worlds.get()); ui->worldTreeView->setSortingEnabled(true); ui->worldTreeView->setModel(proxy); ui->worldTreeView->installEventFilter(this); ui->worldTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + ui->worldTreeView->setIconSize(QSize(64,64)); connect(ui->worldTreeView, &QTreeView::customContextMenuRequested, this, &WorldListPage::ShowContextMenu); auto head = ui->worldTreeView->header(); @@ -142,6 +170,37 @@ void WorldListPage::on_actionView_Folder_triggered() DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true); } +void WorldListPage::on_actionDatapacks_triggered() +{ + QModelIndex index = getSelectedWorld(); + + if (!index.isValid()) + { + return; + } + + if(!worldSafetyNagQuestion()) + return; + + auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); + + DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true); +} + + +void WorldListPage::on_actionReset_Icon_triggered() +{ + auto proxiedIndex = getSelectedWorld(); + + if(!proxiedIndex.isValid()) + return; + + if(m_worlds->resetIcon(proxiedIndex.row())) { + ui->actionReset_Icon->setEnabled(false); + } +} + + QModelIndex WorldListPage::getSelectedWorld() { auto index = ui->worldTreeView->selectionModel()->currentIndex(); @@ -255,6 +314,9 @@ void WorldListPage::worldChanged(const QModelIndex ¤t, const QModelIndex & ui->actionRemove->setEnabled(enable); ui->actionCopy->setEnabled(enable); ui->actionRename->setEnabled(enable); + ui->actionDatapacks->setEnabled(enable); + bool hasIcon = !index.data(WorldList::IconFileRole).isNull(); + ui->actionReset_Icon->setEnabled(enable && hasIcon); } void WorldListPage::on_actionAdd_triggered() @@ -342,3 +404,5 @@ void WorldListPage::on_actionRefresh_triggered() { m_worlds->update(); } + +#include "WorldListPage.moc" diff --git a/application/pages/instance/WorldListPage.h b/application/pages/instance/WorldListPage.h index c39420da..4fc9aa09 100644 --- a/application/pages/instance/WorldListPage.h +++ b/application/pages/instance/WorldListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2019 MultiMC Contributors +/* Copyright 2015-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. @@ -90,6 +90,8 @@ private slots: void on_actionRename_triggered(); void on_actionRefresh_triggered(); void on_actionView_Folder_triggered(); + void on_actionDatapacks_triggered(); + void on_actionReset_Icon_triggered(); void worldChanged(const QModelIndex ¤t, const QModelIndex &previous); void mceditState(LoggedProcess::State state); diff --git a/application/pages/instance/WorldListPage.ui b/application/pages/instance/WorldListPage.ui index ddb3dfa9..ed078d94 100644 --- a/application/pages/instance/WorldListPage.ui +++ b/application/pages/instance/WorldListPage.ui @@ -41,6 +41,12 @@ <property name="alternatingRowColors"> <bool>true</bool> </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> <property name="sortingEnabled"> <bool>true</bool> </property> @@ -79,6 +85,8 @@ <addaction name="actionCopy"/> <addaction name="actionRemove"/> <addaction name="actionMCEdit"/> + <addaction name="actionDatapacks"/> + <addaction name="actionReset_Icon"/> <addaction name="separator"/> <addaction name="actionCopy_Seed"/> <addaction name="actionRefresh"/> @@ -124,6 +132,22 @@ <string>View Folder</string> </property> </action> + <action name="actionReset_Icon"> + <property name="text"> + <string>Reset Icon</string> + </property> + <property name="toolTip"> + <string>Remove world icon to make the game re-generate it on next load.</string> + </property> + </action> + <action name="actionDatapacks"> + <property name="text"> + <string>Datapacks</string> + </property> + <property name="toolTip"> + <string>Manage datapacks inside the world.</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/application/pages/modplatform/ImportPage.cpp b/application/pages/modplatform/ImportPage.cpp index 3910dfda..c2369bdc 100644 --- a/application/pages/modplatform/ImportPage.cpp +++ b/application/pages/modplatform/ImportPage.cpp @@ -71,6 +71,7 @@ void ImportPage::updateState() { QFileInfo fi(url.fileName()); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + dialog->setSuggestedIcon("default"); } } else @@ -83,6 +84,7 @@ void ImportPage::updateState() // hook, line and sinker. QFileInfo fi(url.fileName()); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + dialog->setSuggestedIcon("default"); } } else diff --git a/application/pages/modplatform/ImportPage.h b/application/pages/modplatform/ImportPage.h index 3afb0045..67e3c201 100644 --- a/application/pages/modplatform/ImportPage.h +++ b/application/pages/modplatform/ImportPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/modplatform/VanillaPage.cpp b/application/pages/modplatform/VanillaPage.cpp index 17535f1e..02638315 100644 --- a/application/pages/modplatform/VanillaPage.cpp +++ b/application/pages/modplatform/VanillaPage.cpp @@ -82,10 +82,19 @@ BaseVersionPtr VanillaPage::selectedVersion() const void VanillaPage::suggestCurrent() { - if(m_selectedVersion && isOpened) + if (!isOpened) { - dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + return; } + + if(!m_selectedVersion) + { + dialog->setSuggestedPack(); + return; + } + + dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + dialog->setSuggestedIcon("default"); } void VanillaPage::setSelectedVersion(BaseVersionPtr version) diff --git a/application/pages/modplatform/VanillaPage.h b/application/pages/modplatform/VanillaPage.h index cc77733c..af6fd392 100644 --- a/application/pages/modplatform/VanillaPage.h +++ b/application/pages/modplatform/VanillaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/modplatform/atlauncher/AtlFilterModel.cpp b/application/pages/modplatform/atlauncher/AtlFilterModel.cpp new file mode 100644 index 00000000..b5d8f22b --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlFilterModel.cpp @@ -0,0 +1,81 @@ +#include "AtlFilterModel.h" + +#include <QDebug> + +#include <modplatform/atlauncher/ATLPackIndex.h> +#include <Version.h> +#include <MMCStrings.h> + +namespace Atl { + +FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) +{ + currentSorting = Sorting::ByPopularity; + sortings.insert(tr("Sort by popularity"), Sorting::ByPopularity); + sortings.insert(tr("Sort by name"), Sorting::ByName); + sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion); + + searchTerm = ""; +} + +const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings() +{ + return sortings; +} + +QString FilterModel::translateCurrentSorting() +{ + return sortings.key(currentSorting); +} + +void FilterModel::setSorting(Sorting sorting) +{ + currentSorting = sorting; + invalidate(); +} + +FilterModel::Sorting FilterModel::getCurrentSorting() +{ + return currentSorting; +} + +void FilterModel::setSearchTerm(const QString term) +{ + searchTerm = term.trimmed(); + invalidate(); +} + +bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (searchTerm.isEmpty()) { + return true; + } + + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>(); + return pack.name.contains(searchTerm, Qt::CaseInsensitive); +} + +bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + ATLauncher::IndexedPack leftPack = sourceModel()->data(left, Qt::UserRole).value<ATLauncher::IndexedPack>(); + ATLauncher::IndexedPack rightPack = sourceModel()->data(right, Qt::UserRole).value<ATLauncher::IndexedPack>(); + + if (currentSorting == ByPopularity) { + return leftPack.position > rightPack.position; + } + else if (currentSorting == ByGameVersion) { + Version lv(leftPack.versions.at(0).minecraft); + Version rv(rightPack.versions.at(0).minecraft); + return lv < rv; + } + else if (currentSorting == ByName) { + return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; + } + + // Invalid sorting set, somehow... + qWarning() << "Invalid sorting set!"; + return true; +} + +} diff --git a/application/pages/modplatform/atlauncher/AtlFilterModel.h b/application/pages/modplatform/atlauncher/AtlFilterModel.h new file mode 100644 index 00000000..bd72ad91 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlFilterModel.h @@ -0,0 +1,34 @@ +#pragma once + +#include <QtCore/QSortFilterProxyModel> + +namespace Atl { + +class FilterModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + FilterModel(QObject* parent = Q_NULLPTR); + enum Sorting { + ByPopularity, + ByGameVersion, + ByName, + }; + const QMap<QString, Sorting> getAvailableSortings(); + QString translateCurrentSorting(); + void setSorting(Sorting sorting); + Sorting getCurrentSorting(); + void setSearchTerm(QString term); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + +private: + QMap<QString, Sorting> sortings; + Sorting currentSorting; + QString searchTerm; + +}; + +} diff --git a/application/pages/modplatform/atlauncher/AtlListModel.cpp b/application/pages/modplatform/atlauncher/AtlListModel.cpp new file mode 100644 index 00000000..f3be6198 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlListModel.cpp @@ -0,0 +1,194 @@ +#include "AtlListModel.h" + +#include <BuildConfig.h> +#include <MultiMC.h> +#include <Env.h> +#include <Json.h> + +namespace Atl { + +ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +ListModel::~ListModel() +{ +} + +int ListModel::rowCount(const QModelIndex &parent) const +{ + return modpacks.size(); +} + +int ListModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant ListModel::data(const QModelIndex &index, int role) const +{ + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QString("INVALID INDEX %1").arg(pos); + } + + ATLauncher::IndexedPack pack = modpacks.at(pos); + if(role == Qt::DisplayRole) + { + return pack.name; + } + else if (role == Qt::ToolTipRole) + { + return pack.name; + } + else if(role == Qt::DecorationRole) + { + if(m_logoMap.contains(pack.safeName)) + { + return (m_logoMap.value(pack.safeName)); + } + auto icon = MMC->getThemedIcon("atlauncher-placeholder"); + + auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower()); + ((ListModel *)this)->requestLogo(pack.safeName, url); + + return icon; + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); +} + +void ListModel::request() +{ + beginResetModel(); + modpacks.clear(); + endResetModel(); + + auto *netJob = new NetJob("Atl::Request"); + auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json"); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); + jobPtr = netJob; + jobPtr->start(); + + QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished); + QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed); +} + +void ListModel::requestFinished() +{ + jobPtr.reset(); + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + if(parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + QList<ATLauncher::IndexedPack> newList; + + auto packs = doc.array(); + for(auto packRaw : packs) { + auto packObj = packRaw.toObject(); + + ATLauncher::IndexedPack pack; + + try { + ATLauncher::loadIndexedPack(pack, packObj); + } + catch (const JSONValidationError &e) { + qDebug() << QString::fromUtf8(response); + qWarning() << "Error while reading pack manifest from ATLauncher: " << e.cause(); + return; + } + + // ignore packs without a published version + if(pack.versions.length() == 0) continue; + // only display public packs (for now) + if(pack.type != ATLauncher::PackType::Public) continue; + // ignore "system" packs (Vanilla, Vanilla with Forge, etc) + if(pack.system) continue; + + newList.append(pack); + } + + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void ListModel::requestFailed(QString reason) +{ + jobPtr.reset(); +} + +void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) +{ + if(m_logoMap.contains(logo)) + { + callback(ENV.metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + } + else + { + requestLogo(logo, logoUrl); + } +} + +void ListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +void ListModel::logoLoaded(QString logo, QIcon out) +{ + m_loadingLogos.removeAll(logo); + m_logoMap.insert(logo, out); + + for(int i = 0; i < modpacks.size(); i++) { + if(modpacks[i].safeName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + } + } +} + +void ListModel::requestLogo(QString file, QString url) +{ + if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) + { + return; + } + + MetaEntryPtr entry = ENV.metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0))); + NetJob *job = new NetJob(QString("ATLauncher Icon Download %1").arg(file)); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath] + { + emit logoLoaded(file, QIcon(fullPath)); + if(waitingCallbacks.contains(file)) + { + waitingCallbacks.value(file)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, file] + { + emit logoFailed(file); + }); + + job->start(); + + m_loadingLogos.append(file); +} + +} diff --git a/application/pages/modplatform/atlauncher/AtlListModel.h b/application/pages/modplatform/atlauncher/AtlListModel.h new file mode 100644 index 00000000..2d30a64e --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlListModel.h @@ -0,0 +1,52 @@ +#pragma once + +#include <QAbstractListModel> + +#include "net/NetJob.h" +#include <QIcon> +#include <modplatform/atlauncher/ATLPackIndex.h> + +namespace Atl { + +typedef QMap<QString, QIcon> LogoMap; +typedef std::function<void(QString)> LogoCallback; + +class ListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ListModel(QObject *parent); + virtual ~ListModel(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + void request(); + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + +private slots: + void requestFinished(); + void requestFailed(QString reason); + + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); + +private: + void requestLogo(QString file, QString url); + +private: + QList<ATLauncher::IndexedPack> modpacks; + + QStringList m_failedLogos; + QStringList m_loadingLogos; + LogoMap m_logoMap; + QMap<QString, LogoCallback> waitingCallbacks; + + NetJobPtr jobPtr; + QByteArray response; +}; + +} diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp new file mode 100644 index 00000000..14bbd18b --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -0,0 +1,209 @@ +#include "AtlOptionalModDialog.h" +#include "ui_AtlOptionalModDialog.h" + +AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods) + : QAbstractListModel(parent), m_mods(mods) { + + // fill mod index + for (int i = 0; i < m_mods.size(); i++) { + auto mod = m_mods.at(i); + m_index[mod.name] = i; + } + // set initial state + for (int i = 0; i < m_mods.size(); i++) { + auto mod = m_mods.at(i); + m_selection[mod.name] = false; + setMod(mod, i, mod.selected, false); + } +} + +QVector<QString> AtlOptionalModListModel::getResult() { + QVector<QString> result; + + for (const auto& mod : m_mods) { + if (m_selection[mod.name]) { + result.push_back(mod.name); + } + } + + return result; +} + +int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const { + return m_mods.size(); +} + +int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const { + // Enabled, Name, Description + return 3; +} + +QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const { + auto row = index.row(); + auto mod = m_mods.at(row); + + if (role == Qt::DisplayRole) { + if (index.column() == NameColumn) { + return mod.name; + } + if (index.column() == DescriptionColumn) { + return mod.description; + } + } + else if (role == Qt::ToolTipRole) { + if (index.column() == DescriptionColumn) { + return mod.description; + } + } + else if (role == Qt::CheckStateRole) { + if (index.column() == EnabledColumn) { + return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked; + } + } + + return QVariant(); +} + +bool AtlOptionalModListModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role == Qt::CheckStateRole) { + auto row = index.row(); + auto mod = m_mods.at(row); + + toggleMod(mod, row); + return true; + } + + return false; +} + +QVariant AtlOptionalModListModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + switch (section) { + case EnabledColumn: + return QString(); + case NameColumn: + return QString("Name"); + case DescriptionColumn: + return QString("Description"); + } + } + + return QVariant(); +} + +Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const { + auto flags = QAbstractListModel::flags(index); + if (index.isValid() && index.column() == EnabledColumn) { + flags |= Qt::ItemIsUserCheckable; + } + return flags; +} + +void AtlOptionalModListModel::selectRecommended() { + for (const auto& mod : m_mods) { + m_selection[mod.name] = mod.recommended; + } + + emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), + AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn)); +} + +void AtlOptionalModListModel::clearAll() { + for (const auto& mod : m_mods) { + m_selection[mod.name] = false; + } + + emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), + AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn)); +} + +void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) { + setMod(mod, index, !m_selection[mod.name]); +} + +void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) { + if (m_selection[mod.name] == enable) return; + + m_selection[mod.name] = enable; + + // disable other mods in the group, if applicable + if (enable && !mod.group.isEmpty()) { + for (int i = 0; i < m_mods.size(); i++) { + if (index == i) continue; + auto other = m_mods.at(i); + + if (mod.group == other.group) { + setMod(other, i, false, shouldEmit); + } + } + } + + for (const auto& dependencyName : mod.depends) { + auto dependencyIndex = m_index[dependencyName]; + auto dependencyMod = m_mods.at(dependencyIndex); + + // enable/disable dependencies + if (enable) { + setMod(dependencyMod, dependencyIndex, true, shouldEmit); + } + + // if the dependency is 'effectively hidden', then track which mods + // depend on it - so we can efficiently disable it when no more dependents + // depend on it. + auto dependants = m_dependants[dependencyName]; + + if (enable) { + dependants.append(mod.name); + } + else { + dependants.removeAll(mod.name); + + // if there are no longer any dependents, let's disable the mod + if (dependencyMod.effectively_hidden && dependants.isEmpty()) { + setMod(dependencyMod, dependencyIndex, false, shouldEmit); + } + } + } + + // disable mods that depend on this one, if disabling + if (!enable) { + auto dependants = m_dependants[mod.name]; + for (const auto& dependencyName : dependants) { + auto dependencyIndex = m_index[dependencyName]; + auto dependencyMod = m_mods.at(dependencyIndex); + + setMod(dependencyMod, dependencyIndex, false, shouldEmit); + } + } + + if (shouldEmit) { + emit dataChanged(AtlOptionalModListModel::index(index, EnabledColumn), + AtlOptionalModListModel::index(index, EnabledColumn)); + } +} + + +AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods) + : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { + ui->setupUi(this); + + listModel = new AtlOptionalModListModel(this, mods); + ui->treeView->setModel(listModel); + + ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + ui->treeView->header()->setSectionResizeMode( + AtlOptionalModListModel::NameColumn, QHeaderView::ResizeToContents); + ui->treeView->header()->setSectionResizeMode( + AtlOptionalModListModel::DescriptionColumn, QHeaderView::Stretch); + + connect(ui->selectRecommendedButton, &QPushButton::pressed, + listModel, &AtlOptionalModListModel::selectRecommended); + connect(ui->clearAllButton, &QPushButton::pressed, + listModel, &AtlOptionalModListModel::clearAll); + connect(ui->installButton, &QPushButton::pressed, + this, &QDialog::close); +} + +AtlOptionalModDialog::~AtlOptionalModDialog() { + delete ui; +} diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h new file mode 100644 index 00000000..a1df43f6 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -0,0 +1,66 @@ +#pragma once + +#include <QDialog> +#include <QAbstractListModel> + +#include "modplatform/atlauncher/ATLPackIndex.h" + +namespace Ui { +class AtlOptionalModDialog; +} + +class AtlOptionalModListModel : public QAbstractListModel { + Q_OBJECT + +public: + enum Columns + { + EnabledColumn = 0, + NameColumn, + DescriptionColumn, + }; + + AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods); + + QVector<QString> getResult(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + +public slots: + void selectRecommended(); + void clearAll(); + +private: + void toggleMod(ATLauncher::VersionMod mod, int index); + void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true); + +private: + QVector<ATLauncher::VersionMod> m_mods; + QMap<QString, bool> m_selection; + QMap<QString, int> m_index; + QMap<QString, QVector<QString>> m_dependants; +}; + +class AtlOptionalModDialog : public QDialog { + Q_OBJECT + +public: + AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods); + ~AtlOptionalModDialog() override; + + QVector<QString> getResult() { + return listModel->getResult(); + } + +private: + Ui::AtlOptionalModDialog *ui; + + AtlOptionalModListModel *listModel; +}; diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui new file mode 100644 index 00000000..5d3193a4 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AtlOptionalModDialog</class> + <widget class="QDialog" name="AtlOptionalModDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>550</width> + <height>310</height> + </rect> + </property> + <property name="windowTitle"> + <string>Select Mods To Install</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="3"> + <widget class="QPushButton" name="installButton"> + <property name="text"> + <string>Install</string> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="selectRecommendedButton"> + <property name="text"> + <string>Select Recommended</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="shareCodeButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Use Share Code</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="clearAllButton"> + <property name="text"> + <string>Clear All</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="4"> + <widget class="ModListView" name="treeView"/> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ModListView</class> + <extends>QTreeView</extends> + <header>widgets/ModListView.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/application/pages/modplatform/atlauncher/AtlPage.cpp b/application/pages/modplatform/atlauncher/AtlPage.cpp new file mode 100644 index 00000000..9fdf111f --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlPage.cpp @@ -0,0 +1,175 @@ +#include "AtlPage.h" +#include "ui_AtlPage.h" + +#include "dialogs/NewInstanceDialog.h" +#include "AtlOptionalModDialog.h" +#include <modplatform/atlauncher/ATLPackInstallTask.h> +#include <BuildConfig.h> +#include <dialogs/VersionSelectDialog.h> + +AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), ui(new Ui::AtlPage), dialog(dialog) +{ + ui->setupUi(this); + + filterModel = new Atl::FilterModel(this); + listModel = new Atl::ListModel(this); + filterModel->setSourceModel(listModel); + ui->packView->setModel(filterModel); + ui->packView->setSortingEnabled(true); + + ui->packView->header()->hide(); + ui->packView->setIndentation(0); + + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + + for(int i = 0; i < filterModel->getAvailableSortings().size(); i++) + { + ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i)); + } + ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting()); + + connect(ui->searchEdit, &QLineEdit::textChanged, this, &AtlPage::triggerSearch); + connect(ui->resetButton, &QPushButton::clicked, this, &AtlPage::resetSearch); + connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged); +} + +AtlPage::~AtlPage() +{ + delete ui; +} + +bool AtlPage::shouldDisplay() const +{ + return true; +} + +void AtlPage::openedImpl() +{ + if(!initialized) + { + listModel->request(); + initialized = true; + } + + suggestCurrent(); +} + +void AtlPage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + + if (selectedVersion.isEmpty()) + { + dialog->setSuggestedPack(); + return; + } + + dialog->setSuggestedPack(selected.name, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion)); + auto editedLogoName = selected.safeName; + auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower()); + listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); +} + +void AtlPage::triggerSearch() +{ + filterModel->setSearchTerm(ui->searchEdit->text()); +} + +void AtlPage::resetSearch() +{ + ui->searchEdit->setText(""); +} + +void AtlPage::onSortingSelectionChanged(QString data) +{ + auto toSet = filterModel->getAvailableSortings().value(data); + filterModel->setSorting(toSet); +} + +void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + ui->versionSelectionBox->clear(); + + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedPack(); + } + return; + } + + selected = filterModel->data(first, Qt::UserRole).value<ATLauncher::IndexedPack>(); + + ui->packDescription->setHtml(selected.description.replace("\n", "<br>")); + + for(const auto& version : selected.versions) { + ui->versionSelectionBox->addItem(version.version); + } + + suggestCurrent(); +} + +void AtlPage::onVersionSelectionChanged(QString data) +{ + if(data.isNull() || data.isEmpty()) + { + selectedVersion = ""; + return; + } + + selectedVersion = data; + suggestCurrent(); +} + +QVector<QString> AtlPage::chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) { + AtlOptionalModDialog optionalModDialog(this, mods); + optionalModDialog.exec(); + return optionalModDialog.getResult(); +} + +QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) { + VersionSelectDialog vselect(vlist.get(), "Choose Version", MMC->activeWindow(), false); + if (minecraftVersion != Q_NULLPTR) { + vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); + vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion)); + } + else { + vselect.setEmptyString(tr("No versions are currently available")); + } + vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!")); + + // select recommended build + for (int i = 0; i < vlist->versions().size(); i++) { + auto version = vlist->versions().at(i); + auto reqs = version->requires(); + + // filter by minecraft version, if the loader depends on a certain version. + if (minecraftVersion != Q_NULLPTR) { + auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) { + return req.uid == "net.minecraft"; + }); + if (iter == reqs.end()) continue; + if (iter->equalsVersion != minecraftVersion) continue; + } + + // first recommended build we find, we use. + if (version->isRecommended()) { + vselect.setCurrentVersion(version->descriptor()); + break; + } + } + + vselect.exec(); + return vselect.selectedVersion()->descriptor(); +} diff --git a/application/pages/modplatform/atlauncher/AtlPage.h b/application/pages/modplatform/atlauncher/AtlPage.h new file mode 100644 index 00000000..932ec6a6 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlPage.h @@ -0,0 +1,87 @@ +/* Copyright 2013-2019 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "AtlFilterModel.h" +#include "AtlListModel.h" + +#include <QWidget> +#include <modplatform/atlauncher/ATLPackInstallTask.h> + +#include "MultiMC.h" +#include "pages/BasePage.h" +#include "tasks/Task.h" + +namespace Ui +{ + class AtlPage; +} + +class NewInstanceDialog; + +class AtlPage : public QWidget, public BasePage, public ATLauncher::UserInteractionSupport +{ +Q_OBJECT + +public: + explicit AtlPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~AtlPage(); + virtual QString displayName() const override + { + return tr("ATLauncher"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("atlauncher"); + } + virtual QString id() const override + { + return "atl"; + } + virtual QString helpPage() const override + { + return "ATL-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + +private: + void suggestCurrent(); + + QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; + QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) override; + +private slots: + void triggerSearch(); + void resetSearch(); + + void onSortingSelectionChanged(QString data); + + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + +private: + Ui::AtlPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + Atl::ListModel* listModel = nullptr; + Atl::FilterModel* filterModel = nullptr; + + ATLauncher::IndexedPack selected; + QString selectedVersion; + + bool initialized = false; +}; diff --git a/application/pages/modplatform/atlauncher/AtlPage.ui b/application/pages/modplatform/atlauncher/AtlPage.ui new file mode 100644 index 00000000..f16c24b8 --- /dev/null +++ b/application/pages/modplatform/atlauncher/AtlPage.ui @@ -0,0 +1,97 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AtlPage</class> + <widget class="QWidget" name="AtlPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>837</width> + <height>685</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="1" column="0"> + <widget class="QTreeView" name="packView"> + <property name="iconSize"> + <size> + <width>96</width> + <height>48</height> + </size> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QTextBrowser" name="packDescription"> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + <property name="openLinks"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> + <item row="0" column="2"> + <widget class="QComboBox" name="versionSelectionBox"/> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Version selected:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QComboBox" name="sortByBox"/> + </item> + </layout> + </item> + <item row="0" column="1"> + <widget class="QPushButton" name="resetButton"> + <property name="text"> + <string>Reset</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLineEdit" name="searchEdit"> + <property name="placeholderText"> + <string>Search and filter ...</string> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>searchEdit</tabstop> + <tabstop>resetButton</tabstop> + <tabstop>packView</tabstop> + <tabstop>packDescription</tabstop> + <tabstop>sortByBox</tabstop> + <tabstop>versionSelectionBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/application/pages/modplatform/twitch/TwitchModel.cpp b/application/pages/modplatform/flame/FlameModel.cpp index 9e3c3ad2..228a88c5 100644 --- a/application/pages/modplatform/twitch/TwitchModel.cpp +++ b/application/pages/modplatform/flame/FlameModel.cpp @@ -1,5 +1,6 @@ -#include "TwitchModel.h" +#include "FlameModel.h" #include "MultiMC.h" +#include <Json.h> #include <MMCStrings.h> #include <Version.h> @@ -10,7 +11,7 @@ #include <RWStorage.h> #include <Env.h> -namespace Twitch { +namespace Flame { ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) { @@ -38,7 +39,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const return QString("INVALID INDEX %1").arg(pos); } - Modpack pack = modpacks.at(pos); + IndexedPack pack = modpacks.at(pos); if(role == Qt::DisplayRole) { return pack.name; @@ -99,12 +100,12 @@ void ListModel::requestLogo(QString logo, QString url) return; } - MetaEntryPtr entry = ENV.metacache()->resolveEntry("TwitchPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); - NetJob *job = new NetJob(QString("Twitch Icon Download %1").arg(logo)); + MetaEntryPtr entry = ENV.metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0))); + NetJob *job = new NetJob(QString("Flame Icon Download %1").arg(logo)); job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath] + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath] { emit logoLoaded(logo, QIcon(fullPath)); if(waitingCallbacks.contains(logo)) @@ -127,7 +128,7 @@ void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallbac { if(m_logoMap.contains(logo)) { - callback(ENV.metacache()->resolveEntry("TwitchPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + callback(ENV.metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); } else { @@ -158,18 +159,17 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { - NetJob *netJob = new NetJob("Twitch::Search"); + NetJob *netJob = new NetJob("Flame::Search"); auto searchUrl = QString( "https://addons-ecs.forgesvc.net/api/v2/addon/search?" "categoryId=0&" "gameId=432&" - //"gameVersion=1.12.2&" "index=%1&" "pageSize=25&" "searchFilter=%2&" "sectionId=4471&" - "sort=0" - ).arg(nextSearchOffset).arg(currentSearchTerm); + "sort=%3" + ).arg(nextSearchOffset).arg(currentSearchTerm).arg(currentSort); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -177,12 +177,13 @@ void ListModel::performPaginatedSearch() QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); } -void ListModel::searchWithTerm(const QString& term) +void ListModel::searchWithTerm(const QString& term, int sort) { - if(currentSearchTerm == term) { + if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; } currentSearchTerm = term; + currentSort = sort; if(jobPtr) { jobPtr->abort(); searchState = ResetRequested; @@ -198,90 +199,36 @@ void ListModel::searchWithTerm(const QString& term) performPaginatedSearch(); } -void Twitch::ListModel::searchRequestFinished() +void Flame::ListModel::searchRequestFinished() { jobPtr.reset(); QJsonParseError parse_error; QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Twitch at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << response; return; } - QList<Modpack> newList; - auto objs = doc.array(); - for(auto projectIter: objs) { - Modpack pack; - auto project = projectIter.toObject(); - pack.addonId = project.value("id").toInt(0); - if (pack.addonId == 0) { - qWarning() << "Pack without an ID, skipping: " << pack.name; - continue; - } - pack.name = project.value("name").toString(); - pack.websiteUrl = project.value("websiteUrl").toString(); - pack.description = project.value("summary").toString(); - bool thumbnailFound = false; - auto attachments = project.value("attachments").toArray(); - for(auto attachmentIter: attachments) { - auto attachment = attachmentIter.toObject(); - bool isDefault = attachment.value("isDefault").toBool(false); - if(isDefault) { - thumbnailFound = true; - pack.logoName = attachment.value("title").toString(); - pack.logoUrl = attachment.value("thumbnailUrl").toString(); - break; - } - } - if(!thumbnailFound) { - qWarning() << "Pack without an icon, skipping: " << pack.name; - continue; - } - auto authors = project.value("authors").toArray(); - for(auto authorIter: authors) { - auto author = authorIter.toObject(); - ModpackAuthor packAuthor; - packAuthor.name = author.value("name").toString(); - packAuthor.url = author.value("url").toString(); - pack.authors.append(packAuthor); - } - int defaultFileId = project.value("defaultFileId").toInt(0); - if(defaultFileId == 0) { - qWarning() << "Pack without default file, skipping: " << pack.name; - continue; - } - bool found = false; - auto files = project.value("latestFiles").toArray(); - for(auto fileIter: files) { - auto file = fileIter.toObject(); - int id = file.value("id").toInt(0); - // NOTE: for now, ignore everything that's not the default... - if(id != defaultFileId) { - continue; - } - pack.latestFile.addonId = pack.addonId; - pack.latestFile.fileId = id; - // FIXME: what to do when there's more than one, or there's no version? - auto versionArray = file.value("gameVersion").toArray(); - if(versionArray.size() != 1) { - continue; - } - pack.latestFile.mcVersion = versionArray[0].toString(); - pack.latestFile.version = file.value("displayName").toString(); - pack.latestFile.downloadUrl = file.value("downloadUrl").toString(); - found = true; - break; + QList<Flame::IndexedPack> newList; + auto packs = doc.array(); + for(auto packRaw : packs) { + auto packObj = packRaw.toObject(); + + Flame::IndexedPack pack; + try + { + Flame::loadIndexedPack(pack, packObj); + newList.append(pack); } - if(!found) { - qWarning() << "Pack with no good file, skipping: " << pack.name; + catch(const JSONValidationError &e) + { + qWarning() << "Error while loading pack from CurseForge: " << e.cause(); continue; } - pack.broken = false; - newList.append(pack); } - if(objs.size() < 25) { + if(packs.size() < 25) { searchState = Finished; } else { nextSearchOffset += 25; @@ -292,7 +239,7 @@ void Twitch::ListModel::searchRequestFinished() endInsertRows(); } -void Twitch::ListModel::searchRequestFailed(QString reason) +void Flame::ListModel::searchRequestFailed(QString reason) { jobPtr.reset(); diff --git a/application/pages/modplatform/twitch/TwitchModel.h b/application/pages/modplatform/flame/FlameModel.h index ad355c64..24383db0 100644 --- a/application/pages/modplatform/twitch/TwitchModel.h +++ b/application/pages/modplatform/flame/FlameModel.h @@ -1,6 +1,5 @@ #pragma once -#include <modplatform/legacy_ftb/PackHelpers.h> #include <RWStorage.h> #include <QAbstractListModel> @@ -16,9 +15,9 @@ #include <functional> #include <net/NetJob.h> -#include "TwitchData.h" +#include <modplatform/flame/FlamePackIndex.h> -namespace Twitch { +namespace Flame { typedef QMap<QString, QIcon> LogoMap; @@ -40,7 +39,7 @@ public: void fetchMore(const QModelIndex & parent) override; void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); - void searchWithTerm(const QString & term); + void searchWithTerm(const QString & term, const int sort); private slots: void performPaginatedSearch(); @@ -55,13 +54,14 @@ private: void requestLogo(QString file, QString url); private: - QList<Modpack> modpacks; + QList<IndexedPack> modpacks; QStringList m_failedLogos; QStringList m_loadingLogos; LogoMap m_logoMap; QMap<QString, LogoCallback> waitingCallbacks; QString currentSearchTerm; + int currentSort = 0; int nextSearchOffset = 0; enum SearchState { None, diff --git a/application/pages/modplatform/flame/FlamePage.cpp b/application/pages/modplatform/flame/FlamePage.cpp new file mode 100644 index 00000000..ade58431 --- /dev/null +++ b/application/pages/modplatform/flame/FlamePage.cpp @@ -0,0 +1,185 @@ +#include "FlamePage.h" +#include "ui_FlamePage.h" + +#include "MultiMC.h" +#include <Json.h> +#include "dialogs/NewInstanceDialog.h" +#include <InstanceImportTask.h> +#include "FlameModel.h" +#include <QKeyEvent> + +FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) +{ + ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch); + ui->searchEdit->installEventFilter(this); + listModel = new Flame::ListModel(this); + ui->packView->setModel(listModel); + + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + + // index is used to set the sorting with the curseforge api + ui->sortByBox->addItem(tr("Sort by featured")); + ui->sortByBox->addItem(tr("Sort by popularity")); + ui->sortByBox->addItem(tr("Sort by last updated")); + ui->sortByBox->addItem(tr("Sort by name")); + ui->sortByBox->addItem(tr("Sort by author")); + ui->sortByBox->addItem(tr("Sort by total downloads")); + + connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlamePage::onVersionSelectionChanged); +} + +FlamePage::~FlamePage() +{ + delete ui; +} + +bool FlamePage::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } + } + return QWidget::eventFilter(watched, event); +} + +bool FlamePage::shouldDisplay() const +{ + return true; +} + +void FlamePage::openedImpl() +{ + suggestCurrent(); + triggerSearch(); +} + +void FlamePage::triggerSearch() +{ + listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); +} + +void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + ui->versionSelectionBox->clear(); + + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedPack(); + } + return; + } + + current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>(); + QString text = ""; + QString name = current.name; + + if (current.websiteUrl.isEmpty()) + text = name; + else + text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; + if (!current.authors.empty()) { + auto authorToStr = [](Flame::ModpackAuthor & author) { + if(author.url.isEmpty()) { + return author.name; + } + return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); + }; + QStringList authorStrs; + for(auto & author: current.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "<br>" + tr(" by ") + authorStrs.join(", "); + } + text += "<br><br>"; + + ui->packDescription->setHtml(text + current.description); + + if (current.versionsLoaded == false) + { + qDebug() << "Loading flame modpack versions"; + NetJob *netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name)); + std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); + int addonId = current.addonId; + netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get())); + + QObject::connect(netJob, &NetJob::succeeded, this, [this, response] + { + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if(parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + QJsonArray arr = doc.array(); + try + { + Flame::loadIndexedPackVersions(current, arr); + } + catch(const JSONValidationError &e) + { + qDebug() << *response; + qWarning() << "Error while reading flame modpack version: " << e.cause(); + } + + for(auto version : current.versions) { + ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); + } + + suggestCurrent(); + }); + netJob->start(); + } + else + { + for(auto version : current.versions) { + ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl)); + } + + suggestCurrent(); + } +} + +void FlamePage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + + if (selectedVersion.isEmpty()) + { + dialog->setSuggestedPack(); + return; + } + + dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion)); + QString editedLogoName; + editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0); + listModel->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); +} + +void FlamePage::onVersionSelectionChanged(QString data) +{ + if(data.isNull() || data.isEmpty()) + { + selectedVersion = ""; + return; + } + selectedVersion = ui->versionSelectionBox->currentData().toString(); + suggestCurrent(); +} diff --git a/application/pages/modplatform/twitch/TwitchPage.h b/application/pages/modplatform/flame/FlamePage.h index 04e3a1c6..467bb44b 100644 --- a/application/pages/modplatform/twitch/TwitchPage.h +++ b/application/pages/modplatform/flame/FlamePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -20,41 +20,41 @@ #include "pages/BasePage.h" #include <MultiMC.h> #include "tasks/Task.h" -#include "TwitchData.h" +#include <modplatform/flame/FlamePackIndex.h> namespace Ui { -class TwitchPage; +class FlamePage; } class NewInstanceDialog; -namespace Twitch { +namespace Flame { class ListModel; } -class TwitchPage : public QWidget, public BasePage +class FlamePage : public QWidget, public BasePage { Q_OBJECT public: - explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~TwitchPage(); + explicit FlamePage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~FlamePage(); virtual QString displayName() const override { - return tr("Twitch"); + return tr("CurseForge"); } virtual QIcon icon() const override { - return MMC->getThemedIcon("twitch"); + return MMC->getThemedIcon("flame"); } virtual QString id() const override { - return "twitch"; + return "flame"; } virtual QString helpPage() const override { - return "Twitch-platform"; + return "Flame-platform"; } virtual bool shouldDisplay() const override; @@ -68,10 +68,13 @@ private: private slots: void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); private: - Ui::TwitchPage *ui = nullptr; + Ui::FlamePage *ui = nullptr; NewInstanceDialog* dialog = nullptr; - Twitch::ListModel* model = nullptr; - Twitch::Modpack current; + Flame::ListModel* listModel = nullptr; + Flame::IndexedPack current; + + QString selectedVersion; }; diff --git a/application/pages/modplatform/flame/FlamePage.ui b/application/pages/modplatform/flame/FlamePage.ui new file mode 100644 index 00000000..9723815a --- /dev/null +++ b/application/pages/modplatform/flame/FlamePage.ui @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FlamePage</class> + <widget class="QWidget" name="FlamePage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>837</width> + <height>685</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="1" column="0"> + <widget class="QListView" name="packView"> + <property name="iconSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QTextBrowser" name="packDescription"> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + <property name="openLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> + <item row="0" column="2"> + <widget class="QComboBox" name="versionSelectionBox"/> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Version selected:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QComboBox" name="sortByBox"/> + </item> + </layout> + </item> + <item row="0" column="1"> + <widget class="QPushButton" name="searchButton"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLineEdit" name="searchEdit"> + <property name="placeholderText"> + <string>Search and filter ...</string> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>searchEdit</tabstop> + <tabstop>searchButton</tabstop> + <tabstop>packView</tabstop> + <tabstop>packDescription</tabstop> + <tabstop>sortByBox</tabstop> + <tabstop>versionSelectionBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/application/pages/modplatform/ftb/FtbFilterModel.cpp b/application/pages/modplatform/ftb/FtbFilterModel.cpp new file mode 100644 index 00000000..dec3a017 --- /dev/null +++ b/application/pages/modplatform/ftb/FtbFilterModel.cpp @@ -0,0 +1,64 @@ +#include "FtbFilterModel.h" + +#include <QDebug> + +#include "modplatform/modpacksch/FTBPackManifest.h" +#include <MMCStrings.h> + +namespace Ftb { + +FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) +{ + currentSorting = Sorting::ByPlays; + sortings.insert(tr("Sort by plays"), Sorting::ByPlays); + sortings.insert(tr("Sort by installs"), Sorting::ByInstalls); + sortings.insert(tr("Sort by name"), Sorting::ByName); +} + +const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings() +{ + return sortings; +} + +QString FilterModel::translateCurrentSorting() +{ + return sortings.key(currentSorting); +} + +void FilterModel::setSorting(Sorting sorting) +{ + currentSorting = sorting; + invalidate(); +} + +FilterModel::Sorting FilterModel::getCurrentSorting() +{ + return currentSorting; +} + +bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + return true; +} + +bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + ModpacksCH::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<ModpacksCH::Modpack>(); + ModpacksCH::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<ModpacksCH::Modpack>(); + + if (currentSorting == ByPlays) { + return leftPack.plays < rightPack.plays; + } + else if (currentSorting == ByInstalls) { + return leftPack.installs < rightPack.installs; + } + else if (currentSorting == ByName) { + return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; + } + + // Invalid sorting set, somehow... + qWarning() << "Invalid sorting set!"; + return true; +} + +} diff --git a/application/pages/modplatform/ftb/FtbFilterModel.h b/application/pages/modplatform/ftb/FtbFilterModel.h new file mode 100644 index 00000000..4fe2a274 --- /dev/null +++ b/application/pages/modplatform/ftb/FtbFilterModel.h @@ -0,0 +1,33 @@ +#pragma once + +#include <QtCore/QSortFilterProxyModel> + +namespace Ftb { + +class FilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + FilterModel(QObject* parent = Q_NULLPTR); + enum Sorting { + ByPlays, + ByInstalls, + ByName, + }; + const QMap<QString, Sorting> getAvailableSortings(); + QString translateCurrentSorting(); + void setSorting(Sorting sorting); + Sorting getCurrentSorting(); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + +private: + QMap<QString, Sorting> sortings; + Sorting currentSorting; + +}; + +} diff --git a/application/pages/modplatform/ftb/FtbListModel.cpp b/application/pages/modplatform/ftb/FtbListModel.cpp new file mode 100644 index 00000000..98973f2e --- /dev/null +++ b/application/pages/modplatform/ftb/FtbListModel.cpp @@ -0,0 +1,304 @@ +#include "FtbListModel.h" + +#include "BuildConfig.h" +#include "Env.h" +#include "MultiMC.h" +#include "Json.h" + +#include <QPainter> + +namespace Ftb { + +ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +ListModel::~ListModel() +{ +} + +int ListModel::rowCount(const QModelIndex &parent) const +{ + return modpacks.size(); +} + +int ListModel::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +QVariant ListModel::data(const QModelIndex &index, int role) const +{ + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QString("INVALID INDEX %1").arg(pos); + } + + ModpacksCH::Modpack pack = modpacks.at(pos); + if(role == Qt::DisplayRole) + { + return pack.name; + } + else if (role == Qt::ToolTipRole) + { + return pack.synopsis; + } + else if(role == Qt::DecorationRole) + { + QIcon placeholder = MMC->getThemedIcon("screenshot-placeholder"); + + auto iter = m_logoMap.find(pack.name); + if (iter != m_logoMap.end()) { + auto & logo = *iter; + if(!logo.result.isNull()) { + return logo.result; + } + return placeholder; + } + + for(auto art : pack.art) { + if(art.type == "square") { + ((ListModel *)this)->requestLogo(pack.name, art.url); + } + } + return placeholder; + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + + return QVariant(); +} + +void ListModel::performSearch() +{ + auto *netJob = new NetJob("Ftb::Search"); + QString searchUrl; + if(currentSearchTerm.isEmpty()) { + searchUrl = BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all"; + } + else { + searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/search/25?term=%1") + .arg(currentSearchTerm); + } + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); + jobPtr = netJob; + jobPtr->start(); + QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished); + QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); +} + +void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) +{ + if(m_logoMap.contains(logo)) + { + callback(ENV.metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + } + else + { + requestLogo(logo, logoUrl); + } +} + +void ListModel::searchWithTerm(const QString &term) +{ + if(searchState != Failed && currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) { + // unless the search has failed, then there is no need to perform an identical search. + return; + } + currentSearchTerm = term; + + if(jobPtr) { + jobPtr->abort(); + jobPtr.reset(); + } + + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + + performSearch(); +} + +void ListModel::searchRequestFinished() +{ + jobPtr.reset(); + remainingPacks.clear(); + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + if(parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + auto packs = doc.object().value("packs").toArray(); + for(auto pack : packs) { + auto packId = pack.toInt(); + remainingPacks.append(packId); + } + + if(!remainingPacks.isEmpty()) { + currentPack = remainingPacks.at(0); + requestPack(); + } +} + +void ListModel::searchRequestFailed(QString reason) +{ + jobPtr.reset(); + remainingPacks.clear(); + + searchState = Failed; +} + +void ListModel::requestPack() +{ + auto *netJob = new NetJob("Ftb::Search"); + auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1") + .arg(currentPack); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); + jobPtr = netJob; + jobPtr->start(); + + QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::packRequestFinished); + QObject::connect(netJob, &NetJob::failed, this, &ListModel::packRequestFailed); +} + +void ListModel::packRequestFinished() +{ + jobPtr.reset(); + remainingPacks.removeOne(currentPack); + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + + if(parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + auto obj = doc.object(); + + ModpacksCH::Modpack pack; + try + { + ModpacksCH::loadModpack(pack, obj); + } + catch (const JSONValidationError &e) + { + qDebug() << QString::fromUtf8(response); + qWarning() << "Error while reading pack manifest from FTB: " << e.cause(); + return; + } + + // Since there is no guarantee that packs have a version, this will just + // ignore those "dud" packs. + if (pack.versions.empty()) + { + qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions"; + } + else + { + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size()); + modpacks.append(pack); + endInsertRows(); + } + + if(!remainingPacks.isEmpty()) { + currentPack = remainingPacks.at(0); + requestPack(); + } +} + +void ListModel::packRequestFailed(QString reason) +{ + jobPtr.reset(); + remainingPacks.removeOne(currentPack); +} + +void ListModel::logoLoaded(QString logo, bool stale) +{ + auto & logoObj = m_logoMap[logo]; + logoObj.downloadJob.reset(); + QString smallPath = logoObj.fullpath + ".small"; + + QFileInfo smallInfo(smallPath); + + if(stale || !smallInfo.exists()) { + QImage image(logoObj.fullpath); + if (image.isNull()) + { + logoObj.failed = true; + return; + } + QImage small; + if (image.width() > image.height()) { + small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); + } + else { + small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); + } + QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); + QImage square(QSize(256, 256), QImage::Format_ARGB32); + square.fill(Qt::transparent); + + QPainter painter(&square); + painter.drawImage(offset, small); + painter.end(); + + square.save(logoObj.fullpath + ".small", "PNG"); + } + + logoObj.result = QIcon(logoObj.fullpath + ".small"); + for(int i = 0; i < modpacks.size(); i++) { + if(modpacks[i].name == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + } + } +} + +void ListModel::logoFailed(QString logo) +{ + m_logoMap[logo].failed = true; + m_logoMap[logo].downloadJob.reset(); +} + +void ListModel::requestLogo(QString logo, QString url) +{ + if(m_logoMap.contains(logo)) { + return; + } + + MetaEntryPtr entry = ENV.metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); + + bool stale = entry->isStale(); + + NetJob *job = new NetJob(QString("FTB Icon Download %1").arg(logo)); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath, stale] + { + logoLoaded(logo, stale); + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo] + { + logoFailed(logo); + }); + + auto &newLogoEntry = m_logoMap[logo]; + newLogoEntry.downloadJob = job; + newLogoEntry.fullpath = fullPath; + job->start(); +} + +} diff --git a/application/pages/modplatform/ftb/FtbListModel.h b/application/pages/modplatform/ftb/FtbListModel.h new file mode 100644 index 00000000..de94e6ba --- /dev/null +++ b/application/pages/modplatform/ftb/FtbListModel.h @@ -0,0 +1,69 @@ +#pragma once + +#include <QAbstractListModel> + +#include "modplatform/modpacksch/FTBPackManifest.h" +#include "net/NetJob.h" +#include <QIcon> + +namespace Ftb { + +struct Logo { + QString fullpath; + NetJobPtr downloadJob; + QIcon result; + bool failed = false; +}; + +typedef QMap<QString, Logo> LogoMap; +typedef std::function<void(QString)> LogoCallback; + +class ListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ListModel(QObject *parent); + virtual ~ListModel(); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + void searchWithTerm(const QString & term); + +private slots: + void performSearch(); + void searchRequestFinished(); + void searchRequestFailed(QString reason); + + void requestPack(); + void packRequestFinished(); + void packRequestFailed(QString reason); + + void logoFailed(QString logo); + void logoLoaded(QString logo, bool stale); + +private: + void requestLogo(QString file, QString url); + +private: + QList<ModpacksCH::Modpack> modpacks; + LogoMap m_logoMap; + + QString currentSearchTerm; + enum SearchState { + None, + CanPossiblyFetchMore, + ResetRequested, + Finished, + Failed, + } searchState = None; + NetJobPtr jobPtr; + int currentPack; + QList<int> remainingPacks; + QByteArray response; +}; + +} diff --git a/application/pages/modplatform/ftb/FtbPage.cpp b/application/pages/modplatform/ftb/FtbPage.cpp new file mode 100644 index 00000000..b7f35c5d --- /dev/null +++ b/application/pages/modplatform/ftb/FtbPage.cpp @@ -0,0 +1,145 @@ +#include "FtbPage.h" +#include "ui_FtbPage.h" + +#include <QKeyEvent> + +#include "dialogs/NewInstanceDialog.h" +#include "modplatform/modpacksch/FTBPackInstallTask.h" + +#include "HoeDown.h" + +FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), ui(new Ui::FtbPage), dialog(dialog) +{ + ui->setupUi(this); + + filterModel = new Ftb::FilterModel(this); + listModel = new Ftb::ListModel(this); + filterModel->setSourceModel(listModel); + ui->packView->setModel(filterModel); + ui->packView->setSortingEnabled(true); + ui->packView->header()->hide(); + ui->packView->setIndentation(0); + + ui->searchEdit->installEventFilter(this); + + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + + for(int i = 0; i < filterModel->getAvailableSortings().size(); i++) + { + ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i)); + } + ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting()); + + connect(ui->searchButton, &QPushButton::clicked, this, &FtbPage::triggerSearch); + connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged); +} + +FtbPage::~FtbPage() +{ + delete ui; +} + +bool FtbPage::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } + } + return QWidget::eventFilter(watched, event); +} + +bool FtbPage::shouldDisplay() const +{ + return true; +} + +void FtbPage::openedImpl() +{ + triggerSearch(); + suggestCurrent(); +} + +void FtbPage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + + if (selectedVersion.isEmpty()) + { + dialog->setSuggestedPack(); + return; + } + + dialog->setSuggestedPack(selected.name, new ModpacksCH::PackInstallTask(selected, selectedVersion)); + for(auto art : selected.art) { + if(art.type == "square") { + QString editedLogoName; + editedLogoName = selected.name; + + listModel->getLogo(selected.name, art.url, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName); + }); + } + } +} + +void FtbPage::triggerSearch() +{ + listModel->searchWithTerm(ui->searchEdit->text()); +} + +void FtbPage::onSortingSelectionChanged(QString data) +{ + auto toSet = filterModel->getAvailableSortings().value(data); + filterModel->setSorting(toSet); +} + +void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + ui->versionSelectionBox->clear(); + + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedPack(); + } + return; + } + + selected = filterModel->data(first, Qt::UserRole).value<ModpacksCH::Modpack>(); + + HoeDown hoedown; + QString output = hoedown.process(selected.description.toUtf8()); + ui->packDescription->setHtml(output); + + // reverse foreach, so that the newest versions are first + for (auto i = selected.versions.size(); i--;) { + ui->versionSelectionBox->addItem(selected.versions.at(i).name); + } + + suggestCurrent(); +} + +void FtbPage::onVersionSelectionChanged(QString data) +{ + if(data.isNull() || data.isEmpty()) + { + selectedVersion = ""; + return; + } + + selectedVersion = data; + suggestCurrent(); +} diff --git a/application/pages/modplatform/ftb/FtbPage.h b/application/pages/modplatform/ftb/FtbPage.h new file mode 100644 index 00000000..c9c93897 --- /dev/null +++ b/application/pages/modplatform/ftb/FtbPage.h @@ -0,0 +1,80 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "FtbFilterModel.h" +#include "FtbListModel.h" + +#include <QWidget> + +#include "MultiMC.h" +#include "pages/BasePage.h" +#include "tasks/Task.h" + +namespace Ui +{ + class FtbPage; +} + +class NewInstanceDialog; + +class FtbPage : public QWidget, public BasePage +{ +Q_OBJECT + +public: + explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~FtbPage(); + virtual QString displayName() const override + { + return tr("FTB"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("ftb_logo"); + } + virtual QString id() const override + { + return "ftb"; + } + virtual QString helpPage() const override + { + return "FTB-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + bool eventFilter(QObject * watched, QEvent * event) override; + +private: + void suggestCurrent(); + +private slots: + void triggerSearch(); + void onSortingSelectionChanged(QString data); + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + +private: + Ui::FtbPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + Ftb::ListModel* listModel = nullptr; + Ftb::FilterModel* filterModel = nullptr; + + ModpacksCH::Modpack selected; + QString selectedVersion; +}; diff --git a/application/pages/modplatform/ftb/FtbPage.ui b/application/pages/modplatform/ftb/FtbPage.ui new file mode 100644 index 00000000..135afc6d --- /dev/null +++ b/application/pages/modplatform/ftb/FtbPage.ui @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FtbPage</class> + <widget class="QWidget" name="FtbPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>875</width> + <height>745</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> + <item row="0" column="2"> + <widget class="QComboBox" name="versionSelectionBox"/> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Version selected:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QComboBox" name="sortByBox"/> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QLineEdit" name="searchEdit"> + <property name="placeholderText"> + <string>Search and filter ...</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QPushButton" name="searchButton"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QTreeView" name="packView"> + <property name="iconSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QTextBrowser" name="packDescription"> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + <property name="openLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <tabstops> + <tabstop>searchEdit</tabstop> + <tabstop>searchButton</tabstop> + <tabstop>versionSelectionBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/application/pages/modplatform/legacy_ftb/Page.cpp b/application/pages/modplatform/legacy_ftb/Page.cpp index 8e40ba9e..a438f76c 100644 --- a/application/pages/modplatform/legacy_ftb/Page.cpp +++ b/application/pages/modplatform/legacy_ftb/Page.cpp @@ -122,49 +122,50 @@ void Page::openedImpl() void Page::suggestCurrent() { - if(isOpened) + if(!isOpened) { - if(!selected.broken) - { - dialog->setSuggestedPack(selected.name, new PackInstallTask(selected, selectedVersion)); - QString editedLogoName; - if(selected.logo.toLower().startsWith("ftb")) - { - editedLogoName = selected.logo; - } - else - { - editedLogoName = "ftb_" + selected.logo; - } + return; + } - editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png")); + if(selected.broken || selectedVersion.isEmpty()) + { + dialog->setSuggestedPack(); + return; + } - if(selected.type == PackType::Public) - { - publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); - } - else if (selected.type == PackType::ThirdParty) - { - thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); - } - else if (selected.type == PackType::Private) - { - privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); - } - } - else + dialog->setSuggestedPack(selected.name, new PackInstallTask(selected, selectedVersion)); + QString editedLogoName; + if(selected.logo.toLower().startsWith("ftb")) + { + editedLogoName = selected.logo; + } + else + { + editedLogoName = "ftb_" + selected.logo; + } + + editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png")); + + if(selected.type == PackType::Public) + { + publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) { - dialog->setSuggestedPack(); - } + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + else if (selected.type == PackType::ThirdParty) + { + thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + } + else if (selected.type == PackType::Private) + { + privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); } } diff --git a/application/pages/modplatform/legacy_ftb/Page.h b/application/pages/modplatform/legacy_ftb/Page.h index ed6d1657..e840216e 100644 --- a/application/pages/modplatform/legacy_ftb/Page.h +++ b/application/pages/modplatform/legacy_ftb/Page.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/pages/modplatform/legacy_ftb/Page.ui b/application/pages/modplatform/legacy_ftb/Page.ui index 36fb2359..15e5d432 100644 --- a/application/pages/modplatform/legacy_ftb/Page.ui +++ b/application/pages/modplatform/legacy_ftb/Page.ui @@ -29,6 +29,9 @@ <height>16777215</height> </size> </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> </widget> </item> <item row="0" column="1"> @@ -52,6 +55,9 @@ <height>16777215</height> </size> </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> </widget> </item> </layout> @@ -69,6 +75,9 @@ <height>16777215</height> </size> </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> </widget> </item> <item row="1" column="0"> diff --git a/application/pages/modplatform/technic/TechnicData.h b/application/pages/modplatform/technic/TechnicData.h new file mode 100644 index 00000000..50fd75e8 --- /dev/null +++ b/application/pages/modplatform/technic/TechnicData.h @@ -0,0 +1,42 @@ +/* Copyright 2020-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QList> +#include <QString> + +namespace Technic { +struct Modpack { + QString slug; + + QString name; + QString logoUrl; + QString logoName; + + bool broken = true; + + QString url; + bool isSolder = false; + QString minecraftVersion; + + bool metadataLoaded = false; + QString websiteUrl; + QString author; + QString description; +}; +} + +Q_DECLARE_METATYPE(Technic::Modpack) diff --git a/application/pages/modplatform/technic/TechnicModel.cpp b/application/pages/modplatform/technic/TechnicModel.cpp new file mode 100644 index 00000000..def30783 --- /dev/null +++ b/application/pages/modplatform/technic/TechnicModel.cpp @@ -0,0 +1,238 @@ +/* Copyright 2020-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TechnicModel.h" +#include "Env.h" +#include "MultiMC.h" +#include "Json.h" + +#include <QIcon> + +Technic::ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +Technic::ListModel::~ListModel() +{ +} + +QVariant Technic::ListModel::data(const QModelIndex& index, int role) const +{ + int pos = index.row(); + if(pos >= modpacks.size() || pos < 0 || !index.isValid()) + { + return QString("INVALID INDEX %1").arg(pos); + } + + Modpack pack = modpacks.at(pos); + if(role == Qt::DisplayRole) + { + return pack.name; + } + else if(role == Qt::DecorationRole) + { + if(m_logoMap.contains(pack.logoName)) + { + return (m_logoMap.value(pack.logoName)); + } + QIcon icon = MMC->getThemedIcon("screenshot-placeholder"); + ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl); + return icon; + } + else if(role == Qt::UserRole) + { + QVariant v; + v.setValue(pack); + return v; + } + return QVariant(); +} + +int Technic::ListModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int Technic::ListModel::rowCount(const QModelIndex&) const +{ + return modpacks.size(); +} + +void Technic::ListModel::searchWithTerm(const QString& term) +{ + if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) { + return; + } + currentSearchTerm = term; + if(jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } + else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + performSearch(); +} + +void Technic::ListModel::performSearch() +{ + NetJob *netJob = new NetJob("Technic::Search"); + QString searchUrl = ""; + if (currentSearchTerm.isEmpty()) { + searchUrl = "https://api.technicpack.net/trending?build=multimc"; + } + else + { + searchUrl = QString( + "https://api.technicpack.net/search?build=multimc&q=%1" + ).arg(currentSearchTerm); + } + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); + jobPtr = netJob; + jobPtr->start(); + QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished); + QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed); +} + +void Technic::ListModel::searchRequestFinished() +{ + jobPtr.reset(); + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); + if(parse_error.error != QJsonParseError::NoError) + { + qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + QList<Modpack> newList; + try { + auto root = Json::requireObject(doc); + auto objs = Json::requireArray(root, "modpacks"); + for (auto technicPack: objs) { + Modpack pack; + auto technicPackObject = Json::requireObject(technicPack); + pack.name = Json::requireString(technicPackObject, "name"); + pack.slug = Json::requireString(technicPackObject, "slug"); + if (pack.slug == "vanilla") + continue; + + auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); + if(rawURL == "null") { + pack.logoUrl = "null"; + pack.logoName = "null"; + } + else { + pack.logoUrl = rawURL; + pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + } + pack.broken = false; + newList.append(pack); + } + } + catch (const JSONValidationError &err) + { + qCritical() << "Couldn't parse technic search results:" << err.cause() ; + return; + } + searchState = Finished; + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl, Technic::LogoCallback callback) +{ + if(m_logoMap.contains(logo)) + { + callback(ENV.metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))->getFullPath()); + } + else + { + requestLogo(logo, logoUrl); + } +} + +void Technic::ListModel::searchRequestFailed() +{ + jobPtr.reset(); + + if(searchState == ResetRequested) + { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + performSearch(); + } + else + { + searchState = Finished; + } +} + + +void Technic::ListModel::logoLoaded(QString logo, QString out) +{ + m_loadingLogos.removeAll(logo); + m_logoMap.insert(logo, QIcon(out)); + for(int i = 0; i < modpacks.size(); i++) + { + if(modpacks[i].logoName == logo) + { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + } + } +} + +void Technic::ListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +void Technic::ListModel::requestLogo(QString logo, QString url) +{ + if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null") + { + return; + } + + MetaEntryPtr entry = ENV.metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo)); + NetJob *job = new NetJob(QString("Technic Icon Download %1").arg(logo)); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath] + { + logoLoaded(logo, fullPath); + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo] + { + logoFailed(logo); + }); + + job->start(); + + m_loadingLogos.append(logo); +} diff --git a/application/pages/modplatform/technic/TechnicModel.h b/application/pages/modplatform/technic/TechnicModel.h new file mode 100644 index 00000000..82a03842 --- /dev/null +++ b/application/pages/modplatform/technic/TechnicModel.h @@ -0,0 +1,70 @@ +/* Copyright 2020-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QModelIndex> + +#include "TechnicData.h" +#include "net/NetJob.h" + +namespace Technic { + +typedef std::function<void(QString)> LogoCallback; + +class ListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ListModel(QObject *parent); + virtual ~ListModel(); + + virtual QVariant data(const QModelIndex& index, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + void searchWithTerm(const QString & term); + +private slots: + void searchRequestFinished(); + void searchRequestFailed(); + + void logoFailed(QString logo); + void logoLoaded(QString logo, QString out); + +private: + void performSearch(); + void requestLogo(QString logo, QString url); + +private: + QList<Modpack> modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + QMap<QString, QIcon> m_logoMap; + QMap<QString, LogoCallback> waitingCallbacks; + + QString currentSearchTerm; + enum SearchState { + None, + ResetRequested, + Finished + } searchState = None; + NetJobPtr jobPtr; + QByteArray response; +}; + +} diff --git a/application/pages/modplatform/technic/TechnicPage.cpp b/application/pages/modplatform/technic/TechnicPage.cpp new file mode 100644 index 00000000..e836f767 --- /dev/null +++ b/application/pages/modplatform/technic/TechnicPage.cpp @@ -0,0 +1,198 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TechnicPage.h" +#include "ui_TechnicPage.h" + +#include "MultiMC.h" +#include "dialogs/NewInstanceDialog.h" +#include "TechnicModel.h" +#include <QKeyEvent> +#include "modplatform/technic/SingleZipPackInstallTask.h" +#include "modplatform/technic/SolderPackInstallTask.h" +#include "Json.h" + +TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) +{ + ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch); + ui->searchEdit->installEventFilter(this); + model = new Technic::ListModel(this); + ui->packView->setModel(model); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); +} + +bool TechnicPage::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } + } + return QWidget::eventFilter(watched, event); +} + +TechnicPage::~TechnicPage() +{ + delete ui; +} + +bool TechnicPage::shouldDisplay() const +{ + return true; +} + +void TechnicPage::openedImpl() +{ + suggestCurrent(); + triggerSearch(); +} + +void TechnicPage::triggerSearch() { + model->searchWithTerm(ui->searchEdit->text()); +} + +void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedPack(); + } + //ui->frame->clear(); + return; + } + + current = model->data(first, Qt::UserRole).value<Technic::Modpack>(); + suggestCurrent(); +} + +void TechnicPage::suggestCurrent() +{ + if (!isOpened) + { + return; + } + if (current.broken) + { + dialog->setSuggestedPack(); + return; + } + + QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0); + model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); + + if (current.metadataLoaded) + { + metadataLoaded(); + return; + } + + NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name)); + std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); + QString slug = current.slug; + netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get())); + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug] + { + if (current.slug != slug) + { + return; + } + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + QJsonObject obj = doc.object(); + if(parse_error.error != QJsonParseError::NoError) + { + qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + if (!obj.contains("url")) + { + qWarning() << "Json doesn't contain an url key"; + return; + } + QJsonValueRef url = obj["url"]; + if (url.isString()) + { + current.url = url.toString(); + } + else + { + if (!obj.contains("solder")) + { + qWarning() << "Json doesn't contain a valid url or solder key"; + return; + } + QJsonValueRef solderUrl = obj["solder"]; + if (solderUrl.isString()) + { + current.url = solderUrl.toString(); + current.isSolder = true; + } + else + { + qWarning() << "Json doesn't contain a valid url or solder key"; + return; + } + } + + current.minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__"); + current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__"); + current.author = Json::ensureString(obj, "user", QString(), "__placeholder__"); + current.description = Json::ensureString(obj, "description", QString(), "__placeholder__"); + current.metadataLoaded = true; + metadataLoaded(); + }); + netJob->start(); +} + +// expects current.metadataLoaded to be true +void TechnicPage::metadataLoaded() +{ + QString text = ""; + QString name = current.name; + + if (current.websiteUrl.isEmpty()) + // This allows injecting HTML here. + text = name; + else + // URL not properly escaped for inclusion in HTML. The name allows for injecting HTML. + text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; + if (!current.author.isEmpty()) { + // This allows injecting HTML here + text += tr(" by ") + current.author; + } + + ui->frame->setModText(text); + ui->frame->setModDescription(current.description); + if (!current.isSolder) + { + dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); + } + else + { + while (current.url.endsWith('/')) current.url.chop(1); + dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(current.url + "/modpack/" + current.slug, current.minecraftVersion)); + } +} diff --git a/application/pages/modplatform/technic/TechnicPage.h b/application/pages/modplatform/technic/TechnicPage.h new file mode 100644 index 00000000..27e1258a --- /dev/null +++ b/application/pages/modplatform/technic/TechnicPage.h @@ -0,0 +1,78 @@ +/* Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <QWidget> + +#include "pages/BasePage.h" +#include <MultiMC.h> +#include "tasks/Task.h" +#include "TechnicData.h" + +namespace Ui +{ +class TechnicPage; +} + +class NewInstanceDialog; + +namespace Technic { + class ListModel; +} + +class TechnicPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~TechnicPage(); + virtual QString displayName() const override + { + return tr("Technic"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("technic"); + } + virtual QString id() const override + { + return "technic"; + } + virtual QString helpPage() const override + { + return "Technic-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + bool eventFilter(QObject* watched, QEvent* event) override; + +private: + void suggestCurrent(); + void metadataLoaded(); + +private slots: + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); + +private: + Ui::TechnicPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + Technic::ListModel* model = nullptr; + Technic::Modpack current; +}; diff --git a/application/pages/modplatform/twitch/TwitchPage.ui b/application/pages/modplatform/technic/TechnicPage.ui index c78d8ce0..2ca45dd2 100644 --- a/application/pages/modplatform/twitch/TwitchPage.ui +++ b/application/pages/modplatform/technic/TechnicPage.ui @@ -1,27 +1,65 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>TwitchPage</class> - <widget class="QWidget" name="TwitchPage"> + <class>TechnicPage</class> + <widget class="QWidget" name="TechnicPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>875</width> - <height>745</height> + <width>546</width> + <height>405</height> </rect> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLineEdit" name="searchEdit"/> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLineEdit" name="searchEdit"> + <property name="placeholderText"> + <string>Search and filter ...</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="searchButton"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + </layout> + </widget> </item> - <item row="0" column="1"> - <widget class="QPushButton" name="searchButton"> - <property name="text"> - <string>Search</string> + <item> + <widget class="QListView" name="packView"> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="iconSize"> + <size> + <width>48</width> + <height>48</height> + </size> </property> </widget> </item> - <item row="2" column="0" colspan="2"> + <item> <widget class="MCModInfoFrame" name="frame"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> @@ -37,22 +75,6 @@ </property> </widget> </item> - <item row="1" column="0" colspan="2"> - <widget class="QListView" name="packView"> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="iconSize"> - <size> - <width>48</width> - <height>48</height> - </size> - </property> - </widget> - </item> </layout> </widget> <customwidgets> diff --git a/application/pages/modplatform/twitch/TwitchData.h b/application/pages/modplatform/twitch/TwitchData.h deleted file mode 100644 index dd000b84..00000000 --- a/application/pages/modplatform/twitch/TwitchData.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include <QString> -#include <QList> - -namespace Twitch { - -struct ModpackAuthor { - QString name; - QString url; -}; - -struct ModpackFile { - int addonId; - int fileId; - QString version; - QString mcVersion; - QString downloadUrl; -}; - -struct Modpack -{ - bool broken = true; - int addonId = 0; - - QString name; - QString description; - QList<ModpackAuthor> authors; - QString mcVersion; - QString logoName; - QString logoUrl; - QString websiteUrl; - - ModpackFile latestFile; -}; -} - -Q_DECLARE_METATYPE(Twitch::Modpack) diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp deleted file mode 100644 index 1e9f9dbb..00000000 --- a/application/pages/modplatform/twitch/TwitchPage.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "TwitchPage.h" -#include "ui_TwitchPage.h" - -#include "MultiMC.h" -#include "dialogs/NewInstanceDialog.h" -#include <InstanceImportTask.h> -#include "TwitchModel.h" -#include <QKeyEvent> - -TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) -{ - ui->setupUi(this); - connect(ui->searchButton, &QPushButton::clicked, this, &TwitchPage::triggerSearch); - ui->searchEdit->installEventFilter(this); - model = new Twitch::ListModel(this); - ui->packView->setModel(model); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TwitchPage::onSelectionChanged); -} - -TwitchPage::~TwitchPage() -{ - delete ui; -} - -bool TwitchPage::eventFilter(QObject* watched, QEvent* event) -{ - if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); - if (keyEvent->key() == Qt::Key_Return) { - triggerSearch(); - keyEvent->accept(); - return true; - } - } - return QWidget::eventFilter(watched, event); -} - -bool TwitchPage::shouldDisplay() const -{ - return true; -} - -void TwitchPage::openedImpl() -{ - suggestCurrent(); -} - -void TwitchPage::triggerSearch() -{ - model->searchWithTerm(ui->searchEdit->text()); -} - -void TwitchPage::onSelectionChanged(QModelIndex first, QModelIndex second) -{ - if(!first.isValid()) - { - if(isOpened) - { - dialog->setSuggestedPack(); - } - ui->frame->clear(); - return; - } - - current = model->data(first, Qt::UserRole).value<Twitch::Modpack>(); - QString text = ""; - QString name = current.name; - - if (current.websiteUrl.isEmpty()) - text = name; - else - text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; - if (!current.authors.empty()) { - auto authorToStr = [](Twitch::ModpackAuthor & author) { - if(author.url.isEmpty()) { - return author.name; - } - return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); - }; - QStringList authorStrs; - for(auto & author: current.authors) { - authorStrs.push_back(authorToStr(author)); - } - text += tr(" by ") + authorStrs.join(", "); - } - - ui->frame->setModText(text); - ui->frame->setModDescription(current.description); - suggestCurrent(); -} - -void TwitchPage::suggestCurrent() -{ - if(!isOpened) - { - return; - } - if(current.broken) - { - dialog->setSuggestedPack(); - } - - dialog->setSuggestedPack(current.name, new InstanceImportTask(current.latestFile.downloadUrl)); - QString editedLogoName; - editedLogoName = "twitch_" + current.logoName.section(".", 0, 0); - model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); -} diff --git a/application/resources/MultiMC.ico b/application/resources/MultiMC.ico Binary files differindex 1846964e..a86a1f0d 100644 --- a/application/resources/MultiMC.ico +++ b/application/resources/MultiMC.ico diff --git a/application/resources/assets/underconstruction.png b/application/resources/assets/underconstruction.png Binary files differnew file mode 100644 index 00000000..6ae06476 --- /dev/null +++ b/application/resources/assets/underconstruction.png diff --git a/application/resources/multimc/16x16/patreon.png b/application/resources/multimc/16x16/patreon.png Binary files differindex cde2b326..9150c478 100644 --- a/application/resources/multimc/16x16/patreon.png +++ b/application/resources/multimc/16x16/patreon.png diff --git a/application/resources/multimc/22x22/patreon.png b/application/resources/multimc/22x22/patreon.png Binary files differindex b6235ad2..f2c2076c 100644 --- a/application/resources/multimc/22x22/patreon.png +++ b/application/resources/multimc/22x22/patreon.png diff --git a/application/resources/multimc/24x24/patreon.png b/application/resources/multimc/24x24/patreon.png Binary files differindex c1da080f..add80668 100644 --- a/application/resources/multimc/24x24/patreon.png +++ b/application/resources/multimc/24x24/patreon.png diff --git a/application/resources/multimc/32x32/patreon.png b/application/resources/multimc/32x32/patreon.png Binary files differindex f5ae8a5e..70085aa1 100644 --- a/application/resources/multimc/32x32/patreon.png +++ b/application/resources/multimc/32x32/patreon.png diff --git a/application/resources/multimc/48x48/patreon.png b/application/resources/multimc/48x48/patreon.png Binary files differindex 2708a85a..7aec4d7d 100644 --- a/application/resources/multimc/48x48/patreon.png +++ b/application/resources/multimc/48x48/patreon.png diff --git a/application/resources/multimc/64x64/patreon.png b/application/resources/multimc/64x64/patreon.png Binary files differindex 7b4814ec..ef5d690e 100644 --- a/application/resources/multimc/64x64/patreon.png +++ b/application/resources/multimc/64x64/patreon.png diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc index 4f039a99..249e8e28 100644 --- a/application/resources/multimc/multimc.qrc +++ b/application/resources/multimc/multimc.qrc @@ -11,12 +11,17 @@ <!-- REDDIT logo icon, needs reddit license! --> <file>scalable/reddit-alien.svg</file> - <!-- twitch logo icon --> - <file>scalable/twitch.svg</file> + <!-- Icon for CurseForge. Unknown license? --> + <file alias="32x32/flame.png">32x32/instances/flame.png</file> + <file alias="128x128/flame.png">128x128/instances/flame.png</file> <!-- technic logo icon --> <file>scalable/technic.svg</file> + <!-- ATLauncher logo icon (and related bits) --> + <file>scalable/atlauncher.svg</file> + <file>scalable/atlauncher-placeholder.png</file> + <!-- A proxy icon. Our own. SSSsss --> <file>scalable/proxy.svg</file> diff --git a/application/resources/multimc/scalable/atlauncher-placeholder.png b/application/resources/multimc/scalable/atlauncher-placeholder.png Binary files differnew file mode 100644 index 00000000..f4314c43 --- /dev/null +++ b/application/resources/multimc/scalable/atlauncher-placeholder.png diff --git a/application/resources/multimc/scalable/atlauncher.svg b/application/resources/multimc/scalable/atlauncher.svg new file mode 100644 index 00000000..1bb5f359 --- /dev/null +++ b/application/resources/multimc/scalable/atlauncher.svg @@ -0,0 +1,15 @@ +<svg viewBox="0 0 2084 2084" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" + stroke-linejoin="round" stroke-miterlimit="2"> + <g fill-rule="nonzero"> + <path d="M1041.67 81.38l272.437 159.032-825.246 478.685-272.438-157.971L1041.67 81.38zm87.28 371.074l274.024-159.032 463.937 271.945-276.14 153.73-461.821-266.643z" + fill="#3b3b3b"/> + <path d="M216.42 561.126v961.081l825.247 479.746V1684.95l-551.222-321.774-1.587-644.079L216.42 561.126z" + fill="#2e2e2e"/> + <path d="M1866.91 1517.97l-825.246 483.986v-317.003l550.164-320.714-1.058-645.139 276.14-153.73v952.6z" + fill="#333"/> + <path d="M1590.77 719.097l-549.106 310.112v165.393l214.246-122.984v488.757l138.599-81.106V989.451l196.261-115.563V719.097z" + fill="#89c236"/> + <path d="M488.858 719.097l1.587 644.079 152.353 90.118v-198.79l230.645 132.527v199.319l168.753 98.6v-655.741L488.858 719.097zm383.527 531.166l-227.471-131.466v-150.02l227.471 127.225v154.261z" + fill="#7baf31"/> + </g> +</svg> diff --git a/application/resources/multimc/scalable/technic.svg b/application/resources/multimc/scalable/technic.svg index 827b590a..91cbd3d7 100644 --- a/application/resources/multimc/scalable/technic.svg +++ b/application/resources/multimc/scalable/technic.svg @@ -1 +1,13 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2479" viewBox="0 0 1464.248 1452.156"><path fill="#1389D2" d="M644.592 5.192c159.049-19.094 324.188 14.764 461.941 96.756 143.299 84.188 256.41 218.769 313.509 374.945 62.353 168.133 58.63 359.689-10.432 525.205-63.691 154.529-182.775 285.021-330.227 363.417-149.71 80.709-327.849 105.845-494.333 71.979-172.465-34.279-331.022-133.533-437.232-273.729C51.059 1037.725-2.015 878.924.058 719.941c.548-167.949 63.08-334.559 172.343-461.998C290.451 118.177 462.794 25.934 644.592 5.192m-120.67 181.494c-131.408 49.538-243.479 148.488-308.511 273.003-60.641 114.509-80.038 249.94-54.906 377.018 25.076 130.193 97.609 250.192 200.162 334.012 101.76 84.247 232.924 131.896 365.063 132.754-.795-37.831-.366-75.591-1.04-113.416-.792-31.415-30.866-53.744-59.722-58.563-.917 28.305.119 62.895-24.771 82.113-23.731 16.288-55.638 15.919-82.479 8.782-27.149-7.626-42.216-35.808-43.011-62.532-1.647-40.565-.244-81.196-.729-121.828-10.86-.059-21.781-.059-32.702-.059-.06-35.442-.06-70.892 0-106.273 10.92-.065 21.84-.125 32.762-.125 0-12.076 0-24.093-.06-36.115-14.033-4.635-30.263-10.188-35.934-25.379-14.033-33.186 3.417-70.462-10.188-103.891-13.911-35.203-27.759-74.125-14.825-111.888 7.992-26.598 34.042-43.56 61.007-45.694-.06-76.808 0-153.553-.06-230.358-.429-40.754 16.288-82.725 48.924-108.164 37.215-29.525 86.57-37.092 132.812-36.786 102.921.061 203.701 48.864 270.2 126.893-.124 18.362 0 36.727-.124 55.149-92.185.061-184.359-.063-276.54 0-10.006-1.828-19.523 7.505-17.754 17.569.304 58.812-.549 117.682-.308 176.492 17.021.061 34.104.304 51.125-.488 2.866-11.774 6.038-23.425 9.454-35.019 74.982 18.424 149.893 36.789 224.809 55.335-2.502 8.6-4.938 17.266-7.382 25.927-6.588 2.5-13.177 5.001-19.767 7.504-2.621 203.154-4.331 406.363-6.648 609.514 176.251-60.091 319.795-208.828 369.755-388.73 39.415-137.021 25.262-288.622-39.527-415.699-63.628-126.646-175.579-227.98-307.72-279.104-133.783-52.65-287.092-53.321-421.365-1.954m140.741 710.665c0 11.401 0 22.817.063 34.221 10.125.06 20.253.06 30.381.06.061 35.448.061 70.896 0 106.34-10.128 0-20.315 0-30.442.059v32.333c19.949.06 39.961.06 59.909-.061-.917-57.646-.365-115.305-1.283-172.95a9391.232 9391.232 0 0 0-58.628-.002z"/></svg>
\ No newline at end of file +<?xml version="1.0" encoding="UTF-8"?> +<svg width="2546.7" height="2526" version="1.1" viewBox="0 0 1491.6 1479.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <metadata> + <rdf:RDF> + <cc:Work rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> + <dc:title/> + </cc:Work> + </rdf:RDF> + </metadata> + <path d="m658.25 18.964c159.05-19.094 324.19 14.764 461.94 96.756 143.3 84.188 256.41 218.77 313.51 374.94 62.353 168.13 58.63 359.69-10.432 525.21-63.691 154.53-182.78 285.02-330.23 363.42-149.71 80.709-327.85 105.84-494.33 71.979-172.46-34.279-331.02-133.53-437.23-273.73-96.759-126.04-149.83-284.84-147.76-443.82 0.548-167.95 63.08-334.56 172.34-462 118.05-139.77 290.39-232.01 472.19-252.75m-120.67 181.49c-131.41 49.538-243.48 148.49-308.51 273-60.641 114.51-80.038 249.94-54.906 377.02 25.076 130.19 97.609 250.19 200.16 334.01 101.76 84.247 232.92 131.9 365.06 132.75-0.795-37.831-0.366-75.591-1.04-113.42-0.792-31.415-30.866-53.744-59.722-58.563-0.917 28.305 0.119 62.895-24.771 82.113-23.731 16.288-55.638 15.919-82.479 8.782-27.149-7.626-42.216-35.808-43.011-62.532-1.647-40.565-0.244-81.196-0.729-121.83-10.86-0.059-21.781-0.059-32.702-0.059-0.06-35.442-0.06-70.892 0-106.27 10.92-0.065 21.84-0.125 32.762-0.125 0-12.076 0-24.093-0.06-36.115-14.033-4.635-30.263-10.188-35.934-25.379-14.033-33.186 3.417-70.462-10.188-103.89-13.911-35.203-27.759-74.125-14.825-111.89 7.992-26.598 34.042-43.56 61.007-45.694-0.06-76.808 0-153.55-0.06-230.36-0.429-40.754 16.288-82.725 48.924-108.16 37.215-29.525 86.57-37.092 132.81-36.786 102.92 0.061 203.7 48.864 270.2 126.89-0.124 18.362 0 36.727-0.124 55.149-92.185 0.061-184.36-0.063-276.54 0-10.006-1.828-19.523 7.505-17.754 17.569 0.304 58.812-0.549 117.68-0.308 176.49 17.021 0.061 34.104 0.304 51.125-0.488 2.866-11.774 6.038-23.425 9.454-35.019 74.982 18.424 149.89 36.789 224.81 55.335-2.502 8.6-4.938 17.266-7.382 25.927-6.588 2.5-13.177 5.001-19.767 7.504-2.621 203.15-4.331 406.36-6.648 609.51 176.25-60.091 319.8-208.83 369.76-388.73 39.415-137.02 25.262-288.62-39.527-415.7-63.628-126.65-175.58-227.98-307.72-279.1-133.78-52.65-287.09-53.321-421.36-1.954m140.74 710.66c0 11.401 0 22.817 0.063 34.221 10.125 0.06 20.253 0.06 30.381 0.06 0.061 35.448 0.061 70.896 0 106.34-10.128 0-20.315 0-30.442 0.059v32.333c19.949 0.06 39.961 0.06 59.909-0.061-0.917-57.646-0.365-115.31-1.283-172.95a9391.2 9391.2 0 0 0-58.628 0z" fill="#1389d2" stroke="#000" stroke-width="27.532"/> +</svg> diff --git a/application/resources/multimc/scalable/twitch.svg b/application/resources/multimc/scalable/twitch.svg deleted file mode 100644 index 80999380..00000000 --- a/application/resources/multimc/scalable/twitch.svg +++ /dev/null @@ -1,63 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - id="Layer_1" - data-name="Layer 1" - viewBox="0 0 134 134" - version="1.1" - sodipodi:docname="twitch.svg" - inkscape:version="0.92.2 2405546, 2018-03-11" - width="134" - height="134"> - <metadata - id="metadata13"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title>Glitch</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1414" - inkscape:window-height="944" - id="namedview11" - showgrid="false" - inkscape:zoom="1.761194" - inkscape:cx="-109.17797" - inkscape:cy="66.999998" - inkscape:window-x="2640" - inkscape:window-y="554" - inkscape:window-maximized="0" - inkscape:current-layer="Layer_1" /> - <defs - id="defs4"> - <style - id="style2">.cls-1{fill:#6441a4;fill-rule:evenodd;}</style> - </defs> - <title - id="title6">Glitch</title> - <path - class="cls-1" - d="M 9,0 0,23 v 94 h 32 v 17 H 50 L 67,117 H 93 L 128,82 V 0 Z M 116,76 96,96 H 64 L 47,113 V 96 H 20 V 12 h 96 z M 96,35 V 70 H 84 V 35 Z M 64,35 V 70 H 52 V 35 Z" - id="path8" - style="fill:#6441a4;fill-opacity:1;fill-rule:evenodd" - inkscape:connector-curvature="0" /> -</svg> diff --git a/application/setupwizard/SetupWizard.h b/application/setupwizard/SetupWizard.h index 08b0d805..9b8adb4d 100644 --- a/application/setupwizard/SetupWizard.h +++ b/application/setupwizard/SetupWizard.h @@ -1,4 +1,4 @@ -/* Copyright 2017-2019 MultiMC Contributors +/* Copyright 2017-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. diff --git a/application/themes/SystemTheme.cpp b/application/themes/SystemTheme.cpp index 00b2300d..49b1afaa 100644 --- a/application/themes/SystemTheme.cpp +++ b/application/themes/SystemTheme.cpp @@ -6,16 +6,19 @@ SystemTheme::SystemTheme() { + qDebug() << "Determining System Theme..."; const auto & style = QApplication::style(); systemPalette = style->standardPalette(); QString lowerThemeName = style->objectName(); - qDebug() << systemTheme; + qDebug() << "System theme seems to be:" << lowerThemeName; QStringList styles = QStyleFactory::keys(); for(auto &st: styles) { + qDebug() << "Considering theme from theme factory:" << st.toLower(); if(st.toLower() == lowerThemeName) { systemTheme = st; + qDebug() << "System theme has been determined to be:" << systemTheme; return; } } diff --git a/application/widgets/CustomCommands.h b/application/widgets/CustomCommands.h index 4d447f7b..8db991fa 100644 --- a/application/widgets/CustomCommands.h +++ b/application/widgets/CustomCommands.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2019 MultiMC Contributors +/* Copyright 2018-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. diff --git a/application/widgets/JavaSettingsWidget.cpp b/application/widgets/JavaSettingsWidget.cpp index a11dd1aa..7f53dc23 100644 --- a/application/widgets/JavaSettingsWidget.cpp +++ b/application/widgets/JavaSettingsWidget.cpp @@ -19,7 +19,7 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) { - m_availableMemory = Sys::getSystemRam() / Sys::megabyte; + m_availableMemory = Sys::getSystemRam() / Sys::mebibyte; goodIcon = MMC->getThemedIcon("status-good"); yellowIcon = MMC->getThemedIcon("status-yellow"); diff --git a/application/widgets/LabeledToolButton.cpp b/application/widgets/LabeledToolButton.cpp index ab273b65..ab2d3278 100644 --- a/application/widgets/LabeledToolButton.cpp +++ b/application/widgets/LabeledToolButton.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/LabeledToolButton.h b/application/widgets/LabeledToolButton.h index 136ebd23..51f99e9b 100644 --- a/application/widgets/LabeledToolButton.h +++ b/application/widgets/LabeledToolButton.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/LanguageSelectionWidget.h b/application/widgets/LanguageSelectionWidget.h index 03e29bd8..e65936db 100644 --- a/application/widgets/LanguageSelectionWidget.h +++ b/application/widgets/LanguageSelectionWidget.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/MCModInfoFrame.cpp b/application/widgets/MCModInfoFrame.cpp index 577b32a7..5b1f6230 100644 --- a/application/widgets/MCModInfoFrame.cpp +++ b/application/widgets/MCModInfoFrame.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. @@ -135,6 +135,7 @@ void MCModInfoFrame::setModDescription(QString text) ui->label_ModDescription->setOpenExternalLinks(false); ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); desc = text; + // This allows injecting HTML here. labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); } diff --git a/application/widgets/MCModInfoFrame.h b/application/widgets/MCModInfoFrame.h index 51aa4489..0b7ef537 100644 --- a/application/widgets/MCModInfoFrame.h +++ b/application/widgets/MCModInfoFrame.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/ModListView.cpp b/application/widgets/ModListView.cpp index 99972a40..c8ccd292 100644 --- a/application/widgets/ModListView.cpp +++ b/application/widgets/ModListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/ModListView.h b/application/widgets/ModListView.h index 5a07e868..881e092f 100644 --- a/application/widgets/ModListView.h +++ b/application/widgets/ModListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/PageContainer.cpp b/application/widgets/PageContainer.cpp index 376e119b..05a5e6b4 100644 --- a/application/widgets/PageContainer.cpp +++ b/application/widgets/PageContainer.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/PageContainer.h b/application/widgets/PageContainer.h index 925f4ba3..976d34e9 100644 --- a/application/widgets/PageContainer.h +++ b/application/widgets/PageContainer.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/PageContainer_p.h b/application/widgets/PageContainer_p.h index 4a5a9239..da1a66f4 100644 --- a/application/widgets/PageContainer_p.h +++ b/application/widgets/PageContainer_p.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/ServerStatus.cpp b/application/widgets/ServerStatus.cpp index ce0aed9c..87c34f70 100644 --- a/application/widgets/ServerStatus.cpp +++ b/application/widgets/ServerStatus.cpp @@ -65,7 +65,7 @@ ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, addStatus("authserver.mojang.com", tr("Auth")); addLine(); - addStatus("sessionserver.mojang.com", tr("Session")); + addStatus("session.minecraft.net", tr("Session")); addLine(); addStatus("textures.minecraft.net", tr("Skins")); addLine(); diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp index fdcb84e6..8424fedd 100644 --- a/application/widgets/VersionListView.cpp +++ b/application/widgets/VersionListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/VersionListView.h b/application/widgets/VersionListView.h index 37f7b27e..4153b314 100644 --- a/application/widgets/VersionListView.h +++ b/application/widgets/VersionListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. diff --git a/application/widgets/VersionSelectWidget.h b/application/widgets/VersionSelectWidget.h index 701f568e..0a649408 100644 --- a/application/widgets/VersionSelectWidget.h +++ b/application/widgets/VersionSelectWidget.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2019 MultiMC Contributors +/* 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. |