diff options
Diffstat (limited to 'application')
250 files changed, 9014 insertions, 3584 deletions
diff --git a/application/BuildConfig.cpp.in b/application/BuildConfig.cpp.in deleted file mode 100644 index a1d236b2..00000000 --- a/application/BuildConfig.cpp.in +++ /dev/null @@ -1,54 +0,0 @@ -#include "BuildConfig.h" -#include <QObject> - -Config BuildConfig; - -Config::Config() -{ - // Version information - VERSION_MAJOR = @MultiMC_VERSION_MAJOR@; - VERSION_MINOR = @MultiMC_VERSION_MINOR@; - VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@; - VERSION_BUILD = @MultiMC_VERSION_BUILD@; - - BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@"; - CHANLIST_URL = "@MultiMC_CHANLIST_URL@"; - ANALYTICS_ID = "@MultiMC_ANALYTICS_ID@"; - NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@"; - FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@"; - - GIT_COMMIT = "@MultiMC_GIT_COMMIT@"; - GIT_REFSPEC = "@MultiMC_GIT_REFSPEC@"; - if(GIT_REFSPEC.startsWith("refs/heads/") && !CHANLIST_URL.isEmpty() && VERSION_BUILD >= 0) - { - VERSION_CHANNEL = GIT_REFSPEC; - VERSION_CHANNEL.remove("refs/heads/"); - UPDATER_ENABLED = true; - } - else - { - VERSION_CHANNEL = QObject::tr("custom"); - } - - VERSION_STR = "@MultiMC_VERSION_STRING@"; - NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@"; - PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@"; -} - -QString Config::printableVersionString() const -{ - QString vstr = QString("%1.%2.%3").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR), QString::number(VERSION_HOTFIX)); - - // If the build is not a main release, append the channel - if(VERSION_CHANNEL != "stable") - { - vstr += "-" + VERSION_CHANNEL; - } - - // if a build number is set, also add it to the end - if(VERSION_BUILD >= 0) - { - vstr += "-" + QString::number(VERSION_BUILD); - } - return vstr; -} diff --git a/application/BuildConfig.h b/application/BuildConfig.h deleted file mode 100644 index 05fff490..00000000 --- a/application/BuildConfig.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once -#include <QString> - -/** - * \brief The Config class holds all the build-time information passed from the build system. - */ -class Config -{ -public: - Config(); - /// The major version number. - int VERSION_MAJOR; - /// The minor version number. - int VERSION_MINOR; - /// The hotfix number. - int VERSION_HOTFIX; - /// The build number. - int VERSION_BUILD; - - /** - * The version channel - * This is used by the updater to determine what channel the current version came from. - */ - QString VERSION_CHANNEL; - - bool UPDATER_ENABLED = false; - - /// A short string identifying this build's platform. For example, "lin64" or "win32". - QString BUILD_PLATFORM; - - /// URL for the updater's channel - QString CHANLIST_URL; - - /// Google analytics ID - QString ANALYTICS_ID; - - /// URL for notifications - QString NOTIFICATION_URL; - - /// Used for matching notifications - QString FULL_VERSION_STR; - - /// The git commit hash of this build - QString GIT_COMMIT; - - /// The git refspec of this build - QString GIT_REFSPEC; - - /// This is printed on start to standard output - QString VERSION_STR; - - /** - * This is used to fetch the news RSS feed. - * It defaults in CMakeLists.txt to "http://multimc.org/rss.xml" - */ - QString NEWS_RSS_URL; - - /** - * API key you can get from paste.ee when you register an account - */ - QString PASTE_EE_KEY; - - /** - * \brief Converts the Version to a string. - * \return The version number in string format (major.minor.revision.build). - */ - QString printableVersionString() const; -}; - -extern Config BuildConfig; diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 2ba23828..ab2b9960 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -1,8 +1,5 @@ project(application) -######## Configure the file with build properties ######## -configure_file("${PROJECT_SOURCE_DIR}/BuildConfig.cpp.in" "${PROJECT_BINARY_DIR}/BuildConfig.cpp") - ################################ FILES ################################ ######## Sources and headers ######## @@ -11,8 +8,6 @@ SET(MULTIMC_SOURCES main.cpp MultiMC.h MultiMC.cpp - BuildConfig.h - ${PROJECT_BINARY_DIR}/BuildConfig.cpp UpdateController.cpp UpdateController.h @@ -64,9 +59,6 @@ SET(MULTIMC_SOURCES themes/SystemTheme.cpp themes/SystemTheme.h - # GUI - settings-specific wrappers for paged dialog - SettingsUI.h - # Processes LaunchController.h LaunchController.cpp @@ -84,14 +76,14 @@ SET(MULTIMC_SOURCES pages/BasePageProvider.h # GUI - instance pages + pages/instance/GameOptionsPage.cpp + pages/instance/GameOptionsPage.h pages/instance/VersionPage.cpp pages/instance/VersionPage.h pages/instance/TexturePackPage.h pages/instance/ResourcePackPage.h pages/instance/ModFolderPage.cpp pages/instance/ModFolderPage.h - pages/instance/NewModFolderPage.cpp - pages/instance/NewModFolderPage.h pages/instance/NotesPage.cpp pages/instance/NotesPage.h pages/instance/LogPage.cpp @@ -118,6 +110,8 @@ SET(MULTIMC_SOURCES pages/global/ExternalToolsPage.h pages/global/JavaPage.cpp pages/global/JavaPage.h + pages/global/LanguagePage.cpp + pages/global/LanguagePage.h pages/global/MinecraftPage.cpp pages/global/MinecraftPage.h pages/global/MultiMCPage.cpp @@ -126,20 +120,42 @@ SET(MULTIMC_SOURCES pages/global/ProxyPage.h pages/global/PasteEEPage.cpp pages/global/PasteEEPage.h - pages/global/PackagesPage.cpp - pages/global/PackagesPage.h # GUI - platform pages pages/modplatform/VanillaPage.cpp pages/modplatform/VanillaPage.h - pages/modplatform/FTBPage.cpp - pages/modplatform/FTBPage.h - pages/modplatform/FtbListModel.h - pages/modplatform/FtbListModel.cpp - pages/modplatform/TwitchPage.cpp - pages/modplatform/TwitchPage.h - pages/modplatform/TechnicPage.cpp - pages/modplatform/TechnicPage.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/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 @@ -160,8 +176,6 @@ SET(MULTIMC_SOURCES dialogs/IconPickerDialog.h dialogs/LoginDialog.cpp dialogs/LoginDialog.h - dialogs/ModEditDialogCommon.cpp - dialogs/ModEditDialogCommon.h dialogs/NewComponentDialog.cpp dialogs/NewComponentDialog.h dialogs/NewInstanceDialog.cpp @@ -185,6 +199,8 @@ SET(MULTIMC_SOURCES widgets/Common.h widgets/CustomCommands.cpp widgets/CustomCommands.h + widgets/DropLabel.cpp + widgets/DropLabel.h widgets/FocusLineEdit.cpp widgets/FocusLineEdit.h widgets/IconLabel.cpp @@ -193,6 +209,8 @@ SET(MULTIMC_SOURCES widgets/JavaSettingsWidget.h widgets/LabeledToolButton.cpp widgets/LabeledToolButton.h + widgets/LanguageSelectionWidget.cpp + widgets/LanguageSelectionWidget.h widgets/LineSeparator.cpp widgets/LineSeparator.h widgets/LogView.cpp @@ -212,10 +230,15 @@ SET(MULTIMC_SOURCES widgets/VersionSelectWidget.h widgets/ProgressWidget.h widgets/ProgressWidget.cpp + widgets/WideBar.h + widgets/WideBar.cpp # GUI - instance group view groupview/GroupedProxyModel.cpp groupview/GroupedProxyModel.h + groupview/AccessibleGroupView.cpp + groupview/AccessibleGroupView.h + groupview/AccessibleGroupView_p.h groupview/GroupView.cpp groupview/GroupView.h groupview/InstanceDelegate.cpp @@ -227,9 +250,9 @@ SET(MULTIMC_SOURCES ######## UIs ######## SET(MULTIMC_UIS # Instance pages + pages/instance/GameOptionsPage.ui pages/instance/VersionPage.ui pages/instance/ModFolderPage.ui - pages/instance/NewModFolderPage.ui pages/instance/LogPage.ui pages/instance/InstanceSettingsPage.ui pages/instance/NotesPage.ui @@ -247,15 +270,19 @@ SET(MULTIMC_UIS pages/global/MultiMCPage.ui pages/global/ProxyPage.ui pages/global/PasteEEPage.ui - pages/global/PackagesPage.ui # Platform pages pages/modplatform/VanillaPage.ui - pages/modplatform/FTBPage.ui - pages/modplatform/TwitchPage.ui - pages/modplatform/TechnicPage.ui + pages/modplatform/atlauncher/AtlPage.ui + pages/modplatform/ftb/FtbPage.ui + pages/modplatform/legacy_ftb/Page.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 @@ -277,7 +304,6 @@ SET(MULTIMC_UIS ) set(MULTIMC_QRCS - resources/assets/assets.qrc resources/backgrounds/backgrounds.qrc resources/multimc/multimc.qrc resources/pe_dark/pe_dark.qrc diff --git a/application/HoeDown.h b/application/HoeDown.h index ba94da8c..b9e06ffb 100644 --- a/application/HoeDown.h +++ b/application/HoeDown.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 ae279d94..3cb723c4 100644 --- a/application/InstancePageProvider.h +++ b/application/InstancePageProvider.h @@ -7,7 +7,6 @@ #include "pages/instance/LogPage.h" #include "pages/instance/VersionPage.h" #include "pages/instance/ModFolderPage.h" -#include "pages/instance/NewModFolderPage.h" #include "pages/instance/ResourcePackPage.h" #include "pages/instance/TexturePackPage.h" #include "pages/instance/NotesPage.h" @@ -17,6 +16,7 @@ #include "pages/instance/LegacyUpgradePage.h" #include "pages/instance/WorldListPage.h" #include "pages/instance/ServersPage.h" +#include "pages/instance/GameOptionsPage.h" #include "Env.h" @@ -38,25 +38,16 @@ public: if(onesix) { values.append(new VersionPage(onesix.get())); - if(ENV.isFeatureEnabled("NewModsPage")) - { - auto modsPage = new NewModFolderPage(onesix.get(), onesix->modsModel(), "mods", "loadermods", tr("Mods"), "Mods-page"); - modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); - values.append(modsPage); - } - else - { - auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods"); - modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); - values.append(modsPage); - } - + auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods"); + modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); + values.append(modsPage); values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods")); values.append(new ResourcePackPage(onesix.get())); values.append(new TexturePackPage(onesix.get())); values.append(new NotesPage(onesix.get())); - values.append(new WorldListPage(onesix.get(), onesix->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds")); - values.append(new ServersPage(onesix.get())); + values.append(new WorldListPage(onesix.get(), onesix->worldList())); + 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())); } @@ -65,7 +56,7 @@ public: { values.append(new LegacyUpgradePage(legacy)); values.append(new NotesPage(legacy.get())); - values.append(new WorldListPage(legacy.get(), legacy->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds")); + values.append(new WorldListPage(legacy.get(), legacy->worldList())); values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots"))); } auto logMatcher = inst->getLogFileMatcher(); diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp index 711141f2..015ffe1c 100644 --- a/application/InstanceWindow.cpp +++ b/application/InstanceWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -50,6 +50,7 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) m_container = new PageContainer(provider.get(), "console", this); m_container->setParentContainer(this); setCentralWidget(m_container); + setContentsMargins(0, 0, 0, 0); } // Add custom buttons to the page container layout. @@ -122,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); } @@ -135,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() @@ -144,15 +151,18 @@ void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() MMC->launch(m_instance, false, nullptr); } -void InstanceWindow::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc) +void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc) { m_proc = proc; } -void InstanceWindow::on_RunningState_changed(bool) +void InstanceWindow::on_RunningState_changed(bool running) { updateLaunchButtons(); m_container->refreshContainer(); + if(running) { + selectPage("log"); + } } void InstanceWindow::on_closeButton_clicked() diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h index c1d56143..cd7d2494 100644 --- a/application/InstanceWindow.h +++ b/application/InstanceWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -52,7 +52,7 @@ slots: void on_btnKillMinecraft_clicked(); void on_btnLaunchMinecraftOffline_clicked(); - void on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc); + void on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc); void on_RunningState_changed(bool running); void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); @@ -63,7 +63,7 @@ private: void updateLaunchButtons(); private: - std::shared_ptr<LaunchTask> m_proc; + shared_qobject_ptr<LaunchTask> m_proc; InstancePtr m_instance; bool m_doNotSave = false; PageContainer *m_container = nullptr; 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 0115bba4..ee764082 100644 --- a/application/LaunchController.cpp +++ b/application/LaunchController.cpp @@ -9,13 +9,15 @@ #include "InstanceWindow.h" #include "BuildConfig.h" #include "JavaCommon.h" -#include "SettingsUI.h" #include <QLineEdit> #include <QInputDialog> #include <tasks/Task.h> #include <minecraft/auth/YggdrasilTask.h> #include <launch/steps/TextPrint.h> #include <QStringList> +#include <QHostInfo> +#include <QList> +#include <QHostAddress> LaunchController::LaunchController(QObject *parent) : Task(parent) { @@ -25,7 +27,7 @@ void LaunchController::executeTask() { if (!m_instance) { - emitFailed(tr("No instance specified")); + emitFailed(tr("No instance specified!")); return; } @@ -53,7 +55,7 @@ void LaunchController::login() if (reply == QMessageBox::Yes) { // Open the account manager. - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), m_parentWidget, "accounts"); + MMC->ShowGlobalSettings(m_parentWidget, "accounts"); } } else if (account.get() == nullptr) @@ -75,7 +77,7 @@ void LaunchController::login() // if no account is selected, we bail if (!account.get()) { - emitFailed(tr("No account selected for launch")); + emitFailed(tr("No account selected for launch.")); return; } @@ -84,8 +86,7 @@ void LaunchController::login() // we loop until the user succeeds in logging in or gives up bool tryagain = true; // the failure. the default failure. - const QString needLoginAgain = tr("Your account is currently not logged in. Please enter " - "your password to log in again."); + const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. <br /> <br /> This could be caused by a password change."); QString failReason = needLoginAgain; while (tryagain) @@ -194,12 +195,12 @@ void LaunchController::launchInstance() if(!m_instance->reloadSettings()) { - QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile.")); + QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile.")); emitFailed(tr("Couldn't load the instance profile.")); 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.")); @@ -217,8 +218,47 @@ 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"; - m_launcher->prependStep(std::make_shared<TextPrint>(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC)); + // 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(); } @@ -234,8 +274,8 @@ void LaunchController::readyForLaunch() if (!m_profiler->check(&error)) { m_launcher->abort(); - QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error)); - emitFailed("Profiler startup failed"); + QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error)); + emitFailed("Profiler startup failed!"); return; } BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); @@ -246,7 +286,7 @@ void LaunchController::readyForLaunch() msg.setText(tr("The game launch is delayed until you press the " "button. This is the right time to setup the profiler, as the " "profiler server is running now.\n\n%1").arg(message)); - msg.setWindowTitle(tr("Waiting")); + msg.setWindowTitle(tr("Waiting.")); msg.setIcon(QMessageBox::Information); msg.addButton(tr("Launch"), QMessageBox::AcceptRole); msg.setModal(true); @@ -263,7 +303,7 @@ void LaunchController::readyForLaunch() msg.setModal(true); msg.exec(); m_launcher->abort(); - emitFailed("Profiler startup failed"); + emitFailed("Profiler startup failed!"); }); profilerInstance->beginProfiling(m_launcher); } diff --git a/application/LaunchController.h b/application/LaunchController.h index 1434dec9..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(); @@ -57,5 +63,6 @@ private: QWidget * m_parentWidget = nullptr; InstanceWindow *m_console = nullptr; AuthSessionPtr m_session; - std::shared_ptr <LaunchTask> m_launcher; + shared_qobject_ptr<LaunchTask> m_launcher; + MinecraftServerTargetPtr m_serverToJoin; }; diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index 4ee49b80..13a7c7ae 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2021 MultiMC Contributors * * Authors: Andrew Okin * Peterix @@ -56,7 +56,7 @@ #include <launch/LaunchTask.h> #include <minecraft/auth/MojangAccountList.h> #include <SkinUtils.h> -#include <net/URLConstants.h> +#include <BuildConfig.h> #include <net/NetJob.h> #include <net/Download.h> #include <news/NewsChecker.h> @@ -70,7 +70,6 @@ #include "InstanceProxyModel.h" #include "JavaCommon.h" #include "LaunchController.h" -#include "SettingsUI.h" #include "groupview/GroupView.h" #include "groupview/InstanceDelegate.h" #include "widgets/LabeledToolButton.h" @@ -289,6 +288,7 @@ public: foldersMenuButton->setPopupMode(QToolButton::InstantPopup); foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder")); + foldersMenuButton->setFocusPolicy(Qt::NoFocus); all_toolbuttons.append(&foldersMenuButton); QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow); foldersButtonAction->setDefaultWidget(foldersMenuButton); @@ -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")); @@ -346,6 +352,7 @@ public: helpMenuButton->setPopupMode(QToolButton::InstantPopup); helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); helpMenuButton->setIcon(MMC->getThemedIcon("help")); + helpMenuButton->setFocusPolicy(Qt::NoFocus); all_toolbuttons.append(&helpMenuButton); QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow); helpButtonAction->setDefaultWidget(helpMenuButton); @@ -574,9 +581,12 @@ public: { MainWindow->setObjectName(QStringLiteral("MainWindow")); } - MainWindow->resize(694, 563); + MainWindow->resize(800, 600); MainWindow->setWindowIcon(MMC->getThemedIcon("logo")); MainWindow->setWindowTitle("MultiMC 5"); +#ifndef QT_NO_ACCESSIBILITY + MainWindow->setAccessibleName("MultiMC"); +#endif createMainToolbar(MainWindow); @@ -584,7 +594,6 @@ public: centralWidget->setObjectName(QStringLiteral("centralWidget")); horizontalLayout = new QHBoxLayout(centralWidget); horizontalLayout->setSpacing(0); - horizontalLayout->setContentsMargins(11, 11, 11, 11); horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint); horizontalLayout->setContentsMargins(0, 0, 0, 0); @@ -653,6 +662,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow newsLabel->setIcon(MMC->getThemedIcon("news")); newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + newsLabel->setFocusPolicy(Qt::NoFocus); ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel); QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked); QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel); @@ -681,6 +691,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); view->setModel(proxymodel); + view->setSourceOfGroupCollapseStatus([](const QString & groupName)->bool { + return MMC->instances()->isGroupCollapsed(groupName); + }); + connect(view, &GroupView::groupStateChanged, MMC->instances().get(), &InstanceList::on_GroupStateChanged); ui->horizontalLayout->addWidget(view); } // The cat background @@ -692,7 +706,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow setCatBackground(cat_enable); } // start instance when double-clicked - connect(view, &GroupView::doubleClicked, this, &MainWindow::instanceActivated); + connect(view, &GroupView::activated, this, &MainWindow::instanceActivated); // track the selection -- update the instance toolbar connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged); @@ -703,6 +717,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow // model reset -> selection is invalid. All the instance pointers are wrong. connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad); + // handle newly added instances + connect(MMC->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest); + + // When the global settings page closes, we want to know about it and update our state + connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed); + m_statusLeft = new QLabel(tr("No instance selected"), this); m_statusRight = new ServerStatus(this); statusBar()->addPermanentWidget(m_statusLeft, 1); @@ -718,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); @@ -758,7 +777,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow for (auto profile : account->profiles()) { auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); - auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta); + auto action = Net::Download::makeCached(QUrl(BuildConfig.SKINS_BASE + profile.id + ".png"), meta); skin_dls.append(action); meta->setStale(true); } @@ -817,15 +836,37 @@ 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() { } +QMenu * MainWindow::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() ); + return filteredMenu; +} + void MainWindow::konamiTriggered() { - ENV.enableFeature("NewModsPage"); + // ENV.enableFeature("NewModsPage"); qDebug() << "Super Secret Mode ACTIVATED!"; } @@ -902,15 +943,21 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) void MainWindow::updateToolsMenu() { QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); + QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); + if(!m_selectedInstance || m_selectedInstance->isRunning()) { ui->actionLaunchInstance->setMenu(nullptr); + ui->actionLaunchInstanceOffline->setMenu(nullptr); launchButton->setPopupMode(QToolButton::InstantPopup); + launchOfflineButton->setPopupMode(QToolButton::InstantPopup); return; } QMenu *launchMenu = ui->actionLaunchInstance->menu(); + QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); launchButton->setPopupMode(QToolButton::MenuButtonPopup); + launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); if (launchMenu) { launchMenu->clear(); @@ -919,21 +966,39 @@ void MainWindow::updateToolsMenu() { launchMenu = new QMenu(this); } + if (launchOfflineMenu) { + launchOfflineMenu->clear(); + } + else + { + launchOfflineMenu = new QMenu(this); + } QAction *normalLaunch = launchMenu->addAction(tr("Launch")); + QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); connect(normalLaunch, &QAction::triggered, [this]() { - MMC->launch(m_selectedInstance); + MMC->launch(m_selectedInstance, true); }); - launchMenu->addSeparator()->setText(tr("Profilers")); + connect(normalLaunchOffline, &QAction::triggered, [this]() + { + MMC->launch(m_selectedInstance, false); + }); + QString profilersTitle = tr("Profilers"); + launchMenu->addSeparator()->setText(profilersTitle); + launchOfflineMenu->addSeparator()->setText(profilersTitle); for (auto profiler : MMC->profilers().values()) { QAction *profilerAction = launchMenu->addAction(profiler->name()); + QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); QString error; if (!profiler->check(&error)) { profilerAction->setDisabled(true); - profilerAction->setToolTip(tr("Profiler not setup correctly. Go into settings, \"External Tools\".")); + profilerOfflineAction->setDisabled(true); + QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); + profilerAction->setToolTip(profilerToolTip); + profilerOfflineAction->setToolTip(profilerToolTip); } else { @@ -941,9 +1006,14 @@ void MainWindow::updateToolsMenu() { MMC->launch(m_selectedInstance, true, profiler.get()); }); + connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() + { + MMC->launch(m_selectedInstance, false, profiler.get()); + }); } } ui->actionLaunchInstance->setMenu(launchMenu); + ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); } QString profileInUseFilter(const QString & profile, bool used) @@ -1091,10 +1161,12 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev) QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); switch (keyEvent->key()) { + /* case Qt::Key_Enter: case Qt::Key_Return: activateInstance(m_selectedInstance); return true; + */ case Qt::Key_Delete: on_actionDeleteInstance_triggered(); return true; @@ -1239,20 +1311,34 @@ void MainWindow::onCatToggled(bool state) MMC->settings()->set("TheCat", state); } +namespace { +template <typename T> +T non_stupid_abs(T in) +{ + if (in < 0) + return -in; + return in; +} +} + void MainWindow::setCatBackground(bool enabled) { if (enabled) { - view->setStyleSheet(R"( + QDateTime now = QDateTime::currentDateTime(); + QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); + ; + QString cat = (non_stupid_abs(now.daysTo(xmas)) <= 4) ? "catmas" : "kitteh"; + view->setStyleSheet(QString(R"( GroupView { - background-image: url(:/backgrounds/kitteh); + background-image: url(:/backgrounds/%1); background-attachment: fixed; background-clip: padding; background-position: top right; background-repeat: none; background-color:palette(base); -})"); +})").arg(cat)); } else { @@ -1281,7 +1367,7 @@ void MainWindow::runModalTask(Task *task) void MainWindow::instanceFromInstanceTask(InstanceTask *rawTask) { - std::unique_ptr<Task> task(MMC->instances()->wrapInstanceTask(rawTask)); + unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(rawTask)); runModalTask(task.get()); } @@ -1294,11 +1380,11 @@ void MainWindow::on_actionCopyInstance_triggered() if (!copyInstDlg.exec()) return; - auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves()); + auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime()); copyTask->setName(copyInstDlg.instName()); copyTask->setGroup(copyInstDlg.instGroup()); copyTask->setIcon(copyInstDlg.iconKey()); - std::unique_ptr<Task> task(MMC->instances()->wrapInstanceTask(copyTask)); + unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(copyTask)); runModalTask(task.get()); } @@ -1389,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() @@ -1478,7 +1564,12 @@ void MainWindow::deleteGroup() QString groupName = map["group"].toString(); if(!groupName.isEmpty()) { - MMC->instances()->deleteGroup(groupName); + auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1") + .arg(groupName), QMessageBox::Yes | QMessageBox::No); + if(reply == QMessageBox::Yes) + { + MMC->instances()->deleteGroup(groupName); + } } } @@ -1522,7 +1613,11 @@ void MainWindow::checkForUpdates() void MainWindow::on_actionSettings_triggered() { - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "global-settings"); + MMC->ShowGlobalSettings(this, "global-settings"); +} + +void MainWindow::globalSettingsClosed() +{ // FIXME: quick HACK to make this work. improve, optimize. MMC->instances()->loadList(); proxymodel->invalidate(); @@ -1558,22 +1653,22 @@ void MainWindow::on_actionScreenshots_triggered() void MainWindow::on_actionManageAccounts_triggered() { - SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "accounts"); + MMC->ShowGlobalSettings(this, "accounts"); } 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() { - DesktopServices::openUrl(QUrl("http://www.patreon.com/multimc")); + DesktopServices::openUrl(QUrl("https://www.patreon.com/multimc")); } void MainWindow::on_actionMoreNews_triggered() { - DesktopServices::openUrl(QUrl("http://multimc.org/posts.html")); + DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); } void MainWindow::newsButtonClicked() @@ -1585,7 +1680,7 @@ void MainWindow::newsButtonClicked() } else { - DesktopServices::openUrl(QUrl("http://multimc.org/posts.html")); + DesktopServices::openUrl(QUrl("https://multimc.org/posts.html")); } } @@ -1595,29 +1690,24 @@ void MainWindow::on_actionAbout_triggered() dialog.exec(); } -void MainWindow::on_mainToolBar_visibilityChanged(bool) -{ - // Don't allow hiding the main toolbar. - // This is the only way I could find to prevent it... :/ - ui->mainToolBar->setVisible(true); -} - void MainWindow::on_actionDeleteInstance_triggered() { if (!m_selectedInstance) { return; } + auto id = m_selectedInstance->id(); auto response = CustomMessageBox::selectable( this, tr("CAREFUL!"), tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()), QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No + QMessageBox::Yes | QMessageBox::No, + QMessageBox::No )->exec(); if (response == QMessageBox::Yes) { - MMC->instances()->deleteInstance(m_selectedInstance->id()); + MMC->instances()->deleteInstance(id); } } @@ -1634,19 +1724,7 @@ void MainWindow::on_actionRenameInstance_triggered() { if (m_selectedInstance) { - bool ok = false; - QString name(m_selectedInstance->name()); - name = QInputDialog::getText(this, tr("Instance name"), tr("Enter a new instance name."), QLineEdit::Normal, name, &ok); - - name = name.trimmed(); - if (name.length() > 0) - { - if (ok && name.length()) - { - m_selectedInstance->setName(name); - ui->renameButton->setText(name); - } - } + view->edit(view->currentIndex()); } } @@ -1687,7 +1765,7 @@ void MainWindow::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { - ui->retranslateUi(this); + retranslateUi(); } QMainWindow::changeEvent(event); } @@ -1791,6 +1869,11 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & } } +void MainWindow::instanceSelectRequest(QString id) +{ + setSelectedInstanceById(id); +} + void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { auto current = view->selectionModel()->currentIndex(); @@ -1840,7 +1923,7 @@ void MainWindow::checkInstancePathForProblems() warning.setDefaultButton(QMessageBox::Ok); warning.exec(); } - else if (pathfoldername.contains(QDir::tempPath())) + else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/")) { QMessageBox warning(this); warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath())); diff --git a/application/MainWindow.h b/application/MainWindow.h index 3f370fda..08c6b969 100644 --- a/application/MainWindow.h +++ b/application/MainWindow.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -57,9 +57,14 @@ public: void checkInstancePathForProblems(); void updatesAllowedChanged(bool allowed); + + void droppedURLs(QList<QUrl> urls); signals: void isClosing(); +protected: + QMenu * createPopupMenu() override; + private slots: void onCatToggled(bool); @@ -109,8 +114,6 @@ private slots: void newsButtonClicked(); - void on_mainToolBar_visibilityChanged(bool); - void on_actionLaunchInstance_triggered(); void on_actionLaunchInstanceOffline_triggered(); @@ -152,6 +155,8 @@ private slots: void instanceChanged(const QModelIndex ¤t, const QModelIndex &previous); + void instanceSelectRequest(QString id); + void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void selectionBad(); @@ -177,11 +182,13 @@ private slots: */ void downloadUpdates(GoUpdate::Status status); - void droppedURLs(QList<QUrl> urls); - void konamiTriggered(); + 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 3a9c281e..2dd4f83f 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -2,15 +2,19 @@ #include "BuildConfig.h" #include "MainWindow.h" #include "InstanceWindow.h" + +#include "groupview/AccessibleGroupView.h" +#include <QAccessible> + #include "pages/BasePageProvider.h" #include "pages/global/MultiMCPage.h" #include "pages/global/MinecraftPage.h" #include "pages/global/JavaPage.h" +#include "pages/global/LanguagePage.h" #include "pages/global/ProxyPage.h" #include "pages/global/ExternalToolsPage.h" #include "pages/global/AccountListPage.h" #include "pages/global/PasteEEPage.h" -#include "pages/global/PackagesPage.h" #include "pages/global/CustomCommandsPage.h" #include "themes/ITheme.h" @@ -30,6 +34,7 @@ #include <QNetworkAccessManager> #include <QTranslator> #include <QLibraryInfo> +#include <QList> #include <QStringList> #include <QDebug> #include <QStyleFactory> @@ -40,7 +45,6 @@ #include <minecraft/auth/MojangAccountList.h> #include "icons/IconList.h" #include "net/HttpMetaCache.h" -#include "net/URLConstants.h" #include "Env.h" #include "java/JavaUtils.h" @@ -65,6 +69,8 @@ #include <ganalytics.h> #include <sys.h> +#include "pagedialog/PageDialog.h" + #if defined Q_OS_WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -139,6 +145,27 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) startTime = QDateTime::currentDateTime(); +#ifdef Q_OS_LINUX + { + QFile osrelease("/proc/sys/kernel/osrelease"); + if (osrelease.open(QFile::ReadOnly | QFile::Text)) { + QTextStream in(&osrelease); + auto contents = in.readAll(); + if( + contents.contains("WSL", Qt::CaseInsensitive) || + contents.contains("Microsoft", Qt::CaseInsensitive) + ) { + showFatalErrorMessage( + "Unsupported system detected!", + "Linux-on-Windows distributions are not supported.\n\n" + "Please use the Windows MultiMC binary when playing on Windows." + ); + return; + } + } + } +#endif + // Don't quit on hiding the last window this->setQuitOnLastWindowClosed(false); @@ -150,23 +177,32 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) // --help parser.addSwitch("help"); parser.addShortOpt("help", 'h'); - parser.addDocumentation("help", "display this help and exit."); + parser.addDocumentation("help", "Display this help and exit."); // --version parser.addSwitch("version"); parser.addShortOpt("version", 'V'); - parser.addDocumentation("version", "display program version and exit."); + parser.addDocumentation("version", "Display program version and exit."); // --dir parser.addOption("dir"); parser.addShortOpt("dir", 'd'); - parser.addDocumentation("dir", "use the supplied folder as MultiMC root instead of " + parser.addDocumentation("dir", "Use the supplied folder as MultiMC root instead of " "the binary location (use '.' for current)"); // --launch parser.addOption("launch"); parser.addShortOpt("launch", 'l'); - parser.addDocumentation("launch", "launch the specified instance (by instance ID)"); + 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"); + parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts"); + // --import + parser.addOption("import"); + parser.addShortOpt("import", 'I'); + parser.addDocumentation("import", "Import instance from specified zip (local path or URL)"); // parse the arguments try @@ -176,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; } @@ -200,7 +237,9 @@ 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(); QString origcwdPath = QDir::currentPath(); QString binPath = applicationDirPath(); @@ -260,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. @@ -272,13 +318,28 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived); if(m_peerInstance->isClient()) { + int timeout = 2000; + if(m_instanceIdToLaunch.isEmpty()) { - m_peerInstance->sendMessage("activate", 2000); + m_peerInstance->sendMessage("activate", timeout); + + if(!m_zipToImport.isEmpty()) + { + m_peerInstance->sendMessage("import " + m_zipToImport.toString(), timeout); + } } else { - m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000); + 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; @@ -339,7 +400,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) ); #endif - qDebug() << "MultiMC 5, (c) 2013-2018 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; @@ -359,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."; } @@ -375,7 +440,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) auto payload = appID.toString().toUtf8(); if(check.write(payload) != payload.size()) { - qWarning() << "Could not write into" << liveCheckFile; + qWarning() << "Could not write into" << liveCheckFile << "!"; check.remove(); break; } @@ -466,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"); @@ -517,9 +591,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) m_globalSettingsProvider->addPage<MultiMCPage>(); m_globalSettingsProvider->addPage<MinecraftPage>(); m_globalSettingsProvider->addPage<JavaPage>(); + m_globalSettingsProvider->addPage<LanguagePage>(); m_globalSettingsProvider->addPage<CustomCommandsPage>(); m_globalSettingsProvider->addPage<ProxyPage>(); - // m_globalSettingsProvider->addPage<PackagesPage>(); m_globalSettingsProvider->addPage<ExternalToolsPage>(); m_globalSettingsProvider->addPage<AccountListPage>(); m_globalSettingsProvider->addPage<PasteEEPage>(); @@ -527,6 +601,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Settings loaded."; } +#ifndef QT_NO_ACCESSIBILITY + QAccessible::installFactory(groupViewAccessibleFactory); +#endif /* !QT_NO_ACCESSIBILITY */ + // load translations { m_translations.reset(new TranslationsModel("translations")); @@ -550,7 +628,8 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { ":/icons/multimc/32x32/instances/", ":/icons/multimc/50x50/instances/", - ":/icons/multimc/128x128/instances/" + ":/icons/multimc/128x128/instances/", + ":/icons/multimc/scalable/instances/" }; m_icons.reset(new IconList(instFolders, setting->get().toString())); connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value) @@ -589,12 +668,12 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) { auto InstDirSetting = m_settings->getSetting("InstanceDir"); // instance path: check for problems with '!' in instance path and warn the user in the log - // and rememer that we have to show him a dialog when the gui starts (if it does so) + // and remember that we have to show him a dialog when the gui starts (if it does so) QString instDir = InstDirSetting->get().toString(); qDebug() << "Instance path : " << instDir; if (FS::checkProblemticPathJava(QDir(instDir))) { - qWarning() << "Your instance path contains \'!\' and this is known to cause java problems"; + qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!"; } m_instances.reset(new InstanceList(m_settings, instDir, this)); connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged); @@ -790,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; } } @@ -801,6 +891,11 @@ void MultiMC::performMainStartupAction() showMainWindow(false); qDebug() << "<> Main window shown."; } + if(!m_zipToImport.isEmpty()) + { + qDebug() << "<> Importing instance from zip:" << m_zipToImport; + m_mainWindow->droppedURLs({ m_zipToImport }); + } } void MultiMC::showFatalErrorMessage(const QString& title, const QString& content) @@ -837,18 +932,66 @@ void MultiMC::messageReceived(const QString& message) qDebug() << "Received message" << message << "while still initializing. It will be ignored."; return; } - if(message == "activate") + + QString command = message.section(' ', 0, 0); + + if(command == "activate") { showMainWindow(); } - else + else if(command == "import") + { + QString arg = message.section(' ', 1); + if(arg.isEmpty()) + { + qWarning() << "Received" << command << "message without a zip path/URL."; + return; + } + m_mainWindow->droppedURLs({ QUrl(arg) }); + } + else if(command == "launch") { - auto inst = instances()->getInstanceById(message); + QString arg = message.section(' ', 1); + if(arg.isEmpty()) + { + qWarning() << "Received" << command << "message without an instance ID."; + return; + } + auto inst = instances()->getInstanceById(arg); if(inst) { 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; + } } void MultiMC::analyticsSettingChanged(const Setting&, QVariant value) @@ -932,11 +1075,15 @@ 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."; + qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; } else if(instance->canLaunch()) { @@ -954,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); @@ -985,7 +1133,7 @@ bool MultiMC::kill(InstancePtr instance) { if (!instance->isRunning()) { - qWarning() << "Attempted to kill instance" << instance->id() << "which isn't running."; + qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running."; return false; } auto & extras = m_instanceExtras[instance->id()]; @@ -1085,6 +1233,20 @@ void MultiMC::controllerFailed(const QString& error) } } +void MultiMC::ShowGlobalSettings(class QWidget* parent, QString open_page) +{ + if(!m_globalSettingsProvider) { + return; + } + emit globalSettingsAboutToOpen(); + { + SettingsObject::Lock lock(MMC->settings()); + PageDialog dlg(m_globalSettingsProvider.get(), open_page, parent); + dlg.exec(); + } + emit globalSettingsClosed(); +} + MainWindow* MultiMC::showMainWindow(bool minimized) { if(m_mainWindow) diff --git a/application/MultiMC.h b/application/MultiMC.h index 9fe98aa1..af2b41c1 100644 --- a/application/MultiMC.h +++ b/application/MultiMC.h @@ -6,10 +6,13 @@ #include <QFlag> #include <QIcon> #include <QDateTime> +#include <QUrl> #include <updater/GoUpdate.h> #include <BaseInstance.h> +#include "minecraft/launch/MinecraftServerTarget.h" + class LaunchController; class LocalPeer; class InstanceWindow; @@ -65,11 +68,6 @@ public: return m_settings; } - std::shared_ptr<GenericPageProvider> globalSettingsPages() const - { - return m_globalSettingsProvider; - } - qint64 timeSinceStart() const { return startTime.msecsTo(QDateTime::currentDateTime()); @@ -146,11 +144,20 @@ public: void updateIsRunning(bool running); bool updatesAreAllowed(); + void ShowGlobalSettings(class QWidget * parent, QString open_page = QString()); + signals: void updateAllowedChanged(bool status); + void globalSettingsAboutToOpen(); + 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,8 @@ 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/SettingsUI.h b/application/SettingsUI.h deleted file mode 100644 index 474bc1ab..00000000 --- a/application/SettingsUI.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "pages/BasePageProvider.h" -#include "MultiMC.h" -#include "pagedialog/PageDialog.h" -#include "InstancePageProvider.h" -#include <settings/SettingsObject.h> -#include <BaseInstance.h> - -/* - * FIXME: this is a fragment. find a better place for it. - */ -namespace SettingsUI -{ -template <typename T> -void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QString()) -{ - auto provider = std::dynamic_pointer_cast<BasePageProvider>(raw_provider); - if(!provider) - return; - { - SettingsObject::Lock lock(MMC->settings()); - PageDialog dlg(provider.get(), open_page, parent); - dlg.exec(); - } -} -} diff --git a/application/VersionProxyModel.cpp b/application/VersionProxyModel.cpp index 338a6064..5587136f 100644 --- a/application/VersionProxyModel.cpp +++ b/application/VersionProxyModel.cpp @@ -126,7 +126,14 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const switch(column) { case Name: - return sourceModel()->data(parentIndex, BaseVersionList::VersionRole); + { + QString version = sourceModel()->data(parentIndex, BaseVersionList::VersionRole).toString(); + if(version == m_currentVersion) + { + return tr("%1 (installed)").arg(version); + } + return version; + } case ParentVersion: return sourceModel()->data(parentIndex, BaseVersionList::ParentVersionRole); case Branch: @@ -432,5 +439,9 @@ void VersionProxyModel::sourceRowsRemoved(const QModelIndex& parent, int first, endRemoveRows(); } +void VersionProxyModel::setCurrentVersion(const QString &version) +{ + m_currentVersion = version; +} #include "VersionProxyModel.moc" diff --git a/application/VersionProxyModel.h b/application/VersionProxyModel.h index 457acfeb..8991c31b 100644 --- a/application/VersionProxyModel.h +++ b/application/VersionProxyModel.h @@ -42,6 +42,7 @@ public: void clearFilters(); QModelIndex getRecommended() const; QModelIndex getVersion(const QString & version) const; + void setCurrentVersion(const QString &version); private slots: void sourceDataChanged(const QModelIndex &source_top_left,const QModelIndex &source_bottom_right); @@ -62,4 +63,5 @@ private: VersionFilterModel * filterModel; bool hasRecommended = false; bool hasLatest = false; + QString m_currentVersion; }; diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp index 411f10fc..c97c471e 100644 --- a/application/dialogs/AboutDialog.cpp +++ b/application/dialogs/AboutDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -23,56 +23,44 @@ #include "HoeDown.h" +namespace { // Credits // This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument... -static QString getCreditsHtml(QStringList patrons) +QString getCreditsHtml(QStringList patrons) { - QString creditsHtml = QObject::tr( - "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/REC-html40/strict.dtd'>" - "<html>" - "" - "<head>" - "<meta name='qrichtext' content='1' />" - "<style type='text/css'>" - "p { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }" - "</style>" - "</head>" - "" - "<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>" - "" - "<h3>MultiMC Developers</h3>" - "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>" - "<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>" - "<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>" - "<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>" - "<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>" - "" - "<h3>With thanks to</h3>" - "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>" - "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>" - "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>" - "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>" - "" - "<h3>Patrons</h3>" - "%1" - "" - "</body>" - "</html>"); - if (patrons.isEmpty()) - return creditsHtml.arg(QObject::tr("<p>Loading...</p>")); - else - { - QString patronsStr; + QString patronsHeading = QObject::tr("Patrons", "About Credits"); + QString output; + QTextStream stream(&output); + stream << "<center>\n"; + // TODO: possibly retrieve from git history at build time? + stream << "<h3>" << QObject::tr("MultiMC Developers", "About Credits") << "</h3>\n"; + stream << "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>\n"; + stream << "<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>\n"; + stream << "<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>\n"; + stream << "<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>\n"; + stream << "<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>\n"; + stream << "<br />\n"; + + stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n"; + stream << "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>\n"; + stream << "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>\n"; + stream << "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>\n"; + stream << "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>\n"; + stream << "<p>Zeker Zhayard <<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>></p>\n"; + stream << "<br />\n"; + + if(!patrons.isEmpty()) { + stream << "<h3>" << QObject::tr("Patrons", "About Credits") << "</h3>\n"; for (QString patron : patrons) { - patronsStr.append(QString("<p>%1</p>").arg(patron)); + stream << "<p>" << patron << "</p>\n"; } - - return creditsHtml.arg(patronsStr); } + stream << "</center>\n"; + return output; } -static QString getLicenseHtml() +QString getLicenseHtml() { HoeDown hoedown; QFile dataFile(":/documents/COPYING.md"); @@ -81,6 +69,8 @@ static QString getLicenseHtml() return output; } +} + AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); @@ -109,6 +99,15 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia else ui->channelLabel->setVisible(false); + ui->redistributionText->setHtml(tr( +"<p>We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</p>\n" +"<p>Part of the reason for using the Apache license is we don't want people using the "MultiMC" name when redistributing the project. " +"This means people must take the time to go through the source code and remove all references to "MultiMC", including but not limited to the project " +"icon and the title of windows, (no <b>MultiMC-fork</b> in the title).</p>\n" +"<p>The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. " +"However, it should be abundantly clear that the project is a fork <b>without</b> implying that you have our blessing.</p>" + )); + connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt); @@ -124,7 +123,7 @@ AboutDialog::~AboutDialog() void AboutDialog::loadPatronList() { netJob.reset(new NetJob("Patreon Patron List")); - netJob->addNetAction(Net::Download::makeByteArray(QUrl("http://files.multimc.org/patrons.txt"), &dataSink)); + netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://files.multimc.org/patrons.txt"), &dataSink)); connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded); netJob->start(); } diff --git a/application/dialogs/AboutDialog.h b/application/dialogs/AboutDialog.h index 96624041..c7621c37 100644 --- a/application/dialogs/AboutDialog.h +++ b/application/dialogs/AboutDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 5e8e3e68..c6de9ebb 100644 --- a/application/dialogs/AboutDialog.ui +++ b/application/dialogs/AboutDialog.ui @@ -165,7 +165,7 @@ </font> </property> <property name="text"> - <string>© 2012-2018 MultiMC Contributors</string> + <string>© 2012-2021 MultiMC Contributors</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -180,7 +180,7 @@ </font> </property> <property name="text"> - <string notr="true"><html><head/><body><p><a href="http://github.com/MultiMC/MultiMC5">http://github.com/MultiMC/MultiMC5</a></p></body></html></string> + <string notr="true"><html><head/><body><p><a href="https://github.com/MultiMC/MultiMC5">https://github.com/MultiMC/MultiMC5</a></p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -212,28 +212,11 @@ <property name="readOnly"> <bool>true</bool> </property> - <property name="html"> - <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html></string> - </property> <property name="textInteractionFlags"> <set>Qt::TextBrowserInteraction</set> </property> </widget> </item> - <item> - <widget class="QLineEdit" name="translationInfo"> - <property name="text"> - <string extracomment="Hey, Translator, feel free to put credit to you here">No Language file loaded.</string> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> </layout> </widget> <widget class="QWidget" name="licenseTab"> @@ -257,13 +240,6 @@ p, li { white-space: pre-wrap; } <property name="readOnly"> <bool>true</bool> </property> - <property name="html"> - <string notr="true"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:12pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></string> - </property> <property name="textInteractionFlags"> <set>Qt::TextBrowserInteraction</set> </property> @@ -277,18 +253,7 @@ p, li { white-space: pre-wrap; } </attribute> <layout class="QVBoxLayout" name="verticalLayout_4"> <item> - <widget class="QTextEdit" name="textEdit"> - <property name="html"> - <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork </span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;">without</span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;"> implying that you have our blessing.</span></p></body></html></string> - </property> + <widget class="QTextEdit" name="redistributionText"> <property name="textInteractionFlags"> <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> </property> @@ -337,14 +302,11 @@ p, li { white-space: pre-wrap; } <tabstops> <tabstop>tabWidget</tabstop> <tabstop>creditsText</tabstop> - <tabstop>translationInfo</tabstop> <tabstop>licenseText</tabstop> - <tabstop>textEdit</tabstop> + <tabstop>redistributionText</tabstop> <tabstop>aboutQt</tabstop> <tabstop>closeButton</tabstop> </tabstops> - <resources> - <include location="../../resources/multimc/multimc.qrc"/> - </resources> + <resources/> <connections/> </ui> diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp index 78f7512e..5fe90334 100644 --- a/application/dialogs/CopyInstanceDialog.cpp +++ b/application/dialogs/CopyInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -53,6 +53,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->groupBox->setCurrentIndex(index); ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); ui->copySavesCheckbox->setChecked(m_copySaves); + ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime); } CopyInstanceDialog::~CopyInstanceDialog() @@ -123,3 +124,21 @@ void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) m_copySaves = true; } } + +bool CopyInstanceDialog::shouldKeepPlaytime() const +{ + return m_keepPlaytime; +} + + +void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) +{ + if(state == Qt::Unchecked) + { + m_keepPlaytime = false; + } + else if(state == Qt::Checked) + { + m_keepPlaytime = true; + } +} diff --git a/application/dialogs/CopyInstanceDialog.h b/application/dialogs/CopyInstanceDialog.h index 83ef2b3a..bf3cd920 100644 --- a/application/dialogs/CopyInstanceDialog.h +++ b/application/dialogs/CopyInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -40,16 +40,19 @@ public: QString instGroup() const; QString iconKey() const; bool shouldCopySaves() const; + bool shouldKeepPlaytime() const; private slots: void on_iconButton_clicked(); void on_instNameTextBox_textChanged(const QString &arg1); void on_copySavesCheckbox_stateChanged(int state); + void on_keepPlaytimeCheckbox_stateChanged(int state); private: Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; bool m_copySaves = true; + bool m_keepPlaytime = true; }; diff --git a/application/dialogs/CopyInstanceDialog.ui b/application/dialogs/CopyInstanceDialog.ui index bbb1bbb3..fa675455 100644 --- a/application/dialogs/CopyInstanceDialog.ui +++ b/application/dialogs/CopyInstanceDialog.ui @@ -10,7 +10,7 @@ <x>0</x> <y>0</y> <width>345</width> - <height>240</height> + <height>323</height> </rect> </property> <property name="windowTitle"> @@ -117,6 +117,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="keepPlaytimeCheckbox"> + <property name="text"> + <string>Keep play time</string> + </property> + </widget> + </item> + <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -128,6 +135,13 @@ </item> </layout> </widget> + <tabstops> + <tabstop>iconButton</tabstop> + <tabstop>instNameTextBox</tabstop> + <tabstop>groupBox</tabstop> + <tabstop>copySavesCheckbox</tabstop> + <tabstop>keepPlaytimeCheckbox</tabstop> + </tabstops> <resources> <include location="../../graphics.qrc"/> </resources> diff --git a/application/dialogs/CustomMessageBox.cpp b/application/dialogs/CustomMessageBox.cpp index 0ef3c8ce..19029f68 100644 --- a/application/dialogs/CustomMessageBox.cpp +++ b/application/dialogs/CustomMessageBox.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 09e7b46a..712c6518 100644 --- a/application/dialogs/CustomMessageBox.h +++ b/application/dialogs/CustomMessageBox.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 5ab3b025..002c064b 100644 --- a/application/dialogs/EditAccountDialog.cpp +++ b/application/dialogs/EditAccountDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 1346d00e..6b5eb4aa 100644 --- a/application/dialogs/EditAccountDialog.h +++ b/application/dialogs/EditAccountDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.ui b/application/dialogs/EditAccountDialog.ui index 85e235ce..e87509bc 100644 --- a/application/dialogs/EditAccountDialog.ui +++ b/application/dialogs/EditAccountDialog.ui @@ -30,7 +30,7 @@ <item> <widget class="QLineEdit" name="userTextBox"> <property name="placeholderText"> - <string>Email / Username</string> + <string>Email</string> </property> </widget> </item> diff --git a/application/dialogs/ExportInstanceDialog.cpp b/application/dialogs/ExportInstanceDialog.cpp index a9045829..a42779d4 100644 --- a/application/dialogs/ExportInstanceDialog.cpp +++ b/application/dialogs/ExportInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -343,43 +343,37 @@ void SaveIcon(InstancePtr m_instance) auto iconKey = m_instance->iconKey(); auto iconList = MMC->icons(); auto mmcIcon = iconList->icon(iconKey); - if(mmcIcon) + if(!mmcIcon || mmcIcon->isBuiltIn()) { + return; + } + auto path = mmcIcon->getFilePath(); + if(!path.isNull()) { + QFileInfo inInfo (path); + FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) (); + return; + } + auto & image = mmcIcon->m_images[mmcIcon->type()]; + auto & icon = image.icon; + auto sizes = icon.availableSizes(); + if(sizes.size() == 0) { - bool saveIcon = false; - switch(mmcIcon->type()) - { - case IconType::FileBased: - case IconType::Transient: - saveIcon = true; - default: - break; - } - if(saveIcon) + return; + } + auto areaOf = [](QSize size) + { + return size.width() * size.height(); + }; + QSize largest = sizes[0]; + // find variant with largest area + for(auto size: sizes) + { + if(areaOf(largest) < areaOf(size)) { - auto & image = mmcIcon->m_images[mmcIcon->type()]; - auto & icon = image.icon; - auto sizes = icon.availableSizes(); - if(sizes.size() == 0) - { - return; - } - auto areaOf = [](QSize size) - { - return size.width() * size.height(); - }; - QSize largest = sizes[0]; - // find variant with largest area - for(auto size: sizes) - { - if(areaOf(largest) < areaOf(size)) - { - largest = size; - } - } - auto pixmap = icon.pixmap(largest); - pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); + largest = size; } } + auto pixmap = icon.pixmap(largest); + pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png")); } bool ExportInstanceDialog::doExport() diff --git a/application/dialogs/ExportInstanceDialog.h b/application/dialogs/ExportInstanceDialog.h index 4032314d..dea02d1b 100644 --- a/application/dialogs/ExportInstanceDialog.h +++ b/application/dialogs/ExportInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 b4260861..90436554 100644 --- a/application/dialogs/IconPickerDialog.cpp +++ b/application/dialogs/IconPickerDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -25,6 +25,7 @@ #include "groupview/InstanceDelegate.h" #include "icons/IconList.h" +#include "icons/IconUtils.h" #include <DesktopServices.h> IconPickerDialog::IconPickerDialog(QWidget *parent) @@ -103,8 +104,8 @@ void IconPickerDialog::addNewIcon() //: The title of the select icons open file dialog QString selectIcons = tr("Select Icons"); //: The type of icon files - QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), - tr("Icons") + "(*.png *.jpg *.jpeg *.ico *.svg *.gif)"); + auto filter = IconUtils::getIconFilter(); + QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), tr("Icons %1").arg(filter)); MMC->icons()->installIcons(fileNames); } diff --git a/application/dialogs/IconPickerDialog.h b/application/dialogs/IconPickerDialog.h index c76f9a34..9af6a678 100644 --- a/application/dialogs/IconPickerDialog.h +++ b/application/dialogs/IconPickerDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 276aebb9..32f8a48f 100644 --- a/application/dialogs/LoginDialog.cpp +++ b/application/dialogs/LoginDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 355599ec..16bdddfb 100644 --- a/application/dialogs/LoginDialog.h +++ b/application/dialogs/LoginDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 99b98215..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> @@ -36,7 +46,7 @@ <item> <widget class="QLineEdit" name="userTextBox"> <property name="placeholderText"> - <string>Email / Username</string> + <string>Email</string> </property> </widget> </item> diff --git a/application/dialogs/ModEditDialogCommon.cpp b/application/dialogs/ModEditDialogCommon.cpp deleted file mode 100644 index e92c5c4d..00000000 --- a/application/dialogs/ModEditDialogCommon.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "ModEditDialogCommon.h" -#include "CustomMessageBox.h" -#include <QUrl> - -bool lastfirst(QModelIndexList &list, int &first, int &last) -{ - if (list.isEmpty()) - return false; - first = last = list[0].row(); - for (auto item : list) - { - int row = item.row(); - if (row < first) - first = row; - if (row > last) - last = row; - } - return true; -} - -void showWebsiteForMod(QWidget *parentDlg, Mod &m) -{ - QString url = m.homeurl(); - if (url.size()) - { - // catch the cases where the protocol is missing - if (!url.startsWith("http")) - { - url = "http://" + url; - } - DesktopServices::openUrl(url); - } - else - { - CustomMessageBox::selectable( - parentDlg, QObject::tr("How sad!"), - QObject::tr("The mod author didn't provide a website link for this mod."), - QMessageBox::Warning); - } -} diff --git a/application/dialogs/ModEditDialogCommon.h b/application/dialogs/ModEditDialogCommon.h deleted file mode 100644 index fc5e3c2b..00000000 --- a/application/dialogs/ModEditDialogCommon.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once -#include <QModelIndex> -#include <DesktopServices.h> -#include <QWidget> -#include <minecraft/Mod.h> - -bool lastfirst(QModelIndexList &list, int &first, int &last); - -void showWebsiteForMod(QWidget *parentDlg, Mod &m); diff --git a/application/dialogs/NewComponentDialog.cpp b/application/dialogs/NewComponentDialog.cpp index c82ba62b..f4d6274f 100644 --- a/application/dialogs/NewComponentDialog.cpp +++ b/application/dialogs/NewComponentDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 23192605..8c790beb 100644 --- a/application/dialogs/NewComponentDialog.h +++ b/application/dialogs/NewComponentDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.ui b/application/dialogs/NewComponentDialog.ui index b30c2431..03b0d222 100644 --- a/application/dialogs/NewComponentDialog.ui +++ b/application/dialogs/NewComponentDialog.ui @@ -14,7 +14,7 @@ </rect> </property> <property name="windowTitle"> - <string>Copy Instance</string> + <string>Add Empty Component</string> </property> <property name="windowIcon"> <iconset> diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp index bdcd1bce..86963149 100644 --- a/application/dialogs/NewInstanceDialog.cpp +++ b/application/dialogs/NewInstanceDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,10 +34,14 @@ #include "widgets/PageContainer.h" #include <pages/modplatform/VanillaPage.h> -#include <pages/modplatform/FTBPage.h> -#include <pages/modplatform/TwitchPage.h> +#include <pages/modplatform/atlauncher/AtlPage.h> +#include <pages/modplatform/ftb/FtbPage.h> +#include <pages/modplatform/legacy_ftb/Page.h> +#include <pages/modplatform/flame/FlamePage.h> #include <pages/modplatform/ImportPage.h> -#include <pages/modplatform/TechnicPage.h> +#include <pages/modplatform/technic/TechnicPage.h> + + NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) : QDialog(parent), ui(new Ui::NewInstanceDialog) @@ -94,6 +98,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString if(!url.isEmpty()) { + QUrl actualUrl(url); m_container->selectPage("import"); importPage->setUrl(url); } @@ -119,13 +124,17 @@ void NewInstanceDialog::accept() QList<BasePage *> NewInstanceDialog::getPages() { importPage = new ImportPage(this); + flamePage = new FlamePage(this); + auto technicPage = new TechnicPage(this); return { new VanillaPage(this), - new FTBPage(this), importPage, - new TwitchPage(this), - new TechnicPage(this) + new AtlPage(this), + flamePage, + new FtbPage(this), + new LegacyFTB::Page(this), + technicPage }; } @@ -164,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 5d4ec5ed..53abf8cf 100644 --- a/application/dialogs/NewInstanceDialog.h +++ b/application/dialogs/NewInstanceDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,6 +29,7 @@ class NewInstanceDialog; class PageContainer; class QDialogButtonBox; class ImportPage; +class FlamePage; class NewInstanceDialog : public QDialog, public BasePageProvider { @@ -42,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(); @@ -67,6 +69,7 @@ private: QString InstIconKey; ImportPage *importPage = 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 bf31ed02..ae34709f 100644 --- a/application/dialogs/ProfileSelectDialog.cpp +++ b/application/dialogs/ProfileSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 d4017eb3..9f95830c 100644 --- a/application/dialogs/ProfileSelectDialog.h +++ b/application/dialogs/ProfileSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 0951607d..4b092859 100644 --- a/application/dialogs/ProgressDialog.cpp +++ b/application/dialogs/ProgressDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 6856f4c0..b28ad4fa 100644 --- a/application/dialogs/ProgressDialog.h +++ b/application/dialogs/ProgressDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.cpp b/application/dialogs/UpdateDialog.cpp index 242a5b70..2baaf5e9 100644 --- a/application/dialogs/UpdateDialog.cpp +++ b/application/dialogs/UpdateDialog.cpp @@ -22,6 +22,7 @@ UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), u ui->btnUpdateNow->setHidden(true); ui->btnUpdateLater->setText(tr("Close")); } + ui->changelogBrowser->setHtml(tr("<center><h1>Loading changelog...</h1></center>")); loadChangelog(); restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("UpdateDialogGeometry").toByteArray())); } diff --git a/application/dialogs/UpdateDialog.h b/application/dialogs/UpdateDialog.h index 6ee3df1d..ae1799c3 100644 --- a/application/dialogs/UpdateDialog.h +++ b/application/dialogs/UpdateDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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/UpdateDialog.ui b/application/dialogs/UpdateDialog.ui index 8e34a521..b0b3dd83 100644 --- a/application/dialogs/UpdateDialog.ui +++ b/application/dialogs/UpdateDialog.ui @@ -42,13 +42,6 @@ </item> <item> <widget class="QTextBrowser" name="changelogBrowser"> - <property name="html"> - <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;"> -<p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:22pt;">Loading changelog...</span></p></body></html></string> - </property> <property name="openExternalLinks"> <bool>true</bool> </property> diff --git a/application/dialogs/VersionSelectDialog.cpp b/application/dialogs/VersionSelectDialog.cpp index 59287ee9..ed1210ba 100644 --- a/application/dialogs/VersionSelectDialog.cpp +++ b/application/dialogs/VersionSelectDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 8b5d108f..ed30d3f3 100644 --- a/application/dialogs/VersionSelectDialog.h +++ b/application/dialogs/VersionSelectDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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/AccessibleGroupView.cpp b/application/groupview/AccessibleGroupView.cpp new file mode 100644 index 00000000..c6541f18 --- /dev/null +++ b/application/groupview/AccessibleGroupView.cpp @@ -0,0 +1,778 @@ +#include "GroupView.h" +#include "AccessibleGroupView.h" +#include "AccessibleGroupView_p.h" + +#include <qvariant.h> +#include <qaccessible.h> +#include <qheaderview.h> + +#ifndef QT_NO_ACCESSIBILITY + +QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object) +{ + QAccessibleInterface *iface = 0; + if (!object || !object->isWidgetType()) + return iface; + + QWidget *widget = static_cast<QWidget*>(object); + + if (classname == QLatin1String("GroupView")) { + iface = new AccessibleGroupView((GroupView *)widget); + } + return iface; +} + + +QAbstractItemView *AccessibleGroupView::view() const +{ + return qobject_cast<QAbstractItemView*>(object()); +} + +int AccessibleGroupView::logicalIndex(const QModelIndex &index) const +{ + if (!view()->model() || !index.isValid()) + return -1; + return index.row() * (index.model()->columnCount()) + index.column(); +} + +AccessibleGroupView::AccessibleGroupView(QWidget *w) + : QAccessibleObject(w) +{ + Q_ASSERT(view()); +} + +bool AccessibleGroupView::isValid() const +{ + return view(); +} + +AccessibleGroupView::~AccessibleGroupView() +{ + for (QAccessible::Id id : childToId) { + QAccessible::deleteAccessibleInterface(id); + } +} + +QAccessibleInterface *AccessibleGroupView::cellAt(int row, int column) const +{ + if (!view()->model()) { + return 0; + } + + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (Q_UNLIKELY(!index.isValid())) { + qWarning() << "AccessibleGroupView::cellAt: invalid index: " << index << " for " << view(); + return 0; + } + + return child(logicalIndex(index)); +} + +QAccessibleInterface *AccessibleGroupView::caption() const +{ + return 0; +} + +QString AccessibleGroupView::columnDescription(int column) const +{ + if (!view()->model()) + return QString(); + + return view()->model()->headerData(column, Qt::Horizontal).toString(); +} + +int AccessibleGroupView::columnCount() const +{ + if (!view()->model()) + return 0; + return 1; +} + +int AccessibleGroupView::rowCount() const +{ + if (!view()->model()) + return 0; + return view()->model()->rowCount(); +} + +int AccessibleGroupView::selectedCellCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedIndexes().count(); +} + +int AccessibleGroupView::selectedColumnCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedColumns().count(); +} + +int AccessibleGroupView::selectedRowCount() const +{ + if (!view()->selectionModel()) + return 0; + return view()->selectionModel()->selectedRows().count(); +} + +QString AccessibleGroupView::rowDescription(int row) const +{ + if (!view()->model()) + return QString(); + return view()->model()->headerData(row, Qt::Vertical).toString(); +} + +QList<QAccessibleInterface *> AccessibleGroupView::selectedCells() const +{ + QList<QAccessibleInterface*> cells; + if (!view()->selectionModel()) + return cells; + const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes(); + cells.reserve(selectedIndexes.size()); + for (const QModelIndex &index : selectedIndexes) + cells.append(child(logicalIndex(index))); + return cells; +} + +QList<int> AccessibleGroupView::selectedColumns() const +{ + if (!view()->selectionModel()) { + return QList<int>(); + } + + const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns(); + + QList<int> columns; + columns.reserve(selectedColumns.size()); + for (const QModelIndex &index : selectedColumns) { + columns.append(index.column()); + } + + return columns; +} + +QList<int> AccessibleGroupView::selectedRows() const +{ + if (!view()->selectionModel()) { + return QList<int>(); + } + + QList<int> rows; + + const QModelIndexList selectedRows = view()->selectionModel()->selectedRows(); + + rows.reserve(selectedRows.size()); + for (const QModelIndex &index : selectedRows) { + rows.append(index.row()); + } + + return rows; +} + +QAccessibleInterface *AccessibleGroupView::summary() const +{ + return 0; +} + +bool AccessibleGroupView::isColumnSelected(int column) const +{ + if (!view()->selectionModel()) { + return false; + } + + return view()->selectionModel()->isColumnSelected(column, QModelIndex()); +} + +bool AccessibleGroupView::isRowSelected(int row) const +{ + if (!view()->selectionModel()) { + return false; + } + + return view()->selectionModel()->isRowSelected(row, QModelIndex()); +} + +bool AccessibleGroupView::selectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) { + return false; + } + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: { + return false; + } + case QAbstractItemView::SingleSelection: { + if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 ) + return false; + view()->clearSelection(); + break; + } + case QAbstractItemView::ContiguousSelection: { + if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) { + view()->clearSelection(); + } + break; + } + default: { + break; + } + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); + return true; +} + +bool AccessibleGroupView::selectColumn(int column) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + QModelIndex index = view()->model()->index(0, column, view()->rootIndex()); + + if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) { + return false; + } + + switch (view()->selectionMode()) { + case QAbstractItemView::NoSelection: { + return false; + } + case QAbstractItemView::SingleSelection: { + if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) { + return false; + } + // fallthrough intentional + } + case QAbstractItemView::ContiguousSelection: { + if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { + view()->clearSelection(); + } + break; + } + default: { + break; + } + } + + view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns); + return true; +} + +bool AccessibleGroupView::unselectRow(int row) +{ + if (!view()->model() || !view()->selectionModel()) { + return false; + } + + QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); + if (!index.isValid()) { + return false; + } + + QItemSelection selection(index, index); + auto selectionModel = view()->selectionModel(); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: + // no unselect + if (selectedRowCount() == 1) { + return false; + } + break; + case QAbstractItemView::ContiguousSelection: { + // no unselect + if (selectedRowCount() == 1) { + return false; + } + + + if ((!row || selectionModel->isRowSelected(row - 1, view()->rootIndex())) && selectionModel->isRowSelected(row + 1, view()->rootIndex())) { + //If there are rows selected both up the current row and down the current rown, + //the ones which are down the current row will be deselected + selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex())); + } + } + default: { + break; + } + } + + selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); + return true; +} + +bool AccessibleGroupView::unselectColumn(int column) +{ + auto model = view()->model(); + if (!model || !view()->selectionModel()) { + return false; + } + + QModelIndex index = model->index(0, column, view()->rootIndex()); + if (!index.isValid()) { + return false; + } + + QItemSelection selection(index, index); + + switch (view()->selectionMode()) { + case QAbstractItemView::SingleSelection: { + //In SingleSelection and ContiguousSelection once an item + //is selected, there's no way for the user to unselect all items + if (selectedColumnCount() == 1) { + return false; + } + break; + } + case QAbstractItemView::ContiguousSelection: + if (selectedColumnCount() == 1) { + return false; + } + + if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) + && view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { + //If there are columns selected both at the left of the current row and at the right + //of the current row, the ones which are at the right will be deselected + selection = QItemSelection(index, model->index(0, columnCount() - 1, view()->rootIndex())); + } + default: + break; + } + + view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns); + return true; +} + +QAccessible::Role AccessibleGroupView::role() const +{ + return QAccessible::List; +} + +QAccessible::State AccessibleGroupView::state() const +{ + return QAccessible::State(); +} + +QAccessibleInterface *AccessibleGroupView::childAt(int x, int y) const +{ + QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0)); + QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset); + // FIXME: if indexPosition < 0 in one coordinate, return header + + QModelIndex index = view()->indexAt(indexPosition); + if (index.isValid()) { + return child(logicalIndex(index)); + } + return 0; +} + +int AccessibleGroupView::childCount() const +{ + if (!view()->model()) { + return 0; + } + return (view()->model()->rowCount()) * (view()->model()->columnCount()); +} + +int AccessibleGroupView::indexOfChild(const QAccessibleInterface *iface) const +{ + if (!view()->model()) + return -1; + QAccessibleInterface *parent = iface->parent(); + if (parent->object() != view()) + return -1; + + Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + const AccessibleGroupViewItem* cell = static_cast<const AccessibleGroupViewItem*>(iface); + return logicalIndex(cell->m_index); + } else if (iface->role() == QAccessible::Pane) { + return 0; // corner button + } else { + qWarning() << "AccessibleGroupView::indexOfChild has a child with unknown role..." << iface->role() << iface->text(QAccessible::Name); + } + // FIXME: we are in denial of our children. this should stop. + return -1; +} + +QString AccessibleGroupView::text(QAccessible::Text t) const +{ + if (t == QAccessible::Description) + return view()->accessibleDescription(); + return view()->accessibleName(); +} + +QRect AccessibleGroupView::rect() const +{ + if (!view()->isVisible()) + return QRect(); + QPoint pos = view()->mapToGlobal(QPoint(0, 0)); + return QRect(pos.x(), pos.y(), view()->width(), view()->height()); +} + +QAccessibleInterface *AccessibleGroupView::parent() const +{ + if (view() && view()->parent()) { + if (qstrcmp("QComboBoxPrivateContainer", view()->parent()->metaObject()->className()) == 0) { + return QAccessible::queryAccessibleInterface(view()->parent()->parent()); + } + return QAccessible::queryAccessibleInterface(view()->parent()); + } + return 0; +} + +QAccessibleInterface *AccessibleGroupView::child(int logicalIndex) const +{ + if (!view()->model()) + return 0; + + auto id = childToId.constFind(logicalIndex); + if (id != childToId.constEnd()) + return QAccessible::accessibleInterface(id.value()); + + int columns = view()->model()->columnCount(); + + int row = logicalIndex / columns; + int column = logicalIndex % columns; + + QAccessibleInterface *iface = 0; + + QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); + if (Q_UNLIKELY(!index.isValid())) { + qWarning("AccessibleGroupView::child: Invalid index at: %d %d", row, column); + return 0; + } + iface = new AccessibleGroupViewItem(view(), index); + + QAccessible::registerAccessibleInterface(iface); + childToId.insert(logicalIndex, QAccessible::uniqueId(iface)); + return iface; +} + +void *AccessibleGroupView::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableInterface) + return static_cast<QAccessibleTableInterface*>(this); + return 0; +} + +void AccessibleGroupView::modelChange(QAccessibleTableModelChangeEvent *event) +{ + // if there is no cache yet, we don't update anything + if (childToId.isEmpty()) + return; + + switch (event->modelChangeType()) { + case QAccessibleTableModelChangeEvent::ModelReset: + for (QAccessible::Id id : childToId) + QAccessible::deleteAccessibleInterface(id); + childToId.clear(); + break; + + // rows are inserted: move every row after that + case QAccessibleTableModelChangeEvent::RowsInserted: + case QAccessibleTableModelChangeEvent::ColumnsInserted: { + + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (indexOfChild(iface) >= 0) { + newCache.insert(indexOfChild(iface), id); + } else { + // ### This should really not happen, + // but it might if the view has a root index set. + // This needs to be fixed. + QAccessible::deleteAccessibleInterface(id); + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::ColumnsRemoved: + case QAccessibleTableModelChangeEvent::RowsRemoved: { + ChildCache newCache; + ChildCache::ConstIterator iter = childToId.constBegin(); + while (iter != childToId.constEnd()) { + QAccessible::Id id = iter.value(); + QAccessibleInterface *iface = QAccessible::accessibleInterface(id); + Q_ASSERT(iface); + if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { + Q_ASSERT(iface->tableCellInterface()); + AccessibleGroupViewItem *cell = static_cast<AccessibleGroupViewItem*>(iface->tableCellInterface()); + // Since it is a QPersistentModelIndex, we only need to check if it is valid + if (cell->m_index.isValid()) + newCache.insert(indexOfChild(cell), id); + else + QAccessible::deleteAccessibleInterface(id); + } + ++iter; + } + childToId = newCache; + break; + } + + case QAccessibleTableModelChangeEvent::DataChanged: + // nothing to do in this case + break; + } +} + +// TABLE CELL + +AccessibleGroupViewItem::AccessibleGroupViewItem(QAbstractItemView *view_, const QModelIndex &index_) + : view(view_), m_index(index_) +{ + if (Q_UNLIKELY(!index_.isValid())) + qWarning() << "AccessibleGroupViewItem::AccessibleGroupViewItem with invalid index: " << index_; +} + +void *AccessibleGroupViewItem::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::TableCellInterface) + return static_cast<QAccessibleTableCellInterface*>(this); + if (t == QAccessible::ActionInterface) + return static_cast<QAccessibleActionInterface*>(this); + return 0; +} + +int AccessibleGroupViewItem::columnExtent() const { return 1; } +int AccessibleGroupViewItem::rowExtent() const { return 1; } + +QList<QAccessibleInterface*> AccessibleGroupViewItem::rowHeaderCells() const +{ + return {}; +} + +QList<QAccessibleInterface*> AccessibleGroupViewItem::columnHeaderCells() const +{ + return {}; +} + +int AccessibleGroupViewItem::columnIndex() const +{ + if (!isValid()) { + return -1; + } + + return m_index.column(); +} + +int AccessibleGroupViewItem::rowIndex() const +{ + if (!isValid()) { + return -1; + } + + return m_index.row(); +} + +bool AccessibleGroupViewItem::isSelected() const +{ + if (!isValid()) { + return false; + } + + return view->selectionModel()->isSelected(m_index); +} + +QStringList AccessibleGroupViewItem::actionNames() const +{ + QStringList names; + names << toggleAction(); + return names; +} + +void AccessibleGroupViewItem::doAction(const QString& actionName) +{ + if (actionName == toggleAction()) { + if (isSelected()) { + unselectCell(); + } + else { + selectCell(); + } + } +} + +QStringList AccessibleGroupViewItem::keyBindingsForAction(const QString &) const +{ + return QStringList(); +} + + +void AccessibleGroupViewItem::selectCell() +{ + if (!isValid()) { + return; + } + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (selectionMode == QAbstractItemView::NoSelection) { + return; + } + + Q_ASSERT(table()); + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->selectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->selectRow(m_index.row()); + return; + } + + if (selectionMode == QAbstractItemView::SingleSelection) { + view->clearSelection(); + } + + view->selectionModel()->select(m_index, QItemSelectionModel::Select); +} + +void AccessibleGroupViewItem::unselectCell() +{ + if (!isValid()) + return; + QAbstractItemView::SelectionMode selectionMode = view->selectionMode(); + if (selectionMode == QAbstractItemView::NoSelection) + return; + + QAccessibleTableInterface *cellTable = table()->tableInterface(); + + switch (view->selectionBehavior()) { + case QAbstractItemView::SelectItems: + break; + case QAbstractItemView::SelectColumns: + if (cellTable) + cellTable->unselectColumn(m_index.column()); + return; + case QAbstractItemView::SelectRows: + if (cellTable) + cellTable->unselectRow(m_index.row()); + return; + } + + //If the mode is not MultiSelection or ExtendedSelection and only + //one cell is selected it cannot be unselected by the user + if ((selectionMode != QAbstractItemView::MultiSelection) && (selectionMode != QAbstractItemView::ExtendedSelection) && (view->selectionModel()->selectedIndexes().count() <= 1)) + return; + + view->selectionModel()->select(m_index, QItemSelectionModel::Deselect); +} + +QAccessibleInterface *AccessibleGroupViewItem::table() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessible::Role AccessibleGroupViewItem::role() const +{ + return QAccessible::ListItem; +} + +QAccessible::State AccessibleGroupViewItem::state() const +{ + QAccessible::State st; + if (!isValid()) + return st; + + QRect globalRect = view->rect(); + globalRect.translate(view->mapToGlobal(QPoint(0,0))); + if (!globalRect.intersects(rect())) + st.invisible = true; + + if (view->selectionModel()->isSelected(m_index)) + st.selected = true; + if (view->selectionModel()->currentIndex() == m_index) + st.focused = true; + if (m_index.model()->data(m_index, Qt::CheckStateRole).toInt() == Qt::Checked) + st.checked = true; + + Qt::ItemFlags flags = m_index.flags(); + if (flags & Qt::ItemIsSelectable) { + st.selectable = true; + st.focusable = true; + if (view->selectionMode() == QAbstractItemView::MultiSelection) + st.multiSelectable = true; + if (view->selectionMode() == QAbstractItemView::ExtendedSelection) + st.extSelectable = true; + } + return st; +} + + +QRect AccessibleGroupViewItem::rect() const +{ + QRect r; + if (!isValid()) + return r; + r = view->visualRect(m_index); + + if (!r.isNull()) { + r.translate(view->viewport()->mapTo(view, QPoint(0,0))); + r.translate(view->mapToGlobal(QPoint(0, 0))); + } + return r; +} + +QString AccessibleGroupViewItem::text(QAccessible::Text t) const +{ + QString value; + if (!isValid()) + return value; + QAbstractItemModel *model = view->model(); + switch (t) { + case QAccessible::Name: + value = model->data(m_index, Qt::AccessibleTextRole).toString(); + if (value.isEmpty()) + value = model->data(m_index, Qt::DisplayRole).toString(); + break; + case QAccessible::Description: + value = model->data(m_index, Qt::AccessibleDescriptionRole).toString(); + break; + default: + break; + } + return value; +} + +void AccessibleGroupViewItem::setText(QAccessible::Text /*t*/, const QString &text) +{ + if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable)) + return; + view->model()->setData(m_index, text); +} + +bool AccessibleGroupViewItem::isValid() const +{ + return view && view->model() && m_index.isValid(); +} + +QAccessibleInterface *AccessibleGroupViewItem::parent() const +{ + return QAccessible::queryAccessibleInterface(view); +} + +QAccessibleInterface *AccessibleGroupViewItem::child(int) const +{ + return 0; +} + +#endif /* !QT_NO_ACCESSIBILITY */ diff --git a/application/groupview/AccessibleGroupView.h b/application/groupview/AccessibleGroupView.h new file mode 100644 index 00000000..9bfd1745 --- /dev/null +++ b/application/groupview/AccessibleGroupView.h @@ -0,0 +1,6 @@ +#pragma once + +#include <QString> +class QAccessibleInterface; + +QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object); diff --git a/application/groupview/AccessibleGroupView_p.h b/application/groupview/AccessibleGroupView_p.h new file mode 100644 index 00000000..e74da3be --- /dev/null +++ b/application/groupview/AccessibleGroupView_p.h @@ -0,0 +1,118 @@ +#pragma once + +#include "QtCore/qpointer.h" +#include <QtGui/qaccessible.h> +#include <QAccessibleWidget> +#include <QAbstractItemView> +#ifndef QT_NO_ACCESSIBILITY +#include "GroupView.h" +// #include <QHeaderView> + +class QAccessibleTableCell; +class QAccessibleTableHeaderCell; + +class AccessibleGroupView :public QAccessibleTableInterface, public QAccessibleObject +{ +public: + explicit AccessibleGroupView(QWidget *w); + bool isValid() const override; + + QAccessible::Role role() const override; + QAccessible::State state() const override; + QString text(QAccessible::Text t) const override; + QRect rect() const override; + + QAccessibleInterface *childAt(int x, int y) const override; + int childCount() const override; + int indexOfChild(const QAccessibleInterface *) const override; + + QAccessibleInterface *parent() const override; + QAccessibleInterface *child(int index) const override; + + void *interface_cast(QAccessible::InterfaceType t) override; + + // table interface + QAccessibleInterface *cellAt(int row, int column) const override; + QAccessibleInterface *caption() const override; + QAccessibleInterface *summary() const override; + QString columnDescription(int column) const override; + QString rowDescription(int row) const override; + int columnCount() const override; + int rowCount() const override; + + // selection + int selectedCellCount() const override; + int selectedColumnCount() const override; + int selectedRowCount() const override; + QList<QAccessibleInterface*> selectedCells() const override; + QList<int> selectedColumns() const override; + QList<int> selectedRows() const override; + bool isColumnSelected(int column) const override; + bool isRowSelected(int row) const override; + bool selectRow(int row) override; + bool selectColumn(int column) override; + bool unselectRow(int row) override; + bool unselectColumn(int column) override; + + QAbstractItemView *view() const; + + void modelChange(QAccessibleTableModelChangeEvent *event) override; + +protected: + // maybe vector + typedef QHash<int, QAccessible::Id> ChildCache; + mutable ChildCache childToId; + + virtual ~AccessibleGroupView(); + +private: + inline int logicalIndex(const QModelIndex &index) const; +}; + +class AccessibleGroupViewItem: public QAccessibleInterface, public QAccessibleTableCellInterface, public QAccessibleActionInterface +{ +public: + AccessibleGroupViewItem(QAbstractItemView *view, const QModelIndex &m_index); + + void *interface_cast(QAccessible::InterfaceType t) override; + QObject *object() const override { return nullptr; } + QAccessible::Role role() const override; + QAccessible::State state() const override; + QRect rect() const override; + bool isValid() const override; + + QAccessibleInterface *childAt(int, int) const override { return nullptr; } + int childCount() const override { return 0; } + int indexOfChild(const QAccessibleInterface *) const override { return -1; } + + QString text(QAccessible::Text t) const override; + void setText(QAccessible::Text t, const QString &text) override; + + QAccessibleInterface *parent() const override; + QAccessibleInterface *child(int) const override; + + // cell interface + int columnExtent() const override; + QList<QAccessibleInterface*> columnHeaderCells() const override; + int columnIndex() const override; + int rowExtent() const override; + QList<QAccessibleInterface*> rowHeaderCells() const override; + int rowIndex() const override; + bool isSelected() const override; + QAccessibleInterface* table() const override; + + //action interface + QStringList actionNames() const override; + void doAction(const QString &actionName) override; + QStringList keyBindingsForAction(const QString &actionName) const override; + +private: + QPointer<QAbstractItemView > view; + QPersistentModelIndex m_index; + + void selectCell(); + void unselectCell(); + + friend class AccessibleGroupView; +}; +#endif /* !QT_NO_ACCESSIBILITY */ diff --git a/application/groupview/GroupView.cpp b/application/groupview/GroupView.cpp index a1b44e64..6bfc9381 100644 --- a/application/groupview/GroupView.cpp +++ b/application/groupview/GroupView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -25,6 +25,7 @@ #include <QMimeData> #include <QCache> #include <QScrollBar> +#include <QAccessible> #include "VisualGroup.h" #include <QDebug> @@ -88,6 +89,20 @@ void GroupView::rowsRemoved() scheduleDelayedItemsLayout(); } +void GroupView::currentChanged(const QModelIndex& current, const QModelIndex& previous) +{ + QAbstractItemView::currentChanged(current, previous); + // TODO: for accessibility support, implement+register a factory, steal QAccessibleTable from Qt and return an instance of it for GroupView. +#ifndef QT_NO_ACCESSIBILITY + if (QAccessible::isActive() && current.isValid()) { + QAccessibleEvent event(this, QAccessible::Focus); + event.setChild(current.row()); + QAccessible::updateAccessibility(&event); + } +#endif /* !QT_NO_ACCESSIBILITY */ +} + + class LocaleString : public QString { public: @@ -162,6 +177,9 @@ void GroupView::updateGeometries() else { auto cat = new VisualGroup(groupName, this); + if(fVisibility) { + cat->collapsed = fVisibility(groupName); + } cats.insert(groupName, cat); cat->update(); } @@ -220,6 +238,8 @@ VisualGroup *GroupView::categoryAt(const QPoint &pos, VisualGroup::HitResults & QString GroupView::groupNameAt(const QPoint &point) { + executeDelayedItemsLayout(); + VisualGroup::HitResults hitresult; auto group = categoryAt(point + offset(), hitresult); if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit))) @@ -246,7 +266,7 @@ int GroupView::itemWidth() const void GroupView::mousePressEvent(QMouseEvent *event) { - // endCategoryEditor(); + executeDelayedItemsLayout(); QPoint visualPos = event->pos(); QPoint geometryPos = event->pos() + offset(); @@ -295,6 +315,8 @@ void GroupView::mousePressEvent(QMouseEvent *event) void GroupView::mouseMoveEvent(QMouseEvent *event) { + executeDelayedItemsLayout(); + QPoint topLeft; QPoint visualPos = event->pos(); QPoint geometryPos = event->pos() + offset(); @@ -351,6 +373,8 @@ void GroupView::mouseMoveEvent(QMouseEvent *event) void GroupView::mouseReleaseEvent(QMouseEvent *event) { + executeDelayedItemsLayout(); + QPoint visualPos = event->pos(); QPoint geometryPos = event->pos() + offset(); QPersistentModelIndex index = indexAt(visualPos); @@ -365,17 +389,25 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event) if (state() == ExpandingState) { m_pressedCategory->collapsed = false; + emit groupStateChanged(m_pressedCategory->text, false); + updateGeometries(); viewport()->update(); event->accept(); + m_pressedCategory = nullptr; + setState(NoState); return; } else if (state() == CollapsingState) { m_pressedCategory->collapsed = true; + emit groupStateChanged(m_pressedCategory->text, true); + updateGeometries(); viewport()->update(); event->accept(); + m_pressedCategory = nullptr; + setState(NoState); return; } } @@ -405,6 +437,8 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event) void GroupView::mouseDoubleClickEvent(QMouseEvent *event) { + executeDelayedItemsLayout(); + QModelIndex index = indexAt(event->pos()); if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) { @@ -417,6 +451,12 @@ void GroupView::mouseDoubleClickEvent(QMouseEvent *event) // signal handlers may change the model QPersistentModelIndex persistent = index; emit doubleClicked(persistent); + + QStyleOptionViewItem option = viewOptions(); + if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) + { + emit activated(index); + } } void GroupView::paintEvent(QPaintEvent *event) @@ -522,6 +562,8 @@ void GroupView::resizeEvent(QResizeEvent *event) void GroupView::dragEnterEvent(QDragEnterEvent *event) { + executeDelayedItemsLayout(); + if (!isDragEventAccepted(event)) { return; @@ -533,6 +575,8 @@ void GroupView::dragEnterEvent(QDragEnterEvent *event) void GroupView::dragMoveEvent(QDragMoveEvent *event) { + executeDelayedItemsLayout(); + if (!isDragEventAccepted(event)) { return; @@ -544,12 +588,16 @@ void GroupView::dragMoveEvent(QDragMoveEvent *event) void GroupView::dragLeaveEvent(QDragLeaveEvent *event) { + executeDelayedItemsLayout(); + m_lastDragPosition = QPoint(); viewport()->update(); } void GroupView::dropEvent(QDropEvent *event) { + executeDelayedItemsLayout(); + m_lastDragPosition = QPoint(); stopAutoScroll(); @@ -572,8 +620,7 @@ void GroupView::dropEvent(QDropEvent *event) const QString categoryText = category->text; if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) { - model()->setData(model()->index(row, 0), categoryText, - GroupViewRoles::GroupRole); + model()->setData(model()->index(row, 0), categoryText, GroupViewRoles::GroupRole); event->setDropAction(Qt::MoveAction); event->accept(); } @@ -600,6 +647,8 @@ void GroupView::dropEvent(QDropEvent *event) void GroupView::startDrag(Qt::DropActions supportedActions) { + executeDelayedItemsLayout(); + QModelIndexList indexes = selectionModel()->selectedIndexes(); if(indexes.count() == 0) return; @@ -645,11 +694,15 @@ void GroupView::startDrag(Qt::DropActions supportedActions) QRect GroupView::visualRect(const QModelIndex &index) const { + const_cast<GroupView*>(this)->executeDelayedItemsLayout(); + return geometryRect(index).translated(-offset()); } QRect GroupView::geometryRect(const QModelIndex &index) const { + const_cast<GroupView*>(this)->executeDelayedItemsLayout(); + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) { return QRect(); @@ -689,9 +742,10 @@ QModelIndex GroupView::indexAt(const QPoint &point) const return QModelIndex(); } -void GroupView::setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) +void GroupView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) { + executeDelayedItemsLayout(); + for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); @@ -726,8 +780,7 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons return pixmap; } -QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const +QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const { Q_ASSERT(r); QRect &rect = *r; diff --git a/application/groupview/GroupView.h b/application/groupview/GroupView.h index ceb602fc..cc5a58aa 100644 --- a/application/groupview/GroupView.h +++ b/application/groupview/GroupView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,6 +20,7 @@ #include <QScrollBar> #include <QCache> #include "VisualGroup.h" +#include <functional> struct GroupViewRoles { @@ -41,6 +42,11 @@ public: void setModel(QAbstractItemModel *model) override; + using visibilityFunction = std::function<bool(const QString &)>; + void setSourceOfGroupCollapseStatus(visibilityFunction f) { + fVisibility = f; + } + /// return geometry rectangle occupied by the specified model item QRect geometryRect(const QModelIndex &index) const; /// return visual rectangle occupied by the specified model item @@ -48,8 +54,7 @@ public: /// get the model index at the specified visual point virtual QModelIndex indexAt(const QPoint &point) const override; QString groupNameAt(const QPoint &point); - void setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) override; + void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; virtual int horizontalOffset() const override; virtual int verticalOffset() const override; @@ -76,9 +81,11 @@ protected slots: virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; void modelReset(); void rowsRemoved(); + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; signals: void droppedURLs(QList<QUrl> urls); + void groupStateChanged(QString group, bool collapsed); protected: virtual bool isIndexHidden(const QModelIndex &index) const override; @@ -102,6 +109,8 @@ private: friend struct VisualGroup; QList<VisualGroup *> m_groups; + visibilityFunction fVisibility; + // geometry int m_leftMargin = 5; int m_rightMargin = 5; diff --git a/application/groupview/GroupedProxyModel.cpp b/application/groupview/GroupedProxyModel.cpp index 5617a1ee..dc4212d5 100644 --- a/application/groupview/GroupedProxyModel.cpp +++ b/application/groupview/GroupedProxyModel.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 e1c51c0f..fabf11c1 100644 --- a/application/groupview/GroupedProxyModel.h +++ b/application/groupview/GroupedProxyModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 42860aef..fc959565 100644 --- a/application/groupview/InstanceDelegate.cpp +++ b/application/groupview/InstanceDelegate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -19,11 +19,13 @@ #include <QTextLayout> #include <QApplication> #include <QtMath> +#include <QDebug> #include "GroupView.h" #include "BaseInstance.h" #include "InstanceList.h" #include <xdgicon.h> +#include <QTextEdit> // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, @@ -165,8 +167,7 @@ static QSize viewItemTextSize(const QStyleOptionViewItem *option) textLayout.setTextOption(textOption); textLayout.setFont(option->font); textLayout.setText(option->text); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; QRect bounds(0, 0, 100 - 2 * textMargin, 600); qreal height = 0, widthUsed = 0; viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); @@ -204,6 +205,8 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti { // FIXME: unused // QSize textSize = viewItemTextSize ( &opt ); + drawSelectionRect(painter, opt, textHighlightRect); + /* QPalette::ColorGroup cg; QStyleOptionViewItem opt2(opt); @@ -218,10 +221,13 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti { cg = QPalette::Disabled; } + */ + /* opt2.palette.setCurrentColorGroup(cg); // fill in background, if any + if (opt.backgroundBrush.style() != Qt::NoBrush) { QPointF oldBO = painter->brushOrigin(); @@ -231,6 +237,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti } drawSelectionRect(painter, opt2, textHighlightRect); + */ /* if (opt.showDecorationSelected) @@ -331,8 +338,7 @@ QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); - const int textMargin = - style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; + const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables QSize szz = viewItemTextSize(&opt); height += szz.height(); @@ -341,3 +347,82 @@ QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, return sz; } +class NoReturnTextEdit: public QTextEdit +{ + Q_OBJECT +public: + explicit NoReturnTextEdit(QWidget *parent) : QTextEdit(parent) + { + setTextInteractionFlags(Qt::TextEditorInteraction); + setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + } + bool event(QEvent * event) override + { + auto eventType = event->type(); + if(eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease) + { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); + auto key = keyEvent->key(); + if (key == Qt::Key_Return || key == Qt::Key_Enter) + { + emit editingDone(); + return true; + } + if(key == Qt::Key_Tab) + { + return true; + } + } + return QTextEdit::event(event); + } +signals: + void editingDone(); +}; + +void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + const int iconSize = 48; + QRect textRect = option.rect; + // QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + textRect.adjust(0, iconSize + 5, 0, 0); + editor->setGeometry(textRect); +} + +void ListViewDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + auto text = index.data(Qt::EditRole).toString(); + QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor); + realeditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop); + realeditor->append(text); + realeditor->selectAll(); + realeditor->document()->clearUndoRedoStacks(); +} + +void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor); + QString text = realeditor->toPlainText(); + text.replace(QChar('\n'), QChar(' ')); + text = text.trimmed(); + if(text.size() != 0) + { + model->setData(index, text); + } +} + +QWidget * ListViewDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + auto editor = new NoReturnTextEdit(parent); + connect(editor, &NoReturnTextEdit::editingDone, this, &ListViewDelegate::editingDone); + return editor; +} + +void ListViewDelegate::editingDone() +{ + NoReturnTextEdit *editor = qobject_cast<NoReturnTextEdit *>(sender()); + emit commitData(editor); + emit closeEditor(editor); +} + +#include "InstanceDelegate.moc" diff --git a/application/groupview/InstanceDelegate.h b/application/groupview/InstanceDelegate.h index d0076e60..d95279f3 100644 --- a/application/groupview/InstanceDelegate.h +++ b/application/groupview/InstanceDelegate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,11 +20,20 @@ class ListViewDelegate : public QStyledItemDelegate { + Q_OBJECT + public: explicit ListViewDelegate(QObject *parent = 0); + virtual ~ListViewDelegate() {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + + void setEditorData(QWidget * editor, const QModelIndex & index) const override; + void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; -protected: - void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +private slots: + void editingDone(); }; diff --git a/application/groupview/VisualGroup.cpp b/application/groupview/VisualGroup.cpp index e08cb241..76bf8678 100644 --- a/application/groupview/VisualGroup.cpp +++ b/application/groupview/VisualGroup.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 0ffcf236..239ee9d7 100644 --- a/application/groupview/VisualGroup.h +++ b/application/groupview/VisualGroup.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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/main.cpp b/application/main.cpp index f724845e..b0360c7e 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -29,7 +29,8 @@ int main(int argc, char *argv[]) #endif #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #endif // initialize Qt @@ -42,7 +43,6 @@ int main(int argc, char *argv[]) { Q_INIT_RESOURCE(multimc); Q_INIT_RESOURCE(backgrounds); - Q_INIT_RESOURCE(assets); Q_INIT_RESOURCE(pe_dark); Q_INIT_RESOURCE(pe_light); diff --git a/application/package/linux/multimc.desktop b/application/package/linux/multimc.desktop index 770f24f1..c25be047 100755 --- a/application/package/linux/multimc.desktop +++ b/application/package/linux/multimc.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Version=1.0 Name=MultiMC -GenericName=MultiMC +GenericName=Minecraft Launcher Comment=Free, open source launcher and instance manager for Minecraft. Type=Application Terminal=false 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 158ce5a4..3e0f570c 100644 --- a/application/package/ubuntu/multimc/DEBIAN/control +++ b/application/package/ubuntu/multimc/DEBIAN/control @@ -1,11 +1,11 @@ Package: multimc -Version: 1.2-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 +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/opt/multimc/run.sh b/application/package/ubuntu/multimc/opt/multimc/run.sh index f4f2fa42..c493a513 100755 --- a/application/package/ubuntu/multimc/opt/multimc/run.sh +++ b/application/package/ubuntu/multimc/opt/multimc/run.sh @@ -22,12 +22,12 @@ deploy() { runmmc() { cd ${INSTDIR} - ./MultiMC + ./MultiMC "$@" } if [[ ! -f ${INSTDIR}/MultiMC ]]; then deploy - runmmc + runmmc "$@" else - runmmc + runmmc "$@" fi 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 50ce65fd..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.2-1` and then run: -``` -fakeroot dpkg-deb --build multimc_1.2-1 -``` - -Replace the version with whatever is appropriate. diff --git a/application/pagedialog/PageDialog.cpp b/application/pagedialog/PageDialog.cpp index c9ee93d8..fd5d36d4 100644 --- a/application/pagedialog/PageDialog.cpp +++ b/application/pagedialog/PageDialog.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -39,6 +39,7 @@ PageDialog::PageDialog(BasePageProvider *pageProvider, QString defaultId, QWidge QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close); buttons->button(QDialogButtonBox::Close)->setDefault(true); + buttons->setContentsMargins(6, 0, 6, 0); m_container->addButtons(buttons); connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close())); diff --git a/application/pagedialog/PageDialog.h b/application/pagedialog/PageDialog.h index 4b7ea708..1029bc30 100644 --- a/application/pagedialog/PageDialog.h +++ b/application/pagedialog/PageDialog.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 e1169c08..408965d0 100644 --- a/application/pages/BasePage.h +++ b/application/pages/BasePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 e403800c..7bfaaf3b 100644 --- a/application/pages/BasePageProvider.h +++ b/application/pages/BasePageProvider.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 b89c410f..ff3736ed 100644 --- a/application/pages/global/AccountListPage.cpp +++ b/application/pages/global/AccountListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -17,11 +17,11 @@ #include "ui_AccountListPage.h" #include <QItemSelectionModel> +#include <QMenu> #include <QDebug> #include "net/NetJob.h" -#include "net/URLConstants.h" #include "Env.h" #include "dialogs/ProgressDialog.h" @@ -30,28 +30,38 @@ #include "dialogs/SkinUploadDialog.h" #include "tasks/Task.h" #include "minecraft/auth/YggdrasilTask.h" +#include "minecraft/services/SkinDelete.h" #include "MultiMC.h" +#include "BuildConfig.h" + AccountListPage::AccountListPage(QWidget *parent) - : QWidget(parent), ui(new Ui::AccountListPage) + : QMainWindow(parent), ui(new Ui::AccountListPage) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->listView->setEmptyString(tr( + "Welcome!\n" + "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account." + )); + ui->listView->setEmptyMode(VersionListView::String); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); m_accounts = MMC->accounts(); ui->listView->setModel(m_accounts.get()); ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui->listView->setSelectionMode(QAbstractItemView::SingleSelection); // Expand the account column ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch); QItemSelectionModel *selectionModel = ui->listView->selectionModel(); - connect(selectionModel, &QItemSelectionModel::selectionChanged, - [this](const QItemSelection &sel, const QItemSelection &dsel) - { updateButtonStates(); }); + connect(selectionModel, &QItemSelectionModel::selectionChanged, [this](const QItemSelection &sel, const QItemSelection &dsel) { + updateButtonStates(); + }); + connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu); connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged())); connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged())); @@ -64,18 +74,41 @@ AccountListPage::~AccountListPage() delete ui; } +void AccountListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->listView->mapToGlobal(pos)); + delete menu; +} + +void AccountListPage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + } + QMainWindow::changeEvent(event); +} + +QMenu * AccountListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->toolBar->toggleViewAction() ); + return filteredMenu; +} + + void AccountListPage::listChanged() { updateButtonStates(); } -void AccountListPage::on_addAccountBtn_clicked() +void AccountListPage::on_actionAdd_triggered() { - addAccount(tr("Please enter your Mojang or Minecraft account username and password to add " - "your account.")); + addAccount(tr("Please enter your Minecraft account email and password to add your account.")); } -void AccountListPage::on_rmAccountBtn_clicked() +void AccountListPage::on_actionRemove_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() > 0) @@ -85,7 +118,7 @@ void AccountListPage::on_rmAccountBtn_clicked() } } -void AccountListPage::on_setDefaultBtn_clicked() +void AccountListPage::on_actionSetDefault_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() > 0) @@ -97,7 +130,7 @@ void AccountListPage::on_setDefaultBtn_clicked() } } -void AccountListPage::on_noDefaultBtn_clicked() +void AccountListPage::on_actionNoDefault_triggered() { m_accounts->setActiveAccount(""); } @@ -107,11 +140,20 @@ void AccountListPage::updateButtonStates() // If there is no selection, disable buttons that require something selected. QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); - ui->rmAccountBtn->setEnabled(selection.size() > 0); - ui->setDefaultBtn->setEnabled(selection.size() > 0); - ui->uploadSkinBtn->setEnabled(selection.size() > 0); + 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); + ui->actionNoDefault->setChecked(true); + } + else { + ui->actionNoDefault->setEnabled(true); + ui->actionNoDefault->setChecked(false); + } - ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr); } void AccountListPage::addAccount(const QString &errMsg) @@ -131,7 +173,7 @@ void AccountListPage::addAccount(const QString &errMsg) for (AccountProfile profile : account->profiles()) { auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png"); - auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta); + auto action = Net::Download::makeCached(QUrl(BuildConfig.SKINS_BASE + profile.id + ".png"), meta); job->addNetAction(action); meta->setStale(true); } @@ -140,7 +182,7 @@ void AccountListPage::addAccount(const QString &errMsg) } } -void AccountListPage::on_uploadSkinBtn_clicked() +void AccountListPage::on_actionUploadSkin_triggered() { QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() > 0) @@ -151,3 +193,25 @@ void AccountListPage::on_uploadSkinBtn_clicked() 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 ad93c904..fba1833f 100644 --- a/application/pages/global/AccountListPage.h +++ b/application/pages/global/AccountListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -15,7 +15,7 @@ #pragma once -#include <QDialog> +#include <QMainWindow> #include <memory> #include "pages/BasePage.h" @@ -30,7 +30,7 @@ class AccountListPage; class AuthenticateTask; -class AccountListPage : public QWidget, public BasePage +class AccountListPage : public QMainWindow, public BasePage { Q_OBJECT public: @@ -59,30 +59,26 @@ public: return "Getting-Started#adding-an-account"; } -public -slots: - void on_addAccountBtn_clicked(); - - void on_rmAccountBtn_clicked(); - - void on_setDefaultBtn_clicked(); - - void on_noDefaultBtn_clicked(); - - void on_uploadSkinBtn_clicked(); +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 f4e87680..71647db3 100644 --- a/application/pages/global/AccountListPage.ui +++ b/application/pages/global/AccountListPage.ui @@ -1,122 +1,98 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>AccountListPage</class> - <widget class="QWidget" name="AccountListPage"> + <widget class="QMainWindow" name="AccountListPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>694</width> - <height>609</height> + <width>800</width> + <height>600</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="leftMargin"> - <number>0</number> + <widget class="QWidget" name="centralwidget"> + <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="VersionListView" name="listView"/> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <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"> + <string>Add</string> </property> - <property name="topMargin"> - <number>0</number> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> </property> - <property name="rightMargin"> - <number>0</number> + </action> + <action name="actionSetDefault"> + <property name="text"> + <string>Set Default</string> </property> - <property name="bottomMargin"> - <number>0</number> + </action> + <action name="actionNoDefault"> + <property name="checkable"> + <bool>true</bool> </property> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="welcomeLabel"> - <property name="text"> - <string><html><head/><body><p>Welcome! If you're new here, you can click the &quot;Add&quot; button to add your Mojang or Minecraft account.</p></body></html></string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QTreeView" name="listView"/> - </item> - <item> - <layout class="QVBoxLayout" name="manageAcctsBtnBox"> - <item> - <widget class="QPushButton" name="addAccountBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmAccountBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <spacer name="buttonSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="setDefaultBtn"> - <property name="toolTip"> - <string><html><head/><body><p>Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).</p></body></html></string> - </property> - <property name="text"> - <string>&Set Default</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="noDefaultBtn"> - <property name="toolTip"> - <string>Set no default account. This will cause MultiMC to prompt you to select an account every time you launch an instance that doesn't have its own default set.</string> - </property> - <property name="text"> - <string>&No Default</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="uploadSkinBtn"> - <property name="toolTip"> - <string>Opens a dialog to select and upload a skin image to the selected account.</string> - </property> - <property name="text"> - <string>&Upload Skin</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + <property name="text"> + <string>No Default</string> + </property> + </action> + <action name="actionUploadSkin"> + <property name="text"> + <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> + <class>VersionListView</class> + <extends>QTreeView</extends> + <header>widgets/VersionListView.h</header> + </customwidget> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> + </customwidgets> <resources/> <connections/> </ui> diff --git a/application/pages/global/CustomCommandsPage.cpp b/application/pages/global/CustomCommandsPage.cpp index f2c3b185..3b182319 100644 --- a/application/pages/global/CustomCommandsPage.cpp +++ b/application/pages/global/CustomCommandsPage.cpp @@ -13,6 +13,7 @@ CustomCommandsPage::CustomCommandsPage(QWidget* parent): QWidget(parent) auto tabWidget = new QTabWidget(this); tabWidget->setObjectName(QStringLiteral("tabWidget")); commands = new CustomCommands(this); + commands->setContentsMargins(6, 6, 6, 6); tabWidget->addTab(commands, "Foo"); tabWidget->tabBar()->hide(); verticalLayout->addWidget(tabWidget); diff --git a/application/pages/global/CustomCommandsPage.h b/application/pages/global/CustomCommandsPage.h index d7206dfa..414c3259 100644 --- a/application/pages/global/CustomCommandsPage.h +++ b/application/pages/global/CustomCommandsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2018 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 41ed3f7c..6a0a38be 100644 --- a/application/pages/global/ExternalToolsPage.cpp +++ b/application/pages/global/ExternalToolsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 bc42d2dd..0fc8ebe1 100644 --- a/application/pages/global/ExternalToolsPage.h +++ b/application/pages/global/ExternalToolsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.ui b/application/pages/global/ExternalToolsPage.ui index 5f19898b..e79e9388 100644 --- a/application/pages/global/ExternalToolsPage.ui +++ b/application/pages/global/ExternalToolsPage.ui @@ -63,7 +63,7 @@ <item> <widget class="QLabel" name="jprofilerLink"> <property name="text"> - <string notr="true"><html><head/><body><p><a href="http://www.ej-technologies.com/products/jprofiler/overview.html">http://www.ej-technologies.com/products/jprofiler/overview.html</a></p></body></html></string> + <string notr="true"><html><head/><body><p><a href="https://www.ej-technologies.com/products/jprofiler/overview.html">https://www.ej-technologies.com/products/jprofiler/overview.html</a></p></body></html></string> </property> </widget> </item> @@ -137,7 +137,7 @@ <item> <widget class="QLabel" name="mceditLink"> <property name="text"> - <string notr="true"><html><head/><body><p><a href="http://www.mcedit.net/">http://www.mcedit.net/</a></p></body></html></string> + <string notr="true"><html><head/><body><p><a href="https://www.mcedit.net/">https://www.mcedit.net/</a></p></body></html></string> </property> </widget> </item> diff --git a/application/pages/global/JavaPage.cpp b/application/pages/global/JavaPage.cpp index 57e60ddf..cde0e035 100644 --- a/application/pages/global/JavaPage.cpp +++ b/application/pages/global/JavaPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 dc53402a..832f460b 100644 --- a/application/pages/global/JavaPage.h +++ b/application/pages/global/JavaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.cpp b/application/pages/global/LanguagePage.cpp new file mode 100644 index 00000000..ae3168cc --- /dev/null +++ b/application/pages/global/LanguagePage.cpp @@ -0,0 +1,51 @@ +#include "LanguagePage.h" + +#include "widgets/LanguageSelectionWidget.h" +#include <QVBoxLayout> + +LanguagePage::LanguagePage(QWidget* parent) : + QWidget(parent) +{ + setObjectName(QStringLiteral("languagePage")); + auto layout = new QVBoxLayout(this); + mainWidget = new LanguageSelectionWidget(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(mainWidget); + retranslate(); +} + +LanguagePage::~LanguagePage() +{ +} + +bool LanguagePage::apply() +{ + applySettings(); + return true; +} + +void LanguagePage::applySettings() +{ + auto settings = MMC->settings(); + QString key = mainWidget->getSelectedLanguageKey(); + settings->set("Language", key); +} + +void LanguagePage::loadSettings() +{ + // NIL +} + +void LanguagePage::retranslate() +{ + mainWidget->retranslate(); +} + +void LanguagePage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) + { + retranslate(); + } + QWidget::changeEvent(event); +} diff --git a/application/pages/global/LanguagePage.h b/application/pages/global/LanguagePage.h new file mode 100644 index 00000000..ca8eecc6 --- /dev/null +++ b/application/pages/global/LanguagePage.h @@ -0,0 +1,60 @@ +/* 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 <memory> +#include "pages/BasePage.h" +#include <MultiMC.h> +#include <QWidget> + +class LanguageSelectionWidget; + +class LanguagePage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit LanguagePage(QWidget *parent = 0); + virtual ~LanguagePage(); + + QString displayName() const override + { + return tr("Language"); + } + QIcon icon() const override + { + return MMC->getThemedIcon("language"); + } + QString id() const override + { + return "language-settings"; + } + QString helpPage() const override + { + return "Language-settings"; + } + bool apply() override; + + void changeEvent(QEvent * ) override; + +private: + void applySettings(); + void loadSettings(); + void retranslate(); + +private: + LanguageSelectionWidget *mainWidget; +}; diff --git a/application/pages/global/MinecraftPage.cpp b/application/pages/global/MinecraftPage.cpp index 06429449..6c9bd307 100644 --- a/application/pages/global/MinecraftPage.cpp +++ b/application/pages/global/MinecraftPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 e5d5f854..5e781aed 100644 --- a/application/pages/global/MinecraftPage.h +++ b/application/pages/global/MinecraftPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 1991c9ba..80d5c544 100644 --- a/application/pages/global/MultiMCPage.cpp +++ b/application/pages/global/MultiMCPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -78,12 +78,12 @@ MultiMCPage::MultiMCPage(QWidget *parent) : QWidget(parent), ui(new Ui::MultiMCP } connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview())); connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview())); - connect(ui->languageBox, SIGNAL(currentIndexChanged(int)), SLOT(languageIndexChanged(int))); } MultiMCPage::~MultiMCPage() { delete ui; + delete defaultFormat; } bool MultiMCPage::apply() @@ -147,19 +147,6 @@ void MultiMCPage::on_modsDirBrowseBtn_clicked() } } -void MultiMCPage::languageIndexChanged(int index) -{ - auto languageCode = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); - if(languageCode.isEmpty()) - { - qWarning() << "Unknown language at index" << index; - return; - } - auto translations = MMC->translations(); - translations->selectLanguage(languageCode); - translations->updateLanguage(languageCode); -} - void MultiMCPage::refreshUpdateChannelList() { // Stop listening for selection changes. It's going to change a lot while we update it and @@ -236,9 +223,6 @@ void MultiMCPage::applySettings() { auto s = MMC->settings(); - // Language - s->set("Language", ui->languageBox->itemData(ui->languageBox->currentIndex()).toString()); - if (ui->resetNotificationsBtn->isChecked()) { s->set("ShownNotifications", QString()); @@ -331,12 +315,6 @@ void MultiMCPage::applySettings() void MultiMCPage::loadSettings() { auto s = MMC->settings(); - // Language - { - ui->languageBox->setModel(m_languageModel.get()); - ui->languageBox->setCurrentIndex(ui->languageBox->findData(s->get("Language").toString())); - } - // Updates ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); m_currentUpdateChannel = s->get("UpdateChannel").toString(); diff --git a/application/pages/global/MultiMCPage.h b/application/pages/global/MultiMCPage.h index bf21305e..e81832eb 100644 --- a/application/pages/global/MultiMCPage.h +++ b/application/pages/global/MultiMCPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -68,8 +68,6 @@ slots: void on_modsDirBrowseBtn_clicked(); void on_iconsDirBrowseBtn_clicked(); - void languageIndexChanged(int index); - /*! * Updates the list of update channels in the combo box. */ diff --git a/application/pages/global/MultiMCPage.ui b/application/pages/global/MultiMCPage.ui index 124401c3..ea034919 100644 --- a/application/pages/global/MultiMCPage.ui +++ b/application/pages/global/MultiMCPage.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>467</width> + <width>514</width> <height>629</height> </rect> </property> @@ -229,18 +229,6 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Language:</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QComboBox" name="languageBox"/> - </item> - </layout> - </widget> - </item> - <item> <widget class="QGroupBox" name="themeBox"> <property name="title"> <string>Theme</string> @@ -570,7 +558,6 @@ <tabstop>resetNotificationsBtn</tabstop> <tabstop>sortLastLaunchedBtn</tabstop> <tabstop>sortByNameBtn</tabstop> - <tabstop>languageBox</tabstop> <tabstop>themeComboBox</tabstop> <tabstop>themeComboBoxColors</tabstop> <tabstop>showConsoleCheck</tabstop> diff --git a/application/pages/global/PackagesPage.cpp b/application/pages/global/PackagesPage.cpp deleted file mode 100644 index b6a7887b..00000000 --- a/application/pages/global/PackagesPage.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* Copyright 2015-2018 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 "PackagesPage.h" -#include "ui_PackagesPage.h" - -#include <QDateTime> -#include <QSortFilterProxyModel> -#include <QRegularExpression> - -#include "dialogs/ProgressDialog.h" -#include "VersionProxyModel.h" - -#include "meta/Index.h" -#include "meta/VersionList.h" -#include "meta/Version.h" -#include "Env.h" -#include "MultiMC.h" - -using namespace Meta; - -static QString formatRequires(const VersionPtr &version) -{ - QStringList lines; - auto & reqs = version->requires(); - auto iter = reqs.begin(); - while (iter != reqs.end()) - { - auto &uid = iter->uid; - auto &version = iter->equalsVersion; - const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid; - if(!version.isEmpty()) - { - lines.append(QString("%1 (%2)").arg(readable, version)); - } - else - { - lines.append(QString("%1").arg(readable)); - } - iter++; - } - return lines.join('\n'); -} - -PackagesPage::PackagesPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::PackagesPage) -{ - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - - m_fileProxy = new QSortFilterProxyModel(this); - m_fileProxy->setSortRole(Qt::DisplayRole); - m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterRole(Qt::DisplayRole); - m_fileProxy->setFilterKeyColumn(0); - m_fileProxy->sort(0); - m_fileProxy->setSourceModel(ENV.metadataIndex().get()); - ui->indexView->setModel(m_fileProxy); - - m_filterProxy = new QSortFilterProxyModel(this); - m_filterProxy->setSortRole(VersionList::SortRole); - m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterProxy->setFilterRole(Qt::DisplayRole); - m_filterProxy->setFilterKeyColumn(0); - m_filterProxy->sort(0, Qt::DescendingOrder); - ui->versionsView->setModel(m_filterProxy); - - m_versionProxy = new VersionProxyModel(this); - m_filterProxy->setSourceModel(m_versionProxy); - - connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList); - connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion); - connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged); - - updateCurrentVersionList(QModelIndex()); - updateVersion(); -} - -PackagesPage::~PackagesPage() -{ - delete ui; -} - -QIcon PackagesPage::icon() const -{ - return MMC->getThemedIcon("packages"); -} - -void PackagesPage::on_refreshIndexBtn_clicked() -{ - ENV.metadataIndex()->load(Net::Mode::Online); -} -void PackagesPage::on_refreshFileBtn_clicked() -{ - VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value<VersionListPtr>(); - if (!list) - { - return; - } - list->load(Net::Mode::Online); -} -void PackagesPage::on_refreshVersionBtn_clicked() -{ - VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value<VersionPtr>(); - if (!version) - { - return; - } - version->load(Net::Mode::Online); -} - -void PackagesPage::on_fileSearchEdit_textChanged(const QString &search) -{ - if (search.isEmpty()) - { - m_fileProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } -} -void PackagesPage::on_versionSearchEdit_textChanged(const QString &search) -{ - if (search.isEmpty()) - { - m_filterProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } -} - -void PackagesPage::updateCurrentVersionList(const QModelIndex &index) -{ - if (index.isValid()) - { - VersionListPtr list = index.data(Index::ListPtrRole).value<VersionListPtr>(); - ui->versionsBox->setEnabled(true); - ui->refreshFileBtn->setEnabled(true); - ui->fileUidLabel->setEnabled(true); - ui->fileUid->setText(list->uid()); - ui->fileNameLabel->setEnabled(true); - ui->fileName->setText(list->name()); - m_versionProxy->setSourceModel(list.get()); - ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable())); - list->load(Net::Mode::Offline); - } - else - { - ui->versionsBox->setEnabled(false); - ui->refreshFileBtn->setEnabled(false); - ui->fileUidLabel->setEnabled(false); - ui->fileUid->clear(); - ui->fileNameLabel->setEnabled(false); - ui->fileName->clear(); - m_versionProxy->setSourceModel(nullptr); - ui->refreshFileBtn->setText(tr("Refresh")); - } -} - -void PackagesPage::versionListDataChanged(const QModelIndex &tl, const QModelIndex &br) -{ - if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex())) - { - updateVersion(); - } -} - -void PackagesPage::updateVersion() -{ - VersionPtr version = std::dynamic_pointer_cast<Version>( - ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value<BaseVersionPtr>()); - if (version) - { - ui->refreshVersionBtn->setEnabled(true); - ui->versionVersionLabel->setEnabled(true); - ui->versionVersion->setText(version->version()); - ui->versionTimeLabel->setEnabled(true); - ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm")); - ui->versionTypeLabel->setEnabled(true); - ui->versionType->setText(version->type()); - ui->versionRequiresLabel->setEnabled(true); - ui->versionRequires->setText(formatRequires(version)); - ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version())); - } - else - { - ui->refreshVersionBtn->setEnabled(false); - ui->versionVersionLabel->setEnabled(false); - ui->versionVersion->clear(); - ui->versionTimeLabel->setEnabled(false); - ui->versionTime->clear(); - ui->versionTypeLabel->setEnabled(false); - ui->versionType->clear(); - ui->versionRequiresLabel->setEnabled(false); - ui->versionRequires->clear(); - ui->refreshVersionBtn->setText(tr("Refresh")); - } -} - -void PackagesPage::openedImpl() -{ - ENV.metadataIndex()->load(Net::Mode::Offline); -} diff --git a/application/pages/global/PackagesPage.h b/application/pages/global/PackagesPage.h deleted file mode 100644 index c8aa6da6..00000000 --- a/application/pages/global/PackagesPage.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright 2015-2018 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" - -namespace Ui { -class PackagesPage; -} - -class QSortFilterProxyModel; -class VersionProxyModel; - -class PackagesPage : public QWidget, public BasePage -{ - Q_OBJECT -public: - explicit PackagesPage(QWidget *parent = 0); - ~PackagesPage(); - - QString id() const override { return "packages-global"; } - QString displayName() const override { return tr("Packages"); } - QIcon icon() const override; - void openedImpl() override; - -private slots: - void on_refreshIndexBtn_clicked(); - void on_refreshFileBtn_clicked(); - void on_refreshVersionBtn_clicked(); - void on_fileSearchEdit_textChanged(const QString &search); - void on_versionSearchEdit_textChanged(const QString &search); - void updateCurrentVersionList(const QModelIndex &index); - void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br); - -private: - Ui::PackagesPage *ui; - QSortFilterProxyModel *m_fileProxy; - QSortFilterProxyModel *m_filterProxy; - VersionProxyModel *m_versionProxy; - - void updateVersion(); -}; diff --git a/application/pages/global/PackagesPage.ui b/application/pages/global/PackagesPage.ui deleted file mode 100644 index 158bf1b4..00000000 --- a/application/pages/global/PackagesPage.ui +++ /dev/null @@ -1,252 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>PackagesPage</class> - <widget class="QWidget" name="PackagesPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>636</width> - <height>621</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string>Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="2"> - <widget class="QGroupBox" name="versionsBox"> - <property name="title"> - <string>Versions</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QLineEdit" name="versionSearchEdit"> - <property name="placeholderText"> - <string>Search...</string> - </property> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTreeView" name="versionsView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <attribute name="headerVisible"> - <bool>false</bool> - </attribute> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QPushButton" name="refreshVersionBtn"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QFormLayout" name="formLayout_2"> - <item row="0" column="0"> - <widget class="QLabel" name="versionVersionLabel"> - <property name="text"> - <string>Version:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="versionVersion"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="versionTimeLabel"> - <property name="text"> - <string>Time:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="versionTime"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="versionTypeLabel"> - <property name="text"> - <string>Type:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLabel" name="versionType"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="versionRequiresLabel"> - <property name="text"> - <string>Dependencies:</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLabel" name="versionRequires"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item> - <spacer name="verticalSpacer_2"> - <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> - </item> - <item row="1" column="1"> - <widget class="QGroupBox" name="versionListsBox"> - <property name="title"> - <string>Resources</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLineEdit" name="fileSearchEdit"> - <property name="placeholderText"> - <string>Search...</string> - </property> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTreeView" name="indexView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <attribute name="headerVisible"> - <bool>false</bool> - </attribute> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QPushButton" name="refreshFileBtn"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QFormLayout" name="formLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="fileUidLabel"> - <property name="text"> - <string>UID:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="fileUid"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="fileNameLabel"> - <property name="text"> - <string>Name:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="fileName"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </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> - </item> - <item row="0" column="1" colspan="2"> - <widget class="QPushButton" name="refreshIndexBtn"> - <property name="text"> - <string>Refresh Index</string> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/application/pages/global/PasteEEPage.cpp b/application/pages/global/PasteEEPage.cpp index b144c832..f932dede 100644 --- a/application/pages/global/PasteEEPage.cpp +++ b/application/pages/global/PasteEEPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 5d64d567..001decdb 100644 --- a/application/pages/global/PasteEEPage.h +++ b/application/pages/global/PasteEEPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 6dbd0a5d..809059ff 100644 --- a/application/pages/global/ProxyPage.cpp +++ b/application/pages/global/ProxyPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,6 +20,7 @@ #include "settings/SettingsObject.h" #include "MultiMC.h" +#include "Env.h" ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage) { @@ -75,6 +76,9 @@ void ProxyPage::applySettings() s->set("ProxyPort", ui->proxyPortEdit->value()); s->set("ProxyUser", ui->proxyUserEdit->text()); s->set("ProxyPass", ui->proxyPassEdit->text()); + + ENV.updateProxySettings(proxyType, ui->proxyAddrEdit->text(), ui->proxyPortEdit->value(), + ui->proxyUserEdit->text(), ui->proxyPassEdit->text()); } void ProxyPage::loadSettings() { @@ -91,7 +95,7 @@ void ProxyPage::loadSettings() ui->proxyHTTPBtn->setChecked(true); ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString()); - ui->proxyPortEdit->setValue(s->get("ProxyPort").value<qint16>()); + ui->proxyPortEdit->setValue(s->get("ProxyPort").value<uint16_t>()); ui->proxyUserEdit->setText(s->get("ProxyUser").toString()); ui->proxyPassEdit->setText(s->get("ProxyPass").toString()); } diff --git a/application/pages/global/ProxyPage.h b/application/pages/global/ProxyPage.h index 47b3004e..ff94ec49 100644 --- a/application/pages/global/ProxyPage.h +++ b/application/pages/global/ProxyPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 new file mode 100644 index 00000000..782f2ab3 --- /dev/null +++ b/application/pages/instance/GameOptionsPage.cpp @@ -0,0 +1,37 @@ +#include "GameOptionsPage.h" +#include "ui_GameOptionsPage.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/gameoptions/GameOptions.h" + +GameOptionsPage::GameOptionsPage(MinecraftInstance * inst, QWidget* parent) + : QWidget(parent), ui(new Ui::GameOptionsPage) +{ + ui->setupUi(this); + ui->tabWidget->tabBar()->hide(); + m_model = inst->gameOptionsModel(); + ui->optionsView->setModel(m_model.get()); + auto head = ui->optionsView->header(); + if(head->count()) + { + head->setSectionResizeMode(0, QHeaderView::ResizeToContents); + for(int i = 1; i < head->count(); i++) + { + head->setSectionResizeMode(i, QHeaderView::Stretch); + } + } +} + +GameOptionsPage::~GameOptionsPage() +{ + // m_model->save(); +} + +void GameOptionsPage::openedImpl() +{ + // m_model->observe(); +} + +void GameOptionsPage::closedImpl() +{ + // m_model->unobserve(); +} diff --git a/application/pages/modplatform/TwitchPage.h b/application/pages/instance/GameOptionsPage.h index 36080016..0fd2fbff 100644 --- a/application/pages/modplatform/TwitchPage.h +++ b/application/pages/instance/GameOptionsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -16,46 +16,48 @@ #pragma once #include <QWidget> +#include <QString> #include "pages/BasePage.h" #include <MultiMC.h> -#include "tasks/Task.h" namespace Ui { -class TwitchPage; +class GameOptionsPage; } -class NewInstanceDialog; +class GameOptions; +class MinecraftInstance; -class TwitchPage : public QWidget, public BasePage +class GameOptionsPage : public QWidget, public BasePage { Q_OBJECT public: - explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~TwitchPage(); + explicit GameOptionsPage(MinecraftInstance *inst, QWidget *parent = 0); + virtual ~GameOptionsPage(); + + void openedImpl() override; + void closedImpl() override; + virtual QString displayName() const override { - return tr("Twitch"); + return tr("Game Options"); } virtual QIcon icon() const override { - return MMC->getThemedIcon("twitch"); + return MMC->getThemedIcon("settings"); } virtual QString id() const override { - return "twitch"; + return "gameoptions"; } virtual QString helpPage() const override { - return "Twitch-platform"; + return "Game-Options-management"; } - virtual bool shouldDisplay() const override; - - void openedImpl() override; -private: - Ui::TwitchPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; +private: // data + Ui::GameOptionsPage *ui = nullptr; + std::shared_ptr<GameOptions> m_model; }; diff --git a/application/pages/instance/GameOptionsPage.ui b/application/pages/instance/GameOptionsPage.ui new file mode 100644 index 00000000..f0a5ce0e --- /dev/null +++ b/application/pages/instance/GameOptionsPage.ui @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>GameOptionsPage</class> + <widget class="QWidget" name="GameOptionsPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>706</width> + <height>575</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <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 row="0" column="0"> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <attribute name="title"> + <string notr="true">Tab 1</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0" colspan="2"> + <widget class="QTreeView" name="optionsView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="iconSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>tabWidget</tabstop> + <tabstop>optionsView</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/application/pages/instance/InstanceSettingsPage.cpp b/application/pages/instance/InstanceSettingsPage.cpp index 0704ffc5..7bd424c0 100644 --- a/application/pages/instance/InstanceSettingsPage.cpp +++ b/application/pages/instance/InstanceSettingsPage.cpp @@ -19,8 +19,11 @@ 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); + connect(MMC, &MultiMC::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); loadSettings(); } @@ -34,6 +37,21 @@ InstanceSettingsPage::~InstanceSettingsPage() delete ui; } +void InstanceSettingsPage::globalSettingsButtonClicked(bool) +{ + switch(ui->settingsTabs->currentIndex()) { + case 0: + MMC->ShowGlobalSettings(this, "java-settings"); + return; + case 1: + MMC->ShowGlobalSettings(this, "minecraft-settings"); + return; + case 2: + MMC->ShowGlobalSettings(this, "custom-commands"); + return; + } +} + bool InstanceSettingsPage::apply() { applySettings(); @@ -145,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() @@ -176,6 +234,11 @@ void InstanceSettingsPage::loadSettings() ui->maxMemSpinBox->setValue(min); } ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt()); + bool permGenVisible = m_settings->get("PermGenVisible").toBool(); + ui->permGenSpinBox->setVisible(permGenVisible); + ui->labelPermGen->setVisible(permGenVisible); + ui->labelPermgenNote->setVisible(permGenVisible); + // Java Settings bool overrideJava = m_settings->get("OverrideJava").toBool(); @@ -196,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() @@ -210,6 +286,11 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked() { java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion()); ui->javaPathTextBox->setText(java->path); + bool visible = java->id.requiresPermGen() && m_settings->get("OverrideMemory").toBool(); + ui->permGenSpinBox->setVisible(visible); + ui->labelPermGen->setVisible(visible); + ui->labelPermgenNote->setVisible(visible); + m_settings->set("PermGenVisible", visible); } } @@ -224,12 +305,18 @@ void InstanceSettingsPage::on_javaBrowseBtn_clicked() } QString cooked_path = FS::NormalizePath(raw_path); - QFileInfo javaInfo(cooked_path);; + QFileInfo javaInfo(cooked_path); if(!javaInfo.exists() || !javaInfo.isExecutable()) { return; } ui->javaPathTextBox->setText(cooked_path); + + // custom Java could be anything... enable perm gen option + ui->permGenSpinBox->setVisible(true); + ui->labelPermGen->setVisible(true); + ui->labelPermgenNote->setVisible(true); + m_settings->set("PermGenVisible", true); } void InstanceSettingsPage::on_javaTestBtn_clicked() diff --git a/application/pages/instance/InstanceSettingsPage.h b/application/pages/instance/InstanceSettingsPage.h index cc35732e..068213a8 100644 --- a/application/pages/instance/InstanceSettingsPage.h +++ b/application/pages/instance/InstanceSettingsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -66,6 +66,8 @@ private slots: void checkerFinished(); + void globalSettingsButtonClicked(bool checked); + private: Ui::InstanceSettingsPage *ui; BaseInstance *m_instance; diff --git a/application/pages/instance/InstanceSettingsPage.ui b/application/pages/instance/InstanceSettingsPage.ui index 0c180df3..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>553</width> - <height>522</height> + <width>691</width> + <height>581</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> @@ -24,6 +24,16 @@ <number>0</number> </property> <item> + <widget class="QCommandLinkButton" name="openGlobalJavaSettingsButton"> + <property name="text"> + <string>Open Global Settings</string> + </property> + <property name="description"> + <string>The settings here are overrides for global settings.</string> + </property> + </widget> + </item> + <item> <widget class="QTabWidget" name="settingsTabs"> <property name="tabShape"> <enum>QTabWidget::Rounded</enum> @@ -106,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> @@ -128,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> @@ -150,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> @@ -354,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> @@ -367,6 +516,7 @@ </customwidget> </customwidgets> <tabstops> + <tabstop>openGlobalJavaSettingsButton</tabstop> <tabstop>settingsTabs</tabstop> <tabstop>javaSettingsGroupBox</tabstop> <tabstop>javaPathTextBox</tabstop> @@ -387,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.cpp b/application/pages/instance/LegacyUpgradePage.cpp index 15fd10cc..af800b03 100644 --- a/application/pages/instance/LegacyUpgradePage.cpp +++ b/application/pages/instance/LegacyUpgradePage.cpp @@ -40,7 +40,7 @@ void LegacyUpgradePage::on_upgradeButton_clicked() upgradeTask->setName(newName); upgradeTask->setGroup(MMC->instances()->getInstanceGroup(m_inst->id())); upgradeTask->setIcon(m_inst->iconKey()); - std::unique_ptr<Task> task(MMC->instances()->wrapInstanceTask(upgradeTask)); + unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(upgradeTask)); runModalTask(task.get()); } diff --git a/application/pages/instance/LegacyUpgradePage.h b/application/pages/instance/LegacyUpgradePage.h index 4136d703..df34e33a 100644 --- a/application/pages/instance/LegacyUpgradePage.h +++ b/application/pages/instance/LegacyUpgradePage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 0e480a3a..3d2085c6 100644 --- a/application/pages/instance/LogPage.cpp +++ b/application/pages/instance/LogPage.cpp @@ -192,7 +192,7 @@ void LogPage::UIToModelState() m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked); } -void LogPage::setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial) +void LogPage::setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial) { m_process = proc; if(m_process) @@ -215,7 +215,7 @@ void LogPage::setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, boo } } -void LogPage::onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc) +void LogPage::onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc) { setInstanceLaunchTaskChanged(proc, false); } @@ -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 b9c4d302..b0b0e04b 100644 --- a/application/pages/instance/LogPage.h +++ b/application/pages/instance/LogPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -69,17 +69,17 @@ private slots: void findNextActivated(); void findPreviousActivated(); - void onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc); + void onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc); private: void modelStateToUI(); void UIToModelState(); - void setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial); + void setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial); private: Ui::LogPage *ui; InstancePtr m_instance; - std::shared_ptr<LaunchTask> m_process; + shared_qobject_ptr<LaunchTask> m_process; LogFormatProxyModel * m_proxy; shared_qobject_ptr <LogModel> m_model; diff --git a/application/pages/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp index d891c068..98f20e77 100644 --- a/application/pages/instance/ModFolderPage.cpp +++ b/application/pages/instance/ModFolderPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,32 +20,135 @@ #include <QEvent> #include <QKeyEvent> #include <QAbstractItemModel> +#include <QMenu> #include "MultiMC.h" #include "dialogs/CustomMessageBox.h" -#include "dialogs/ModEditDialogCommon.h" #include <GuiUtil.h> -#include "minecraft/SimpleModList.h" -#include "minecraft/Mod.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/Mod.h" #include "minecraft/VersionFilterData.h" -#include "minecraft/ComponentList.h" +#include "minecraft/PackProfile.h" #include <DesktopServices.h> -ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id, - QString iconName, QString displayName, QString helpPage, - QWidget *parent) - : QWidget(parent), ui(new Ui::ModFolderPage) +#include <QSortFilterProxyModel> +#include "Version.h" + +namespace { + // FIXME: wasteful + void RemoveThePrefix(QString & string) { + QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +")); + string.remove(regex); + string = string.trimmed(); + } +} + +class ModSortProxy : public QSortFilterProxyModel +{ +public: + explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent) + { + } + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override { + ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel()); + if(!model) { + return false; + } + const auto &mod = model->at(source_row); + if(mod.name().contains(filterRegExp())) { + return true; + } + if(mod.description().contains(filterRegExp())) { + return true; + } + for(auto & author: mod.authors()) { + if (author.contains(filterRegExp())) { + return true; + } + } + return false; + } + + bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override + { + ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel()); + if( + !model || + !source_left.isValid() || + !source_right.isValid() || + source_left.column() != source_right.column() + ) { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + + // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed. + + auto column = (ModFolderModel::Columns) source_left.column(); + bool invert = false; + switch(column) { + // GH-2550 - sort by enabled/disabled + case ModFolderModel::ActiveColumn: { + auto dataL = source_left.data(Qt::CheckStateRole).toBool(); + auto dataR = source_right.data(Qt::CheckStateRole).toBool(); + if(dataL != dataR) { + return dataL > dataR; + } + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2722 - sort mod names in a way that discards "The" prefixes + case ModFolderModel::NameColumn: { + auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataL); + auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString(); + RemoveThePrefix(dataR); + + auto less = dataL.compare(dataR, sortCaseSensitivity()); + if(less != 0) { + return invert ? (less > 0) : (less < 0); + } + // fallthrough + invert = sortOrder() == Qt::DescendingOrder; + } + // GH-2762 - sort versions by parsing them as versions + case ModFolderModel::VersionColumn: { + auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString()); + auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString()); + return invert ? (dataL > dataR) : (dataL < dataR); + } + default: { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + } + } +}; + +ModFolderPage::ModFolderPage( + BaseInstance *inst, + std::shared_ptr<ModFolderModel> mods, + QString id, + QString iconName, + QString displayName, + QString helpPage, + QWidget *parent +) : + QMainWindow(parent), + ui(new Ui::ModFolderPage) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->actionsToolbar->insertSpacer(ui->actionView_configs); + m_inst = inst; + on_RunningState_changed(m_inst && m_inst->isRunning()); m_mods = mods; m_id = id; m_displayName = displayName; m_iconName = iconName; m_helpName = helpPage; m_fileSelectionFilter = "%1 (*.zip *.jar)"; - m_filterModel = new QSortFilterProxyModel(this); + m_filterModel = new ModSortProxy(this); m_filterModel->setDynamicSortFilter(true); m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); @@ -54,9 +157,37 @@ ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> ui->modTreeView->setModel(m_filterModel); ui->modTreeView->installEventFilter(this); ui->modTreeView->sortByColumn(1, Qt::AscendingOrder); + ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu); + connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated); + 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); +} + +void ModFolderPage::modItemActivated(const QModelIndex&) +{ + if(!m_controlsEnabled) { + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle); +} + +QMenu * ModFolderPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() ); + return filteredMenu; +} + +void ModFolderPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->modTreeView->mapToGlobal(pos)); + delete menu; } void ModFolderPage::openedImpl() @@ -76,7 +207,7 @@ void ModFolderPage::on_filterTextChanged(const QString& newContents) } -CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, +CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id, QString iconName, QString displayName, QString helpPage, QWidget *parent) : ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent) @@ -89,10 +220,20 @@ ModFolderPage::~ModFolderPage() delete ui; } +void ModFolderPage::on_RunningState_changed(bool running) +{ + if(m_controlsEnabled == !running) { + return; + } + m_controlsEnabled = !running; + ui->actionAdd->setEnabled(m_controlsEnabled); + ui->actionDisable->setEnabled(m_controlsEnabled); + ui->actionEnable->setEnabled(m_controlsEnabled); + ui->actionRemove->setEnabled(m_controlsEnabled); +} + bool ModFolderPage::shouldDisplay() const { - if (m_inst) - return !m_inst->isRunning(); return true; } @@ -103,7 +244,7 @@ bool CoreModFolderPage::shouldDisplay() const auto inst = dynamic_cast<MinecraftInstance *>(m_inst); if (!inst) return true; - auto version = inst->getComponentList(); + auto version = inst->getPackProfile(); if (!version) return true; if(!version->getComponent("net.minecraftforge")) @@ -127,10 +268,10 @@ bool ModFolderPage::modListFilter(QKeyEvent *keyEvent) switch (keyEvent->key()) { case Qt::Key_Delete: - on_rmModBtn_clicked(); + on_actionRemove_triggered(); return true; case Qt::Key_Plus: - on_addModBtn_clicked(); + on_actionAdd_triggered(); return true; default: break; @@ -150,8 +291,11 @@ bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev) return QWidget::eventFilter(obj, ev); } -void ModFolderPage::on_addModBtn_clicked() +void ModFolderPage::on_actionAdd_triggered() { + if(!m_controlsEnabled) { + return; + } auto list = GuiUtil::BrowseForFiles( m_helpName, tr("Select %1", @@ -168,30 +312,39 @@ void ModFolderPage::on_addModBtn_clicked() } } -void ModFolderPage::on_enableModBtn_clicked() +void ModFolderPage::on_actionEnable_triggered() { + if(!m_controlsEnabled) { + return; + } auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), true); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable); } -void ModFolderPage::on_disableModBtn_clicked() +void ModFolderPage::on_actionDisable_triggered() { + if(!m_controlsEnabled) { + return; + } auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), false); + m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable); } -void ModFolderPage::on_rmModBtn_clicked() +void ModFolderPage::on_actionRemove_triggered() { + if(!m_controlsEnabled) { + return; + } auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); m_mods->deleteMods(selection.indexes()); } -void ModFolderPage::on_configFolderBtn_clicked() +void ModFolderPage::on_actionView_configs_triggered() { DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); } -void ModFolderPage::on_viewModBtn_clicked() +void ModFolderPage::on_actionView_Folder_triggered() { DesktopServices::openDirectory(m_mods->dir().absolutePath(), true); } diff --git a/application/pages/instance/ModFolderPage.h b/application/pages/instance/ModFolderPage.h index 52f19e87..f653a8c0 100644 --- a/application/pages/instance/ModFolderPage.h +++ b/application/pages/instance/ModFolderPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -15,26 +15,32 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "minecraft/MinecraftInstance.h" #include "pages/BasePage.h" #include <MultiMC.h> -class SimpleModList; +class ModFolderModel; namespace Ui { class ModFolderPage; } -class ModFolderPage : public QWidget, public BasePage +class ModFolderPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit ModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); + explicit ModFolderPage( + BaseInstance *inst, + std::shared_ptr<ModFolderModel> mods, + QString id, + QString iconName, + QString displayName, + QString helpPage = "", + QWidget *parent = 0 + ); virtual ~ModFolderPage(); void setFilter(const QString & filter) @@ -65,20 +71,22 @@ public: protected: bool eventFilter(QObject *obj, QEvent *ev) override; bool modListFilter(QKeyEvent *ev); + QMenu * createPopupMenu() override; protected: - BaseInstance *m_inst; + BaseInstance *m_inst = nullptr; protected: - Ui::ModFolderPage *ui; - std::shared_ptr<SimpleModList> m_mods; - QSortFilterProxyModel *m_filterModel; + Ui::ModFolderPage *ui = nullptr; + std::shared_ptr<ModFolderModel> m_mods; + QSortFilterProxyModel *m_filterModel = nullptr; QString m_iconName; QString m_id; QString m_displayName; QString m_helpName; QString m_fileSelectionFilter; QString m_viewFilter; + bool m_controlsEnabled = true; public slots: @@ -86,19 +94,22 @@ slots: private slots: + void modItemActivated(const QModelIndex &index); void on_filterTextChanged(const QString & newContents); - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - void on_enableModBtn_clicked(); - void on_disableModBtn_clicked(); - void on_configFolderBtn_clicked(); + void on_RunningState_changed(bool running); + void on_actionAdd_triggered(); + void on_actionRemove_triggered(); + void on_actionEnable_triggered(); + void on_actionDisable_triggered(); + void on_actionView_Folder_triggered(); + void on_actionView_configs_triggered(); + void ShowContextMenu(const QPoint &pos); }; class CoreModFolderPage : public ModFolderPage { public: - explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id, + explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id, QString iconName, QString displayName, QString helpPage = "", QWidget *parent = 0); virtual ~CoreModFolderPage() diff --git a/application/pages/instance/ModFolderPage.ui b/application/pages/instance/ModFolderPage.ui index b5597bdc..954a0167 100644 --- a/application/pages/instance/ModFolderPage.ui +++ b/application/pages/instance/ModFolderPage.ui @@ -1,155 +1,141 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ModFolderPage</class> - <widget class="QWidget" name="ModFolderPage"> + <widget class="QMainWindow" name="ModFolderPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>723</width> - <height>532</height> + <width>1042</width> + <height>501</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <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 row="4" column="1" colspan="3"> + <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 row="2" column="1" colspan="3"> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="1" colspan="3"> + <widget class="ModListView" name="modTreeView"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0"> - <item row="0" column="2"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="addModBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmModBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="enableModBtn"> - <property name="text"> - <string>Enable</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="disableModBtn"> - <property name="text"> - <string>Disable</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="configFolderBtn"> - <property name="toolTip"> - <string>Open the 'config' folder in the system file manager.</string> - </property> - <property name="text"> - <string>View configs</string> - </property> - </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> - <item> - <widget class="QPushButton" name="viewModBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0" colspan="3"> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item row="0" column="0" colspan="2"> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="1"> - <widget class="QLineEdit" name="filterEdit"> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="filterLabel"> - <property name="text"> - <string>Filter:</string> - </property> - </widget> - </item> - <item row="0" column="0" colspan="3"> - <widget class="ModListView" name="modTreeView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - </layout> - </item> - </layout> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DropOnly</enum> + </property> </widget> - </widget> - </item> - </layout> + </item> + </layout> + </widget> + <widget class="WideBar" name="actionsToolbar"> + <property name="windowTitle"> + <string>Actions</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <addaction name="separator"/> + <addaction name="actionRemove"/> + <addaction name="actionEnable"/> + <addaction name="actionDisable"/> + <addaction name="actionView_configs"/> + <addaction name="actionView_Folder"/> + </widget> + <action name="actionAdd"> + <property name="text"> + <string>&Add</string> + </property> + <property name="toolTip"> + <string>Add mods</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>&Remove</string> + </property> + <property name="toolTip"> + <string>Remove selected mods</string> + </property> + </action> + <action name="actionEnable"> + <property name="text"> + <string>&Enable</string> + </property> + <property name="toolTip"> + <string>Enable selected mods</string> + </property> + </action> + <action name="actionDisable"> + <property name="text"> + <string>&Disable</string> + </property> + <property name="toolTip"> + <string>Disable selected mods</string> + </property> + </action> + <action name="actionView_configs"> + <property name="text"> + <string>View &Configs</string> + </property> + <property name="toolTip"> + <string>Open the 'config' folder in the system file manager.</string> + </property> + </action> + <action name="actionView_Folder"> + <property name="text"> + <string>View &Folder</string> + </property> + </action> </widget> <customwidgets> <customwidget> @@ -163,17 +149,15 @@ <header>widgets/MCModInfoFrame.h</header> <container>1</container> </customwidget> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> </customwidgets> <tabstops> - <tabstop>tabWidget</tabstop> <tabstop>modTreeView</tabstop> <tabstop>filterEdit</tabstop> - <tabstop>addModBtn</tabstop> - <tabstop>rmModBtn</tabstop> - <tabstop>enableModBtn</tabstop> - <tabstop>disableModBtn</tabstop> - <tabstop>configFolderBtn</tabstop> - <tabstop>viewModBtn</tabstop> </tabstops> <resources/> <connections/> diff --git a/application/pages/instance/NewModFolderPage.cpp b/application/pages/instance/NewModFolderPage.cpp deleted file mode 100644 index da65bc9a..00000000 --- a/application/pages/instance/NewModFolderPage.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* Copyright 2013-2018 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 "NewModFolderPage.h" -#include "ui_NewModFolderPage.h" - -#include <QMessageBox> -#include <QEvent> -#include <QKeyEvent> -#include <QAbstractItemModel> - -#include "MultiMC.h" -#include "dialogs/CustomMessageBox.h" -#include "dialogs/ModEditDialogCommon.h" -#include <GuiUtil.h> -#include "minecraft/ModsModel.h" -#include "minecraft/Mod.h" -#include "minecraft/VersionFilterData.h" -#include "minecraft/ComponentList.h" -#include <DesktopServices.h> - -NewModFolderPage::NewModFolderPage(BaseInstance *inst, std::shared_ptr<ModsModel> mods, QString id, - QString iconName, QString displayName, QString helpPage, - QWidget *parent) - : QWidget(parent), ui(new Ui::NewModFolderPage) -{ - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - m_inst = inst; - m_mods = mods; - m_id = id; - m_displayName = displayName; - m_iconName = iconName; - m_helpName = helpPage; - m_fileSelectionFilter = "%1 (*.zip *.jar)"; - m_filterModel = new QSortFilterProxyModel(this); - m_filterModel->setDynamicSortFilter(true); - m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); - m_filterModel->setSourceModel(m_mods.get()); - m_filterModel->setFilterKeyColumn(-1); - ui->modTreeView->setModel(m_filterModel); - ui->modTreeView->installEventFilter(this); - ui->modTreeView->sortByColumn(1, Qt::AscendingOrder); - auto smodel = ui->modTreeView->selectionModel(); - connect(smodel, &QItemSelectionModel::currentChanged, this, &NewModFolderPage::modCurrent); - connect(ui->filterEdit, &QLineEdit::textChanged, this, &NewModFolderPage::on_filterTextChanged ); -} - -void NewModFolderPage::openedImpl() -{ - m_mods->startWatching(); -} - -void NewModFolderPage::closedImpl() -{ - m_mods->stopWatching(); -} - -void NewModFolderPage::on_filterTextChanged(const QString& newContents) -{ - m_viewFilter = newContents; - m_filterModel->setFilterFixedString(m_viewFilter); -} - - -NewModFolderPage::~NewModFolderPage() -{ - m_mods->stopWatching(); - delete ui; -} - -bool NewModFolderPage::shouldDisplay() const -{ - if (m_inst) - return !m_inst->isRunning(); - return true; -} - -bool NewModFolderPage::modListFilter(QKeyEvent *keyEvent) -{ - switch (keyEvent->key()) - { - case Qt::Key_Delete: - on_rmModBtn_clicked(); - return true; - case Qt::Key_Plus: - on_addModBtn_clicked(); - return true; - default: - break; - } - return QWidget::eventFilter(ui->modTreeView, keyEvent); -} - -bool NewModFolderPage::eventFilter(QObject *obj, QEvent *ev) -{ - if (ev->type() != QEvent::KeyPress) - { - return QWidget::eventFilter(obj, ev); - } - QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev); - if (obj == ui->modTreeView) - return modListFilter(keyEvent); - return QWidget::eventFilter(obj, ev); -} - -void NewModFolderPage::on_addModBtn_clicked() -{ - auto list = GuiUtil::BrowseForFiles( - m_helpName, - tr("Select %1", - "Select whatever type of files the page contains. Example: 'Loader Mods'") - .arg(m_displayName), - m_fileSelectionFilter.arg(m_displayName), MMC->settings()->get("CentralModsDir").toString(), - this->parentWidget()); - if (!list.empty()) - { - for (auto filename : list) - { - m_mods->installMod(filename); - } - } -} - -void NewModFolderPage::on_enableModBtn_clicked() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), true); -} - -void NewModFolderPage::on_disableModBtn_clicked() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->enableMods(selection.indexes(), false); -} - -void NewModFolderPage::on_rmModBtn_clicked() -{ - auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection()); - m_mods->deleteMods(selection.indexes()); -} - -void NewModFolderPage::on_configFolderBtn_clicked() -{ - DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true); -} - -void NewModFolderPage::on_viewModBtn_clicked() -{ - DesktopServices::openDirectory(m_mods->dir().absolutePath(), true); -} - -void NewModFolderPage::modCurrent(const QModelIndex ¤t, const QModelIndex &previous) -{ - if (!current.isValid()) - { - ui->frame->clear(); - return; - } - auto sourceCurrent = m_filterModel->mapToSource(current); - int row = sourceCurrent.row(); - Mod &m = m_mods->operator[](row); - ui->frame->updateWithMod(m); -} diff --git a/application/pages/instance/NewModFolderPage.h b/application/pages/instance/NewModFolderPage.h deleted file mode 100644 index 5446a607..00000000 --- a/application/pages/instance/NewModFolderPage.h +++ /dev/null @@ -1,97 +0,0 @@ -/* Copyright 2013-2018 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 "minecraft/MinecraftInstance.h" -#include "pages/BasePage.h" -#include <MultiMC.h> - -class SimpleModList; -namespace Ui -{ -class NewModFolderPage; -} - -class NewModFolderPage : public QWidget, public BasePage -{ - Q_OBJECT - -public: - explicit NewModFolderPage(BaseInstance *inst, std::shared_ptr<ModsModel> mods, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); - virtual ~NewModFolderPage(); - - void setFilter(const QString & filter) - { - m_fileSelectionFilter = filter; - } - - virtual QString displayName() const override - { - return m_displayName; - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon(m_iconName); - } - virtual QString id() const override - { - return m_id; - } - virtual QString helpPage() const override - { - return m_helpName; - } - virtual bool shouldDisplay() const override; - - virtual void openedImpl() override; - virtual void closedImpl() override; -protected: - bool eventFilter(QObject *obj, QEvent *ev) override; - bool modListFilter(QKeyEvent *ev); - -protected: - BaseInstance *m_inst; - -protected: - Ui::NewModFolderPage *ui; - std::shared_ptr<ModsModel> m_mods; - QSortFilterProxyModel *m_filterModel; - QString m_iconName; - QString m_id; - QString m_displayName; - QString m_helpName; - QString m_fileSelectionFilter; - QString m_viewFilter; - -public -slots: - void modCurrent(const QModelIndex ¤t, const QModelIndex &previous); - -private -slots: - void on_filterTextChanged(const QString & newContents); - void on_addModBtn_clicked(); - void on_rmModBtn_clicked(); - void on_viewModBtn_clicked(); - void on_enableModBtn_clicked(); - void on_disableModBtn_clicked(); - void on_configFolderBtn_clicked(); -}; - diff --git a/application/pages/instance/NewModFolderPage.ui b/application/pages/instance/NewModFolderPage.ui deleted file mode 100644 index 48c36383..00000000 --- a/application/pages/instance/NewModFolderPage.ui +++ /dev/null @@ -1,180 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>NewModFolderPage</class> - <widget class="QWidget" name="NewModFolderPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>723</width> - <height>532</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0"> - <item row="0" column="2"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="addModBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmModBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="enableModBtn"> - <property name="text"> - <string>Enable</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="disableModBtn"> - <property name="text"> - <string>Disable</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="configFolderBtn"> - <property name="toolTip"> - <string>Open the 'config' folder in the system file manager.</string> - </property> - <property name="text"> - <string>View configs</string> - </property> - </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> - <item> - <widget class="QPushButton" name="viewModBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0" colspan="3"> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - <item row="0" column="0" colspan="2"> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="1"> - <widget class="QLineEdit" name="filterEdit"> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="filterLabel"> - <property name="text"> - <string>Filter:</string> - </property> - </widget> - </item> - <item row="0" column="0" colspan="3"> - <widget class="ModListView" name="modTreeView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DropOnly</enum> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>ModListView</class> - <extends>QTreeView</extends> - <header>widgets/ModListView.h</header> - </customwidget> - <customwidget> - <class>MCModInfoFrame</class> - <extends>QFrame</extends> - <header>widgets/MCModInfoFrame.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <tabstops> - <tabstop>tabWidget</tabstop> - <tabstop>modTreeView</tabstop> - <tabstop>filterEdit</tabstop> - <tabstop>addModBtn</tabstop> - <tabstop>rmModBtn</tabstop> - <tabstop>enableModBtn</tabstop> - <tabstop>disableModBtn</tabstop> - <tabstop>configFolderBtn</tabstop> - <tabstop>viewModBtn</tabstop> - </tabstops> - <resources/> - <connections/> -</ui> diff --git a/application/pages/instance/NotesPage.cpp b/application/pages/instance/NotesPage.cpp index 6cc2c2f4..fa966c91 100644 --- a/application/pages/instance/NotesPage.cpp +++ b/application/pages/instance/NotesPage.cpp @@ -6,7 +6,6 @@ NotesPage::NotesPage(BaseInstance *inst, QWidget *parent) : QWidget(parent), ui(new Ui::NotesPage), m_inst(inst) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); ui->noteEditor->setText(m_inst->notes()); } diff --git a/application/pages/instance/NotesPage.h b/application/pages/instance/NotesPage.h index 9941be4f..d0c00ac1 100644 --- a/application/pages/instance/NotesPage.h +++ b/application/pages/instance/NotesPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.ui b/application/pages/instance/NotesPage.ui index 88cca92f..67cb261c 100644 --- a/application/pages/instance/NotesPage.ui +++ b/application/pages/instance/NotesPage.ui @@ -10,7 +10,7 @@ <height>538</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout"> <property name="leftMargin"> <number>0</number> </property> @@ -24,34 +24,26 @@ <number>0</number> </property> <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> + <widget class="QTextEdit" name="noteEditor"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="tabChangesFocus"> + <bool>true</bool> + </property> + <property name="acceptRichText"> + <bool>false</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTextEdit" name="noteEditor"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="acceptRichText"> - <bool>false</bool> - </property> - <property name="textInteractionFlags"> - <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> - </property> - </widget> - </item> - </layout> - </widget> </widget> </item> </layout> </widget> + <tabstops> + <tabstop>noteEditor</tabstop> + </tabstops> <resources/> <connections/> </ui> diff --git a/application/pages/instance/OtherLogsPage.cpp b/application/pages/instance/OtherLogsPage.cpp index 69c33a85..b67b84bd 100644 --- a/application/pages/instance/OtherLogsPage.cpp +++ b/application/pages/instance/OtherLogsPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 15e9f422..7f21c0fa 100644 --- a/application/pages/instance/OtherLogsPage.h +++ b/application/pages/instance/OtherLogsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 00e215da..1486bf52 100644 --- a/application/pages/instance/ResourcePackPage.h +++ b/application/pages/instance/ResourcePackPage.h @@ -1,18 +1,20 @@ #pragma once + #include "ModFolderPage.h" #include "ui_ModFolderPage.h" class ResourcePackPage : public ModFolderPage { + Q_OBJECT public: explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) : ModFolderPage(instance, instance->resourcePackList(), "resourcepacks", "resourcepacks", tr("Resource packs"), "Resource-packs", parent) { - ui->configFolderBtn->setHidden(true); + 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.cpp b/application/pages/instance/ScreenshotsPage.cpp index 3420e86b..efa0f9f2 100644 --- a/application/pages/instance/ScreenshotsPage.cpp +++ b/application/pages/instance/ScreenshotsPage.cpp @@ -13,6 +13,7 @@ #include <QPainter> #include <QClipboard> #include <QKeyEvent> +#include <QMenu> #include <MultiMC.h> @@ -209,7 +210,7 @@ public: }; ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) - : QWidget(parent), ui(new Ui::ScreenshotsPage) + : QMainWindow(parent), ui(new Ui::ScreenshotsPage) { m_model.reset(new QFileSystemModel()); m_filterModel.reset(new FilterModel()); @@ -222,7 +223,8 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) m_valid = FS::ensureFolderPathExists(m_folder); ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); + ui->toolBar->insertSpacer(ui->actionView_Folder); + ui->listView->setIconSize(QSize(128, 128)); ui->listView->setGridSize(QSize(192, 160)); ui->listView->setSpacing(9); @@ -233,6 +235,8 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) ui->listView->installEventFilter(this); ui->listView->setEditTriggers(0); ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex))); } @@ -248,10 +252,10 @@ bool ScreenshotsPage::eventFilter(QObject *obj, QEvent *evt) switch (keyEvent->key()) { case Qt::Key_Delete: - on_deleteBtn_clicked(); + on_actionDelete_triggered(); return true; case Qt::Key_F2: - on_renameBtn_clicked(); + on_actionRename_triggered(); return true; default: break; @@ -264,6 +268,20 @@ ScreenshotsPage::~ScreenshotsPage() delete ui; } +void ScreenshotsPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->listView->mapToGlobal(pos)); + delete menu; +} + +QMenu * ScreenshotsPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; +} + void ScreenshotsPage::onItemActivated(QModelIndex index) { if (!index.isValid()) @@ -273,12 +291,12 @@ void ScreenshotsPage::onItemActivated(QModelIndex index) DesktopServices::openFile(info.absoluteFilePath()); } -void ScreenshotsPage::on_viewFolderBtn_clicked() +void ScreenshotsPage::on_actionView_Folder_triggered() { DesktopServices::openDirectory(m_folder, true); } -void ScreenshotsPage::on_uploadBtn_clicked() +void ScreenshotsPage::on_actionUpload_triggered() { auto selection = ui->listView->selectionModel()->selectedRows(); if (selection.isEmpty()) @@ -286,6 +304,38 @@ void ScreenshotsPage::on_uploadBtn_clicked() QList<ScreenshotPtr> uploaded; auto job = NetJobPtr(new NetJob("Screenshot Upload")); + if(selection.size() < 2) + { + auto item = selection.at(0); + auto info = m_model->fileInfo(item); + auto screenshot = std::make_shared<ScreenShot>(info); + job->addNetAction(ImgurUpload::make(screenshot)); + + m_uploadActive = true; + ProgressDialog dialog(this); + if(dialog.execWithTask(job.get()) != QDialog::Accepted) + { + CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), + tr("Unknown error"), QMessageBox::Warning)->exec(); + } + else + { + auto link = screenshot->m_url; + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(link); + CustomMessageBox::selectable( + this, + tr("Upload finished"), + tr("The <a href=\"%1\">link to the uploaded screenshot</a> has been placed in your clipboard.") + .arg(link), + QMessageBox::Information + )->exec(); + } + + m_uploadActive = false; + return; + } + for (auto item : selection) { auto info = m_model->fileInfo(item); @@ -321,7 +371,7 @@ void ScreenshotsPage::on_uploadBtn_clicked() m_uploadActive = false; } -void ScreenshotsPage::on_deleteBtn_clicked() +void ScreenshotsPage::on_actionDelete_triggered() { auto mbox = CustomMessageBox::selectable( this, tr("Are you sure?"), tr("This will delete all selected screenshots."), @@ -338,7 +388,7 @@ void ScreenshotsPage::on_deleteBtn_clicked() } } -void ScreenshotsPage::on_renameBtn_clicked() +void ScreenshotsPage::on_actionRename_triggered() { auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.isEmpty()) diff --git a/application/pages/instance/ScreenshotsPage.h b/application/pages/instance/ScreenshotsPage.h index 88a64dfb..03a809de 100644 --- a/application/pages/instance/ScreenshotsPage.h +++ b/application/pages/instance/ScreenshotsPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -15,7 +15,7 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "pages/BasePage.h" #include <MultiMC.h> @@ -31,7 +31,7 @@ struct ScreenShot; class ScreenshotList; class ImgurAlbumCreation; -class ScreenshotsPage : public QWidget, public BasePage +class ScreenshotsPage : public QMainWindow, public BasePage { Q_OBJECT @@ -67,12 +67,17 @@ public: { return !m_uploadActive; } + +protected: + QMenu * createPopupMenu() override; + private slots: - void on_uploadBtn_clicked(); - void on_deleteBtn_clicked(); - void on_renameBtn_clicked(); - void on_viewFolderBtn_clicked(); + void on_actionUpload_triggered(); + void on_actionDelete_triggered(); + void on_actionRename_triggered(); + void on_actionView_Folder_triggered(); void onItemActivated(QModelIndex); + void ShowContextMenu(const QPoint &pos); private: Ui::ScreenshotsPage *ui; diff --git a/application/pages/instance/ScreenshotsPage.ui b/application/pages/instance/ScreenshotsPage.ui index d05c4384..f11f4cd4 100644 --- a/application/pages/instance/ScreenshotsPage.ui +++ b/application/pages/instance/ScreenshotsPage.ui @@ -1,106 +1,87 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ScreenshotsPage</class> - <widget class="QWidget" name="ScreenshotsPage"> + <widget class="QMainWindow" name="ScreenshotsPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>723</width> - <height>532</height> + <width>800</width> + <height>600</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="leftMargin"> - <number>0</number> + <widget class="QWidget" name="centralwidget"> + <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="QListView" name="listView"> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>Actions</string> </property> - <property name="topMargin"> - <number>0</number> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> </property> - <property name="rightMargin"> - <number>0</number> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionUpload"/> + <addaction name="actionDelete"/> + <addaction name="actionRename"/> + <addaction name="actionView_Folder"/> + </widget> + <action name="actionUpload"> + <property name="text"> + <string>Upload</string> </property> - <property name="bottomMargin"> - <number>0</number> + </action> + <action name="actionDelete"> + <property name="text"> + <string>Delete</string> </property> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QListView" name="listView"> - <property name="selectionMode"> - <enum>QAbstractItemView::ExtendedSelection</enum> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectRows</enum> - </property> - </widget> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="uploadBtn"> - <property name="text"> - <string>&Upload</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="deleteBtn"> - <property name="text"> - <string>&Delete</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="renameBtn"> - <property name="text"> - <string>&Rename</string> - </property> - </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> - <item> - <widget class="QPushButton" name="viewFolderBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + </action> + <action name="actionRename"> + <property name="text"> + <string>Rename</string> + </property> + </action> + <action name="actionView_Folder"> + <property name="text"> + <string>View Folder</string> + </property> + </action> </widget> - <tabstops> - <tabstop>listView</tabstop> - <tabstop>uploadBtn</tabstop> - <tabstop>deleteBtn</tabstop> - <tabstop>renameBtn</tabstop> - <tabstop>viewFolderBtn</tabstop> - </tabstops> + <customwidgets> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> + </customwidgets> <resources/> <connections/> </ui> diff --git a/application/pages/instance/ServersPage.cpp b/application/pages/instance/ServersPage.cpp index 35f167aa..d63c6e70 100644 --- a/application/pages/instance/ServersPage.cpp +++ b/application/pages/instance/ServersPage.cpp @@ -11,6 +11,7 @@ #include <minecraft/MinecraftInstance.h> #include <QFileSystemWatcher> +#include <QMenu> static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. @@ -64,8 +65,8 @@ struct Server void serialize(nbt::tag_compound& server) { - server.insert("name", m_name.toUtf8().toStdString()); - server.insert("ip", m_address.toUtf8().toStdString()); + server.insert("name", m_name.trimmed().toUtf8().toStdString()); + server.insert("ip", m_address.trimmed().toUtf8().toStdString()); if(m_icon.size()) { server.insert("icon", m_icon.toBase64().toStdString()); @@ -555,15 +556,17 @@ private: QTimer m_saveTimer; }; -ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent) - : QWidget(parent), ui(new Ui::ServersPage) +ServersPage::ServersPage(InstancePtr inst, QWidget* parent) + : QMainWindow(parent), ui(new Ui::ServersPage) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); m_inst = inst; m_model = new ServersModel(inst->gameRoot(), this); ui->serversView->setIconSize(QSize(64,64)); ui->serversView->setModel(m_model); + ui->serversView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->serversView, &QTreeView::customContextMenuRequested, this, &ServersPage::ShowContextMenu); + auto head = ui->serversView->header(); if(head->count()) { @@ -576,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))); @@ -594,6 +597,21 @@ ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent) ServersPage::~ServersPage() { m_model->saveNow(); + delete ui; +} + +void ServersPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->serversView->mapToGlobal(pos)); + delete menu; +} + +QMenu * ServersPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } void ServersPage::on_RunningState_changed(bool running) @@ -674,9 +692,10 @@ void ServersPage::updateState() ui->addressLine->setEnabled(serverEditEnabled); ui->nameLine->setEnabled(serverEditEnabled); ui->resourceComboBox->setEnabled(serverEditEnabled); - ui->moveDownBtn->setEnabled(serverEditEnabled); - ui->moveUpBtn->setEnabled(serverEditEnabled); - ui->removeBtn->setEnabled(serverEditEnabled); + ui->actionMove_Down->setEnabled(serverEditEnabled); + ui->actionMove_Up->setEnabled(serverEditEnabled); + ui->actionRemove->setEnabled(serverEditEnabled); + ui->actionJoin->setEnabled(serverEditEnabled); if(server) { @@ -691,7 +710,7 @@ void ServersPage::updateState() ui->resourceComboBox->setCurrentIndex(0); } - ui->addBtn->setDisabled(m_locked); + ui->actionAdd->setDisabled(m_locked); } void ServersPage::openedImpl() @@ -704,7 +723,7 @@ void ServersPage::closedImpl() m_model->unobserve(); } -void ServersPage::on_addBtn_clicked() +void ServersPage::on_actionAdd_triggered() { int position = m_model->addEmptyRow(currentServer + 1); if(position < 0) @@ -719,12 +738,12 @@ void ServersPage::on_addBtn_clicked() currentServer = position; } -void ServersPage::on_removeBtn_clicked() +void ServersPage::on_actionRemove_triggered() { m_model->removeRow(currentServer); } -void ServersPage::on_moveUpBtn_clicked() +void ServersPage::on_actionMove_Up_triggered() { if(m_model->moveUp(currentServer)) { @@ -732,7 +751,7 @@ void ServersPage::on_moveUpBtn_clicked() } } -void ServersPage::on_moveDownBtn_clicked() +void ServersPage::on_actionMove_Down_triggered() { if(m_model->moveDown(currentServer)) { @@ -740,4 +759,10 @@ void ServersPage::on_moveDownBtn_clicked() } } +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 fd690e9e..8c5b7eb8 100644 --- a/application/pages/instance/ServersPage.h +++ b/application/pages/instance/ServersPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -15,7 +15,7 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include <QString> #include "pages/BasePage.h" @@ -30,12 +30,12 @@ struct Server; class ServersModel; class MinecraftInstance; -class ServersPage : public QWidget, public BasePage +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; @@ -57,6 +57,10 @@ public: { return "Servers-management"; } + +protected: + QMenu * createPopupMenu() override; + private: void updateState(); void scheduleSave(); @@ -66,21 +70,25 @@ private slots: void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); void rowsRemoved(const QModelIndex &parent, int first, int last); - void on_addBtn_clicked(); - void on_removeBtn_clicked(); - void on_moveUpBtn_clicked(); - void on_moveDownBtn_clicked(); + void on_actionAdd_triggered(); + void on_actionRemove_triggered(); + void on_actionMove_Up_triggered(); + void on_actionMove_Down_triggered(); + void on_actionJoin_triggered(); + void on_RunningState_changed(bool running); void nameEdited(const QString & name); void addressEdited(const QString & address); - void resourceIndexChanged(int index); + void resourceIndexChanged(int index);\ + + void ShowContextMenu(const QPoint &pos); private: // data int currentServer = -1; 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 faa918f4..d89b7cba 100644 --- a/application/pages/instance/ServersPage.ui +++ b/application/pages/instance/ServersPage.ui @@ -1,198 +1,193 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ServersPage</class> - <widget class="QWidget" name="ServersPage"> + <widget class="QMainWindow" name="ServersPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>706</width> - <height>575</height> + <width>1318</width> + <height>879</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> + <widget class="QWidget" name="centralwidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <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="QTreeView" name="serversView"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> - <attribute name="title"> - <string notr="true">Tab 1</string> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="iconSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> </attribute> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <layout class="QGridLayout" name="gridLayout"> - <item row="3" column="1"> - <widget class="QComboBox" name="resourceComboBox"> - <item> - <property name="text"> - <string>Ask to download</string> - </property> - </item> - <item> - <property name="text"> - <string>Always download</string> - </property> - </item> - <item> - <property name="text"> - <string>Never download</string> - </property> - </item> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="resourcesLabel"> - <property name="text"> - <string>Reso&urces</string> - </property> - <property name="buddy"> - <cstring>resourceComboBox</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="addressLine"/> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="nameLabel"> - <property name="text"> - <string>&Name</string> - </property> - <property name="buddy"> - <cstring>nameLine</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="nameLine"/> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="addressLabel"> - <property name="text"> - <string>Address</string> - </property> - <property name="buddy"> - <cstring>addressLine</cstring> - </property> - </widget> - </item> - <item row="0" column="0" colspan="2"> - <widget class="QTreeView" name="serversView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="selectionMode"> - <enum>QAbstractItemView::SingleSelection</enum> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectRows</enum> - </property> - <property name="iconSize"> - <size> - <width>64</width> - <height>64</height> - </size> - </property> - <property name="rootIsDecorated"> - <bool>false</bool> - </property> - <attribute name="headerStretchLastSection"> - <bool>false</bool> - </attribute> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="addBtn"> - <property name="text"> - <string>&Add</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="removeBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveUpBtn"> - <property name="text"> - <string>Move Up</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveDownBtn"> - <property name="text"> - <string>Move Down</string> - </property> - </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> - </item> - </layout> </widget> - </widget> - </item> - </layout> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="nameLabel"> + <property name="text"> + <string>&Name</string> + </property> + <property name="buddy"> + <cstring>nameLine</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="nameLine"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="addressLabel"> + <property name="text"> + <string>Address</string> + </property> + <property name="buddy"> + <cstring>addressLine</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="addressLine"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="resourcesLabel"> + <property name="text"> + <string>Reso&urces</string> + </property> + <property name="buddy"> + <cstring>resourceComboBox</cstring> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="resourceComboBox"> + <item> + <property name="text"> + <string>Ask to download</string> + </property> + </item> + <item> + <property name="text"> + <string>Always download</string> + </property> + </item> + <item> + <property name="text"> + <string>Never download</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>Actions</string> + </property> + <property name="allowedAreas"> + <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <property name="floatable"> + <bool>false</bool> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <addaction name="actionRemove"/> + <addaction name="actionMove_Up"/> + <addaction name="actionMove_Down"/> + <addaction name="actionJoin"/> + </widget> + <action name="actionAdd"> + <property name="text"> + <string>Add</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> + </property> + </action> + <action name="actionMove_Up"> + <property name="text"> + <string>Move Up</string> + </property> + </action> + <action name="actionMove_Down"> + <property name="text"> + <string>Move Down</string> + </property> + </action> + <action name="actionJoin"> + <property name="text"> + <string>Join</string> + </property> + </action> </widget> + <customwidgets> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> + </customwidgets> <tabstops> - <tabstop>tabWidget</tabstop> <tabstop>serversView</tabstop> <tabstop>nameLine</tabstop> <tabstop>addressLine</tabstop> <tabstop>resourceComboBox</tabstop> - <tabstop>addBtn</tabstop> - <tabstop>removeBtn</tabstop> - <tabstop>moveUpBtn</tabstop> - <tabstop>moveDownBtn</tabstop> </tabstops> <resources/> <connections/> diff --git a/application/pages/instance/TexturePackPage.h b/application/pages/instance/TexturePackPage.h index 094cc199..3f04997d 100644 --- a/application/pages/instance/TexturePackPage.h +++ b/application/pages/instance/TexturePackPage.h @@ -1,17 +1,20 @@ #pragma once + #include "ModFolderPage.h" #include "ui_ModFolderPage.h" class TexturePackPage : public ModFolderPage { + Q_OBJECT public: explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) : ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks", tr("Texture packs"), "Texture-packs", parent) { - ui->configFolderBtn->setHidden(true); + 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 54a228a4..eff12c9c 100644 --- a/application/pages/instance/VersionPage.cpp +++ b/application/pages/instance/VersionPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -16,8 +16,10 @@ #include "MultiMC.h" #include <QMessageBox> +#include <QLabel> #include <QEvent> #include <QKeyEvent> +#include <QMenu> #include "VersionPage.h" #include "ui_VersionPage.h" @@ -25,7 +27,6 @@ #include "dialogs/CustomMessageBox.h" #include "dialogs/VersionSelectDialog.h" #include "dialogs/NewComponentDialog.h" -#include "dialogs/ModEditDialogCommon.h" #include "dialogs/ProgressDialog.h" #include <GuiUtil.h> @@ -36,13 +37,13 @@ #include <QString> #include <QUrl> -#include "minecraft/ComponentList.h" +#include "minecraft/PackProfile.h" #include "minecraft/auth/MojangAccountList.h" -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" #include "icons/IconList.h" #include "Exception.h" - -#include "MultiMC.h" +#include "Version.h" +#include "DesktopServices.h" #include <meta/Index.h> #include <meta/VersionList.h> @@ -60,7 +61,7 @@ public: virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override { - QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role); + QVariant var = QIdentityProxyModel::data(proxyIndex, role); int column = proxyIndex.column(); if(column == 0 && role == Qt::DecorationRole && m_parentWidget) { @@ -96,38 +97,53 @@ QIcon VersionPage::icon() const } bool VersionPage::shouldDisplay() const { - return !m_inst->isRunning(); + return true; +} + +QMenu * VersionPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; } VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) - : QWidget(parent), ui(new Ui::VersionPage), m_inst(inst) + : QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - m_profile = m_inst->getComponentList(); - reloadComponentList(); + ui->toolBar->insertSpacer(ui->actionReload); - if (m_profile) - { - auto proxy = new IconProxy(ui->packageView); - proxy->setSourceModel(m_profile.get()); - ui->packageView->setModel(proxy); - ui->packageView->installEventFilter(this); - ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); - connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); - auto smodel = ui->packageView->selectionModel(); - connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); - updateVersionControls(); - // select first item. - preselect(0); - } - else - { - disableVersionControls(); - } - connect(m_inst, &MinecraftInstance::versionReloaded, this, - &VersionPage::updateVersionControls); + m_profile = m_inst->getPackProfile(); + + reloadPackProfile(); + + auto proxy = new IconProxy(ui->packageView); + proxy->setSourceModel(m_profile.get()); + + 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); + + connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); + auto smodel = ui->packageView->selectionModel(); + connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); + + connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls); + controlsEnabled = !m_inst->isRunning(); + updateVersionControls(); + preselect(0); + connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus); + connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu); + connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged); } VersionPage::~VersionPage() @@ -135,6 +151,13 @@ VersionPage::~VersionPage() delete ui; } +void VersionPage::showContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->packageView->mapToGlobal(pos)); + delete menu; +} + void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &previous) { if (!current.isValid()) @@ -177,23 +200,48 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex & ui->frame->setModDescription(problemOut); } +void VersionPage::updateRunningStatus(bool running) +{ + if(controlsEnabled == running) { + controlsEnabled = !running; + updateVersionControls(); + } +} void VersionPage::updateVersionControls() { - ui->forgeBtn->setEnabled(true); - ui->liteloaderBtn->setEnabled(true); + // FIXME: this is a dirty hack + auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft")); + 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(); } -void VersionPage::disableVersionControls() +void VersionPage::updateButtons(int row) { - ui->forgeBtn->setEnabled(false); - ui->liteloaderBtn->setEnabled(false); - ui->reloadBtn->setEnabled(false); - updateButtons(); + if(row == -1) + row = currentRow(); + auto patch = m_profile->getComponent(row); + ui->actionRemove->setEnabled(controlsEnabled && patch && patch->isRemovable()); + ui->actionMove_down->setEnabled(controlsEnabled && patch && patch->isMoveable()); + ui->actionMove_up->setEnabled(controlsEnabled && patch && patch->isMoveable()); + ui->actionChange_version->setEnabled(controlsEnabled && patch && patch->isVersionChangeable()); + ui->actionEdit->setEnabled(controlsEnabled && patch && patch->isCustom()); + ui->actionCustomize->setEnabled(controlsEnabled && patch && patch->isCustomizable()); + ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible()); + ui->actionDownload_All->setEnabled(controlsEnabled); + ui->actionAdd_Empty->setEnabled(controlsEnabled); + ui->actionReload->setEnabled(controlsEnabled); + ui->actionInstall_mods->setEnabled(controlsEnabled); + ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled); + ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled); } -bool VersionPage::reloadComponentList() +bool VersionPage::reloadPackProfile() { try { @@ -214,13 +262,13 @@ bool VersionPage::reloadComponentList() } } -void VersionPage::on_reloadBtn_clicked() +void VersionPage::on_actionReload_triggered() { - reloadComponentList(); + reloadPackProfile(); m_container->refreshContainer(); } -void VersionPage::on_removeBtn_clicked() +void VersionPage::on_actionRemove_triggered() { if (ui->packageView->currentIndex().isValid()) { @@ -231,11 +279,11 @@ void VersionPage::on_removeBtn_clicked() } } updateButtons(); - reloadComponentList(); + reloadPackProfile(); m_container->refreshContainer(); } -void VersionPage::on_modBtn_clicked() +void VersionPage::on_actionInstall_mods_triggered() { if(m_container) { @@ -243,7 +291,7 @@ void VersionPage::on_modBtn_clicked() } } -void VersionPage::on_jarmodBtn_clicked() +void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() { auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); if(!list.empty()) @@ -253,7 +301,7 @@ void VersionPage::on_jarmodBtn_clicked() updateButtons(); } -void VersionPage::on_jarBtn_clicked() +void VersionPage::on_actionReplace_Minecraft_jar_triggered() { auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget()); if(!jarPath.isEmpty()) @@ -263,11 +311,11 @@ void VersionPage::on_jarBtn_clicked() updateButtons(); } -void VersionPage::on_moveUpBtn_clicked() +void VersionPage::on_actionMove_up_triggered() { try { - m_profile->move(currentRow(), ComponentList::MoveUp); + m_profile->move(currentRow(), PackProfile::MoveUp); } catch (const Exception &e) { @@ -276,11 +324,11 @@ void VersionPage::on_moveUpBtn_clicked() updateButtons(); } -void VersionPage::on_moveDownBtn_clicked() +void VersionPage::on_actionMove_down_triggered() { try { - m_profile->move(currentRow(), ComponentList::MoveDown); + m_profile->move(currentRow(), PackProfile::MoveDown); } catch (const Exception &e) { @@ -289,7 +337,7 @@ void VersionPage::on_moveDownBtn_clicked() updateButtons(); } -void VersionPage::on_changeVersionBtn_clicked() +void VersionPage::on_actionChange_version_triggered() { auto versionRow = currentRow(); if(versionRow == -1) @@ -307,15 +355,21 @@ void VersionPage::on_changeVersionBtn_clicked() // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata... if(uid == "net.minecraftforge") { - on_forgeBtn_clicked(); + on_actionInstall_Forge_triggered(); return; } else if (uid == "com.mumfrey.liteloader") { - on_liteloaderBtn_clicked(); + on_actionInstall_LiteLoader_triggered(); return; } VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); + if (uid == "net.fabricmc.intermediary") + { + vselect.setEmptyString(tr("No intermediary mappings versions are currently available.")); + vselect.setEmptyErrorString(tr("Couldn't load or download the intermediary mappings version lists!")); + vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); + } auto currentVersion = patch->getVersion(); if(!currentVersion.isEmpty()) { @@ -335,7 +389,7 @@ void VersionPage::on_changeVersionBtn_clicked() m_container->refreshContainer(); } -void VersionPage::on_downloadBtn_clicked() +void VersionPage::on_actionDownload_All_triggered() { if (!MMC->accounts()->anyAccountIsValid()) { @@ -360,7 +414,7 @@ void VersionPage::on_downloadBtn_clicked() m_container->refreshContainer(); } -void VersionPage::on_forgeBtn_clicked() +void VersionPage::on_actionInstall_Forge_triggered() { auto vlist = ENV.metadataIndex()->get("net.minecraftforge"); if(!vlist) @@ -389,7 +443,34 @@ void VersionPage::on_forgeBtn_clicked() } } -void VersionPage::on_addEmptyBtn_clicked() +void VersionPage::on_actionInstall_Fabric_triggered() +{ + auto vlist = ENV.metadataIndex()->get("net.fabricmc.fabric-loader"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this); + vselect.setEmptyString(tr("No Fabric Loader versions are currently available.")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!")); + + auto currentVersion = m_profile->getComponentVersion("net.fabricmc.fabric-loader"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("net.fabricmc.fabric-loader", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + +void VersionPage::on_actionAdd_Empty_triggered() { NewComponentDialog compdialog(QString(), QString(), this); QStringList blacklist; @@ -407,7 +488,7 @@ void VersionPage::on_addEmptyBtn_clicked() } } -void VersionPage::on_liteloaderBtn_clicked() +void VersionPage::on_actionInstall_LiteLoader_triggered() { auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader"); if(!vlist) @@ -436,6 +517,16 @@ void VersionPage::on_liteloaderBtn_clicked() } } +void VersionPage::on_actionLibrariesFolder_triggered() +{ + DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true); +} + +void VersionPage::on_actionMinecraftFolder_triggered() +{ + DesktopServices::openDirectory(m_inst->gameRoot(), true); +} + void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) { currentIdx = current.row(); @@ -461,37 +552,9 @@ void VersionPage::preselect(int row) updateButtons(row); } -void VersionPage::updateButtons(int row) -{ - if(row == -1) - row = currentRow(); - auto patch = m_profile->getComponent(row); - if (!patch) - { - ui->removeBtn->setDisabled(true); - ui->moveDownBtn->setDisabled(true); - ui->moveUpBtn->setDisabled(true); - ui->changeVersionBtn->setDisabled(true); - ui->editBtn->setDisabled(true); - ui->customizeBtn->setDisabled(true); - ui->revertBtn->setDisabled(true); - } - else - { - ui->removeBtn->setEnabled(patch->isRemovable()); - ui->moveDownBtn->setEnabled(patch->isMoveable()); - ui->moveUpBtn->setEnabled(patch->isMoveable()); - ui->changeVersionBtn->setEnabled(patch->isVersionChangeable()); - ui->editBtn->setEnabled(patch->isCustom()); - ui->customizeBtn->setEnabled(patch->isCustomizable()); - ui->revertBtn->setEnabled(patch->isRevertible()); - } -} - void VersionPage::onGameUpdateError(QString error) { - CustomMessageBox::selectable(this, tr("Error updating instance"), error, - QMessageBox::Warning)->show(); + CustomMessageBox::selectable(this, tr("Error updating instance"), error, QMessageBox::Warning)->show(); } Component * VersionPage::current() @@ -513,7 +576,7 @@ int VersionPage::currentRow() return ui->packageView->selectionModel()->selectedRows().first().row(); } -void VersionPage::on_customizeBtn_clicked() +void VersionPage::on_actionCustomize_triggered() { auto version = currentRow(); if(version == -1) @@ -534,7 +597,7 @@ void VersionPage::on_customizeBtn_clicked() preselect(currentIdx); } -void VersionPage::on_editBtn_clicked() +void VersionPage::on_actionEdit_triggered() { auto version = current(); if(!version) @@ -550,7 +613,7 @@ void VersionPage::on_editBtn_clicked() MMC->openJsonEditor(filename); } -void VersionPage::on_revertBtn_clicked() +void VersionPage::on_actionRevert_triggered() { auto version = currentRow(); if(version == -1) @@ -566,5 +629,10 @@ void VersionPage::on_revertBtn_clicked() 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 cc990614..b5b4a6f5 100644 --- a/application/pages/instance/VersionPage.h +++ b/application/pages/instance/VersionPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -15,10 +15,10 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "minecraft/MinecraftInstance.h" -#include "minecraft/ComponentList.h" +#include "minecraft/PackProfile.h" #include "pages/BasePage.h" namespace Ui @@ -26,7 +26,7 @@ namespace Ui class VersionPage; } -class VersionPage : public QWidget, public BasePage +class VersionPage : public QMainWindow, public BasePage { Q_OBJECT @@ -49,24 +49,27 @@ public: virtual bool shouldDisplay() const override; private slots: - void on_forgeBtn_clicked(); - void on_addEmptyBtn_clicked(); - void on_liteloaderBtn_clicked(); - void on_reloadBtn_clicked(); - void on_removeBtn_clicked(); - void on_moveUpBtn_clicked(); - void on_moveDownBtn_clicked(); - void on_jarmodBtn_clicked(); - void on_jarBtn_clicked(); - void on_revertBtn_clicked(); - void on_editBtn_clicked(); - void on_modBtn_clicked(); - void on_customizeBtn_clicked(); - void on_downloadBtn_clicked(); + void on_actionChange_version_triggered(); + void on_actionInstall_Forge_triggered(); + void on_actionInstall_Fabric_triggered(); + void on_actionAdd_Empty_triggered(); + void on_actionInstall_LiteLoader_triggered(); + void on_actionReload_triggered(); + void on_actionRemove_triggered(); + void on_actionMove_up_triggered(); + void on_actionMove_down_triggered(); + void on_actionAdd_to_Minecraft_jar_triggered(); + void on_actionReplace_Minecraft_jar_triggered(); + void on_actionRevert_triggered(); + void on_actionEdit_triggered(); + void on_actionInstall_mods_triggered(); + void on_actionCustomize_triggered(); + void on_actionDownload_All_triggered(); + + void on_actionMinecraftFolder_triggered(); + void on_actionLibrariesFolder_triggered(); void updateVersionControls(); - void disableVersionControls(); - void on_changeVersionBtn_clicked(); private: Component * current(); @@ -76,20 +79,26 @@ private: int doUpdate(); protected: + QMenu * createPopupMenu() override; + /// FIXME: this shouldn't be necessary! - bool reloadComponentList(); + bool reloadPackProfile(); private: Ui::VersionPage *ui; - std::shared_ptr<ComponentList> m_profile; + QSortFilterProxyModel *m_filterModel; + std::shared_ptr<PackProfile> m_profile; MinecraftInstance *m_inst; int currentIdx = 0; + bool controlsEnabled = false; public slots: void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); private slots: + void updateRunningStatus(bool running); void onGameUpdateError(QString error); void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); - + 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 d54dd840..84d06e2e 100644 --- a/application/pages/instance/VersionPage.ui +++ b/application/pages/instance/VersionPage.ui @@ -1,279 +1,266 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>VersionPage</class> - <widget class="QWidget" name="VersionPage"> + <widget class="QMainWindow" name="VersionPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>870</width> - <height>1008</height> + <width>961</width> + <height>1091</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="ModListView" name="packageView"> - <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOn</enum> - </property> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="headerHidden"> - <bool>false</bool> - </property> - <attribute name="headerVisible"> - <bool>true</bool> - </attribute> - </widget> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Selection</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="changeVersionBtn"> - <property name="toolTip"> - <string>Change version of the selected package.</string> - </property> - <property name="text"> - <string>Change version</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveUpBtn"> - <property name="toolTip"> - <string>Make the selected package apply sooner.</string> - </property> - <property name="text"> - <string>Move up</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="moveDownBtn"> - <property name="toolTip"> - <string>Make the selected package apply later.</string> - </property> - <property name="text"> - <string>Move down</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="removeBtn"> - <property name="toolTip"> - <string>Remove selected package from the instance.</string> - </property> - <property name="text"> - <string>Remove</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator_4" native="true"/> - </item> - <item> - <widget class="QLabel" name="label_10"> - <property name="text"> - <string>Edit</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="customizeBtn"> - <property name="toolTip"> - <string>Customize selected package.</string> - </property> - <property name="text"> - <string>Customize</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="editBtn"> - <property name="toolTip"> - <string>Edit selected package.</string> - </property> - <property name="text"> - <string>Edit</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="revertBtn"> - <property name="toolTip"> - <string>Revert the selected package to default.</string> - </property> - <property name="text"> - <string>Revert</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator" native="true"/> - </item> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Install</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="forgeBtn"> - <property name="toolTip"> - <string>Install the Minecraft Forge package.</string> - </property> - <property name="text"> - <string>Install Forge</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="liteloaderBtn"> - <property name="toolTip"> - <string>Install the LiteLoader package.</string> - </property> - <property name="text"> - <string>Install LiteLoader</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="modBtn"> - <property name="toolTip"> - <string>Install normal mods.</string> - </property> - <property name="text"> - <string>Install mods</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="widget" native="true"/> - </item> - <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string>Advanced</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="jarmodBtn"> - <property name="toolTip"> - <string>Add a mod into the Minecraft jar file.</string> - </property> - <property name="text"> - <string>Add to Minecraft.jar</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="jarBtn"> - <property name="text"> - <string>Replace Minecraft.jar</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="addEmptyBtn"> - <property name="text"> - <string>Add Empty</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="reloadBtn"> - <property name="toolTip"> - <string>Reload all packages.</string> - </property> - <property name="text"> - <string>Reload</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="downloadBtn"> - <property name="toolTip"> - <string>Download the files needed to launch the instance now.</string> - </property> - <property name="text"> - <string>Download All</string> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_7"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>111</width> - <height>13</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="1" column="0" colspan="2"> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + <widget class="QWidget" name="centralwidget"> + <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> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="ModListView" name="packageView"> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOn</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="headerHidden"> + <bool>false</bool> + </property> + <attribute name="headerVisible"> + <bool>true</bool> + </attribute> + </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"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>Actions</string> + </property> + <property name="allowedAreas"> + <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <property name="floatable"> + <bool>false</bool> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionChange_version"/> + <addaction name="actionMove_up"/> + <addaction name="actionMove_down"/> + <addaction name="actionRemove"/> + <addaction name="separator"/> + <addaction name="actionCustomize"/> + <addaction name="actionEdit"/> + <addaction name="actionRevert"/> + <addaction name="separator"/> + <addaction name="actionInstall_Forge"/> + <addaction name="actionInstall_Fabric"/> + <addaction name="actionInstall_LiteLoader"/> + <addaction name="actionInstall_mods"/> + <addaction name="separator"/> + <addaction name="actionAdd_to_Minecraft_jar"/> + <addaction name="actionReplace_Minecraft_jar"/> + <addaction name="actionAdd_Empty"/> + <addaction name="separator"/> + <addaction name="actionMinecraftFolder"/> + <addaction name="actionLibrariesFolder"/> + <addaction name="separator"/> + <addaction name="actionReload"/> + <addaction name="actionDownload_All"/> + </widget> + <action name="actionChange_version"> + <property name="text"> + <string>Change version</string> + </property> + <property name="toolTip"> + <string>Change version of the selected package.</string> + </property> + </action> + <action name="actionMove_up"> + <property name="text"> + <string>Move up</string> + </property> + <property name="toolTip"> + <string>Make the selected package apply sooner.</string> + </property> + </action> + <action name="actionMove_down"> + <property name="text"> + <string>Move down</string> + </property> + <property name="toolTip"> + <string>Make the selected package apply later.</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> + </property> + <property name="toolTip"> + <string>Remove selected package from the instance.</string> + </property> + </action> + <action name="actionCustomize"> + <property name="text"> + <string>Customize</string> + </property> + <property name="toolTip"> + <string>Customize selected package.</string> + </property> + </action> + <action name="actionEdit"> + <property name="text"> + <string>Edit</string> + </property> + <property name="toolTip"> + <string>Edit selected package.</string> + </property> + </action> + <action name="actionRevert"> + <property name="text"> + <string>Revert</string> + </property> + <property name="toolTip"> + <string>Revert the selected package to default.</string> + </property> + </action> + <action name="actionInstall_Forge"> + <property name="text"> + <string>Install Forge</string> + </property> + <property name="toolTip"> + <string>Install the Minecraft Forge package.</string> + </property> + </action> + <action name="actionInstall_Fabric"> + <property name="text"> + <string>Install Fabric</string> + </property> + <property name="toolTip"> + <string>Install the Fabric Loader package.</string> + </property> + </action> + <action name="actionInstall_LiteLoader"> + <property name="text"> + <string>Install LiteLoader</string> + </property> + <property name="toolTip"> + <string>Install the LiteLoader package.</string> + </property> + </action> + <action name="actionInstall_mods"> + <property name="text"> + <string>Install mods</string> + </property> + <property name="toolTip"> + <string>Install normal mods.</string> + </property> + </action> + <action name="actionAdd_to_Minecraft_jar"> + <property name="text"> + <string>Add to Minecraft.jar</string> + </property> + <property name="toolTip"> + <string>Add a mod into the Minecraft jar file.</string> + </property> + </action> + <action name="actionReplace_Minecraft_jar"> + <property name="text"> + <string>Replace Minecraft.jar</string> + </property> + </action> + <action name="actionAdd_Empty"> + <property name="text"> + <string>Add Empty</string> + </property> + <property name="toolTip"> + <string>Add an empty custom package.</string> + </property> + </action> + <action name="actionReload"> + <property name="text"> + <string>Reload</string> + </property> + <property name="toolTip"> + <string>Reload all packages.</string> + </property> + </action> + <action name="actionDownload_All"> + <property name="text"> + <string>Download All</string> + </property> + <property name="toolTip"> + <string>Download the files needed to launch the instance now.</string> + </property> + </action> + <action name="actionMinecraftFolder"> + <property name="text"> + <string>Open .minecraft</string> + </property> + <property name="toolTip"> + <string>Open the instance's .minecraft folder.</string> + </property> + </action> + <action name="actionLibrariesFolder"> + <property name="text"> + <string>Open libraries</string> + </property> + <property name="toolTip"> + <string>Open the instance's local libraries folder.</string> + </property> + </action> </widget> <customwidgets> <customwidget> @@ -282,37 +269,17 @@ <header>widgets/ModListView.h</header> </customwidget> <customwidget> - <class>LineSeparator</class> - <extends>QWidget</extends> - <header>widgets/LineSeparator.h</header> - <container>1</container> - </customwidget> - <customwidget> <class>MCModInfoFrame</class> <extends>QFrame</extends> <header>widgets/MCModInfoFrame.h</header> <container>1</container> </customwidget> + <customwidget> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> + </customwidget> </customwidgets> - <tabstops> - <tabstop>packageView</tabstop> - <tabstop>changeVersionBtn</tabstop> - <tabstop>moveUpBtn</tabstop> - <tabstop>moveDownBtn</tabstop> - <tabstop>removeBtn</tabstop> - <tabstop>customizeBtn</tabstop> - <tabstop>editBtn</tabstop> - <tabstop>revertBtn</tabstop> - <tabstop>forgeBtn</tabstop> - <tabstop>liteloaderBtn</tabstop> - <tabstop>modBtn</tabstop> - <tabstop>jarmodBtn</tabstop> - <tabstop>jarBtn</tabstop> - <tabstop>addEmptyBtn</tabstop> - <tabstop>reloadBtn</tabstop> - <tabstop>downloadBtn</tabstop> - <tabstop>tabWidget</tabstop> - </tabstops> <resources/> <connections/> </ui> diff --git a/application/pages/instance/WorldListPage.cpp b/application/pages/instance/WorldListPage.cpp index 67a36fd9..119cff3e 100644 --- a/application/pages/instance/WorldListPage.cpp +++ b/application/pages/instance/WorldListPage.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 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. @@ -17,8 +17,8 @@ #include "ui_WorldListPage.h" #include "minecraft/WorldList.h" #include <DesktopServices.h> -#include "dialogs/ModEditDialogCommon.h" #include <QEvent> +#include <QMenu> #include <QKeyEvent> #include <QClipboard> #include <QMessageBox> @@ -31,27 +31,55 @@ #include <QProcess> #include <FileSystem.h> -WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QString id, - QString iconName, QString displayName, QString helpPage, - QWidget *parent) - : QWidget(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds), m_iconName(iconName), m_id(id), m_displayName(displayName), m_helpName(helpPage) +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) { ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this); + + ui->toolBar->insertSpacer(ui->actionRefresh); + + 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(); - head->setSectionResizeMode(0, QHeaderView::Stretch); head->setSectionResizeMode(1, QHeaderView::ResizeToContents); - connect(ui->worldTreeView->selectionModel(), - SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, - SLOT(worldChanged(const QModelIndex &, const QModelIndex &))); + + connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged); worldChanged(QModelIndex(), QModelIndex()); } @@ -71,6 +99,20 @@ WorldListPage::~WorldListPage() delete ui; } +void WorldListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->worldTreeView->mapToGlobal(pos)); + delete menu; +} + +QMenu * WorldListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + return filteredMenu; +} + bool WorldListPage::shouldDisplay() const { return true; @@ -81,7 +123,7 @@ bool WorldListPage::worldListFilter(QKeyEvent *keyEvent) switch (keyEvent->key()) { case Qt::Key_Delete: - on_rmWorldBtn_clicked(); + on_actionRemove_triggered(); return true; default: break; @@ -101,7 +143,7 @@ bool WorldListPage::eventFilter(QObject *obj, QEvent *ev) return QWidget::eventFilter(obj, ev); } -void WorldListPage::on_rmWorldBtn_clicked() +void WorldListPage::on_actionRemove_triggered() { auto proxiedIndex = getSelectedWorld(); @@ -123,11 +165,42 @@ void WorldListPage::on_rmWorldBtn_clicked() m_worlds->startWatching(); } -void WorldListPage::on_viewFolderBtn_clicked() +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(); @@ -136,7 +209,7 @@ QModelIndex WorldListPage::getSelectedWorld() return proxy->mapToSource(index); } -void WorldListPage::on_copySeedBtn_clicked() +void WorldListPage::on_actionCopy_Seed_triggered() { QModelIndex index = getSelectedWorld(); @@ -148,7 +221,7 @@ void WorldListPage::on_copySeedBtn_clicked() MMC->clipboard()->setText(QString::number(seed)); } -void WorldListPage::on_mcEditBtn_clicked() +void WorldListPage::on_actionMCEdit_triggered() { if(m_mceditStarting) return; @@ -236,17 +309,20 @@ void WorldListPage::worldChanged(const QModelIndex ¤t, const QModelIndex & { QModelIndex index = getSelectedWorld(); bool enable = index.isValid(); - ui->copySeedBtn->setEnabled(enable); - ui->mcEditBtn->setEnabled(enable); - ui->rmWorldBtn->setEnabled(enable); - ui->copyBtn->setEnabled(enable); - ui->renameBtn->setEnabled(enable); + ui->actionCopy_Seed->setEnabled(enable); + ui->actionMCEdit->setEnabled(enable); + 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_addBtn_clicked() +void WorldListPage::on_actionAdd_triggered() { auto list = GuiUtil::BrowseForFiles( - m_helpName, + displayName(), tr("Select a Minecraft world zip"), tr("Minecraft World Zip File (*.zip)"), QString(), this->parentWidget()); if (!list.empty()) @@ -279,7 +355,7 @@ bool WorldListPage::worldSafetyNagQuestion() } -void WorldListPage::on_copyBtn_clicked() +void WorldListPage::on_actionCopy_triggered() { QModelIndex index = getSelectedWorld(); if (!index.isValid()) @@ -301,7 +377,7 @@ void WorldListPage::on_copyBtn_clicked() } } -void WorldListPage::on_renameBtn_clicked() +void WorldListPage::on_actionRename_triggered() { QModelIndex index = getSelectedWorld(); if (!index.isValid()) @@ -324,7 +400,9 @@ void WorldListPage::on_renameBtn_clicked() } } -void WorldListPage::on_refreshBtn_clicked() +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 ae9ad4a3..4fc9aa09 100644 --- a/application/pages/instance/WorldListPage.h +++ b/application/pages/instance/WorldListPage.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 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. @@ -15,7 +15,7 @@ #pragma once -#include <QWidget> +#include <QMainWindow> #include "minecraft/MinecraftInstance.h" #include "pages/BasePage.h" @@ -28,31 +28,33 @@ namespace Ui class WorldListPage; } -class WorldListPage : public QWidget, public BasePage +class WorldListPage : public QMainWindow, public BasePage { Q_OBJECT public: - explicit WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QString id, - QString iconName, QString displayName, QString helpPage = "", - QWidget *parent = 0); + explicit WorldListPage( + BaseInstance *inst, + std::shared_ptr<WorldList> worlds, + QWidget *parent = 0 + ); virtual ~WorldListPage(); virtual QString displayName() const override { - return m_displayName; + return tr("Worlds"); } virtual QIcon icon() const override { - return MMC->getThemedIcon(m_iconName); + return MMC->getThemedIcon("worlds"); } virtual QString id() const override { - return m_id; + return "worlds"; } virtual QString helpPage() const override { - return m_helpName; + return "Worlds"; } virtual bool shouldDisplay() const override; @@ -62,6 +64,7 @@ public: protected: bool eventFilter(QObject *obj, QEvent *ev) override; bool worldListFilter(QKeyEvent *ev); + QMenu * createPopupMenu() override; protected: BaseInstance *m_inst; @@ -77,20 +80,20 @@ private: std::shared_ptr<WorldList> m_worlds; unique_qobject_ptr<LoggedProcess> m_mceditProcess; bool m_mceditStarting = false; - QString m_iconName; - QString m_id; - QString m_displayName; - QString m_helpName; private slots: - void on_copySeedBtn_clicked(); - void on_mcEditBtn_clicked(); - void on_rmWorldBtn_clicked(); - void on_addBtn_clicked(); - void on_copyBtn_clicked(); - void on_renameBtn_clicked(); - void on_refreshBtn_clicked(); - void on_viewFolderBtn_clicked(); + void on_actionCopy_Seed_triggered(); + void on_actionMCEdit_triggered(); + void on_actionRemove_triggered(); + void on_actionAdd_triggered(); + void on_actionCopy_triggered(); + 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); + + void ShowContextMenu(const QPoint &pos); }; diff --git a/application/pages/instance/WorldListPage.ui b/application/pages/instance/WorldListPage.ui index 0018ddf3..ed078d94 100644 --- a/application/pages/instance/WorldListPage.ui +++ b/application/pages/instance/WorldListPage.ui @@ -1,168 +1,161 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>WorldListPage</class> - <widget class="QWidget" name="WorldListPage"> + <widget class="QMainWindow" name="WorldListPage"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>723</width> - <height>532</height> + <width>800</width> + <height>600</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <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="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="tab"> - <attribute name="title"> - <string notr="true">Tab 1</string> + <widget class="QWidget" name="centralwidget"> + <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="QTreeView" name="worldTreeView"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="acceptDrops"> + <bool>true</bool> + </property> + <property name="dragDropMode"> + <enum>QAbstractItemView::DragDrop</enum> + </property> + <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> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <attribute name="headerStretchLastSection"> + <bool>false</bool> </attribute> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="2"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPushButton" name="addBtn"> - <property name="text"> - <string>Add</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator" native="true"/> - </item> - <item> - <widget class="QPushButton" name="renameBtn"> - <property name="text"> - <string>Rename</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="copyBtn"> - <property name="text"> - <string>Copy</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="rmWorldBtn"> - <property name="text"> - <string>&Remove</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="mcEditBtn"> - <property name="text"> - <string notr="true">MCEdit</string> - </property> - </widget> - </item> - <item> - <widget class="LineSeparator" name="separator_2" native="true"/> - </item> - <item> - <widget class="QPushButton" name="copySeedBtn"> - <property name="text"> - <string>Copy Seed</string> - </property> - </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> - <item> - <widget class="QPushButton" name="refreshBtn"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="viewFolderBtn"> - <property name="text"> - <string>&View Folder</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <widget class="QTreeView" name="worldTreeView"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="acceptDrops"> - <bool>true</bool> - </property> - <property name="dragDropMode"> - <enum>QAbstractItemView::DragDrop</enum> - </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> - <property name="allColumnsShowFocus"> - <bool>true</bool> - </property> - <attribute name="headerStretchLastSection"> - <bool>false</bool> - </attribute> - </widget> - </item> - </layout> </widget> - </widget> - </item> - </layout> + </item> + </layout> + </widget> + <widget class="WideBar" name="toolBar"> + <property name="windowTitle"> + <string>Actions</string> + </property> + <property name="allowedAreas"> + <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextOnly</enum> + </property> + <property name="floatable"> + <bool>false</bool> + </property> + <attribute name="toolBarArea"> + <enum>RightToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="actionAdd"/> + <addaction name="separator"/> + <addaction name="actionRename"/> + <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"/> + <addaction name="actionView_Folder"/> + </widget> + <action name="actionAdd"> + <property name="text"> + <string>Add</string> + </property> + </action> + <action name="actionRename"> + <property name="text"> + <string>Rename</string> + </property> + </action> + <action name="actionCopy"> + <property name="text"> + <string>Copy</string> + </property> + </action> + <action name="actionRemove"> + <property name="text"> + <string>Remove</string> + </property> + </action> + <action name="actionMCEdit"> + <property name="text"> + <string>MCEdit</string> + </property> + </action> + <action name="actionCopy_Seed"> + <property name="text"> + <string>Copy Seed</string> + </property> + </action> + <action name="actionRefresh"> + <property name="text"> + <string>Refresh</string> + </property> + </action> + <action name="actionView_Folder"> + <property name="text"> + <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> - <class>LineSeparator</class> - <extends>QWidget</extends> - <header>widgets/LineSeparator.h</header> - <container>1</container> + <class>WideBar</class> + <extends>QToolBar</extends> + <header>widgets/WideBar.h</header> </customwidget> </customwidgets> - <tabstops> - <tabstop>tabWidget</tabstop> - <tabstop>worldTreeView</tabstop> - <tabstop>addBtn</tabstop> - <tabstop>renameBtn</tabstop> - <tabstop>copyBtn</tabstop> - <tabstop>rmWorldBtn</tabstop> - <tabstop>mcEditBtn</tabstop> - <tabstop>copySeedBtn</tabstop> - <tabstop>refreshBtn</tabstop> - <tabstop>viewFolderBtn</tabstop> - </tabstops> <resources/> <connections/> </ui> diff --git a/application/pages/modplatform/ImportPage.cpp b/application/pages/modplatform/ImportPage.cpp index 3cd7c2cf..c2369bdc 100644 --- a/application/pages/modplatform/ImportPage.cpp +++ b/application/pages/modplatform/ImportPage.cpp @@ -71,13 +71,20 @@ void ImportPage::updateState() { QFileInfo fi(url.fileName()); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); + dialog->setSuggestedIcon("default"); } } else { + if(input.endsWith("?client=y")) { + input.chop(9); + input.append("/file"); + url = QUrl::fromUserInput(input); + } // 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 120e7b56..67e3c201 100644 --- a/application/pages/modplatform/ImportPage.h +++ b/application/pages/modplatform/ImportPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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/TechnicPage.cpp b/application/pages/modplatform/TechnicPage.cpp deleted file mode 100644 index 2f95bec8..00000000 --- a/application/pages/modplatform/TechnicPage.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "TechnicPage.h" -#include "ui_TechnicPage.h" - -#include "MultiMC.h" -#include "dialogs/NewInstanceDialog.h" - -TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) -{ - ui->setupUi(this); -} - -TechnicPage::~TechnicPage() -{ - delete ui; -} - -bool TechnicPage::shouldDisplay() const -{ - return true; -} - -void TechnicPage::openedImpl() -{ - dialog->setSuggestedPack(); -} diff --git a/application/pages/modplatform/TechnicPage.ui b/application/pages/modplatform/TechnicPage.ui deleted file mode 100644 index 702427b5..00000000 --- a/application/pages/modplatform/TechnicPage.ui +++ /dev/null @@ -1,113 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>TechnicPage</class> - <widget class="QWidget" name="TechnicPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>546</width> - <height>405</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <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> - <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> - <item> - <widget class="QLabel" name="label"> - <property name="font"> - <font> - <pointsize>40</pointsize> - </font> - </property> - <property name="styleSheet"> - <string notr="true">color:#ffc000</string> - </property> - <property name="text"> - <string notr="true">UNDER</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string notr="true"/> - </property> - <property name="pixmap"> - <pixmap resource="../../resources/assets/assets.qrc">:/assets/underconstruction</pixmap> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_2"> - <property name="font"> - <font> - <pointsize>40</pointsize> - </font> - </property> - <property name="styleSheet"> - <string notr="true">color:#7ca32b</string> - </property> - <property name="text"> - <string notr="true">CONSTRUCTION</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_2"> - <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> - <resources> - <include location="../../resources/assets/assets.qrc"/> - </resources> - <connections/> -</ui> diff --git a/application/pages/modplatform/TwitchPage.cpp b/application/pages/modplatform/TwitchPage.cpp deleted file mode 100644 index a984c01c..00000000 --- a/application/pages/modplatform/TwitchPage.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "TwitchPage.h" -#include "ui_TwitchPage.h" - -#include "MultiMC.h" -#include "dialogs/NewInstanceDialog.h" - -TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) -{ - ui->setupUi(this); -} - -TwitchPage::~TwitchPage() -{ - delete ui; -} - -bool TwitchPage::shouldDisplay() const -{ - return false; -} - -void TwitchPage::openedImpl() -{ - dialog->setSuggestedPack(); -} diff --git a/application/pages/modplatform/TwitchPage.ui b/application/pages/modplatform/TwitchPage.ui deleted file mode 100644 index 0930f541..00000000 --- a/application/pages/modplatform/TwitchPage.ui +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>TwitchPage</class> - <widget class="QWidget" name="TwitchPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>546</width> - <height>405</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="label_3"> - <property name="font"> - <font> - <pointsize>40</pointsize> - </font> - </property> - <property name="pixmap"> - <pixmap resource="../../resources/assets/assets.qrc">:/assets/deadglitch</pixmap> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - </layout> - </widget> - <resources> - <include location="../../resources/assets/assets.qrc"/> - </resources> - <connections/> -</ui> diff --git a/application/pages/modplatform/VanillaPage.cpp b/application/pages/modplatform/VanillaPage.cpp index 77362fcc..02638315 100644 --- a/application/pages/modplatform/VanillaPage.cpp +++ b/application/pages/modplatform/VanillaPage.cpp @@ -23,6 +23,7 @@ VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); + connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh); } @@ -58,6 +59,8 @@ void VanillaPage::filterChanged() out << "(old_snapshot)"; if(ui->releaseFilter->isChecked()) out << "(release)"; + if(ui->experimentsFilter->isChecked()) + out << "(experiment)"; auto regexp = out.join('|'); ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); } @@ -79,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 2b292b01..af6fd392 100644 --- a/application/pages/modplatform/VanillaPage.h +++ b/application/pages/modplatform/VanillaPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.ui b/application/pages/modplatform/VanillaPage.ui index ae9cab47..47effc86 100644 --- a/application/pages/modplatform/VanillaPage.ui +++ b/application/pages/modplatform/VanillaPage.ui @@ -99,6 +99,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="experimentsFilter"> + <property name="text"> + <string>Experiments</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -144,6 +154,16 @@ <container>1</container> </customwidget> </customwidgets> + <tabstops> + <tabstop>tabWidget</tabstop> + <tabstop>releaseFilter</tabstop> + <tabstop>snapshotFilter</tabstop> + <tabstop>oldSnapshotFilter</tabstop> + <tabstop>betaFilter</tabstop> + <tabstop>alphaFilter</tabstop> + <tabstop>experimentsFilter</tabstop> + <tabstop>refreshBtn</tabstop> + </tabstops> <resources/> <connections/> </ui> 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/flame/FlameModel.cpp b/application/pages/modplatform/flame/FlameModel.cpp new file mode 100644 index 00000000..228a88c5 --- /dev/null +++ b/application/pages/modplatform/flame/FlameModel.cpp @@ -0,0 +1,259 @@ +#include "FlameModel.h" +#include "MultiMC.h" +#include <Json.h> + +#include <MMCStrings.h> +#include <Version.h> + +#include <QtMath> +#include <QLabel> + +#include <RWStorage.h> +#include <Env.h> + +namespace Flame { + +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); + } + + IndexedPack pack = modpacks.at(pos); + if(role == Qt::DisplayRole) + { + return pack.name; + } + else if (role == Qt::ToolTipRole) + { + if(pack.description.length() > 100) + { + //some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + + } + return pack.description; + } + 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(); +} + +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].logoName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole}); + } + } +} + +void ListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +void ListModel::requestLogo(QString logo, QString url) +{ + if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) + { + return; + } + + 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::succeeded, this, [this, logo, fullPath] + { + emit logoLoaded(logo, QIcon(fullPath)); + if(waitingCallbacks.contains(logo)) + { + waitingCallbacks.value(logo)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo] + { + emit logoFailed(logo); + }); + + job->start(); + + m_loadingLogos.append(logo); +} + +void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback) +{ + if(m_logoMap.contains(logo)) + { + callback(ENV.metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); + } + else + { + requestLogo(logo, logoUrl); + } +} + +Qt::ItemFlags ListModel::flags(const QModelIndex &index) const +{ + return QAbstractListModel::flags(index); +} + +bool ListModel::canFetchMore(const QModelIndex& parent) const +{ + return searchState == CanPossiblyFetchMore; +} + +void ListModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) + return; + if(nextSearchOffset == 0) { + qWarning() << "fetchMore with 0 offset is wrong..."; + return; + } + performPaginatedSearch(); +} + +void ListModel::performPaginatedSearch() +{ + NetJob *netJob = new NetJob("Flame::Search"); + auto searchUrl = QString( + "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "categoryId=0&" + "gameId=432&" + "index=%1&" + "pageSize=25&" + "searchFilter=%2&" + "sectionId=4471&" + "sort=%3" + ).arg(nextSearchOffset).arg(currentSearchTerm).arg(currentSort); + 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::searchWithTerm(const QString& term, int sort) +{ + if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { + return; + } + currentSearchTerm = term; + currentSort = sort; + if(jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } + else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); +} + +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 CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + 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); + } + catch(const JSONValidationError &e) + { + qWarning() << "Error while loading pack from CurseForge: " << e.cause(); + continue; + } + } + if(packs.size() < 25) { + searchState = Finished; + } else { + nextSearchOffset += 25; + searchState = CanPossiblyFetchMore; + } + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void Flame::ListModel::searchRequestFailed(QString reason) +{ + jobPtr.reset(); + + if(searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } +} + +} + diff --git a/application/pages/modplatform/flame/FlameModel.h b/application/pages/modplatform/flame/FlameModel.h new file mode 100644 index 00000000..24383db0 --- /dev/null +++ b/application/pages/modplatform/flame/FlameModel.h @@ -0,0 +1,76 @@ +#pragma once + +#include <RWStorage.h> + +#include <QAbstractListModel> +#include <QSortFilterProxyModel> +#include <QThreadPool> +#include <QIcon> +#include <QStyledItemDelegate> +#include <QList> +#include <QString> +#include <QStringList> +#include <QMetaType> + +#include <functional> +#include <net/NetJob.h> + +#include <modplatform/flame/FlamePackIndex.h> + +namespace Flame { + + +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; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool canFetchMore(const QModelIndex & parent) const override; + void fetchMore(const QModelIndex & parent) override; + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + void searchWithTerm(const QString & term, const int sort); + +private slots: + void performPaginatedSearch(); + + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); + + void searchRequestFinished(); + void searchRequestFailed(QString reason); + +private: + void requestLogo(QString file, QString url); + +private: + 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, + CanPossiblyFetchMore, + ResetRequested, + Finished + } searchState = None; + NetJobPtr jobPtr; + QByteArray response; +}; + +} 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/flame/FlamePage.h b/application/pages/modplatform/flame/FlamePage.h new file mode 100644 index 00000000..467bb44b --- /dev/null +++ b/application/pages/modplatform/flame/FlamePage.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 <QWidget> + +#include "pages/BasePage.h" +#include <MultiMC.h> +#include "tasks/Task.h" +#include <modplatform/flame/FlamePackIndex.h> + +namespace Ui +{ +class FlamePage; +} + +class NewInstanceDialog; + +namespace Flame { + class ListModel; +} + +class FlamePage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit FlamePage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~FlamePage(); + virtual QString displayName() const override + { + return tr("CurseForge"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("flame"); + } + virtual QString id() const override + { + return "flame"; + } + virtual QString helpPage() const override + { + return "Flame-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + bool eventFilter(QObject * watched, QEvent * event) override; + +private: + void suggestCurrent(); + +private slots: + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + +private: + Ui::FlamePage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + 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/FtbListModel.cpp b/application/pages/modplatform/legacy_ftb/ListModel.cpp index f4311afb..32596fb3 100644 --- a/application/pages/modplatform/FtbListModel.cpp +++ b/application/pages/modplatform/legacy_ftb/ListModel.cpp @@ -1,4 +1,4 @@ -#include "FtbListModel.h" +#include "ListModel.h" #include "MultiMC.h" #include <MMCStrings.h> @@ -10,17 +10,21 @@ #include <RWStorage.h> #include <Env.h> -FtbFilterModel::FtbFilterModel(QObject *parent) : QSortFilterProxyModel(parent) +#include <BuildConfig.h> + +namespace LegacyFTB { + +FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) { currentSorting = Sorting::ByGameVersion; sortings.insert(tr("Sort by name"), Sorting::ByName); sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion); } -bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - FtbModpack leftPack = sourceModel()->data(left, Qt::UserRole).value<FtbModpack>(); - FtbModpack rightPack = sourceModel()->data(right, Qt::UserRole).value<FtbModpack>(); + Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>(); + Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>(); if(currentSorting == Sorting::ByGameVersion) { Version lv(leftPack.mcVersion); @@ -36,66 +40,66 @@ bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) return true; } -bool FtbFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { return true; } -const QMap<QString, FtbFilterModel::Sorting> FtbFilterModel::getAvailableSortings() +const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings() { return sortings; } -QString FtbFilterModel::translateCurrentSorting() +QString FilterModel::translateCurrentSorting() { return sortings.key(currentSorting); } -void FtbFilterModel::setSorting(Sorting s) +void FilterModel::setSorting(Sorting s) { currentSorting = s; invalidate(); } -FtbFilterModel::Sorting FtbFilterModel::getCurrentSorting() +FilterModel::Sorting FilterModel::getCurrentSorting() { return currentSorting; } -FtbListModel::FtbListModel(QObject *parent) : QAbstractListModel(parent) +ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) { } -FtbListModel::~FtbListModel() +ListModel::~ListModel() { } -QString FtbListModel::translatePackType(FtbPackType type) const +QString ListModel::translatePackType(PackType type) const { switch(type) { - case FtbPackType::Public: + case PackType::Public: return tr("Public Modpack"); - case FtbPackType::ThirdParty: + case PackType::ThirdParty: return tr("Third Party Modpack"); - case FtbPackType::Private: + case PackType::Private: return tr("Private Modpack"); } qWarning() << "Unknown FTB modpack type:" << int(type); return QString(); } -int FtbListModel::rowCount(const QModelIndex &parent) const +int ListModel::rowCount(const QModelIndex &parent) const { return modpacks.size(); } -int FtbListModel::columnCount(const QModelIndex &parent) const +int ListModel::columnCount(const QModelIndex &parent) const { return 1; } -QVariant FtbListModel::data(const QModelIndex &index, int role) const +QVariant ListModel::data(const QModelIndex &index, int role) const { int pos = index.row(); if(pos >= modpacks.size() || pos < 0 || !index.isValid()) @@ -103,7 +107,7 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const return QString("INVALID INDEX %1").arg(pos); } - FtbModpack pack = modpacks.at(pos); + Modpack pack = modpacks.at(pos); if(role == Qt::DisplayRole) { return pack.name + "\n" + translatePackType(pack.type); @@ -127,7 +131,7 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const return (m_logoMap.value(pack.logo)); } QIcon icon = MMC->getThemedIcon("screenshot-placeholder"); - ((FtbListModel *)this)->requestLogo(pack.logo); + ((ListModel *)this)->requestLogo(pack.logo); return icon; } else if(role == Qt::TextColorRole) @@ -154,33 +158,33 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const return QVariant(); } -void FtbListModel::fill(FtbModpackList modpacks) +void ListModel::fill(ModpackList modpacks) { beginResetModel(); this->modpacks = modpacks; endResetModel(); } -void FtbListModel::addPack(FtbModpack modpack) +void ListModel::addPack(Modpack modpack) { beginResetModel(); this->modpacks.append(modpack); endResetModel(); } -void FtbListModel::clear() +void ListModel::clear() { beginResetModel(); modpacks.clear(); endResetModel(); } -FtbModpack FtbListModel::at(int row) +Modpack ListModel::at(int row) { return modpacks.at(row); } -void FtbListModel::remove(int row) +void ListModel::remove(int row) { if(row < 0 || row >= modpacks.size()) { @@ -192,20 +196,20 @@ void FtbListModel::remove(int row) endRemoveRows(); } -void FtbListModel::logoLoaded(QString logo, QIcon out) +void ListModel::logoLoaded(QString logo, QIcon out) { m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); emit dataChanged(createIndex(0, 0), createIndex(1, 0)); } -void FtbListModel::logoFailed(QString logo) +void ListModel::logoFailed(QString logo) { m_failedLogos.append(logo); m_loadingLogos.removeAll(logo); } -void FtbListModel::requestLogo(QString file) +void ListModel::requestLogo(QString file) { if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) { @@ -214,7 +218,7 @@ void FtbListModel::requestLogo(QString file) MetaEntryPtr entry = ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file)); - job->addNetAction(Net::Download::makeCached(QUrl(QString("https://ftb.cursecdn.com/FTB2/static/%1").arg(file)), entry)); + job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); auto fullPath = entry->getFullPath(); QObject::connect(job, &NetJob::finished, this, [this, file, fullPath] @@ -236,7 +240,7 @@ void FtbListModel::requestLogo(QString file) m_loadingLogos.append(file); } -void FtbListModel::getLogo(const QString &logo, LogoCallback callback) +void ListModel::getLogo(const QString &logo, LogoCallback callback) { if(m_logoMap.contains(logo)) { @@ -248,7 +252,9 @@ void FtbListModel::getLogo(const QString &logo, LogoCallback callback) } } -Qt::ItemFlags FtbListModel::flags(const QModelIndex &index) const +Qt::ItemFlags ListModel::flags(const QModelIndex &index) const { return QAbstractListModel::flags(index); } + +} diff --git a/application/pages/modplatform/FtbListModel.h b/application/pages/modplatform/legacy_ftb/ListModel.h index 98301fd7..c55df000 100644 --- a/application/pages/modplatform/FtbListModel.h +++ b/application/pages/modplatform/legacy_ftb/ListModel.h @@ -1,6 +1,6 @@ #pragma once -#include <modplatform/ftb/PackHelpers.h> +#include <modplatform/legacy_ftb/PackHelpers.h> #include <RWStorage.h> #include <QAbstractListModel> @@ -11,13 +11,16 @@ #include <functional> -typedef QMap<QString, QIcon> FtbLogoMap; +namespace LegacyFTB { + +typedef QMap<QString, QIcon> FTBLogoMap; typedef std::function<void(QString)> LogoCallback; -class FtbFilterModel : public QSortFilterProxyModel +class FilterModel : public QSortFilterProxyModel { + Q_OBJECT public: - FtbFilterModel(QObject* parent = Q_NULLPTR); + FilterModel(QObject* parent = Q_NULLPTR); enum Sorting { ByName, ByGameVersion @@ -37,18 +40,18 @@ private: }; -class FtbListModel : public QAbstractListModel +class ListModel : public QAbstractListModel { Q_OBJECT private: - FtbModpackList modpacks; + ModpackList modpacks; QStringList m_failedLogos; QStringList m_loadingLogos; - FtbLogoMap m_logoMap; + FTBLogoMap m_logoMap; QMap<QString, LogoCallback> waitingCallbacks; void requestLogo(QString file); - QString translatePackType(FtbPackType type) const; + QString translatePackType(PackType type) const; private slots: @@ -56,18 +59,20 @@ private slots: void logoLoaded(QString logo, QIcon out); public: - FtbListModel(QObject *parent); - ~FtbListModel(); + ListModel(QObject *parent); + ~ListModel(); int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; - void fill(FtbModpackList modpacks); - void addPack(FtbModpack modpack); + void fill(ModpackList modpacks); + void addPack(Modpack modpack); void clear(); void remove(int row); - FtbModpack at(int row); + Modpack at(int row); void getLogo(const QString &logo, LogoCallback callback); }; + +} diff --git a/application/pages/modplatform/FTBPage.cpp b/application/pages/modplatform/legacy_ftb/Page.cpp index dca86efd..a438f76c 100644 --- a/application/pages/modplatform/FTBPage.cpp +++ b/application/pages/modplatform/legacy_ftb/Page.cpp @@ -1,27 +1,29 @@ -#include "FTBPage.h" -#include "ui_FTBPage.h" +#include "Page.h" +#include "ui_Page.h" #include <QInputDialog> #include "MultiMC.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/NewInstanceDialog.h" -#include "modplatform/ftb/FtbPackFetchTask.h" -#include "modplatform/ftb/FtbPackInstallTask.h" -#include "modplatform/ftb/FtbPrivatePackManager.h" -#include "FtbListModel.h" +#include "modplatform/legacy_ftb/PackFetchTask.h" +#include "modplatform/legacy_ftb/PackInstallTask.h" +#include "modplatform/legacy_ftb/PrivatePackManager.h" +#include "ListModel.h" -FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), dialog(dialog), ui(new Ui::FTBPage) +namespace LegacyFTB { + +Page::Page(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), dialog(dialog), ui(new Ui::Page) { - ftbFetchTask.reset(new FtbPackFetchTask()); - ftbPrivatePacks.reset(new FtbPrivatePackManager()); + ftbFetchTask.reset(new PackFetchTask()); + ftbPrivatePacks.reset(new PrivatePackManager()); ui->setupUi(this); { - publicFilterModel = new FtbFilterModel(this); - publicListModel = new FtbListModel(this); + publicFilterModel = new FilterModel(this); + publicListModel = new ListModel(this); publicFilterModel->setSourceModel(publicListModel); ui->publicPackList->setModel(publicFilterModel); @@ -39,8 +41,8 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) } { - thirdPartyFilterModel = new FtbFilterModel(this); - thirdPartyModel = new FtbListModel(this); + thirdPartyFilterModel = new FilterModel(this); + thirdPartyModel = new ListModel(this); thirdPartyFilterModel->setSourceModel(thirdPartyModel); ui->thirdPartyPackList->setModel(thirdPartyFilterModel); @@ -53,8 +55,8 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) } { - privateFilterModel = new FtbFilterModel(this); - privateListModel = new FtbListModel(this); + privateFilterModel = new FilterModel(this); + privateListModel = new ListModel(this); privateFilterModel->setSourceModel(privateListModel); ui->privatePackList->setModel(privateFilterModel); @@ -69,17 +71,17 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FTBPage::onSortingSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FTBPage::onVersionSelectionItemChanged); + connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged); - connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPublicPackSelectionChanged); - connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onThirdPartyPackSelectionChanged); - connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPrivatePackSelectionChanged); + connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged); + connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged); + connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged); - connect(ui->addPackBtn, &QPushButton::pressed, this, &FTBPage::onAddPackClicked); - connect(ui->removePackBtn, &QPushButton::pressed, this, &FTBPage::onRemovePackClicked); + connect(ui->addPackBtn, &QPushButton::pressed, this, &Page::onAddPackClicked); + connect(ui->removePackBtn, &QPushButton::pressed, this, &Page::onRemovePackClicked); - connect(ui->tabWidget, &QTabWidget::currentChanged, this, &FTBPage::onTabChanged); + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &Page::onTabChanged); // ui->modpackInfo->setOpenExternalLinks(true); @@ -90,25 +92,25 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) onTabChanged(ui->tabWidget->currentIndex()); } -FTBPage::~FTBPage() +Page::~Page() { delete ui; } -bool FTBPage::shouldDisplay() const +bool Page::shouldDisplay() const { return true; } -void FTBPage::openedImpl() +void Page::openedImpl() { if(!initialized) { - connect(ftbFetchTask.get(), &FtbPackFetchTask::finished, this, &FTBPage::ftbPackDataDownloadSuccessfully); - connect(ftbFetchTask.get(), &FtbPackFetchTask::failed, this, &FTBPage::ftbPackDataDownloadFailed); + connect(ftbFetchTask.get(), &PackFetchTask::finished, this, &Page::ftbPackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &PackFetchTask::failed, this, &Page::ftbPackDataDownloadFailed); - connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFinished, this, &FTBPage::ftbPrivatePackDataDownloadSuccessfully); - connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFailed, this, &FTBPage::ftbPrivatePackDataDownloadFailed); + connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFinished, this, &Page::ftbPrivatePackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFailed, this, &Page::ftbPrivatePackDataDownloadFailed); ftbFetchTask->fetch(); ftbPrivatePacks->load(); @@ -118,71 +120,72 @@ void FTBPage::openedImpl() suggestCurrent(); } -void FTBPage::suggestCurrent() +void Page::suggestCurrent() { - if(isOpened) + if(!isOpened) { - if(!selected.broken) - { - dialog->setSuggestedPack(selected.name, new FtbPackInstallTask(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 == FtbPackType::Public) - { - publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); - } - else if (selected.type == FtbPackType::ThirdParty) - { - thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo) - { - dialog->setSuggestedIconFromFile(logo, editedLogoName); - }); - } - else if (selected.type == FtbPackType::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); + }); } } -void FTBPage::ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks) +void Page::ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks) { publicListModel->fill(publicPacks); thirdPartyModel->fill(thirdPartyPacks); } -void FTBPage::ftbPackDataDownloadFailed(QString reason) +void Page::ftbPackDataDownloadFailed(QString reason) { //TODO: Display the error } -void FTBPage::ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack) +void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack) { privateListModel->addPack(pack); } -void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) +void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) { auto reply = QMessageBox::question( this, @@ -195,40 +198,40 @@ void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) } } -void FTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) { if(!now.isValid()) { onPackSelectionChanged(); return; } - FtbModpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + Modpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&selectedPack); } -void FTBPage::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev) { if(!now.isValid()) { onPackSelectionChanged(); return; } - FtbModpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + Modpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&selectedPack); } -void FTBPage::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev) { if(!now.isValid()) { onPackSelectionChanged(); return; } - FtbModpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + Modpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&selectedPack); } -void FTBPage::onPackSelectionChanged(FtbModpack* pack) +void Page::onPackSelectionChanged(Modpack* pack) { ui->versionSelectionBox->clear(); if(pack) @@ -266,7 +269,7 @@ void FTBPage::onPackSelectionChanged(FtbModpack* pack) suggestCurrent(); } -void FTBPage::onVersionSelectionItemChanged(QString data) +void Page::onVersionSelectionItemChanged(QString data) { if(data.isNull() || data.isEmpty()) { @@ -278,15 +281,15 @@ void FTBPage::onVersionSelectionItemChanged(QString data) suggestCurrent(); } -void FTBPage::onSortingSelectionChanged(QString data) +void Page::onSortingSelectionChanged(QString data) { - FtbFilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); + FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); publicFilterModel->setSorting(toSet); thirdPartyFilterModel->setSorting(toSet); privateFilterModel->setSorting(toSet); } -void FTBPage::onTabChanged(int tab) +void Page::onTabChanged(int tab) { if(tab == 1) { @@ -311,7 +314,7 @@ void FTBPage::onTabChanged(int tab) QModelIndex idx = currentList->currentIndex(); if(idx.isValid()) { - auto pack = currentModel->data(idx, Qt::UserRole).value<FtbModpack>(); + auto pack = currentModel->data(idx, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&pack); } else @@ -320,7 +323,7 @@ void FTBPage::onTabChanged(int tab) } } -void FTBPage::onAddPackClicked() +void Page::onAddPackClicked() { bool ok; QString text = QInputDialog::getText( @@ -338,7 +341,7 @@ void FTBPage::onAddPackClicked() } } -void FTBPage::onRemovePackClicked() +void Page::onRemovePackClicked() { auto index = ui->privatePackList->currentIndex(); if(!index.isValid()) @@ -346,7 +349,7 @@ void FTBPage::onRemovePackClicked() return; } auto row = index.row(); - FtbModpack pack = privateListModel->at(row); + Modpack pack = privateListModel->at(row); auto answer = QMessageBox::question( this, tr("Remove pack"), @@ -362,3 +365,5 @@ void FTBPage::onRemovePackClicked() privateListModel->remove(row); onPackSelectionChanged(); } + +} diff --git a/application/pages/modplatform/FTBPage.h b/application/pages/modplatform/legacy_ftb/Page.h index 6467decc..e840216e 100644 --- a/application/pages/modplatform/FTBPage.h +++ b/application/pages/modplatform/legacy_ftb/Page.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -22,29 +22,32 @@ #include "pages/BasePage.h" #include <MultiMC.h> #include "tasks/Task.h" -#include "modplatform/ftb/PackHelpers.h" -#include "modplatform/ftb/FtbPackFetchTask.h" +#include "modplatform/legacy_ftb/PackHelpers.h" +#include "modplatform/legacy_ftb/PackFetchTask.h" #include "QObjectPtr.h" +class NewInstanceDialog; + +namespace LegacyFTB { + namespace Ui { -class FTBPage; +class Page; } -class FtbListModel; -class FtbFilterModel; -class NewInstanceDialog; -class FtbPrivatePackListModel; -class FtbPrivatePackFilterModel; -class FtbPrivatePackManager; +class ListModel; +class FilterModel; +class PrivatePackListModel; +class PrivatePackFilterModel; +class PrivatePackManager; -class FTBPage : public QWidget, public BasePage +class Page : public QWidget, public BasePage { Q_OBJECT public: - explicit FTBPage(NewInstanceDialog * dialog, QWidget *parent = 0); - virtual ~FTBPage(); + explicit Page(NewInstanceDialog * dialog, QWidget *parent = 0); + virtual ~Page(); QString displayName() const override { return tr("FTB Legacy"); @@ -55,7 +58,7 @@ public: } QString id() const override { - return "ftb"; + return "legacy_ftb"; } QString helpPage() const override { @@ -66,13 +69,13 @@ public: private: void suggestCurrent(); - void onPackSelectionChanged(FtbModpack *pack = nullptr); + void onPackSelectionChanged(Modpack *pack = nullptr); private slots: - void ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); + void ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks); void ftbPackDataDownloadFailed(QString reason); - void ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack); + void ftbPrivatePackDataDownloadSuccessfully(Modpack pack); void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); void onSortingSelectionChanged(QString data); @@ -88,27 +91,29 @@ private slots: void onRemovePackClicked(); private: - FtbFilterModel* currentModel = nullptr; + FilterModel* currentModel = nullptr; QTreeView* currentList = nullptr; QTextBrowser* currentModpackInfo = nullptr; bool initialized = false; - FtbModpack selected; + Modpack selected; QString selectedVersion; - FtbListModel* publicListModel = nullptr; - FtbFilterModel* publicFilterModel = nullptr; + ListModel* publicListModel = nullptr; + FilterModel* publicFilterModel = nullptr; - FtbListModel *thirdPartyModel = nullptr; - FtbFilterModel *thirdPartyFilterModel = nullptr; + ListModel *thirdPartyModel = nullptr; + FilterModel *thirdPartyFilterModel = nullptr; - FtbListModel *privateListModel = nullptr; - FtbFilterModel *privateFilterModel = nullptr; + ListModel *privateListModel = nullptr; + FilterModel *privateFilterModel = nullptr; - unique_qobject_ptr<FtbPackFetchTask> ftbFetchTask; - std::unique_ptr<FtbPrivatePackManager> ftbPrivatePacks; + unique_qobject_ptr<PackFetchTask> ftbFetchTask; + std::unique_ptr<PrivatePackManager> ftbPrivatePacks; NewInstanceDialog* dialog = nullptr; - Ui::FTBPage *ui = nullptr; + Ui::Page *ui = nullptr; }; + +} diff --git a/application/pages/modplatform/FTBPage.ui b/application/pages/modplatform/legacy_ftb/Page.ui index e5ed78cb..15e5d432 100644 --- a/application/pages/modplatform/FTBPage.ui +++ b/application/pages/modplatform/legacy_ftb/Page.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>FTBPage</class> - <widget class="QWidget" name="FTBPage"> + <class>LegacyFTB::Page</class> + <widget class="QWidget" name="LegacyFTB::Page"> <property name="geometry"> <rect> <x>0</x> @@ -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/TechnicPage.h b/application/pages/modplatform/technic/TechnicPage.h index 84ea4636..27e1258a 100644 --- a/application/pages/modplatform/TechnicPage.h +++ b/application/pages/modplatform/technic/TechnicPage.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,6 +20,7 @@ #include "pages/BasePage.h" #include <MultiMC.h> #include "tasks/Task.h" +#include "TechnicData.h" namespace Ui { @@ -28,6 +29,10 @@ class TechnicPage; class NewInstanceDialog; +namespace Technic { + class ListModel; +} + class TechnicPage : public QWidget, public BasePage { Q_OBJECT @@ -55,7 +60,19 @@ public: 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/technic/TechnicPage.ui b/application/pages/modplatform/technic/TechnicPage.ui new file mode 100644 index 00000000..2ca45dd2 --- /dev/null +++ b/application/pages/modplatform/technic/TechnicPage.ui @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TechnicPage</class> + <widget class="QWidget" name="TechnicPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>546</width> + <height>405</height> + </rect> + </property> + <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> + <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> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>MCModInfoFrame</class> + <extends>QFrame</extends> + <header>widgets/MCModInfoFrame.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>searchEdit</tabstop> + <tabstop>searchButton</tabstop> + <tabstop>packView</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> 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/OSX/OSX.qrc b/application/resources/OSX/OSX.qrc index a5c40894..19fd4b6a 100644 --- a/application/resources/OSX/OSX.qrc +++ b/application/resources/OSX/OSX.qrc @@ -14,6 +14,7 @@ <file>scalable/instance-settings.svg</file> <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> + <file>scalable/language.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/application/resources/OSX/scalable/language.svg b/application/resources/OSX/scalable/language.svg new file mode 100644 index 00000000..4f7d002a --- /dev/null +++ b/application/resources/OSX/scalable/language.svg @@ -0,0 +1,40 @@ +<?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" + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 24 24" + enable-background="new 0 0 24 24" + xml:space="preserve"><metadata + id="metadata22"><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></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs20" /><path + fill-rule="evenodd" + clip-rule="evenodd" + fill="#E6E6E6" + d="M21,20H9c-1.1,0-2-0.9-2-2V6c0-1.1,0.9-2,2-2h12c1.1,0,2,0.9,2,2 v12C23,19.1,22.1,20,21,20z" + id="path2" /><rect + fill="none" + width="24" + height="24" + id="rect4" /><g + id="_x36__8_"><g + id="g8"><path + d="M 21,4 H 9 C 7.9,4 7,4.9 7,6 v 12 c 0,1.1 0.9,2 2,2 h 12 c 1.1,0 2,-0.9 2,-2 V 6 C 23,4.9 22.1,4 21,4 Z m 1,14 -1,1 H 9 L 8,18 V 6 C 8,5.4 8.4,5 9,5 h 12 c 0.6,0 1,0.4 1,1 z" + id="path6" + style="fill:#585858" /></g></g><circle + cx="-3.8492424" + cy="11.559504" + r="1" + id="circle11" + style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd" /><path + d="m 10.985086,8.5301236 c -0.285244,0 -0.531238,0.2015787 -0.587348,0.480998 L 9.2012867,14.998205 c -0.064883,0.324431 0.1450781,0.638482 0.4689122,0.703366 0.3279037,0.0672 0.6390781,-0.145078 0.7033681,-0.468911 l 0.384313,-1.91432 h 1.69195 l 0.381898,1.91432 c 0.06544,0.328662 0.388374,0.53424 0.705784,0.468911 0.323873,-0.06488 0.533795,-0.378935 0.468912,-0.703366 L 12.809973,9.0111216 c -0.05611,-0.2794193 -0.302101,-0.480998 -0.587347,-0.480998 z m 0.362559,1.3487256 h 0.256212 l 0.478579,2.3953168 h -1.21337 z m 6.429409,-1.5928481 c -0.330857,0 -0.599434,0.2698964 -0.599434,0.6018494 v 0.5994329 h -1.795884 c -0.330855,0 -0.597015,0.269898 -0.597015,0.6018506 0,0.331951 0.266159,0.599434 0.597015,0.599434 h 0.161945 c 0.340555,1.096391 0.840589,1.953495 1.401899,2.629772 -0.439717,0.403341 -0.871563,0.734012 -1.339055,1.104601 -0.25779,0.207035 -0.298799,0.58673 -0.09185,0.845975 0.205993,0.258844 0.58387,0.299404 0.841141,0.09185 0.507959,-0.402258 0.944446,-0.738141 1.421237,-1.177114 0.476792,0.438973 0.95437,0.774856 1.462328,1.177114 0.257273,0.207554 0.635147,0.166994 0.841141,-0.09185 0.206952,-0.259245 0.163525,-0.63894 -0.09426,-0.845975 -0.467495,-0.370589 -0.938012,-0.70126 -1.377731,-1.104601 0.561312,-0.676317 1.102396,-1.53342 1.442991,-2.629772 h 0.159526 c 0.330857,0 0.599433,-0.267483 0.599433,-0.599434 0,-0.3319526 -0.268576,-0.6018506 -0.599433,-0.6018506 H 18.376487 V 8.8878505 c 0,-0.331953 -0.268576,-0.6018494 -0.599433,-0.6018494 z m -0.983747,2.4025669 h 2.006168 c -0.25847,0.696576 -0.641961,1.260479 -1.022421,1.74029 -0.380459,-0.479811 -0.725279,-1.043674 -0.983747,-1.74029 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#585858;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.7126205;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/assets/assets.qrc b/application/resources/assets/assets.qrc deleted file mode 100644 index 38638e7f..00000000 --- a/application/resources/assets/assets.qrc +++ /dev/null @@ -1,7 +0,0 @@ -<!DOCTYPE RCC> -<RCC version="1.0"> - <qresource prefix="/assets"> - <file alias="underconstruction">underconstruction.png</file> - <file alias="deadglitch">deadglitch.svg</file> - </qresource> -</RCC> diff --git a/application/resources/assets/deadglitch.svg b/application/resources/assets/deadglitch.svg deleted file mode 100644 index 5682b8a3..00000000 --- a/application/resources/assets/deadglitch.svg +++ /dev/null @@ -1,66 +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" - class="tw-svg__asset tw-svg__asset--deadglitch tw-svg__asset--inherit" - width="92px" - height="96px" - version="1.1" - viewBox="0 0 30 30" - x="0px" - y="0px" - id="svg8" - sodipodi:docname="deadglitch.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> - <metadata - id="metadata14"> - <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></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs12" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1353" - inkscape:window-height="828" - id="namedview10" - showgrid="false" - inkscape:zoom="4.9166667" - inkscape:cx="44.285787" - inkscape:cy="52.833458" - inkscape:window-x="2958" - inkscape:window-y="702" - inkscape:window-maximized="0" - inkscape:current-layer="svg8" /> - <g - id="g6" - style="fill:#898395;fill-opacity:1"> - <path - d="M26,17.4589613 L26,3 L4,3 L4,22.0601057 L10.0032868,22.0601057 L10.0032868,26 L14.0004537,22.0601057 L21.3322933,22.0601057 L26,17.4589613 L26,17.4589613 Z M21.0896458,26.0850335 L15.1583403,26.0850335 L11.2051771,30 L7.24798611,30 L7.24798611,26.0850335 L0,26.0850335 L0,5.21746493 L1.97773958,0 L29,0 L29,18.2620736 L21.0896458,26.0850335 L21.0896458,26.0850335 Z" - id="path2" - style="fill:#898395;fill-opacity:1" /> - <path - d="M20.8587626,12.1710126 L22.4052753,13.7175252 L23.7175252,12.4052753 L22.1710126,10.8587626 L23.7175252,9.31224999 L22.4052753,8 L20.8587626,9.54651264 L19.31225,8 L18,9.31224999 L19.5465126,10.8587626 L18,12.4052753 L19.31225,13.7175252 L20.8587626,12.1710126 Z M11.8587626,12.1710126 L13.4052753,13.7175252 L14.7175252,12.4052753 L13.1710126,10.8587626 L14.7175252,9.31224999 L13.4052753,8 L11.8587626,9.54651264 L10.31225,8 L9,9.31224999 L10.5465126,10.8587626 L9,12.4052753 L10.31225,13.7175252 L11.8587626,12.1710126 Z" - id="path4" - style="fill:#898395;fill-opacity:1" /> - </g> -</svg> diff --git a/application/resources/backgrounds/backgrounds.qrc b/application/resources/backgrounds/backgrounds.qrc index 55de139e..83505635 100644 --- a/application/resources/backgrounds/backgrounds.qrc +++ b/application/resources/backgrounds/backgrounds.qrc @@ -2,5 +2,6 @@ <RCC version="1.0"> <qresource prefix="/backgrounds"> <file alias="kitteh">catbgrnd2.png</file> + <file alias="catmas">catmas.png</file> </qresource> </RCC> diff --git a/application/resources/backgrounds/catbgrnd2.png b/application/resources/backgrounds/catbgrnd2.png Binary files differindex 2b949e0b..e9de7f27 100644 --- a/application/resources/backgrounds/catbgrnd2.png +++ b/application/resources/backgrounds/catbgrnd2.png diff --git a/application/resources/backgrounds/catmas.png b/application/resources/backgrounds/catmas.png Binary files differnew file mode 100644 index 00000000..8bdb1d5c --- /dev/null +++ b/application/resources/backgrounds/catmas.png diff --git a/application/resources/flat/flat.qrc b/application/resources/flat/flat.qrc index 67c8d291..b6e2ee38 100644 --- a/application/resources/flat/flat.qrc +++ b/application/resources/flat/flat.qrc @@ -16,6 +16,7 @@ <file>scalable/instance-settings.svg</file> <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> + <file>scalable/language.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/application/resources/flat/scalable/language.svg b/application/resources/flat/scalable/language.svg new file mode 100644 index 00000000..f4d3f2f4 --- /dev/null +++ b/application/resources/flat/scalable/language.svg @@ -0,0 +1,103 @@ +<?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" + fill="#757575" + height="24" + viewBox="0 0 24 24" + width="24" + version="1.1" + id="svg4" + sodipodi:docname="language.svg" + inkscape:version="0.92.2 2405546, 2018-03-11"> + <metadata + id="metadata10"> + <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></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs8"> + + + + + + + + + + + + + + + + + + + + + + + </defs> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3840" + inkscape:window-height="2123" + id="namedview6" + showgrid="true" + showguides="true" + inkscape:guide-bbox="true" + inkscape:zoom="6.9532167" + inkscape:cx="-18.49351" + inkscape:cy="-12.477971" + inkscape:window-x="1200" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg4"> + <inkscape:grid + type="xygrid" + id="grid981" /> + <sodipodi:guide + position="-8,11.440678" + orientation="1,0" + id="guide1060" + inkscape:locked="false" /> + <sodipodi:guide + position="-28.34375,24" + orientation="0,1" + id="guide1062" + inkscape:locked="false" /> + </sodipodi:namedview> + <path + d="M 5,3 C 3.89,3 3,3.89 3,5 v 14 c 0,1.104569 0.895431,2 2,2 h 14 c 1.104569,0 2,-0.895431 2,-2 V 5 C 21,3.89 20.1,3 19,3 Z m 10.359375,4.505859 c 0.400344,0 0.726563,0.326845 0.726563,0.728516 v 0.724609 h 2.21875 c 0.400344,0 0.726562,0.326845 0.726562,0.728516 0,0.401669 -0.326217,0.726562 -0.726562,0.726562 h -0.191407 c -0.412128,1.326612 -1.066893,2.363281 -1.746093,3.181641 0.53207,0.488052 1.100334,0.887517 1.666015,1.335938 0.311924,0.250518 0.365651,0.709744 0.115235,1.023437 -0.249259,0.313205 -0.708226,0.362473 -1.019532,0.111328 -0.614642,-0.486742 -1.192601,-0.892661 -1.769531,-1.423828 -0.576929,0.531167 -1.104107,0.937086 -1.71875,1.423828 -0.311303,0.251148 -0.768322,0.201879 -1.017578,-0.111328 -0.250416,-0.313693 -0.200604,-0.772919 0.111328,-1.023437 0.565677,-0.448421 1.087072,-0.847886 1.619141,-1.335938 -0.679199,-0.818312 -1.283234,-1.854981 -1.695313,-3.181641 h -0.197265 c -0.400344,0 -0.720704,-0.324893 -0.720704,-0.726562 0,-0.401671 0.320361,-0.728516 0.720704,-0.728516 h 2.173828 V 8.234375 c 0,-0.401671 0.324264,-0.728516 0.724609,-0.728516 z M 7.142578,7.800781 h 1.496094 c 0.345155,0 0.643047,0.243927 0.710937,0.582031 l 1.447266,7.244141 c 0.07851,0.39257 -0.174512,0.773053 -0.566406,0.851563 -0.384074,0.07905 -0.774336,-0.168718 -0.853516,-0.566407 L 8.914062,13.595703 H 6.867188 L 6.402344,15.912109 C 6.324551,16.303955 5.947553,16.559826 5.550781,16.478516 5.158934,16.400005 4.905865,16.019523 4.984375,15.626953 L 6.431641,8.382812 C 6.499536,8.044708 6.797425,7.800781 7.142578,7.800781 Z m 0.4375,1.632813 -0.578125,2.898437 H 8.46875 L 7.890625,9.433594 Z m 6.589844,0.980468 c 0.312752,0.842923 0.729088,1.524886 1.189453,2.105469 0.460366,-0.580583 0.925527,-1.262594 1.238281,-2.105469 z" + id="path1072" + inkscape:connector-curvature="0" /> + <g + style="fill:#000000" + id="g821" + transform="matrix(0.0322459,0,0,0.0322459,-17.878956,30.647558)"> + <g + style="fill:#000000" + id="g819" /> + </g> +</svg> diff --git a/application/resources/iOS/iOS.qrc b/application/resources/iOS/iOS.qrc index 7212ce77..511e390b 100644 --- a/application/resources/iOS/iOS.qrc +++ b/application/resources/iOS/iOS.qrc @@ -14,6 +14,7 @@ <file>scalable/instance-settings.svg</file> <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> + <file>scalable/language.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/application/resources/iOS/scalable/language.svg b/application/resources/iOS/scalable/language.svg new file mode 100644 index 00000000..fcc3436e --- /dev/null +++ b/application/resources/iOS/scalable/language.svg @@ -0,0 +1,32 @@ +<?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" + xml:space="preserve" + enable-background="new 0 0 32 32" + viewBox="0 0 32 32" + y="0px" + x="0px" + id="Calque_1" + version="1.1"><metadata + id="metadata15"><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></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs13" /> +<g + id="g8"> + + <path + id="path6" + d="M28,32H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h24c2.2,0,4,1.8,4,4 v24C32,30.2,30.2,32,28,32z M30,4c0-1.1-0.9-2-2-2H4C2.9,2,2,2.9,2,4v24c0,1.1,0.9,2,2,2h24c1.1,0,2-0.9,2-2V4z" + fill="#3366CC" + clip-rule="evenodd" + fill-rule="evenodd" /> +</g> +<path + d="m 8.8837922,9.8498439 c -0.5055801,0 -0.9415895,0.3572871 -1.0410421,0.8525411 L 5.7221093,21.314148 c -0.1150014,0.575036 0.2571428,1.131673 0.8311203,1.246676 0.5811915,0.119103 1.1327283,-0.257142 1.2466794,-0.831119 l 0.6811731,-3.393021 h 2.9988849 l 0.676892,3.393021 c 0.115982,0.582535 0.688371,0.946911 1.250962,0.831119 0.574046,-0.115001 0.94612,-0.67164 0.831119,-1.246676 L 12.118299,10.702385 C 12.018854,10.207131 11.582843,9.8498439 11.07726,9.8498439 Z m 0.642615,2.3905391 h 0.4541203 l 0.8482535,4.245562 H 8.6781534 Z M 20.922167,9.4171507 c -0.586426,0 -1.062464,0.4783758 -1.062464,1.0667433 v 1.062461 h -3.183102 c -0.58642,0 -1.058175,0.478378 -1.058175,1.066747 0,0.588364 0.471753,1.062461 1.058175,1.062461 h 0.28704 c 0.603613,1.943292 1.489894,3.462457 2.484785,4.661121 -0.779374,0.714899 -1.544795,1.300994 -2.373399,1.957842 -0.456918,0.366958 -0.529604,1.039945 -0.162795,1.499442 0.36511,0.458786 1.034877,0.530676 1.490873,0.162795 0.900329,-0.71298 1.673977,-1.308312 2.519064,-2.086367 0.845087,0.778055 1.691565,1.373387 2.591893,2.086367 0.456001,0.367877 1.125761,0.295987 1.490873,-0.162795 0.366811,-0.459497 0.289838,-1.132484 -0.167067,-1.499442 -0.82861,-0.656848 -1.662574,-1.242943 -2.441951,-1.957842 0.994894,-1.198734 1.953935,-2.7179 2.55762,-4.661121 h 0.28275 c 0.586426,0 1.062461,-0.474097 1.062461,-1.062461 0,-0.588369 -0.476035,-1.066747 -1.062461,-1.066747 h -3.251659 v -1.062461 c 0,-0.5883675 -0.476037,-1.0667433 -1.062461,-1.0667433 z m -1.743636,4.2584123 h 3.555818 c -0.458122,1.234642 -1.137838,2.234128 -1.812182,3.084565 -0.674344,-0.850437 -1.285517,-1.849853 -1.743636,-3.084565 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#3366cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.58040667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file 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/instances/brick.png b/application/resources/multimc/32x32/instances/brick.png Binary files differindex 0b534366..c324fda0 100644 --- a/application/resources/multimc/32x32/instances/brick.png +++ b/application/resources/multimc/32x32/instances/brick.png diff --git a/application/resources/multimc/32x32/instances/diamond.png b/application/resources/multimc/32x32/instances/diamond.png Binary files differindex 376ab901..1eb26469 100644 --- a/application/resources/multimc/32x32/instances/diamond.png +++ b/application/resources/multimc/32x32/instances/diamond.png diff --git a/application/resources/multimc/32x32/instances/gold.png b/application/resources/multimc/32x32/instances/gold.png Binary files differindex 9bedda16..593410fa 100644 --- a/application/resources/multimc/32x32/instances/gold.png +++ b/application/resources/multimc/32x32/instances/gold.png diff --git a/application/resources/multimc/32x32/instances/iron.png b/application/resources/multimc/32x32/instances/iron.png Binary files differindex 28960782..3e811bd6 100644 --- a/application/resources/multimc/32x32/instances/iron.png +++ b/application/resources/multimc/32x32/instances/iron.png diff --git a/application/resources/multimc/32x32/instances/planks.png b/application/resources/multimc/32x32/instances/planks.png Binary files differindex 7fcf8467..a94b7502 100644 --- a/application/resources/multimc/32x32/instances/planks.png +++ b/application/resources/multimc/32x32/instances/planks.png diff --git a/application/resources/multimc/32x32/instances/stone.png b/application/resources/multimc/32x32/instances/stone.png Binary files differindex 34f9a751..1b6ef7a4 100644 --- a/application/resources/multimc/32x32/instances/stone.png +++ b/application/resources/multimc/32x32/instances/stone.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/index.theme b/application/resources/multimc/index.theme index 290f42fb..6061b7f8 100644 --- a/application/resources/multimc/index.theme +++ b/application/resources/multimc/index.theme @@ -2,7 +2,7 @@ Name=multimc Comment=MultiMC Default Icons Inherits=default -Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable +Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable,scalable/instances [8x8] Size=8 @@ -51,3 +51,8 @@ Size=48 Type=Scalable MinSize=16 MaxSize=256 + +[scalable/instances] +Size=128 +MinSize=16 +MaxSize=256 diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc index f99cfca2..249e8e28 100644 --- a/application/resources/multimc/multimc.qrc +++ b/application/resources/multimc/multimc.qrc @@ -11,15 +11,23 @@ <!-- 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> + <!-- A free language icon. http://www.languageicon.org/ --> + <file>scalable/language.svg</file> + <!-- Java icon. From Oracle, fixed because it was derpy. --> <file>scalable/java.svg</file> @@ -305,5 +313,8 @@ <file>32x32/instances/tnt.png</file> <file>50x50/instances/enderman.png</file> + + <file>scalable/instances/fox.svg</file> + <file>scalable/instances/bee.svg</file> </qresource> </RCC> 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/instances/bee.svg b/application/resources/multimc/scalable/instances/bee.svg new file mode 100644 index 00000000..49f216c8 --- /dev/null +++ b/application/resources/multimc/scalable/instances/bee.svg @@ -0,0 +1,159 @@ +<?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" + version="1.1" + viewBox="0 0 25.399999 25.4" + height="96" + width="96"> + <g transform="translate(-33.928467,-255.46043)"> + <g transform="rotate(-9.9635201,-96.932986,622.95265)"> + <path style="fill:#f1f2e0;fill-opacity:1;fill-rule:evenodd;stroke:#999999;stroke-width:0.28753757px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 101.98199,286.42583 -1.1502,0.57512 1.14992,0.5755 2.30058,1.14996 1.15019,-0.57514 -2.30058,-1.14995 z m 2.3003,2.30058 -1.1502,0.57512 1.15002,0.57493 1.15019,-0.57513 z m -1.8e-4,1.15005 -1.1502,0.57512 1.15057,0.57502 1.15019,-0.57512 z m 3.7e-4,1.15014 -1.15019,0.57514 -1.1502,0.57512 1.15001,0.57493 1.1502,-0.57513 1.15019,-0.57512 z m -2.30039,1.15026 -1.15001,-0.57494 -1.150566,-0.57502 -1.150193,0.57512 1.150565,0.57502 1.150014,0.57494 z m -2.300576,-1.14996 1.150196,-0.57513 -1.150014,-0.57493 -1.150097,0.57456 z m -1.149915,-0.5755 1.150193,-0.57512 -1.150012,-0.57492 -1.150194,0.57512 z m 1.81e-4,-1.15004 1.150194,-0.57513 -1.150567,-0.57503 -1.150193,0.57512 z m -3.73e-4,-1.15016 1.150194,-0.57512 1.150189,-0.57513 -1.150008,-0.57492 -1.150193,0.57512 -1.150194,0.57512 z m 1.150567,0.57503 1.149916,0.57548 1.15001,0.57494 1.15019,-0.57512 -1.15001,-0.57494 -1.15047,-0.57558 z" /> + </g> + <g style="fill:none;stroke:#999999" transform="matrix(1.0012918,0.26829532,-0.26829532,1.0012918,86.112205,-31.978257)"> + <path style="stroke:#999999;stroke-width:1.03661346px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 85.501953 51.128906 C 82.473901 51.748417 79.445938 52.368362 76.417969 52.988281 L 79.886719 56.064453 C 82.914656 55.444381 85.942682 54.824742 88.970703 54.205078 L 85.501953 51.128906 z " + transform="matrix(0.24654113,-0.06606049,0.06606049,0.24654113,23.141685,280.86706)" /> + <path style="stroke:#999999;stroke-width:1.03661346px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 79.636719 40.972656 L 75.095703 41.902344 C 78.563735 44.978675 82.032556 48.054115 85.501953 51.128906 L 90.042969 50.201172 L 79.636719 40.972656 z " + transform="matrix(0.24654113,-0.06606049,0.06606049,0.24654113,23.141685,280.86706)" /> + </g> + <path style="fill:#fed668" d="m 41.865965,262.07502 -4.233333,2.11667 7.408333,3.70417 4.233334,-2.11667 z" /> + <path style="fill:#0a0707" d="m 50.332633,272.39378 v 1.32291 l 1.058333,0.52917 v -1.32292 z" /> + <path style="fill:#422117" d="m 50.332633,273.7167 v 1.32291 l 1.058333,0.52917 v -1.32292 z" /> + <path style="fill:#0a0707" d="m 48.215966,273.45211 v 1.32291 l 1.058333,0.52917 v -1.32292 z" /> + <path style="fill:#422117" d="m 48.215966,274.77503 v 1.32291 l 1.058333,0.52917 v -1.32292 z" /> + <path style="fill:#422117" d="m 45.040966,275.3042 v 1.32291 l 1.058333,0.52917 v -1.32292 z" /> + <path style="fill:#78621d" d="m 45.040965,277.15626 4.233334,-2.11665 v -9.26042 l -4.233334,2.11667 z" /> + <path style="fill:#1d0c08" d="m 50.332632,265.25002 -1.058333,0.52917 v 9.26042 l 1.058333,-0.52917 z" /> + <path style="fill:#1d0c08" d="m 52.449299,264.19169 -1.058333,0.52917 v 9.26042 l 1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 38.690965,263.66252 1.058334,0.52917 1.058333,-0.52917 1.058333,0.52917 2.116667,-1.05834 -2.116667,-1.05833 z" /> + <path style="fill:#fed668" d="m 42.924299,261.54586 7.408334,3.70417 1.058333,-0.52917 -7.408334,-3.70417 z" /> + <path style="fill:#e4ae3b" d="m 40.807633,262.60419 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#fed668" d="m 45.040966,260.48752 7.408333,3.70417 1.058333,-0.52917 -7.408333,-3.70417 z" /> + <path style="fill:#5f3225" d="m 42.924299,261.54585 -1.058334,0.52917 7.408333,3.70417 1.058334,-0.52917 z" /> + <path style="fill:#e4ae3b" d="m 42.9243,261.54585 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#5f3225" d="m 45.040965,260.48752 -1.058333,0.52917 7.408333,3.70416 1.058333,-0.52916 z" /> + <path style="fill:#e4ae3b" d="m 45.040966,260.48752 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#5f3225" d="m 46.099299,259.95835 7.408333,3.70417 2.116666,-1.05833 -7.408333,-3.70417 z" /> + <path style="fill:#552e22" d="m 47.157633,259.42919 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 43.982633,262.07502 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 46.099299,261.01669 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 40.807633,264.72086 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#e4ae3b" d="m 45.040967,262.60419 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#552e22" d="m 49.2743,260.48753 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 48.215966,262.07502 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 41.865966,266.30836 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#552e22" d="m 50.332633,262.07502 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 46.099299,265.25002 -1.058334,0.52917 1.058334,0.52916 -2.116667,1.05834 1.058333,0.52916 3.175,-1.5875 z" /> + <path style="fill:#edc343" d="m 48.215967,264.19169 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 50.332633,263.13336 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#e4ae3b" d="m 47.157633,265.77919 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#e4ae3b" d="m 49.2743,264.72085 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#e4ae3b" d="m 51.390966,263.66252 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#edc343" d="m 38.690966,264.72086 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#ac8d2e" d="m 37.632633,273.45211 7.408333,3.70415 v -9.2604 l -7.408333,-3.70417 z" /> + <path style="fill:#12121a" d="m 37.632633,268.16044 2.116666,1.05834 v 3.96875 l -2.116666,-1.05834 z" /> + <path style="fill:#78621d" d="m 50.332633,265.25002 v 9.26041 l 1.058333,-0.52916 v -9.26042 z" /> + <path style="fill:#78621d" d="m 52.449299,264.19169 v 9.26041 l 1.058333,-0.52916 v -9.26042 z" /> + <path style="fill:#27120b" d="m 49.274299,265.77919 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" /> + <path style="fill:#09090e" d="m 45.040966,271.86461 1.058333,-0.52917 v 3.96875 l -1.058333,0.52917 -2.116667,-1.05834 v -3.96875 z" /> + <path style="fill:#150704" d="m 49.2743,271.07085 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" /> + <path style="fill:#050303" d="m 49.2743,273.71669 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#150704" d="m 51.390966,268.68961 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" /> + <path style="fill:#050303" d="m 51.390966,271.33544 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" /> + <path style="fill:#2c140d" d="m 51.390966,264.72086 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#1d0c08" d="m 55.624299,262.60419 -2.116666,1.05833 v 9.26042 l 2.116666,-1.05833 z" /> + <path style="fill:#150704" d="m 53.507633,268.95419 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#050303" d="m 53.507633,270.2771 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" /> + <path style="fill:#2c140d" d="m 53.507633,263.66252 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#27120b" d="m 54.565966,263.13335 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" /> + <path style="fill:#150704" d="m 54.565966,269.74794 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" /> + <path style="fill:#27120b" d="m 54.565966,267.10211 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#633f19" d="m 45.040965,277.15626 3.175,-1.58749 v -1.32291 l -3.175,1.5875 z" /> + <path style="fill:#55300a" d="m 48.215966,274.24586 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#694520" d="m 48.215966,272.92294 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#735619" d="m 48.215966,271.60002 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#735619" d="m 47.157633,273.45211 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#131016" d="m 45.040966,271.86461 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#131016" d="m 45.040966,274.51044 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#694520" d="m 45.040966,270.54169 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#735619" d="m 46.0993,270.01252 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#816c31" d="m 45.040966,267.89586 v 1.32291 l 1.058333,-0.52916 v 1.32291 l 1.058334,-0.52916 v -1.32292 l 2.116666,-1.05833 v -1.32292 z" /> + <path style="fill:#49250c" d="m 50.332633,273.18752 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#633f19" d="m 50.332633,271.86461 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#735619" d="m 50.332633,269.21878 v 2.64582 l 1.058333,-0.52916 v -2.64583 z" /> + <path style="fill:#816c31" d="m 50.332633,265.25002 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#49250c" d="m 52.449299,270.80627 10e-7,2.64583 1.058333,-0.52916 -10e-7,-2.64584 z" /> + <path style="fill:#55300a" d="m 52.4493,269.48336 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#694520" d="m 52.4493,268.16044 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#755719" d="m 52.4493,266.83752 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#816c31" d="m 52.4493,264.19169 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#b89b49" d="m 37.632632,265.51461 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" /> + <path style="fill:#b89b49" d="m 40.807633,267.10211 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" /> + <path style="fill:#b89b49" d="m 43.982633,270.01253 1.058333,0.52916 v -2.64584 l -1.058333,-0.52916 z" /> + <path style="fill:#966531" d="m 43.982632,271.33545 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#a57d28" d="m 42.924299,270.80627 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#b89b49" d="m 41.865965,270.27712 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#966531" d="m 43.982632,271.33545 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#a57d28" d="m 39.749299,273.18753 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#a57d28" d="m 41.865965,274.24587 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#a57d28" d="m 40.807633,275.03961 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#8e5c28" d="m 43.982633,276.62711 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#966531" d="m 41.865966,275.56877 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" /> + <path style="fill:#966531" d="m 38.690966,273.98128 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" /> + <path style="fill:#8e5c28" d="m 37.632632,273.45212 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#956531" d="m 37.632633,268.16044 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#b89b49" d="m 39.749299,267.89587 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#1f1c25" d="m 38.690966,267.36669 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#1f1c25" d="m 42.924299,269.48337 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#589197" d="m 42.924299,272.12919 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#589197" d="m 38.690966,270.01252 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#12121a" d="m 43.982633,272.65836 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#12121a" d="m 42.924299,273.45211 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#12121a" d="m 43.982633,275.30419 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#1f1c25" d="m 43.982633,273.98127 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#1f1c25" d="m 42.924299,274.77503 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#1f1c25" d="m 38.690966,272.65836 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#1f1c25" d="m 37.632633,270.80627 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" /> + <path style="fill:#131016" d="m 38.690966,267.10211 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#131016" d="m 36.574299,266.83752 v 1.32291 l 2.116667,-1.05832 v -1.32292 z" /> + <path style="fill:#131016" d="m 41.865966,268.68961 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" /> + <path style="fill:#131016" d="m 39.749299,268.42502 v 1.32291 l 2.116667,-1.05832 v -1.32292 z" /> + <g transform="matrix(1.0012918,0.26829532,-0.26829532,1.0012918,86.112205,-31.978257)"> + <path style="fill:#f1f2e0" d="m 38.073986,286.23688 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#5f3225" d="m 37.015652,286.76606 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#5f3225" d="m 37.015652,287.82438 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 38.073986,288.35355 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 40.190652,286.23689 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 42.307319,286.23688 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 42.307319,287.29521 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 43.365653,287.82437 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 43.365653,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 41.248986,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 40.190653,288.35354 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 45.482319,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 46.540653,288.35355 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 46.540653,287.29521 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 44.423986,286.23688 2.116667,1.05833 1.058333,-0.52917 -2.116667,-1.05833 z" /> + </g> + <g transform="matrix(1.0703659,-0.18803179,0.18803179,1.0703659,-63.348962,-38.123102)"> + <path style="fill:#f1f2e0" d="m 48.392735,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#5f3225" d="m 47.334402,289.41187 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#5f3225" d="m 45.217735,289.41188 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 44.159402,288.8827 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 43.101069,286.23687 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 43.101068,285.17854 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 46.276068,284.64938 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 45.217735,286.23688 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 44.159402,284.64937 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 43.101068,287.2952 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 44.159402,287.82438 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 48.392735,286.76604 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 48.392735,287.82438 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f1f2e0" d="m 46.276068,286.76604 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" /> + <path style="fill:#f7fdfd" d="m 47.334402,285.17854 2.116667,1.05833 1.058333,-0.52917 -2.116667,-1.05833 z" /> + </g> + </g> +</svg> diff --git a/application/resources/multimc/scalable/instances/fox.svg b/application/resources/multimc/scalable/instances/fox.svg new file mode 100644 index 00000000..fcf16b2f --- /dev/null +++ b/application/resources/multimc/scalable/instances/fox.svg @@ -0,0 +1,290 @@ +<?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" + id="svg8" + version="1.1" + viewBox="0 0 33.866666 33.866666" + height="128" + width="128"> + <defs + id="defs2" /> + <metadata + id="metadata5"> + <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></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + transform="translate(0,-263.13334)" + id="layer1"> + <path + id="rect4750" + d="m 4.233333,267.36667 v 6.35 6.35 3.175 3.175 3.175 3.175 h 25.4 v -3.175 -3.175 -3.175 -3.175 -6.35 -6.35 h -6.35 v 6.35 h -12.7 v -3.175 -3.175 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:0.79375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <g + id="g4748"> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4553" + width="6.3500032" + height="6.3499975" + x="4.233326" + y="267.36667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b48f83;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4553-6-2" + width="3.1750014" + height="3.1749992" + x="7.4083276" + y="270.54166" /> + <rect + y="267.36667" + x="23.283335" + height="6.3499975" + width="6.3500032" + id="rect4623" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + y="270.54166" + x="23.283335" + height="3.1749992" + width="3.1750014" + id="rect4627" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b48f83;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e27c21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4672" + width="25.400013" + height="15.875009" + x="4.233326" + y="273.71667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4629" + width="3.1750016" + height="6.3500032" + x="4.2333255" + y="273.71667" /> + <rect + y="273.71667" + x="26.458338" + height="6.3500032" + width="3.1750016" + id="rect4631" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e78f41;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4636" + width="6.3500032" + height="3.1750016" + x="13.758331" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b05122;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4638" + width="3.1750016" + height="3.1750016" + x="4.2333255" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4640" + width="3.1750016" + height="3.1750016" + x="7.4083276" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84189939;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4644" + width="6.3500028" + height="3.1750016" + x="4.233326" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4642" + width="3.1750016" + height="3.1750016" + x="4.2333255" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84189939;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4646" + width="6.3500028" + height="3.1750016" + x="23.283337" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4648" + width="3.1750016" + height="3.1750016" + x="26.45834" + y="283.24167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4650" + width="3.1750016" + height="3.1750016" + x="23.283337" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b05122;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4652" + width="3.1750016" + height="3.1750016" + x="26.45834" + y="280.06668" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4654" + width="25.400013" + height="3.1750016" + x="4.2333255" + y="286.41666" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4656" + width="25.400013" + height="3.1750016" + x="4.2333255" + y="289.59167" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5612604;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4917" + width="25.4" + height="0.26457807" + x="4.2333331" + y="273.71667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.48607069;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4921" + width="0.2645835" + height="19.049997" + x="4.2333331" + y="273.71667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4658" + width="12.700007" + height="6.3500013" + x="10.583333" + y="286.41666" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4660" + width="6.3500037" + height="3.1750007" + x="13.758333" + y="286.41669" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4662" + width="3.1750019" + height="3.1750007" + x="10.583333" + y="286.41669" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4664" + width="3.1750019" + height="3.1750007" + x="20.108334" + y="286.41669" /> + <rect + y="286.41669" + x="10.583333" + height="6.3499832" + width="0.2645835" + id="rect4923" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2806327;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.39686465;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4951" + width="12.699996" + height="0.26456967" + x="10.583333" + y="286.41669" /> + <rect + y="273.71667" + x="29.36875" + height="19.049982" + width="0.26458356" + id="rect4953" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.48607057;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + y="292.50208" + x="23.283333" + height="0.26457682" + width="6.3499994" + id="rect4957" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28062955;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2806325;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4959" + width="0.2645838" + height="6.3499656" + x="23.018749" + y="286.41669" /> + <rect + y="292.50208" + x="4.2333331" + height="0.26457968" + width="6.3499999" + id="rect4961" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + y="267.36667" + x="4.2333331" + height="6.3499956" + width="0.2645835" + id="rect4963" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063297;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063536;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4965" + width="6.349998" + height="0.26458791" + x="4.2333331" + y="267.36667" /> + <rect + y="267.36667" + x="23.283333" + height="6.3499956" + width="0.2645835" + id="rect4963-9" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.280633;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063536;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4965-1" + width="6.349998" + height="0.26458791" + x="23.283333" + y="267.36667" /> + <rect + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063306;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect4985" + width="0.26458356" + height="6.3499975" + x="29.36875" + y="267.36667" /> + <rect + y="267.36667" + x="10.318749" + height="6.3499975" + width="0.26458356" + id="rect4987" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063306;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /> + </g> + </g> +</svg> diff --git a/application/resources/multimc/scalable/language.svg b/application/resources/multimc/scalable/language.svg new file mode 100644 index 00000000..968e3538 --- /dev/null +++ b/application/resources/multimc/scalable/language.svg @@ -0,0 +1,109 @@ +<?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" + viewBox="0 0 128 128" + height="128" + width="128" + xml:space="preserve" + id="svg2" + version="1.1" + sodipodi:docname="language.svg" + inkscape:version="0.92.2 2405546, 2018-03-11"><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="3840" + inkscape:window-height="2123" + id="namedview30" + showgrid="false" + inkscape:zoom="1.84375" + inkscape:cx="325.0346" + inkscape:cy="134.81864" + inkscape:window-x="1200" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="g888" /><metadata + id="metadata8"><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></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs6" /><g + transform="matrix(1.3333333,0,0,-1.3333333,0,128.00004)" + id="g10"><g + transform="scale(0.1)" + id="g12"><g + transform="matrix(0.0334181,0,0,0.0334181,77.111273,13.149509)" + id="g888"><g + id="g14" + transform="scale(1.69573)" + style="fill:#0066cc;fill-opacity:1"><path + d="M 7103.55,14358.7 1603.03,16300 V 4328.22 l 5500.52,1779.59 v 8250.89" + style="fill:#0066cc;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path16" /></g><g + id="g18" + transform="scale(1.69635)"><path + d="M 6968.96,14359.4 12678,16300 V 4332.6 L 6968.96,6111.53 v 8247.87" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path20" /></g><g + id="g22" + transform="scale(1.49453)"><path + d="M 200.466,2533.04 7910.06,5102.75 V 16300 L 200.466,13730.3 V 2533.04" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path24" /></g><g + id="g26" + transform="scale(1.20038)" + style="fill:#003399;fill-opacity:1"><path + d="M 14222.2,2943.15 15582.6,705.027 16300,2784.12 14222.2,2943.15" + style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path28" /></g><g + id="g30" + transform="scale(1.16442)" + style="fill:#003399;fill-opacity:1"><path + d="m 3621.88,15967.1 c -52.56,51.6 68.45,-421.7 236.85,-592 298.61,-301.3 531.85,-340.1 656.04,-345.1 274.81,-11 613.95,68.5 815.34,152.9 194.86,83.1 536.31,257.4 665.56,511.7 27.4,54.4 102.2,145.7 55.22,371.2 -35.64,173.5 -146.08,234.2 -280.74,224.6 -134.66,-9.1 -542.33,-117.8 -739.51,-178.5 -197.26,-59.8 -603.56,-183.5 -780.64,-221.9 -176.65,-38.3 -566.12,17.8 -628.12,77.1" + style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path32" /></g><g + id="g34" + transform="scale(1.0917)" + style="fill:#003399;fill-opacity:1"><path + d="m 9188.43,10995.1 c -83.18,30.2 -1803.98,743 -2047.91,859.8 -199.6,96 -689.02,302.9 -919.3,396.9 648.62,1000.1 1058.07,1754.8 1112.58,1869.8 100.85,210.3 787.39,1553.7 803.42,1636.4 15.57,83.8 35.08,393.4 19.97,467 -15.11,75 -266.83,-69.2 -608.59,-185.1 -342.31,-115.4 -992.86,-538.5 -1244.12,-591.5 -252.17,-52.6 -1058.07,-357.9 -1470.46,-494.7 -412.38,-136.9 -1192.45,-375 -1513.33,-461.6 -321.33,-86.7 -601.81,-93.5 -781.53,-148 0,0 23.91,-251.8 71.63,-327.2 47.18,-75.5 217.19,-260.5 414.86,-312.2 197.67,-52 524.87,-31.2 673.9,2.9 148.95,34.6 406.98,160.7 441.61,215.7 34.99,56 -18.05,228.4 40.85,280.5 59.45,51.6 844.83,235.2 1141.34,324.7 296.51,91.2 1431.53,482.1 1585.42,462.2 C 6860.04,14829 5947.06,13020.6 5653.02,12481.1 5358.89,11941.7 3650.37,9568.39 3286.62,9150.14 3010.54,8832.19 2341.49,8018.6 2109.74,7835.03 c 58.44,-16.12 472.75,19.42 548.23,66.14 470.36,289.73 1253.82,1265 1506.09,1562.06 749.84,879.37 1408.63,1803.07 1931.02,2595.77 h 0.56 c 101.76,-42.4 924.61,-712.8 1139.32,-861.4 214.71,-148.5 1062.01,-621.2 1245.58,-699.7 183.57,-79.4 889.07,-404.6 918.75,-294.5 29.68,111 -127.6,760.1 -210.86,791.7" + style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path36" /></g><g + id="g38" + transform="scale(1.12147)" + style="fill:#003399;fill-opacity:1"><path + d="m 5073.69,1979.54 c 160.5,-98.08 312.09,-178.34 481.51,-258.59 338.84,-169.42 722.26,-347.76 1087.85,-481.51 499.35,-187.25 998.69,-338.838 1498.03,-454.757 276.43,-62.418 579.6,-115.919 873.85,-160.504 26.76,0 820.35,-98.085 980.86,-98.085 h 802.51 c 312.1,26.751 606.4,44.584 918.4,89.169 249.7,35.667 526.1,80.251 793.6,142.669 196.2,44.584 401.3,89.169 597.5,151.587 187.2,53.501 401.2,124.831 606.3,196.171 133.8,44.58 276.4,107 419.1,160.5 115.9,53.5 258.6,115.92 392.3,169.42 160.6,71.34 347.8,169.42 526.1,258.59 142.7,71.34 303.2,160.5 454.8,249.67 115.9,62.42 383.4,267.51 526.1,267.51 160.5,0 267.5,-142.67 267.5,-267.51 0,-258.59 -347.8,-338.84 -508.3,-454.76 -169.4,-115.92 -374.5,-205.08 -552.8,-303.17 -356.7,-187.253 -722.3,-347.756 -1070,-481.509 -454.8,-169.42 -954.1,-329.923 -1400,-436.926 -169.4,-35.667 -338.8,-80.251 -508.2,-107.002 C 12171.5,142.67 11244.1,0 10985.6,0 H 9808.53 c -312.09,26.7505 -642.01,62.4179 -954.1,107.002 -276.42,44.584 -570.68,98.086 -847.1,160.503 -214,44.585 -445.84,107.003 -650.93,169.421 -356.67,98.085 -704.43,222.921 -1043.27,356.674 -615.26,231.84 -1257.28,535.01 -1863.62,936.27 -107,71.33 -115.92,142.67 -115.92,222.92 0,133.75 98.08,258.59 258.59,258.59 142.67,0 428.01,-205.09 481.51,-231.84" + style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path40" /></g><g + id="g42" + transform="scale(1.51227)" + style="fill:#003399;fill-opacity:1"><path + d="M 8014.44,16134.7 V 5025.56 c -6.61,-33.07 -19.84,-66.13 -46.29,-99.19 -13.22,-19.84 -39.67,-46.29 -59.51,-52.9 C 7743.33,4807.34 297.566,2307.79 198.377,2307.79 c -79.351,0 -152.089,52.9 -191.76442,138.86 0,6.62 -6.61258,13.23 -6.61258,26.45 v 11115.7 c 13.2252,33.1 19.8377,79.4 46.288,105.8 52.9006,72.8 145.477,86 204.99,105.8 112.414,39.7 7445.762,2499.6 7551.562,2499.6 66.13,0 211.6,-46.3 211.6,-165.3 z M 7611.07,5190.87 403.367,2790.51 V 13423.5 L 7611.07,15823.9 V 5190.87" + style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path44" /></g><g + id="g46" + transform="scale(1.71411)" + style="fill:#003399;fill-opacity:1"><path + d="M 12723.8,16113.3 V 4311.27 c -5.8,-134.18 -99.2,-192.52 -186.7,-192.52 -75.8,0 -624.2,186.69 -717.6,215.86 -735,227.52 -1475.9,455.05 -2205.18,682.57 -163.35,52.51 -332.54,105.01 -490.05,157.52 -140.02,40.83 -291.7,87.5 -431.71,134.18 -624.23,192.52 -1260.13,385.04 -1884.36,595.06 -23.34,5.83 -81.68,87.51 -81.68,105.01 v 8243.35 c 11.67,29.2 23.34,64.2 52.51,87.5 46.67,52.5 2047.71,717.6 2835.29,980.1 210.02,75.8 2841.08,980.1 2922.78,980.1 105,0 186.7,-75.8 186.7,-186.7 z M 12367.9,4532.96 7076.56,6178.13 V 14083.1 L 12367.9,15880 V 4532.96" + style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path48" /></g><g + id="g50" + transform="scale(1.48515)" + style="fill:#0066cc;fill-opacity:1"><path + d="M 16235.4,2378.81 8076.61,4979.28 8110.74,16300 16235.4,13714.1 V 2378.81" + style="fill:#0066cc;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path52" /></g><g + id="g54" + transform="scale(1.33098)"><path + d="m 12990.3,14581.8 1172.9,-355.3 2136.8,-7701.24 -1204.8,365.52 -432.8,1581.01 -2489.7,754.63 -535.4,-1287.92 -1205.1,365.6 z m 536.2,-2038.8 -893.6,-2159.8 1642.8,-497.94 -749.2,2657.74" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" + id="path56" /></g></g></g></g></svg>
\ No newline at end of file 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/resources/pe_blue/pe_blue.qrc b/application/resources/pe_blue/pe_blue.qrc index 7d28d3d7..98445d88 100644 --- a/application/resources/pe_blue/pe_blue.qrc +++ b/application/resources/pe_blue/pe_blue.qrc @@ -14,6 +14,7 @@ <file>scalable/instance-settings.svg</file> <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> + <file>scalable/language.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/application/resources/pe_blue/scalable/language.svg b/application/resources/pe_blue/scalable/language.svg new file mode 100644 index 00000000..92868516 --- /dev/null +++ b/application/resources/pe_blue/scalable/language.svg @@ -0,0 +1,46 @@ +<?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" + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve"><metadata + id="metadata45"><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></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs43" /><path + fill-rule="evenodd" + clip-rule="evenodd" + fill="#3366CC" + d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z" + id="path2" /><path + fill="#DAEEFF" + fill-rule="evenodd" + clip-rule="evenodd" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + id="path4" /><g + id="g10" /><g + id="g12" /><g + id="g14" /><g + id="g16" /><g + id="g18" /><g + id="g20" /><g + id="g22" /><g + id="g24" /><g + id="g26" /><g + id="g28" /><g + id="g30" /><g + id="g32" /><g + id="g34" /><g + id="g36" /><g + id="g38" /><path + d="m 9.1897907,10.114301 c -0.4838401,0 -0.9011011,0.341924 -0.9962772,0.815883 L 6.1640606,21.085639 c -0.1100564,0.55031 0.2460855,1.083011 0.7953819,1.193069 0.5562003,0.113982 1.0840209,-0.246085 1.1930722,-0.79538 l 0.6518824,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.6149825,2.287746 h 0.4345928 l 0.811779,4.063002 H 8.9929943 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#c1272d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/pe_colored/pe_colored.qrc b/application/resources/pe_colored/pe_colored.qrc index 9a7a89cc..fbaaf9e4 100644 --- a/application/resources/pe_colored/pe_colored.qrc +++ b/application/resources/pe_colored/pe_colored.qrc @@ -14,6 +14,7 @@ <file>scalable/instance-settings.svg</file> <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> + <file>scalable/language.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/application/resources/pe_colored/scalable/language.svg b/application/resources/pe_colored/scalable/language.svg new file mode 100644 index 00000000..80c1dcad --- /dev/null +++ b/application/resources/pe_colored/scalable/language.svg @@ -0,0 +1,44 @@ +<?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" + xml:space="preserve" + enable-background="new 0 0 32 32" + viewBox="0 0 32 32" + y="0px" + x="0px" + id="Calque_1" + version="1.1"><metadata + id="metadata19"><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></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs17" /> +<path + id="path2" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + fill="#F2F2F2" + clip-rule="evenodd" + fill-rule="evenodd" /> + +<g + id="g12"> + <path + id="path6" + d="M6,28h20c1.1,0,2-0.9,2-2V9V6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v3v17C4,27.1,4.9,28,6,28z" + fill="none" /> + <path + id="path8" + d="M26,0H6C2.7,0,0,2.7,0,6v3h4V6c0-1.1,0.9-2,2-2h20c1.1,0,2,0.9,2,2v3h4V6C32,2.7,29.3,0,26,0z" + fill="#39B54A" /> + <path + id="path10" + d="M28,26c0,1.1-0.9,2-2,2H6c-1.1,0-2-0.9-2-2V9H0v17c0,3.3,2.7,6,6,6h20c3.3,0,6-2.7,6-6V9h-4V26z" + fill="#8C6239" /> +</g> +<path + d="m 9.1897919,10.114302 c -0.483841,0 -0.901102,0.341924 -0.996278,0.815883 l -2.029453,10.155454 c -0.110056,0.55031 0.246086,1.083011 0.795382,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247121 H 11.67433 l 0.647785,3.247121 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285214,10.930185 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345931 l 0.811779,4.063002 H 8.9929949 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578073 -1.016777,1.0208753 v 1.016774 h -3.046227 c -0.561204,0 -1.012672,0.457809 -1.012672,1.020876 0,0.563065 0.451466,1.016776 1.012672,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873653 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628601 -1.591082,-1.189495 -2.336946,-1.873653 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208753 -1.016775,-1.0208753 z m -1.668658,4.0753013 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#c1272d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/pe_dark/pe_dark.qrc b/application/resources/pe_dark/pe_dark.qrc index 0a49b0bb..a57b6a14 100644 --- a/application/resources/pe_dark/pe_dark.qrc +++ b/application/resources/pe_dark/pe_dark.qrc @@ -14,6 +14,7 @@ <file>scalable/instance-settings.svg</file> <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> + <file>scalable/language.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/application/resources/pe_dark/scalable/language.svg b/application/resources/pe_dark/scalable/language.svg new file mode 100644 index 00000000..1a9b4c5c --- /dev/null +++ b/application/resources/pe_dark/scalable/language.svg @@ -0,0 +1,45 @@ +<?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" + version="1.1" + id="Calque_1" + x="0px" + y="0px" + viewBox="0 0 32 32" + enable-background="new 0 0 32 32" + xml:space="preserve"><metadata + id="metadata45"><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></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs43" /><path + fill-rule="evenodd" + clip-rule="evenodd" + d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6v20 C32,29.3,29.3,32,26,32z" + id="path2" /><path + fill-rule="evenodd" + clip-rule="evenodd" + fill="#F2F2F2" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + id="path4" /><g + id="g10" /><g + id="g12" /><g + id="g14" /><g + id="g16" /><g + id="g18" /><g + id="g20" /><g + id="g22" /><g + id="g24" /><g + id="g26" /><g + id="g28" /><g + id="g30" /><g + id="g32" /><g + id="g34" /><g + id="g36" /><g + id="g38" /><path + d="m 9.1897911,10.114301 c -0.48384,0 -0.901101,0.341924 -0.996278,0.815883 l -2.029452,10.155455 c -0.110057,0.55031 0.246085,1.083011 0.795381,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345929 l 0.811779,4.063002 H 8.9929941 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/resources/pe_light/pe_light.qrc b/application/resources/pe_light/pe_light.qrc index 98c29e9e..6d77c835 100644 --- a/application/resources/pe_light/pe_light.qrc +++ b/application/resources/pe_light/pe_light.qrc @@ -14,6 +14,7 @@ <file>scalable/instance-settings.svg</file> <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> + <file>scalable/language.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/application/resources/pe_light/scalable/language.svg b/application/resources/pe_light/scalable/language.svg new file mode 100644 index 00000000..57d5e3de --- /dev/null +++ b/application/resources/pe_light/scalable/language.svg @@ -0,0 +1,80 @@ +<?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" + xml:space="preserve" + enable-background="new 0 0 32 32" + viewBox="0 0 32 32" + y="0px" + x="0px" + id="Calque_1" + version="1.1"><metadata + id="metadata43"><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></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs41" /> +<path + id="path2" + d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" + fill="#4D4D4D" + clip-rule="evenodd" + fill-rule="evenodd" /> + +<path + id="path6" + d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20c1.1,0,2-0.9,2-2V6z" + fill="#F2F2F2" + clip-rule="evenodd" + fill-rule="evenodd" /> +<g + id="g8"> +</g> +<g + id="g10"> +</g> +<g + id="g12"> +</g> +<g + id="g14"> +</g> +<g + id="g16"> +</g> +<g + id="g18"> +</g> +<g + id="g20"> +</g> +<g + id="g22"> +</g> +<g + id="g24"> +</g> +<g + id="g26"> +</g> +<g + id="g28"> +</g> +<g + id="g30"> +</g> +<g + id="g32"> +</g> +<g + id="g34"> +</g> +<g + id="g36"> +</g> +<path + d="m 9.1897911,10.114301 c -0.48384,0 -0.901101,0.341924 -0.996278,0.815883 l -2.029452,10.155455 c -0.110057,0.55031 0.246085,1.083011 0.795381,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345929 l 0.811779,4.063002 H 8.9929941 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z" + style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" + id="rect977" /></svg>
\ No newline at end of file diff --git a/application/setupwizard/LanguageWizardPage.cpp b/application/setupwizard/LanguageWizardPage.cpp index dbbe5e7f..ca93c6f5 100644 --- a/application/setupwizard/LanguageWizardPage.cpp +++ b/application/setupwizard/LanguageWizardPage.cpp @@ -2,25 +2,19 @@ #include <MultiMC.h> #include <translations/TranslationsModel.h> +#include "widgets/LanguageSelectionWidget.h" #include <QVBoxLayout> -#include <QListView> LanguageWizardPage::LanguageWizardPage(QWidget *parent) : BaseWizardPage(parent) { setObjectName(QStringLiteral("languagePage")); - verticalLayout = new QVBoxLayout(this); - verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - languageView = new QListView(this); - languageView->setObjectName(QStringLiteral("languageView")); - verticalLayout->addWidget(languageView); - retranslate(); + auto layout = new QVBoxLayout(this); + mainWidget = new LanguageSelectionWidget(this); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(mainWidget); - auto translations = MMC->translations(); - auto index = translations->selectedIndex(); - languageView->setModel(translations.get()); - languageView->setCurrentIndex(index); - connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageWizardPage::languageRowChanged); + retranslate(); } LanguageWizardPage::~LanguageWizardPage() @@ -41,8 +35,7 @@ void LanguageWizardPage::refresh() bool LanguageWizardPage::validatePage() { auto settings = MMC->settings(); - auto translations = MMC->translations(); - QString key = translations->data(languageView->currentIndex(), Qt::UserRole).toString(); + QString key = mainWidget->getSelectedLanguageKey(); settings->set("Language", key); return true; } @@ -51,16 +44,5 @@ void LanguageWizardPage::retranslate() { setTitle(tr("Language")); setSubTitle(tr("Select the language to use in MultiMC")); -} - -void LanguageWizardPage::languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - if (current == previous) - { - return; - } - auto translations = MMC->translations(); - QString key = translations->data(current, Qt::UserRole).toString(); - translations->selectLanguage(key); - translations->updateLanguage(key); + mainWidget->retranslate(); } diff --git a/application/setupwizard/LanguageWizardPage.h b/application/setupwizard/LanguageWizardPage.h index 866f81c3..45a0e5c0 100644 --- a/application/setupwizard/LanguageWizardPage.h +++ b/application/setupwizard/LanguageWizardPage.h @@ -2,8 +2,7 @@ #include "BaseWizardPage.h" -class QVBoxLayout; -class QListView; +class LanguageSelectionWidget; class LanguageWizardPage : public BaseWizardPage { @@ -22,10 +21,6 @@ public: protected: void retranslate() override; -protected slots: - void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); - private: - QVBoxLayout *verticalLayout = nullptr; - QListView *languageView = nullptr; + LanguageSelectionWidget *mainWidget = nullptr; }; diff --git a/application/setupwizard/SetupWizard.h b/application/setupwizard/SetupWizard.h index aa3fb32c..9b8adb4d 100644 --- a/application/setupwizard/SetupWizard.h +++ b/application/setupwizard/SetupWizard.h @@ -1,4 +1,4 @@ -/* Copyright 2017-2018 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.cpp b/application/widgets/CustomCommands.cpp index 9e7673fd..24bdc07d 100644 --- a/application/widgets/CustomCommands.cpp +++ b/application/widgets/CustomCommands.cpp @@ -3,6 +3,7 @@ CustomCommands::~CustomCommands() { + delete ui; } CustomCommands::CustomCommands(QWidget* parent): diff --git a/application/widgets/CustomCommands.h b/application/widgets/CustomCommands.h index 07041479..8db991fa 100644 --- a/application/widgets/CustomCommands.h +++ b/application/widgets/CustomCommands.h @@ -1,4 +1,4 @@ -/* Copyright 2018-2018 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. @@ -28,7 +28,7 @@ class CustomCommands : public QWidget public: explicit CustomCommands(QWidget *parent = 0); - ~CustomCommands(); + virtual ~CustomCommands(); void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit); bool checked() const; diff --git a/application/widgets/CustomCommands.ui b/application/widgets/CustomCommands.ui index d3bc86b8..25b2681b 100644 --- a/application/widgets/CustomCommands.ui +++ b/application/widgets/CustomCommands.ui @@ -10,10 +10,19 @@ <height>646</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout"> + <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="QGroupBox" name="customCommandsGroupBox"> <property name="enabled"> diff --git a/application/widgets/DropLabel.cpp b/application/widgets/DropLabel.cpp new file mode 100644 index 00000000..a900e57c --- /dev/null +++ b/application/widgets/DropLabel.cpp @@ -0,0 +1,41 @@ +#include "DropLabel.h" + +#include <QMimeData> +#include <QDropEvent> + +DropLabel::DropLabel(QWidget *parent) : QLabel(parent) +{ + setAcceptDrops(true); +} + +void DropLabel::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); +} + +void DropLabel::dragMoveEvent(QDragMoveEvent *event) +{ + event->acceptProposedAction(); +} + +void DropLabel::dragLeaveEvent(QDragLeaveEvent *event) +{ + event->accept(); +} + +void DropLabel::dropEvent(QDropEvent *event) +{ + const QMimeData *mimeData = event->mimeData(); + + if (!mimeData) + { + return; + } + + if (mimeData->hasUrls()) { + auto urls = mimeData->urls(); + emit droppedURLs(urls); + } + + event->acceptProposedAction(); +} diff --git a/application/widgets/DropLabel.h b/application/widgets/DropLabel.h new file mode 100644 index 00000000..c5ca0bcc --- /dev/null +++ b/application/widgets/DropLabel.h @@ -0,0 +1,20 @@ +#pragma once + +#include <QLabel> + +class DropLabel : public QLabel +{ + Q_OBJECT + +public: + explicit DropLabel(QWidget *parent = nullptr); + +signals: + void droppedURLs(QList<QUrl> urls); + +protected: + void dropEvent(QDropEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; +}; diff --git a/application/widgets/InstanceCardWidget.ui b/application/widgets/InstanceCardWidget.ui index 3ea3e85f..6eeeb076 100644 --- a/application/widgets/InstanceCardWidget.ui +++ b/application/widgets/InstanceCardWidget.ui @@ -10,9 +10,6 @@ <height>118</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0" rowspan="2"> <widget class="QToolButton" name="iconButton"> 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 ae79fd99..ab2d3278 100644 --- a/application/widgets/LabeledToolButton.cpp +++ b/application/widgets/LabeledToolButton.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 e7ba5924..51f99e9b 100644 --- a/application/widgets/LabeledToolButton.h +++ b/application/widgets/LabeledToolButton.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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.cpp b/application/widgets/LanguageSelectionWidget.cpp new file mode 100644 index 00000000..8d23bdc5 --- /dev/null +++ b/application/widgets/LanguageSelectionWidget.cpp @@ -0,0 +1,66 @@ +#include "LanguageSelectionWidget.h" + +#include <QVBoxLayout> +#include <QTreeView> +#include <QHeaderView> +#include <QLabel> +#include "MultiMC.h" +#include "translations/TranslationsModel.h" + +LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : + QWidget(parent) +{ + verticalLayout = new QVBoxLayout(this); + verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + languageView = new QTreeView(this); + languageView->setObjectName(QStringLiteral("languageView")); + languageView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + languageView->setAlternatingRowColors(true); + languageView->setRootIsDecorated(false); + languageView->setItemsExpandable(false); + languageView->setWordWrap(true); + languageView->header()->setCascadingSectionResizes(true); + languageView->header()->setStretchLastSection(false); + verticalLayout->addWidget(languageView); + helpUsLabel = new QLabel(this); + helpUsLabel->setObjectName(QStringLiteral("helpUsLabel")); + helpUsLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + helpUsLabel->setOpenExternalLinks(true); + helpUsLabel->setWordWrap(true); + verticalLayout->addWidget(helpUsLabel); + + auto translations = MMC->translations(); + auto index = translations->selectedIndex(); + languageView->setModel(translations.get()); + languageView->setCurrentIndex(index); + languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch); + connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged); + verticalLayout->setContentsMargins(0,0,0,0); +} + +QString LanguageSelectionWidget::getSelectedLanguageKey() const +{ + auto translations = MMC->translations(); + return translations->data(languageView->currentIndex(), Qt::UserRole).toString(); +} + +void LanguageSelectionWidget::retranslate() +{ + QString text = tr("Don't see your language or the quality is poor?<br/><a href=\"%1\">Help us with translations!</a>") + .arg("https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC"); + helpUsLabel->setText(text); + +} + +void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous) +{ + if (current == previous) + { + return; + } + auto translations = MMC->translations(); + QString key = translations->data(current, Qt::UserRole).toString(); + translations->selectLanguage(key); + translations->updateLanguage(key); +} diff --git a/application/widgets/LanguageSelectionWidget.h b/application/widgets/LanguageSelectionWidget.h new file mode 100644 index 00000000..e65936db --- /dev/null +++ b/application/widgets/LanguageSelectionWidget.h @@ -0,0 +1,41 @@ +/* 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> + +class QVBoxLayout; +class QTreeView; +class QLabel; + +class LanguageSelectionWidget: public QWidget +{ + Q_OBJECT +public: + explicit LanguageSelectionWidget(QWidget *parent = 0); + virtual ~LanguageSelectionWidget() { }; + + QString getSelectedLanguageKey() const; + void retranslate(); + +protected slots: + void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + +private: + QVBoxLayout *verticalLayout = nullptr; + QTreeView *languageView = nullptr; + QLabel *helpUsLabel = nullptr; +}; diff --git a/application/widgets/MCModInfoFrame.cpp b/application/widgets/MCModInfoFrame.cpp index 3bd2af1e..5b1f6230 100644 --- a/application/widgets/MCModInfoFrame.cpp +++ b/application/widgets/MCModInfoFrame.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -40,7 +40,7 @@ void MCModInfoFrame::updateWithMod(Mod &m) else text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>"; if (!m.authors().isEmpty()) - text += " by " + m.authors(); + text += " by " + m.authors().join(", "); setModText(text); @@ -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 5d27449a..0b7ef537 100644 --- a/application/widgets/MCModInfoFrame.h +++ b/application/widgets/MCModInfoFrame.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -16,7 +16,7 @@ #pragma once #include <QFrame> -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" namespace Ui { diff --git a/application/widgets/ModListView.cpp b/application/widgets/ModListView.cpp index 6f084ba7..c8ccd292 100644 --- a/application/widgets/ModListView.cpp +++ b/application/widgets/ModListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 ee88d634..881e092f 100644 --- a/application/widgets/ModListView.h +++ b/application/widgets/ModListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -16,8 +16,6 @@ #pragma once #include <QTreeView> -class Mod; - class ModListView: public QTreeView { Q_OBJECT diff --git a/application/widgets/PageContainer.cpp b/application/widgets/PageContainer.cpp index 7e681592..05a5e6b4 100644 --- a/application/widgets/PageContainer.cpp +++ b/application/widgets/PageContainer.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -140,15 +140,13 @@ void PageContainer::createUI() QHBoxLayout *headerHLayout = new QHBoxLayout; const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); - headerHLayout->addSpacerItem( - new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); headerHLayout->addWidget(m_header); - headerHLayout->addSpacerItem( - new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + headerHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); headerHLayout->addWidget(m_iconHeader); const int rightMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutRightMargin); - headerHLayout->addSpacerItem( - new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + headerHLayout->setContentsMargins(0, 6, 0, 0); m_pageStack->setMargin(0); m_pageStack->addWidget(new QWidget(this)); @@ -158,6 +156,7 @@ void PageContainer::createUI() m_layout->addWidget(m_pageList, 0, 0, 2, 1); m_layout->addLayout(m_pageStack, 1, 1, 1, 1); m_layout->setColumnStretch(1, 4); + m_layout->setContentsMargins(0,0,0,6); setLayout(m_layout); } diff --git a/application/widgets/PageContainer.h b/application/widgets/PageContainer.h index 9aa1ebb0..976d34e9 100644 --- a/application/widgets/PageContainer.h +++ b/application/widgets/PageContainer.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 8f1c52ea..da1a66f4 100644 --- a/application/widgets/PageContainer_p.h +++ b/application/widgets/PageContainer_p.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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 a7016c0c..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(); @@ -125,7 +125,7 @@ void ServerStatus::addStatus(QString key, QString name) void ServerStatus::clicked() { - DesktopServices::openUrl(QUrl("https://help.mojang.com/")); + DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/Mojang-Services-Status")); } void ServerStatus::setStatus(QString key, int value) diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp index 215905f5..8424fedd 100644 --- a/application/widgets/VersionListView.cpp +++ b/application/widgets/VersionListView.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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,9 +29,8 @@ VersionListView::VersionListView(QWidget *parent) void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end) { - if(!m_itemCount) - viewport()->update(); m_itemCount += end-start+1; + updateEmptyViewPort(); QTreeView::rowsInserted(parent, start, end); } @@ -39,16 +38,14 @@ void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { m_itemCount -= end-start+1; - if(!m_itemCount) - viewport()->update(); + updateEmptyViewPort(); QTreeView::rowsInserted(parent, start, end); } void VersionListView::setModel(QAbstractItemModel *model) { m_itemCount = model->rowCount(); - if(!m_itemCount) - viewport()->update(); + updateEmptyViewPort(); QTreeView::setModel(model); } @@ -58,7 +55,10 @@ void VersionListView::reset() { m_itemCount = model()->rowCount(); } - viewport()->update(); + else { + m_itemCount = 0; + } + updateEmptyViewPort(); QTreeView::reset(); } @@ -82,6 +82,10 @@ void VersionListView::setEmptyMode(VersionListView::EmptyMode mode) void VersionListView::updateEmptyViewPort() { +#ifndef QT_NO_ACCESSIBILITY + setAccessibleDescription(currentEmptyString()); +#endif /* !QT_NO_ACCESSIBILITY */ + if(!m_itemCount) { viewport()->update(); @@ -100,77 +104,60 @@ void VersionListView::paintEvent(QPaintEvent *event) } } -void VersionListView::paintInfoLabel(QPaintEvent *event) +QString VersionListView::currentEmptyString() const { - QString emptyString; + if(m_itemCount) { + return QString(); + } switch(m_emptyMode) { + default: case VersionListView::Empty: - return; + return QString(); case VersionListView::String: - emptyString = m_emptyString; - break; + return m_emptyString; case VersionListView::ErrorString: - emptyString = m_emptyErrorString; - break; + return m_emptyErrorString; } +} + + +void VersionListView::paintInfoLabel(QPaintEvent *event) const +{ + QString emptyString = currentEmptyString(); + //calculate the rect for the overlay QPainter painter(viewport()); painter.setRenderHint(QPainter::Antialiasing, true); QFont font("sans", 20); font.setBold(true); - + QRect bounds = viewport()->geometry(); bounds.moveTop(0); - QTextLayout layout(emptyString, font); - qreal height = 0.0; - qreal widthUsed = 0.0; - QStringList lines = viewItemTextLayout(layout, bounds.width() - 20, height, widthUsed); - QRect rect (0,0, widthUsed, height); - rect.setWidth(rect.width()+20); - rect.setHeight(rect.height()+20); - rect.moveCenter(bounds.center()); + auto innerBounds = bounds; + innerBounds.adjust(10, 10, -10, -10); + + QColor background = QApplication::palette().color(QPalette::Foreground); + QColor foreground = QApplication::palette().color(QPalette::Base); + foreground.setAlpha(190); + painter.setFont(font); + auto fontMetrics = painter.fontMetrics(); + auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); + textRect.moveCenter(bounds.center()); + + auto wrapRect = textRect; + wrapRect.adjust(-10, -10, 10, 10); + //check if we are allowed to draw in our area - if (!event->rect().intersects(rect)) { + if (!event->rect().intersects(wrapRect)) { return; } - //draw the letter of the topmost item semitransparent in the middle - QColor background = QApplication::palette().color(QPalette::Foreground); - QColor foreground = QApplication::palette().color(QPalette::Base); - /* - background.setAlpha(128 - scrollFade); - foreground.setAlpha(128 - scrollFade); - */ + painter.setBrush(QBrush(background)); painter.setPen(foreground); - painter.drawRoundedRect(rect, 5.0, 5.0); - foreground.setAlpha(190); + painter.drawRoundedRect(wrapRect, 5.0, 5.0); + painter.setPen(foreground); painter.setFont(font); - painter.drawText(rect, Qt::AlignCenter, lines.join("\n")); - -} - -/* -void ModListView::setModel ( QAbstractItemModel* model ) -{ - QTreeView::setModel ( model ); - auto head = header(); - head->setStretchLastSection(false); - // HACK: this is true for the checkbox column of mod lists - auto string = model->headerData(0,head->orientation()).toString(); - if(!string.size()) - { - head->setSectionResizeMode(0, QHeaderView::ResizeToContents); - head->setSectionResizeMode(1, QHeaderView::Stretch); - for(int i = 2; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - else - { - head->setSectionResizeMode(0, QHeaderView::Stretch); - for(int i = 1; i < head->count(); i++) - head->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } + painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString); } -*/ diff --git a/application/widgets/VersionListView.h b/application/widgets/VersionListView.h index a7195b38..4153b314 100644 --- a/application/widgets/VersionListView.h +++ b/application/widgets/VersionListView.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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. @@ -16,8 +16,6 @@ #pragma once #include <QTreeView> -class Mod; - class VersionListView : public QTreeView { Q_OBJECT @@ -46,8 +44,9 @@ protected slots: virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; private: /* methods */ - void paintInfoLabel(QPaintEvent *event); + void paintInfoLabel(QPaintEvent *event) const; void updateEmptyViewPort(); + QString currentEmptyString() const; private: /* variables */ int m_itemCount = 0; diff --git a/application/widgets/VersionSelectWidget.cpp b/application/widgets/VersionSelectWidget.cpp index 8e7a0953..9925a6b4 100644 --- a/application/widgets/VersionSelectWidget.cpp +++ b/application/widgets/VersionSelectWidget.cpp @@ -41,6 +41,7 @@ VersionSelectWidget::VersionSelectWidget(QWidget* parent) void VersionSelectWidget::setCurrentVersion(const QString& version) { m_currentVersion = version; + m_proxyModel->setCurrentVersion(version); } void VersionSelectWidget::setEmptyString(QString emptyString) diff --git a/application/widgets/VersionSelectWidget.h b/application/widgets/VersionSelectWidget.h index 32b56c60..0a649408 100644 --- a/application/widgets/VersionSelectWidget.h +++ b/application/widgets/VersionSelectWidget.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 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/WideBar.cpp b/application/widgets/WideBar.cpp new file mode 100644 index 00000000..cbd6c617 --- /dev/null +++ b/application/widgets/WideBar.cpp @@ -0,0 +1,116 @@ +#include "WideBar.h" +#include <QToolButton> +#include <QMenu> + +class ActionButton : public QToolButton +{ + Q_OBJECT +public: + ActionButton(QAction * action, QWidget * parent = 0) : QToolButton(parent), m_action(action) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + connect(action, &QAction::changed, this, &ActionButton::actionChanged); + connect(this, &ActionButton::clicked, action, &QAction::trigger); + actionChanged(); + }; +private slots: + void actionChanged() { + setEnabled(m_action->isEnabled()); + setChecked(m_action->isChecked()); + setCheckable(m_action->isCheckable()); + setText(m_action->text()); + setIcon(m_action->icon()); + setToolTip(m_action->toolTip()); + setHidden(!m_action->isVisible()); + setFocusPolicy(Qt::NoFocus); + } +private: + QAction * m_action; +}; + + +WideBar::WideBar(const QString& title, QWidget* parent) : QToolBar(title, parent) +{ + setFloatable(false); + setMovable(false); +} + +WideBar::WideBar(QWidget* parent) : QToolBar(parent) +{ + setFloatable(false); + setMovable(false); +} + +struct WideBar::BarEntry { + enum Type { + None, + Action, + Separator, + Spacer + } type = None; + QAction *qAction = nullptr; + QAction *wideAction = nullptr; +}; + + +WideBar::~WideBar() +{ + for(auto *iter: m_entries) { + delete iter; + } +} + +void WideBar::addAction(QAction* action) +{ + auto entry = new BarEntry(); + entry->qAction = addWidget(new ActionButton(action, this)); + entry->wideAction = action; + entry->type = BarEntry::Action; + m_entries.push_back(entry); +} + +void WideBar::addSeparator() +{ + auto entry = new BarEntry(); + entry->qAction = QToolBar::addSeparator(); + entry->type = BarEntry::Separator; + m_entries.push_back(entry); +} + +void WideBar::insertSpacer(QAction* action) +{ + auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) { + return entry->wideAction == action; + }); + if(iter == m_entries.end()) { + return; + } + QWidget* spacer = new QWidget(); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + auto entry = new BarEntry(); + entry->qAction = insertWidget((*iter)->qAction, spacer); + entry->type = BarEntry::Spacer; + m_entries.insert(iter, entry); +} + +QMenu * WideBar::createContextMenu(QWidget *parent, const QString & title) +{ + QMenu *contextMenu = new QMenu(title, parent); + for(auto & item: m_entries) { + switch(item->type) { + default: + case BarEntry::None: + break; + case BarEntry::Separator: + case BarEntry::Spacer: + contextMenu->addSeparator(); + break; + case BarEntry::Action: + contextMenu->addAction(item->wideAction); + break; + } + } + return contextMenu; +} + +#include "WideBar.moc" diff --git a/application/widgets/WideBar.h b/application/widgets/WideBar.h new file mode 100644 index 00000000..d1b8cbe7 --- /dev/null +++ b/application/widgets/WideBar.h @@ -0,0 +1,26 @@ +#pragma once + +#include <QToolBar> +#include <QAction> +#include <QMap> + +class QMenu; + +class WideBar : public QToolBar +{ + Q_OBJECT + +public: + explicit WideBar(const QString &title, QWidget * parent = nullptr); + explicit WideBar(QWidget * parent = nullptr); + virtual ~WideBar(); + + void addAction(QAction *action); + void addSeparator(); + void insertSpacer(QAction *action); + QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString()); + +private: + struct BarEntry; + QList<BarEntry *> m_entries; +}; |