diff options
Diffstat (limited to 'launcher')
110 files changed, 2810 insertions, 2477 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 45e34026..a9bcebb1 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -433,7 +433,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) } // seach root path if(!foundLoggingRules) { +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + logRulesPath = FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME, logRulesFile); +#else logRulesPath = FS::PathCombine(m_rootPath, logRulesFile); +#endif qDebug() << "Testing" << logRulesPath << "..."; foundLoggingRules = QFile::exists(logRulesPath); } @@ -569,6 +573,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Language m_settings->registerSetting("Language", QString()); + m_settings->registerSetting("UseSystemLocale", false); // Console m_settings->registerSetting("ShowConsole", false); @@ -688,8 +693,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->reset("PastebinCustomAPIBase"); } } - // meta URL - m_settings->registerSetting("MetaURLOverride", ""); + { + // Meta URL + m_settings->registerSetting("MetaURLOverride", ""); + + QUrl metaUrl(m_settings->get("MetaURLOverride").toString()); + + // get rid of invalid meta urls + if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https") + m_settings->reset("MetaURLOverride"); + } m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("QuitAfterGameStop", false); @@ -911,12 +924,7 @@ bool Application::createSetupWizard() } return false; }(); - bool languageRequired = [&]() - { - if (settings()->get("Language").toString().isEmpty()) - return true; - return false; - }(); + bool languageRequired = settings()->get("Language").toString().isEmpty(); bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; bool themeInterventionRequired = settings()->get("ApplicationTheme") == ""; bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired; @@ -1560,7 +1568,7 @@ QString Application::getJarPath(QString jarFile) { QStringList potentialPaths = { #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - FS::PathCombine(m_rootPath, "share/" + BuildConfig.LAUNCHER_APP_BINARY_NAME), + FS::PathCombine(m_rootPath, "share", BuildConfig.LAUNCHER_NAME), #endif FS::PathCombine(m_rootPath, "jars"), FS::PathCombine(applicationDirPath(), "jars"), diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 9bad2a67..7cba97b4 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -377,8 +377,6 @@ set(MINECRAFT_SOURCES minecraft/services/SkinDelete.cpp minecraft/services/SkinDelete.h - mojang/PackageManifest.h - mojang/PackageManifest.cpp minecraft/Agent.h) # the screenshots feature @@ -489,6 +487,9 @@ set(API_SOURCES modplatform/helpers/HashUtils.cpp modplatform/helpers/OverrideUtils.h modplatform/helpers/OverrideUtils.cpp + + modplatform/helpers/ExportToModList.h + modplatform/helpers/ExportToModList.cpp ) set(FTB_SOURCES @@ -516,6 +517,8 @@ set(FLAME_SOURCES modplatform/flame/FlameCheckUpdate.h modplatform/flame/FlameInstanceCreationTask.h modplatform/flame/FlameInstanceCreationTask.cpp + modplatform/flame/FlamePackExportTask.h + modplatform/flame/FlamePackExportTask.cpp ) set(MODRINTH_SOURCES @@ -684,6 +687,7 @@ SET(LAUNCHER_SOURCES VersionProxyModel.h VersionProxyModel.cpp Markdown.h + Markdown.cpp # Super secret! KonamiCode.h @@ -827,8 +831,8 @@ SET(LAUNCHER_SOURCES ui/pages/global/APIPage.h # GUI - platform pages - ui/pages/modplatform/VanillaPage.cpp - ui/pages/modplatform/VanillaPage.h + ui/pages/modplatform/CustomPage.cpp + ui/pages/modplatform/CustomPage.h ui/pages/modplatform/ResourcePage.cpp ui/pages/modplatform/ResourcePage.h @@ -908,8 +912,10 @@ SET(LAUNCHER_SOURCES ui/dialogs/EditAccountDialog.h ui/dialogs/ExportInstanceDialog.cpp ui/dialogs/ExportInstanceDialog.h - ui/dialogs/ExportMrPackDialog.cpp - ui/dialogs/ExportMrPackDialog.h + ui/dialogs/ExportPackDialog.cpp + ui/dialogs/ExportPackDialog.h + ui/dialogs/ExportToModListDialog.cpp + ui/dialogs/ExportToModListDialog.h ui/dialogs/IconPickerDialog.cpp ui/dialogs/IconPickerDialog.h ui/dialogs/ImportResourceDialog.cpp @@ -1034,7 +1040,7 @@ qt_wrap_ui(LAUNCHER_UI ui/pages/instance/ScreenshotsPage.ui ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlPage.ui - ui/pages/modplatform/VanillaPage.ui + ui/pages/modplatform/CustomPage.ui ui/pages/modplatform/ResourcePage.ui ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/legacy_ftb/Page.ui @@ -1056,7 +1062,8 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ProfileSelectDialog.ui ui/dialogs/SkinUploadDialog.ui ui/dialogs/ExportInstanceDialog.ui - ui/dialogs/ExportMrPackDialog.ui + ui/dialogs/ExportPackDialog.ui + ui/dialogs/ExportToModListDialog.ui ui/dialogs/IconPickerDialog.ui ui/dialogs/ImportResourceDialog.ui ui/dialogs/MSALoginDialog.ui diff --git a/launcher/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp index a3b7d505..4c8c64c7 100644 --- a/launcher/FileIgnoreProxy.cpp +++ b/launcher/FileIgnoreProxy.cpp @@ -40,6 +40,7 @@ #include <QFileSystemModel> #include <QSortFilterProxyModel> #include <QStack> +#include <algorithm> #include "FileSystem.h" #include "SeparatorPrefixTree.h" #include "StringUtils.h" @@ -254,3 +255,25 @@ bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& return true; } + +bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +{ + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel()); + + auto fileInfo = fsm->fileInfo(index); + return !ignoreFile(fileInfo); +} + +bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const +{ + auto fileName = fileInfo.fileName(); + auto path = relPath(fileInfo.absoluteFilePath()); + return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) || + m_ignoreFilePaths.covers(path); +} + +bool FileIgnoreProxy::filterFile(const QString& fileName) const +{ + return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName)); +} diff --git a/launcher/FileIgnoreProxy.h b/launcher/FileIgnoreProxy.h index a5a1153d..e01a2651 100644 --- a/launcher/FileIgnoreProxy.h +++ b/launcher/FileIgnoreProxy.h @@ -36,6 +36,7 @@ #pragma once +#include <QFileInfo> #include <QSortFilterProxyModel> #include "SeparatorPrefixTree.h" @@ -63,10 +64,22 @@ class FileIgnoreProxy : public QSortFilterProxyModel { inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; } inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; } + // list of file names that need to be removed completely from model + inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; } + // list of relative paths that need to be removed completely from model + inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; } + + bool filterFile(const QString& fileName) const; + protected: bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const; + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; + + bool ignoreFile(QFileInfo file) const; private: const QString root; SeparatorPrefixTree<'/'> blocked; + QStringList m_ignoreFiles; + SeparatorPrefixTree<'/'> m_ignoreFilePaths; }; diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 835ad925..4538702f 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,6 +36,7 @@ */ #include "FileSystem.h" +#include <QPair> #include "BuildConfig.h" @@ -102,7 +103,7 @@ namespace fs = ghc::filesystem; #include <linux/fs.h> #include <sys/ioctl.h> #include <unistd.h> -#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD) +#elif defined(Q_OS_MACOS) #include <sys/attr.h> #include <sys/clonefile.h> #elif defined(Q_OS_WIN) @@ -246,6 +247,7 @@ bool copy::operator()(const QString& offset, bool dryRun) { using copy_opts = fs::copy_options; m_copied = 0; // reset counter + m_failedPaths.clear(); // NOTE always deep copy on windows. the alternatives are too messy. #if defined Q_OS_WIN32 @@ -277,6 +279,9 @@ bool copy::operator()(const QString& offset, bool dryRun) qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; + m_failedPaths.append(dst_path); + emit copyFailed(relative_dst_path); + return; } m_copied++; emit fileCopied(relative_dst_path); @@ -372,7 +377,7 @@ void create_link::make_link_list(const QString& offset) auto src_path = source_it.next(); auto relative_path = src_dir.relativeFilePath(src_path); - if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){ + if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth) { relative_path = pathTruncate(relative_path, m_max_depth); src_path = src_dir.filePath(relative_path); if (linkedPaths.contains(src_path)) { @@ -663,7 +668,7 @@ QString pathTruncate(const QString& path, int depth) QString trunc = QFileInfo(path).path(); - if (pathDepth(trunc) > depth ) { + if (pathDepth(trunc) > depth) { return pathTruncate(trunc, depth); } @@ -769,10 +774,47 @@ QString getDesktopDir() // Cross-platform Shortcut creation bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon) { + if (destination.isEmpty()) { + destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name)); + } #if defined(Q_OS_MACOS) - destination += ".command"; + // Create the Application + QDir applicationDirectory = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/"; - QFile f(destination); + if (!applicationDirectory.mkpath(".")) { + qWarning() << "Couldn't create application directory"; + return false; + } + + QDir application = applicationDirectory.path() + "/" + name + ".app/"; + + if (application.exists()) { + qWarning() << "Application already exists!"; + return false; + } + + if (!application.mkpath(".")) { + qWarning() << "Couldn't create application"; + return false; + } + + QDir content = application.path() + "/Contents/"; + QDir resources = content.path() + "/Resources/"; + QDir binaryDir = content.path() + "/MacOS/"; + QFile info = content.path() + "/Info.plist"; + + if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) { + qWarning() << "Couldn't create directories within application"; + return false; + } + info.open(QIODevice::WriteOnly | QIODevice::Text); + + QFile(icon).rename(resources.path() + "/Icon.icns"); + + // Create the Command file + QString exec = binaryDir.path() + "/Run.command"; + + QFile f(exec); f.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream stream(&f); @@ -789,8 +831,32 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther); + // Generate the Info.plist + QTextStream infoStream(&info); + infoStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n" + "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" " + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" + "<plist version=\"1.0\">\n" + "<dict>\n" + " <key>CFBundleExecutable</key>\n" + " <string>Run.command</string>\n" // The path to the executable + " <key>CFBundleIconFile</key>\n" + " <string>Icon.icns</string>\n" + " <key>CFBundleName</key>\n" + " <string>" << name << "</string>\n" // Name of the application + " <key>CFBundlePackageType</key>\n" + " <string>APPL</string>\n" + " <key>CFBundleShortVersionString</key>\n" + " <string>1.0</string>\n" + " <key>CFBundleVersion</key>\n" + " <string>1.0</string>\n" + "</dict>\n" + "</plist>"; + return true; #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated + destination += ".desktop"; QFile f(destination); f.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream stream(&f); @@ -974,7 +1040,7 @@ FilesystemType getFilesystemType(const QString& name) { for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) { auto fs_names = iter.value(); - if(fs_names.contains(name.toUpper())) + if (fs_names.contains(name.toUpper())) return iter.key(); } return FilesystemType::UNKNOWN; @@ -1072,6 +1138,7 @@ bool clone::operator()(const QString& offset, bool dryRun) } m_cloned = 0; // reset counter + m_failedClones.clear(); auto src = PathCombine(m_src.absolutePath(), offset); auto dst = PathCombine(m_dst.absolutePath(), offset); @@ -1092,6 +1159,9 @@ bool clone::operator()(const QString& offset, bool dryRun) qDebug() << "Failed to clone files: error" << err.value() << "message" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; qDebug() << "Destination file:" << dst_path; + m_failedClones.append(qMakePair(src_path, dst_path)); + emit cloneFailed(src_path, dst_path); + return; } m_cloned++; emit fileCloned(src_path, dst_path); @@ -1151,7 +1221,7 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec) return false; } -#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD) +#elif defined(Q_OS_MACOS) if (!macos_bsd_clonefile(src_path, dst_path, ec)) { qDebug() << "failed macos_bsd_clonefile:"; @@ -1380,7 +1450,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std return true; } -#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD) +#elif defined(Q_OS_MACOS) bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec) { diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index cb581d0c..f8a82bae 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -43,6 +43,7 @@ #include <system_error> #include <QDir> +#include <QPair> #include <QFlags> #include <QLocalServer> #include <QObject> @@ -112,9 +113,12 @@ class copy : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } int totalCopied() { return m_copied; } + int totalFailed() { return m_failedPaths.length(); } + QStringList failed() { return m_failedPaths; } signals: void fileCopied(const QString& relativeName); + void copyFailed(const QString& relativeName); // TODO: maybe add a "shouldCopy" signal in the future? private: @@ -127,6 +131,7 @@ class copy : public QObject { QDir m_src; QDir m_dst; int m_copied; + QStringList m_failedPaths; }; struct LinkPair { @@ -471,6 +476,9 @@ class clone : public QObject { bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); } int totalCloned() { return m_cloned; } + int totalFailed() { return m_failedClones.length(); } + + QList<QPair<QString, QString>> failed() { return m_failedClones; } signals: void fileCloned(const QString& src, const QString& dst); @@ -485,6 +493,7 @@ class clone : public QObject { QDir m_src; QDir m_dst; int m_cloned; + QList<QPair<QString, QString>> m_failedClones; }; /** diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 8fadf446..2b52cac0 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -187,8 +187,8 @@ void LaunchController::login() { switch(m_accountToUse->accountState()) { case AccountState::Offline: { m_session->wants_online = false; - // NOTE: fallthrough is intentional } + /* fallthrough */ case AccountState::Online: { if(!m_session->wants_online) { // we ask the user for a player name @@ -267,8 +267,8 @@ void LaunchController::login() { // This means some sort of soft error that we can fix with a refresh ... so let's refresh. case AccountState::Unchecked: { m_accountToUse->refresh(); - // NOTE: fallthrough intentional } + /* fallthrough */ case AccountState::Working: { // refresh is in progress, we need to wait for it to finish to proceed. ProgressDialog progDialog(m_parentWidget); diff --git a/launcher/Markdown.cpp b/launcher/Markdown.cpp new file mode 100644 index 00000000..426067bf --- /dev/null +++ b/launcher/Markdown.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Joshua Goins <josh@redstrate.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "Markdown.h" + +QString markdownToHTML(const QString& markdown) +{ + const QByteArray markdownData = markdown.toUtf8(); + char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE); + + QString htmlStr(buffer); + + free(buffer); + + return htmlStr; +}
\ No newline at end of file diff --git a/launcher/Markdown.h b/launcher/Markdown.h index f115dd57..6b261e60 100644 --- a/launcher/Markdown.h +++ b/launcher/Markdown.h @@ -21,14 +21,4 @@ #include <QString> #include <cmark.h> -static QString markdownToHTML(const QString& markdown) -{ - const QByteArray markdownData = markdown.toUtf8(); - char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE); - - QString htmlStr(buffer); - - free(buffer); - - return htmlStr; -}
\ No newline at end of file +QString markdownToHTML(const QString& markdown);
\ No newline at end of file diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index e5c66566..63a43465 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -193,31 +193,21 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const } case Qt::ToolTipRole: { - switch(column) + if(column == Name && hasRecommended) { - case Name: + auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); + if(value.toBool()) { - if(hasRecommended) + return tr("Recommended"); + } else if(hasLatest) { + auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); + if(value.toBool()) { - auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole); - if(value.toBool()) - { - return tr("Recommended"); - } - else if(hasLatest) - { - auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole); - if(value.toBool()) - { - return tr("Latest"); - } - } + return tr("Latest"); } } - default: - { - return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); - } + } else { + return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole); } } case Qt::DecorationRole: diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index 242aad9f..4dccccca 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -45,10 +45,10 @@ QVariant Index::data(const QModelIndex &index, int role) const switch (role) { case Qt::DisplayRole: - switch (index.column()) - { - case 0: return list->humanReadable(); - default: break; + if (index.column() == 0) { + return list->humanReadable(); + } else { + break; } case UidRole: return list->uid(); case NameRole: return list->name(); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index aab930de..4867cc7a 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -1112,36 +1112,27 @@ JavaVersion MinecraftInstance::getJavaVersion() std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() { - if (!m_loader_mod_list) - { + if (!m_loader_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed)); - m_loader_mod_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); } return m_loader_mod_list; } std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() { - if (!m_core_mod_list) - { + if (!m_core_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed)); - m_core_mod_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); } return m_core_mod_list; } std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList() { - if (!m_nil_mod_list) - { + if (!m_nil_mod_list) { bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false)); - m_nil_mod_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction); } return m_nil_mod_list; } diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index aff05dbc..e8fd2157 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -65,7 +65,8 @@ static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ {"net.minecraftforge", ResourceAPI::Forge}, {"net.fabricmc.fabric-loader", ResourceAPI::Fabric}, - {"org.quiltmc.quilt-loader", ResourceAPI::Quilt} + {"org.quiltmc.quilt-loader", ResourceAPI::Quilt}, + {"com.mumfrey.liteloader", ResourceAPI::LiteLoader} }; PackProfile::PackProfile(MinecraftInstance * instance) diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index c2794147..d6f42b75 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -328,6 +328,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const case AccountState::Gone: { return tr("Gone", "Account status"); } + default: { + return tr("Unknown", "Account status"); + } } } @@ -354,11 +357,12 @@ QVariant AccountList::data(const QModelIndex &index, int role) const return QVariant::fromValue(account); case Qt::CheckStateRole: - switch (index.column()) - { - case ProfileNameColumn: - return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked; + if (index.column() == ProfileNameColumn) { + return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked; + } else { + return QVariant(); } + default: return QVariant(); diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp index 29978411..d3e7ccdd 100644 --- a/launcher/minecraft/auth/Yggdrasil.cpp +++ b/launcher/minecraft/auth/Yggdrasil.cpp @@ -273,6 +273,7 @@ void Yggdrasil::processReply() { AccountTaskState::STATE_FAILED_GONE, tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.") ); + return; } default: changeState( diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index ca75cd2a..c5754638 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -74,6 +74,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con auto res = Resource::compare(other, type); if (res.first != 0) return res; + break; } case SortType::PACK_FORMAT: { auto this_ver = packFormat(); @@ -83,6 +84,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con return { 1, type == SortType::PACK_FORMAT }; if (this_ver < other_ver) return { -1, type == SortType::PACK_FORMAT }; + break; } } return { 0, false }; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index e613ddeb..880dacb1 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -91,6 +91,7 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const auto res = Resource::compare(other, type); if (res.first != 0) return res; + break; } case SortType::VERSION: { auto this_ver = Version(version()); @@ -99,11 +100,13 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const return { 1, type == SortType::VERSION }; if (this_ver < other_ver) return { -1, type == SortType::VERSION }; + break; } case SortType::PROVIDER: { auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive); if (compare_result != 0) return { compare_result, type == SortType::PROVIDER }; + break; } } return { 0, false }; @@ -123,7 +126,7 @@ bool Mod::applyFilter(QRegularExpression filter) const return Resource::applyFilter(filter); } -auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool +auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool { if (!preserve_metadata) { qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); @@ -136,7 +139,7 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool } } - return Resource::destroy(); + return Resource::destroy(attempt_trash); } auto Mod::details() const -> const ModDetails& @@ -166,6 +169,13 @@ auto Mod::homeurl() const -> QString return details().homeurl; } +auto Mod::metaurl() const -> QString +{ + if (metadata() == nullptr) + return homeurl(); + return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); +} + auto Mod::description() const -> QString { return details().description; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index d4e419f4..b67bd465 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -70,6 +70,7 @@ public: auto provider() const -> std::optional<QString>; auto licenses() const -> const QList<ModLicense>&; auto issueTracker() const -> QString; + auto metaurl() const -> QString; /** Get the intneral path to the mod's icon file*/ QString iconPath() const { return m_local_details.icon_file; }; @@ -92,7 +93,7 @@ public: [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; // Delete all the files of this mod - auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool; + auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; void finishResolvingWithDetails(ModDetails&& details); diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index af98d834..51383edf 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -199,10 +199,10 @@ Task* ModFolderModel::createParseTask(Resource& resource) bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) { - for(auto mod : allMods()){ - if(mod->fileinfo().fileName() == filename){ + for(auto mod : allMods()) { + if(mod->fileinfo().fileName() == filename) { auto index_dir = indexDir(); - mod->destroy(index_dir, preserve_metadata); + mod->destroy(index_dir, preserve_metadata, false); update(); @@ -215,16 +215,11 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat bool ModFolderModel::deleteMods(const QModelIndexList& indexes) { - if(!m_can_interact) { - return false; - } - - if(indexes.isEmpty()) + if (indexes.isEmpty()) return true; - for (auto i: indexes) - { - if(i.column() != 0) { + for (auto i : indexes) { + if (i.column() != 0) { continue; } auto m = at(i.row()); diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index a0b8a4bb..098a617f 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -71,6 +71,7 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con return { 1, type == SortType::ENABLED }; if (!enabled() && other.enabled()) return { -1, type == SortType::ENABLED }; + break; case SortType::NAME: { QString this_name{ name() }; QString other_name{ other.name() }; @@ -81,12 +82,14 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive); if (compare_result != 0) return { compare_result, type == SortType::NAME }; + break; } case SortType::DATE: if (dateTimeChanged() > other.dateTimeChanged()) return { 1, type == SortType::DATE }; if (dateTimeChanged() < other.dateTimeChanged()) return { -1, type == SortType::DATE }; + break; } return { 0, false }; @@ -145,14 +148,10 @@ bool Resource::enable(EnableAction action) return true; } -bool Resource::destroy() +bool Resource::destroy(bool attemptTrash) { m_type = ResourceType::UNKNOWN; - - if (FS::trash(m_file_info.filePath())) - return true; - - return FS::deletePath(m_file_info.filePath()); + return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); } bool Resource::isSymLinkUnder(const QString& instPath) const diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index a5e9ae91..94f3160c 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -92,7 +92,7 @@ class Resource : public QObject { } // Delete all files of this resource. - bool destroy(); + bool destroy(bool attemptTrash = true); [[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 7700fd36..39a61067 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -1,14 +1,15 @@ #include "ResourceFolderModel.h" +#include <QMessageBox> #include <QCoreApplication> #include <QDebug> #include <QFileInfo> #include <QIcon> +#include <QMenu> #include <QMimeData> #include <QStyle> #include <QThreadPool> #include <QUrl> -#include <QMenu> #include "Application.h" #include "FileSystem.h" @@ -18,6 +19,7 @@ #include "settings/Setting.h" #include "tasks/Task.h" +#include "ui/dialogs/CustomMessageBox.h" ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir) : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this) @@ -77,10 +79,6 @@ bool ResourceFolderModel::stopWatching(const QStringList paths) bool ResourceFolderModel::installResource(QString original_path) { - if (!m_can_interact) { - return false; - } - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName original_path = FS::NormalizePath(original_path); QFileInfo file_info(original_path); @@ -159,7 +157,7 @@ bool ResourceFolderModel::uninstallResource(QString file_name) { for (auto& resource : m_resources) { if (resource->fileinfo().fileName() == file_name) { - auto res = resource->destroy(); + auto res = resource->destroy(false); update(); @@ -171,9 +169,6 @@ bool ResourceFolderModel::uninstallResource(QString file_name) bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) { - if (!m_can_interact) - return false; - if (indexes.isEmpty()) return true; @@ -192,11 +187,8 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) return true; } -bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action) +bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) { - if (!m_can_interact) - return false; - if (indexes.isEmpty()) return true; @@ -249,15 +241,18 @@ bool ResourceFolderModel::update() connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection); connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); - connect(m_current_update_task.get(), &Task::finished, this, [=] { - m_current_update_task.reset(); - if (m_scheduled_update) { - m_scheduled_update = false; - update(); - } else { - emit updateFinished(); - } - }, Qt::ConnectionType::QueuedConnection); + connect( + m_current_update_task.get(), &Task::finished, this, + [=] { + m_current_update_task.reset(); + if (m_scheduled_update) { + m_scheduled_update = false; + update(); + } else { + emit updateFinished(); + } + }, + Qt::ConnectionType::QueuedConnection); QThreadPool::globalInstance()->start(m_current_update_task.get()); @@ -347,15 +342,9 @@ Qt::DropActions ResourceFolderModel::supportedDropActions() const Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const { Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - auto flags = defaultFlags; - if (!m_can_interact) { - flags &= ~Qt::ItemIsDropEnabled; - } else { - flags |= Qt::ItemIsDropEnabled; - if (index.isValid()) { - flags |= Qt::ItemIsUserCheckable; - } - } + auto flags = defaultFlags | Qt::ItemIsDropEnabled; + if (index.isValid()) + flags |= Qt::ItemIsUserCheckable; return flags; } @@ -428,16 +417,17 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const if (column == NAME_COLUMN) { if (at(row).isSymLinkUnder(instDirPath())) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." - "\nCanonical Path: %1") - .arg(at(row).fileinfo().canonicalFilePath());; + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." + "\nCanonical Path: %1") + .arg(at(row).fileinfo().canonicalFilePath()); + ; } if (at(row).isMoreThanOneHardLink()) { return m_resources[row]->internal_id() + - tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); + tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original."); } } - + return m_resources[row]->internal_id(); case Qt::DecorationRole: { if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink())) @@ -463,8 +453,20 @@ bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& valu if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) return false; - if (role == Qt::CheckStateRole) + if (role == Qt::CheckStateRole) { + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = + CustomMessageBox::selectable(nullptr, "Confirm toggle", + "If you enable/disable this resource while the game is running it may crash your game.\n" + "Are you sure you want to do this?", + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return false; + } return setResourceEnabled({ index }, EnableAction::TOGGLE); + } return false; } @@ -583,16 +585,6 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const return m_column_sort_keys.at(column); } -void ResourceFolderModel::enableInteraction(bool enabled) -{ - if (m_can_interact == enabled) - return; - - m_can_interact = enabled; - if (size()) - emit dataChanged(index(0), index(size() - 1)); -} - /* Standard Proxy Model for createFilterProxyModel */ [[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { @@ -628,6 +620,7 @@ void ResourceFolderModel::enableInteraction(bool enabled) return (compare_result.first > 0); } -QString ResourceFolderModel::instDirPath() const { +QString ResourceFolderModel::instDirPath() const +{ return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index eb1d7c4f..454b84c3 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -14,8 +14,8 @@ #include "BaseInstance.h" -#include "tasks/Task.h" #include "tasks/ConcurrentTask.h" +#include "tasks/Task.h" class QSortFilterProxyModel; @@ -141,10 +141,6 @@ class ResourceFolderModel : public QAbstractListModel { QString instDirPath() const; - public slots: - void enableInteraction(bool enabled); - void disableInteraction(bool disabled) { enableInteraction(!disabled); } - signals: void updateFinished(); @@ -193,7 +189,11 @@ class ResourceFolderModel : public QAbstractListModel { * if the resource is complex and has more stuff to parse. */ virtual void onParseSucceeded(int ticket, QString resource_id); - virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); } + virtual void onParseFailed(int ticket, QString resource_id) + { + Q_UNUSED(ticket); + Q_UNUSED(resource_id); + } protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. @@ -203,8 +203,6 @@ class ResourceFolderModel : public QAbstractListModel { QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")}; QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents }; - bool m_can_interact = true; - QDir m_dir; BaseInstance* m_instance; QFileSystemWatcher m_watcher; diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index e06c1ac1..6d5978d4 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -102,6 +102,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type) auto res = Resource::compare(other, type); if (res.first != 0) return res; + break; } case SortType::PACK_FORMAT: { auto this_ver = packFormat(); @@ -111,6 +112,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type) return { 1, type == SortType::PACK_FORMAT }; if (this_ver < other_ver) return { -1, type == SortType::PACK_FORMAT }; + break; } } return { 0, false }; diff --git a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp index 4d760df2..0894049c 100644 --- a/launcher/minecraft/mod/tasks/LocalResourceParse.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourceParse.cpp @@ -44,7 +44,11 @@ static const QMap<PackedResourceType, QString> s_packed_type_names = { namespace ResourceUtils { PackedResourceType identify(QFileInfo file){ if (file.exists() && file.isFile()) { - if (ResourcePackUtils::validate(file)) { + if (ModUtils::validate(file)) { + // mods can contain resource and data packs so they must be tested first + qDebug() << file.fileName() << "is a mod"; + return PackedResourceType::Mod; + } else if (ResourcePackUtils::validate(file)) { qDebug() << file.fileName() << "is a resource pack"; return PackedResourceType::ResourcePack; } else if (TexturePackUtils::validate(file)) { @@ -53,9 +57,6 @@ PackedResourceType identify(QFileInfo file){ } else if (DataPackUtils::validate(file)) { qDebug() << file.fileName() << "is a data pack"; return PackedResourceType::DataPack; - } else if (ModUtils::validate(file)) { - qDebug() << file.fileName() << "is a mod"; - return PackedResourceType::Mod; } else if (WorldSaveUtils::validate(file)) { qDebug() << file.fileName() << "is a world save"; return PackedResourceType::WorldSave; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 3677a1dc..ef353c70 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -103,7 +103,7 @@ void ModFolderLoadTask::executeTask() while (iter.hasNext()) { auto mod = iter.next().value(); if (mod->status() == ModStatus::NotInstalled) { - mod->destroy(m_index_dir, false); + mod->destroy(m_index_dir, false, false); iter.remove(); } } diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 93b5ce76..c3eadd06 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -145,7 +145,8 @@ void EnsureMetadataTask::executeTask() connect(project_task.get(), &Task::finished, this, [=] { invalidade_leftover(); project_task->deleteLater(); - m_current_task = nullptr; + if (m_current_task) + m_current_task.reset(); }); m_current_task = project_task; @@ -154,7 +155,8 @@ void EnsureMetadataTask::executeTask() connect(version_task.get(), &Task::finished, [=] { version_task->deleteLater(); - m_current_task = nullptr; + if (m_current_task) + m_current_task.reset(); }); if (m_mods.size() > 1) diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 6a507caf..a1c4d891 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -70,11 +70,17 @@ auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString t } QCryptographicHash hash(algo); - if(!hash.addData(device)) + if (!hash.addData(device)) qCritical() << "Failed to read JAR to create hash!"; Q_ASSERT(hash.result().length() == hash.hashLength(algo)); return { hash.result().toHex() }; } +QString getMetaURL(ResourceProvider provider, QVariant projectID) +{ + return ((provider == ModPlatform::ResourceProvider::FLAME) ? "https://www.curseforge.com/projects/" : "https://modrinth.com/mod/") + + projectID.toString(); +} + } // namespace ModPlatform diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 3b0a03a1..2aa91602 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -128,6 +128,7 @@ struct IndexedPack { return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; }); } }; +QString getMetaURL(ResourceProvider provider, QVariant projectID); struct OverrideDep { QString quilt; @@ -144,6 +145,7 @@ inline auto getOverrideDeps() -> QList<OverrideDep> { "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH }, { "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } }; }; +QString getMetaURL(ResourceProvider provider, QVariant projectID); } // namespace ModPlatform diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index ce7a6055..34bd401d 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -21,6 +21,10 @@ bool Flame::FileResolvingTask::abort() void Flame::FileResolvingTask::executeTask() { + if (m_toProcess.files.isEmpty()) { // no file to resolve so leave it empty and emit success immediately + emitSucceeded(); + return; + } setStatus(tr("Resolving mod IDs...")); setProgress(0, 3); m_dljob.reset(new NetJob("Mod id resolver", m_network)); @@ -128,12 +132,13 @@ void Flame::FileResolvingTask::netJobFinished() m_checkJob->start(); } -void Flame::FileResolvingTask::modrinthCheckFinished() { +void Flame::FileResolvingTask::modrinthCheckFinished() +{ setProgress(2, 3); qDebug() << "Finished with blocked mods : " << blockedProjects.size(); for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) { - auto &out = *it; + auto& out = *it; auto bytes = blockedProjects[out]; if (!out->resolved) { continue; @@ -153,15 +158,13 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { out->resolved = false; } } - //copy to an output list and filter out projects found on modrinth + // copy to an output list and filter out projects found on modrinth auto block = std::make_shared<QList<File*>>(); auto it = blockedProjects.keys(); - std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) { - return !f->resolved; - }); - //Display not found mods early + std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File* f) { return !f->resolved; }); + // Display not found mods early if (!block->empty()) { - //blocked mods found, we need the slug for displaying.... we need another job :D ! + // blocked mods found, we need the slug for displaying.... we need another job :D ! m_slugJob.reset(new NetJob("Slug Job", m_network)); int index = 0; for (auto mod : *block) { @@ -173,8 +176,8 @@ void Flame::FileResolvingTask::modrinthCheckFinished() { QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done auto json = QJsonDocument::fromJson(*output); - auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"), - "websiteUrl"); + auto base = + Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json), "data"), "links"), "websiteUrl"); auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId)); mod->websiteUrl = link; }); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 0a6dc78f..49bc316f 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -23,6 +23,8 @@ class FlameAPI : public NetworkResourceAPI { [[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override; + static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt); } + private: static int getClassId(ModPlatform::ResourceType type) { diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index f003ada9..b57db288 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -470,8 +470,9 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) switch (result.type) { case Flame::File::Type::Folder: { logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. + // fallthrough intentional, we treat these as plain old mods and dump them wherever. } + /* fallthrough */ case Flame::File::Type::SingleFile: case Flame::File::Type::Mod: { if (!result.url.isEmpty()) { @@ -562,6 +563,8 @@ void FlameCreationTask::validateZIPResouces() if (FS::move(localPath, destPath)) { return destPath; } + } else { + qDebug() << "Target folder of" << fileName << "is correct at" << targetFolder; } return localPath; }; @@ -583,6 +586,9 @@ void FlameCreationTask::validateZIPResouces() QString worldPath; switch (type) { + case PackedResourceType::Mod : + validatePath(fileName, targetFolder, "mods"); + break; case PackedResourceType::ResourcePack : validatePath(fileName, targetFolder, "resourcepacks"); break; @@ -592,9 +598,6 @@ void FlameCreationTask::validateZIPResouces() case PackedResourceType::DataPack : validatePath(fileName, targetFolder, "datapacks"); break; - case PackedResourceType::Mod : - validatePath(fileName, targetFolder, "mods"); - break; case PackedResourceType::ShaderPack : // in theroy flame API can't do this but who knows, that *may* change ? // better to handle it if it *does* occure in the future diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp new file mode 100644 index 00000000..ac0da214 --- /dev/null +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "FlamePackExportTask.h" +#include <QJsonArray> +#include <QJsonObject> + +#include <QCryptographicHash> +#include <QFileInfo> +#include <QMessageBox> +#include <QtConcurrentRun> +#include <algorithm> +#include <memory> +#include "Json.h" +#include "MMCZip.h" +#include "minecraft/PackProfile.h" +#include "minecraft/mod/ModFolderModel.h" +#include "modplatform/ModIndex.h" +#include "modplatform/flame/FlameModIndex.h" +#include "modplatform/helpers/HashUtils.h" +#include "tasks/Task.h" + +const QString FlamePackExportTask::TEMPLATE = "<li><a href=\"{url}\">{name}{authors}</a></li>\n"; +const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" }); + +FlamePackExportTask::FlamePackExportTask(const QString& name, + const QString& version, + const QString& author, + InstancePtr instance, + const QString& output, + MMCZip::FilterFunction filter) + : name(name) + , version(version) + , author(author) + , instance(instance) + , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) + , gameRoot(instance->gameRoot()) + , output(output) + , filter(filter) +{} + +void FlamePackExportTask::executeTask() +{ + setStatus(tr("Searching for files...")); + setProgress(0, 5); + collectFiles(); +} + +bool FlamePackExportTask::abort() +{ + if (task != nullptr) { + task->abort(); + task = nullptr; + emitAborted(); + return true; + } + + if (buildZipFuture.isRunning()) { + buildZipFuture.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur + // immediately. + return true; + } + + return false; +} + +void FlamePackExportTask::collectFiles() +{ + setAbortable(false); + QCoreApplication::processEvents(); + + files.clear(); + if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) { + emitFailed(tr("Could not search for files")); + return; + } + + pendingHashes.clear(); + resolvedFiles.clear(); + + if (mcInstance != nullptr) { + mcInstance->loaderModList()->update(); + connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &FlamePackExportTask::collectHashes); + } else + collectHashes(); +} + +void FlamePackExportTask::collectHashes() +{ + setAbortable(true); + setStatus(tr("Finding file hashes...")); + setProgress(1, 5); + auto allMods = mcInstance->loaderModList()->allMods(); + ConcurrentTask::Ptr hashingTask(new ConcurrentTask(this, "MakeHashesTask", 10)); + task.reset(hashingTask); + for (const QFileInfo& file : files) { + const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); + // require sensible file types + if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) { + return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled"); + })) + continue; + + if (relative.startsWith("resourcepacks/") && + (relative.endsWith(".zip") || relative.endsWith(".zip.disabled"))) { // is resourcepack + auto hashTask = Hashing::createFlameHasher(file.absoluteFilePath()); + connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, relative, file](QString hash) { + if (m_state == Task::State::Running) { + pendingHashes.insert(hash, { relative, file.absoluteFilePath(), relative.endsWith(".zip") }); + } + }); + connect(hashTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed); + hashingTask->addTask(hashTask); + continue; + } + + if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; }); + modIter != allMods.end()) { + const Mod* mod = *modIter; + if (!mod || mod->type() == ResourceType::FOLDER) { + continue; + } + if (mod->metadata() && mod->metadata()->provider == ModPlatform::ResourceProvider::FLAME) { + resolvedFiles.insert(mod->fileinfo().absoluteFilePath(), + { mod->metadata()->project_id.toInt(), mod->metadata()->file_id.toInt(), mod->enabled(), true, + mod->metadata()->name, mod->metadata()->slug, mod->authors().join(", ") }); + continue; + } + + auto hashTask = Hashing::createFlameHasher(mod->fileinfo().absoluteFilePath()); + connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { + if (m_state == Task::State::Running) { + pendingHashes.insert(hash, { mod->name(), mod->fileinfo().absoluteFilePath(), mod->enabled(), true }); + } + }); + connect(hashTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed); + hashingTask->addTask(hashTask); + } + } + auto progressStep = std::make_shared<TaskStepProgress>(); + connect(hashingTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(hashingTask.get(), &Task::succeeded, this, &FlamePackExportTask::makeApiRequest); + connect(hashingTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propogateStepProgress); + + connect(hashingTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(hashingTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + hashingTask->start(); +} + +void FlamePackExportTask::makeApiRequest() +{ + if (pendingHashes.isEmpty()) { + buildZip(); + return; + } + + setStatus(tr("Finding versions for hashes...")); + setProgress(2, 5); + auto response = std::make_shared<QByteArray>(); + + QList<uint> fingerprints; + for (auto& murmur : pendingHashes.keys()) { + fingerprints.push_back(murmur.toUInt()); + } + + task.reset(api.matchFingerprints(fingerprints, response)); + + connect(task.get(), &Task::succeeded, this, [this, response] { + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from CurseForge::CurrentVersions at " << parseError.offset + << " reason: " << parseError.errorString(); + qWarning() << *response; + + failed(parseError.errorString()); + return; + } + + try { + auto docObj = Json::requireObject(doc); + auto dataObj = Json::requireObject(docObj, "data"); + auto dataArr = Json::requireArray(dataObj, "exactMatches"); + + if (dataArr.isEmpty()) { + qWarning() << "No matches found for fingerprint search!"; + + return; + } + for (auto match : dataArr) { + auto matchObj = Json::ensureObject(match, {}); + auto fileObj = Json::ensureObject(matchObj, "file", {}); + + if (matchObj.isEmpty() || fileObj.isEmpty()) { + qWarning() << "Fingerprint match is empty!"; + + return; + } + + auto fingerprint = QString::number(Json::ensureVariant(fileObj, "fileFingerprint").toUInt()); + auto mod = pendingHashes.find(fingerprint); + if (mod == pendingHashes.end()) { + qWarning() << "Invalid fingerprint from the API response."; + continue; + } + + setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name)); + if (Json::ensureBoolean(fileObj, "isAvailable", false, "isAvailable")) + resolvedFiles.insert(mod->path, { Json::requireInteger(fileObj, "modId"), Json::requireInteger(fileObj, "id"), + mod->enabled, mod->isMod }); + } + + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; + } + pendingHashes.clear(); + }); + connect(task.get(), &Task::finished, this, &FlamePackExportTask::getProjectsInfo); + connect(task.get(), &NetJob::failed, this, &FlamePackExportTask::emitFailed); + task->start(); +} + +void FlamePackExportTask::getProjectsInfo() +{ + setStatus(tr("Finding project info from CurseForge...")); + setProgress(3, 5); + QStringList addonIds; + for (const auto& resolved : resolvedFiles) { + if (resolved.slug.isEmpty()) { + addonIds << QString::number(resolved.addonId); + } + } + + auto response = std::make_shared<QByteArray>(); + Task::Ptr projTask; + + if (addonIds.isEmpty()) { + buildZip(); + return; + } else if (addonIds.size() == 1) { + projTask = api.getProject(*addonIds.begin(), response); + } else { + projTask = api.getProjects(addonIds, response); + } + + connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds] { + QJsonParseError parseError{}; + auto doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from CurseForge projects task at " << parseError.offset + << " reason: " << parseError.errorString(); + qWarning() << *response; + failed(parseError.errorString()); + return; + } + + try { + QJsonArray entries; + if (addonIds.size() == 1) + entries = { Json::requireObject(Json::requireObject(doc), "data") }; + else + entries = Json::requireArray(Json::requireObject(doc), "data"); + + for (auto entry : entries) { + auto entryObj = Json::requireObject(entry); + + try { + setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(Json::requireString(entryObj, "name"))); + + ModPlatform::IndexedPack pack; + FlameMod::loadIndexedPack(pack, entryObj); + for (auto key : resolvedFiles.keys()) { + auto val = resolvedFiles.value(key); + if (val.addonId == pack.addonId) { + val.name = pack.name; + val.slug = pack.slug; + QStringList authors; + for (auto author : pack.authors) + authors << author.name; + + val.authors = authors.join(", "); + resolvedFiles[key] = val; + } + } + + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << entries; + } + } + } catch (Json::JsonException& e) { + qDebug() << e.cause(); + qDebug() << doc; + } + buildZip(); + }); + task.reset(projTask); + task->start(); +} + +void FlamePackExportTask::buildZip() +{ + setStatus(tr("Adding files...")); + setProgress(4, 5); + + buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { + QuaZip zip(output); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(output); + return BuildZipResult(tr("Could not create file")); + } + + if (buildZipFuture.isCanceled()) + return BuildZipResult(); + + QuaZipFile indexFile(&zip); + if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("manifest.json"))) { + QFile::remove(output); + return BuildZipResult(tr("Could not create index")); + } + indexFile.write(generateIndex()); + + QuaZipFile modlist(&zip); + if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) { + QFile::remove(output); + return BuildZipResult(tr("Could not create index")); + } + QString content = ""; + for (auto mod : resolvedFiles) { + if (mod.isMod) { + content += QString(TEMPLATE) + .replace("{name}", mod.name.toHtmlEscaped()) + .replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped()) + .replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : ""); + } + } + content = "<ul>" + content + "</ul>"; + modlist.write(content.toUtf8()); + + auto progressStep = std::make_shared<TaskStepProgress>(); + + size_t progress = 0; + for (const QFileInfo& file : files) { + if (buildZipFuture.isCanceled()) { + QFile::remove(output); + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + return BuildZipResult(); + } + progressStep->update(progress, files.length()); + stepProgress(*progressStep); + + const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); + if (!resolvedFiles.contains(file.absoluteFilePath()) && + !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { + QFile::remove(output); + return BuildZipResult(tr("Could not read and compress %1").arg(relative)); + } + progress++; + } + + zip.close(); + + if (zip.getZipError() != 0) { + QFile::remove(output); + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + return BuildZipResult(tr("A zip error occurred")); + } + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + return BuildZipResult(); + }); + connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish); + buildZipWatcher.setFuture(buildZipFuture); +} + +void FlamePackExportTask::finish() +{ + if (buildZipFuture.isCanceled()) + emitAborted(); + else { + const BuildZipResult result = buildZipFuture.result(); + if (result.has_value()) + emitFailed(result.value()); + else + emitSucceeded(); + } +} + +QByteArray FlamePackExportTask::generateIndex() +{ + QJsonObject obj; + obj["manifestType"] = "minecraftModpack"; + obj["manifestVersion"] = 1; + obj["name"] = name; + obj["version"] = version; + obj["author"] = author; + obj["overrides"] = "overrides"; + if (mcInstance) { + QJsonObject version; + auto profile = mcInstance->getPackProfile(); + // collect all supported components + const ComponentPtr minecraft = profile->getComponent("net.minecraft"); + const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); + const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); + const ComponentPtr forge = profile->getComponent("net.minecraftforge"); + + // convert all available components to mrpack dependencies + if (minecraft != nullptr) + version["version"] = minecraft->m_version; + QString id; + if (quilt != nullptr) + id = "quilt-" + quilt->getVersion(); + else if (fabric != nullptr) + id = "fabric-" + fabric->getVersion(); + else if (forge != nullptr) + id = "forge-" + forge->getVersion(); + version["modLoaders"] = QJsonArray(); + if (!id.isEmpty()) { + QJsonObject loader; + loader["id"] = id; + loader["primary"] = true; + version["modLoaders"] = QJsonArray({ loader }); + } + obj["minecraft"] = version; + } + + QJsonArray files; + for (auto mod : resolvedFiles) { + QJsonObject file; + file["projectID"] = mod.addonId; + file["fileID"] = mod.version; + file["required"] = mod.enabled; + files << file; + } + obj["files"] = files; + + return QJsonDocument(obj).toJson(QJsonDocument::Compact); +} diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h new file mode 100644 index 00000000..3dee0a7e --- /dev/null +++ b/launcher/modplatform/flame/FlamePackExportTask.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QFuture> +#include <QFutureWatcher> +#include "BaseInstance.h" +#include "MMCZip.h" +#include "minecraft/MinecraftInstance.h" +#include "modplatform/flame/FlameAPI.h" +#include "tasks/Task.h" + +class FlamePackExportTask : public Task { + public: + FlamePackExportTask(const QString& name, + const QString& version, + const QString& author, + InstancePtr instance, + const QString& output, + MMCZip::FilterFunction filter); + + protected: + void executeTask() override; + bool abort() override; + + private: + static const QString TEMPLATE; + static const QStringList FILE_EXTENSIONS; + + // inputs + const QString name, version, author; + const InstancePtr instance; + MinecraftInstance* mcInstance; + const QDir gameRoot; + const QString output; + const MMCZip::FilterFunction filter; + + typedef std::optional<QString> BuildZipResult; + struct ResolvedFile { + int addonId; + int version; + bool enabled; + bool isMod; + + QString name; + QString slug; + QString authors; + }; + struct HashInfo { + QString name; + QString path; + bool enabled; + bool isMod; + }; + + FlameAPI api; + + QFileInfoList files; + QMap<QString, HashInfo> pendingHashes{}; + QMap<QString, ResolvedFile> resolvedFiles{}; + Task::Ptr task; + QFuture<BuildZipResult> buildZipFuture; + QFutureWatcher<BuildZipResult> buildZipWatcher; + + void collectFiles(); + void collectHashes(); + void makeApiRequest(); + void getProjectsInfo(); + void buildZip(); + void finish(); + + QByteArray generateIndex(); +}; diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 22008297..ee4d0766 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -76,13 +76,8 @@ bool Flame::File::parseFromObject(const QJsonObject& obj, bool throw_on_blocked // It is also optional type = File::Type::SingleFile; - if (fileName.endsWith(".zip")) { - // this is probably a resource pack - targetFolder = "resourcepacks"; - } else { - // this is probably a mod, dunno what else could modpacks download - targetFolder = "mods"; - } + targetFolder = "mods"; + // get the hash hash = QString(); auto hashes = Json::ensureArray(obj, "hashes"); diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp new file mode 100644 index 00000000..1f01c4a8 --- /dev/null +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +#include "ExportToModList.h" +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> + +namespace ExportToModList { +QString toHTML(QList<Mod*> mods, OptionalData extraData) +{ + QStringList lines; + for (auto mod : mods) { + auto meta = mod->metadata(); + auto modName = mod->name().toHtmlEscaped(); + if (extraData & Url) { + auto url = mod->metaurl().toHtmlEscaped(); + if (!url.isEmpty()) + modName = QString("<a href=\"%1\">%2</a>").arg(url, modName); + } + auto line = modName; + if (extraData & Version) { + auto ver = mod->version(); + if (ver.isEmpty() && meta != nullptr) + ver = meta->version().toString(); + if (!ver.isEmpty()) + line += QString(" [%1]").arg(ver.toHtmlEscaped()); + } + if (extraData & Authors && !mod->authors().isEmpty()) + line += " by " + mod->authors().join(", ").toHtmlEscaped(); + lines.append(QString("<li>%1</li>").arg(line)); + } + return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t")); +} + +QString toMarkdown(QList<Mod*> mods, OptionalData extraData) +{ + QStringList lines; + for (auto mod : mods) { + auto meta = mod->metadata(); + auto modName = mod->name(); + if (extraData & Url) { + auto url = mod->metaurl(); + if (!url.isEmpty()) + modName = QString("[%1](%2)").arg(modName, url); + } + auto line = modName; + if (extraData & Version) { + auto ver = mod->version(); + if (ver.isEmpty() && meta != nullptr) + ver = meta->version().toString(); + if (!ver.isEmpty()) + line += QString(" [%1]").arg(ver); + } + if (extraData & Authors && !mod->authors().isEmpty()) + line += " by " + mod->authors().join(", "); + lines << "- " + line; + } + return lines.join("\n"); +} + +QString toPlainTXT(QList<Mod*> mods, OptionalData extraData) +{ + QStringList lines; + for (auto mod : mods) { + auto meta = mod->metadata(); + auto modName = mod->name(); + + auto line = modName; + if (extraData & Url) { + auto url = mod->metaurl(); + if (!url.isEmpty()) + line += QString(" (%1)").arg(url); + } + if (extraData & Version) { + auto ver = mod->version(); + if (ver.isEmpty() && meta != nullptr) + ver = meta->version().toString(); + if (!ver.isEmpty()) + line += QString(" [%1]").arg(ver); + } + if (extraData & Authors && !mod->authors().isEmpty()) + line += " by " + mod->authors().join(", "); + lines << line; + } + return lines.join("\n"); +} + +QString toJSON(QList<Mod*> mods, OptionalData extraData) +{ + QJsonArray lines; + for (auto mod : mods) { + auto meta = mod->metadata(); + auto modName = mod->name(); + QJsonObject line; + line["name"] = modName; + if (extraData & Url) { + auto url = mod->metaurl(); + if (!url.isEmpty()) + line["url"] = url; + } + if (extraData & Version) { + auto ver = mod->version(); + if (ver.isEmpty() && meta != nullptr) + ver = meta->version().toString(); + if (!ver.isEmpty()) + line["version"] = ver; + } + if (extraData & Authors && !mod->authors().isEmpty()) + line["authors"] = QJsonArray::fromStringList(mod->authors()); + lines << line; + } + QJsonDocument doc; + doc.setArray(lines); + return doc.toJson(); +} + +QString toCSV(QList<Mod*> mods, OptionalData extraData) +{ + QStringList lines; + for (auto mod : mods) { + QStringList data; + auto meta = mod->metadata(); + auto modName = mod->name(); + + data << modName; + if (extraData & Url) + data << mod->metaurl(); + if (extraData & Version) { + auto ver = mod->version(); + if (ver.isEmpty() && meta != nullptr) + ver = meta->version().toString(); + data << ver; + } + if (extraData & Authors) { + QString authors; + if (mod->authors().length() == 1) + authors = mod->authors().back(); + else if (mod->authors().length() > 1) + authors = QString("\"%1\"").arg(mod->authors().join(",")); + data << authors; + } + lines << data.join(","); + } + return lines.join("\n"); +} + +QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData) +{ + switch (format) { + case HTML: + return toHTML(mods, extraData); + case MARKDOWN: + return toMarkdown(mods, extraData); + case PLAINTXT: + return toPlainTXT(mods, extraData); + case JSON: + return toJSON(mods, extraData); + case CSV: + return toCSV(mods, extraData); + default: { + return QString("unknown format:%1").arg(format); + } + } +} + +QString exportToModList(QList<Mod*> mods, QString lineTemplate) +{ + QStringList lines; + for (auto mod : mods) { + auto meta = mod->metadata(); + auto modName = mod->name(); + auto url = mod->metaurl(); + auto ver = mod->version(); + if (ver.isEmpty() && meta != nullptr) + ver = meta->version().toString(); + auto authors = mod->authors().join(", "); + lines << QString(lineTemplate) + .replace("{name}", modName) + .replace("{url}", url) + .replace("{version}", ver) + .replace("{authors}", authors); + } + return lines.join("\n"); +} +} // namespace ExportToModList
\ No newline at end of file diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h new file mode 100644 index 00000000..7ea4ba9c --- /dev/null +++ b/launcher/modplatform/helpers/ExportToModList.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +#pragma once +#include <QList> +#include <QString> +#include "minecraft/mod/Mod.h" + +namespace ExportToModList { + +enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; +enum OptionalData { + Authors = 1 << 0, + Url = 1 << 1, + Version = 1 << 2, +}; +QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData); +QString exportToModList(QList<Mod*> mods, QString lineTemplate); +} // namespace ExportToModList diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 36c142ac..a4c78397 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -37,16 +37,16 @@ #include <QtConcurrent> -#include "MMCZip.h" #include "BaseInstance.h" #include "FileSystem.h" -#include "settings/INISettingsObject.h" +#include "MMCZip.h" +#include "minecraft/GradleSpecifier.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "minecraft/GradleSpecifier.h" +#include "settings/INISettingsObject.h" -#include "BuildConfig.h" #include "Application.h" +#include "BuildConfig.h" namespace LegacyFTB { @@ -65,6 +65,7 @@ void PackInstallTask::executeTask() void PackInstallTask::downloadPack() { setStatus(tr("Downloading zip for %1").arg(m_pack.name)); + setProgress(1, 4); setAbortable(false); archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); @@ -78,11 +79,10 @@ void PackInstallTask::downloadPack() } netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath)); - connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); - connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); + connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip); + connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed); connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); - connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); + connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted); netJobContainer->start(); @@ -90,27 +90,6 @@ void PackInstallTask::downloadPack() progress(1, 4); } -void PackInstallTask::onDownloadSucceeded() -{ - unzip(); -} - -void PackInstallTask::onDownloadFailed(QString reason) -{ - emitFailed(reason); -} - -void PackInstallTask::onDownloadProgress(qint64 current, qint64 total) -{ - progress(current, total * 4); - setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); -} - -void PackInstallTask::onDownloadAborted() -{ - emitAborted(); -} - void PackInstallTask::unzip() { setStatus(tr("Extracting modpack")); @@ -120,16 +99,17 @@ void PackInstallTask::unzip() QDir extractDir(m_stagingPath); m_packZip.reset(new QuaZip(archivePath)); - if(!m_packZip->open(QuaZip::mdUnzip)) - { + if (!m_packZip->open(QuaZip::mdUnzip)) { emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); return; } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip"); + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, + extractDir.absolutePath() + "/unzip"); #else - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); + m_extractFuture = + QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); #endif connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished); connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled); @@ -151,11 +131,9 @@ void PackInstallTask::install() setStatus(tr("Installing modpack")); progress(3, 4); QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); - if(unzipMcDir.exists()) - { - //ok, found minecraft dir, move contents to instance dir - if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) - { + if (unzipMcDir.exists()) { + // ok, found minecraft dir, move contents to instance dir + if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) { emitFailed(tr("Failed to move unzipped Minecraft!")); return; } @@ -172,23 +150,20 @@ void PackInstallTask::install() bool fallback = true; - //handle different versions + // handle different versions QFile packJson(m_stagingPath + "/.minecraft/pack.json"); QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); - if(packJson.exists()) - { + if (packJson.exists()) { packJson.open(QIODevice::ReadOnly | QIODevice::Text); QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); packJson.close(); - //we only care about the libs + // we only care about the libs QJsonArray libs = doc.object().value("libraries").toArray(); - foreach (const QJsonValue &value, libs) - { + foreach (const QJsonValue& value, libs) { QString nameValue = value.toObject().value("name").toString(); - if(!nameValue.startsWith("net.minecraftforge")) - { + if (!nameValue.startsWith("net.minecraftforge")) { continue; } @@ -199,16 +174,13 @@ void PackInstallTask::install() fallback = false; break; } - } - if(jarmodDir.exists()) - { + if (jarmodDir.exists()) { qDebug() << "Found jarmods, installing..."; QStringList jarmods; - for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { + for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { qDebug() << "Jarmod:" << info.fileName(); jarmods.push_back(info.absoluteFilePath()); } @@ -217,12 +189,11 @@ void PackInstallTask::install() fallback = false; } - //just nuke unzip directory, it s not needed anymore + // just nuke unzip directory, it s not needed anymore FS::deletePath(m_stagingPath + "/unzip"); - if(fallback) - { - //TODO: Some fallback mechanism... or just keep failing! + if (fallback) { + // TODO: Some fallback mechanism... or just keep failing! emitFailed(tr("No installation method found!")); return; } @@ -232,8 +203,7 @@ void PackInstallTask::install() progress(4, 4); instance.setName(name()); - if(m_instIcon == "default") - { + if (m_instIcon == "default") { m_instIcon = "ftb_logo"; } instance.setIconKey(m_instIcon); @@ -252,4 +222,4 @@ bool PackInstallTask::abort() return InstanceTask::abort(); } -} +} // namespace LegacyFTB diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index da791e06..30ff4859 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -1,12 +1,12 @@ #pragma once -#include "InstanceTask.h" -#include "net/NetJob.h" #include <quazip/quazip.h> #include <quazip/quazipdir.h> +#include "InstanceTask.h" +#include "PackHelpers.h" #include "meta/Index.h" #include "meta/Version.h" #include "meta/VersionList.h" -#include "PackHelpers.h" +#include "net/NetJob.h" #include "net/NetJob.h" @@ -14,36 +14,31 @@ namespace LegacyFTB { -class PackInstallTask : public InstanceTask -{ +class PackInstallTask : public InstanceTask { Q_OBJECT -public: + public: explicit PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version); - virtual ~PackInstallTask(){} + virtual ~PackInstallTask() {} bool canAbort() const override { return true; } bool abort() override; -protected: + protected: //! Entry point for tasks. virtual void executeTask() override; -private: + private: void downloadPack(); void unzip(); void install(); -private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); - void onDownloadProgress(qint64 current, qint64 total); - void onDownloadAborted(); + private slots: void onUnzipFinished(); void onUnzipCanceled(); -private: /* data */ + private: /* data */ shared_qobject_ptr<QNetworkAccessManager> m_network; bool abortable = false; std::unique_ptr<QuaZip> m_packZip; @@ -56,4 +51,4 @@ private: /* data */ QString m_version; }; -} +} // namespace LegacyFTB diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index e83ed2bf..58af14cc 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -38,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI { static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList { QStringList l; - for (auto loader : { Forge, Fabric, Quilt }) { + for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) { if (types & loader) { l << getModLoaderString(loader); } @@ -92,7 +92,7 @@ class ModrinthAPI : public NetworkResourceAPI { { if (args.loaders.has_value()) { if (!validateModLoaders(args.loaders.value())) { - qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; + qWarning() << "Modrinth - or our interface - does not support any the provided mod loaders!"; return {}; } } @@ -141,7 +141,7 @@ class ModrinthAPI : public NetworkResourceAPI { return s.isEmpty() ? QString() : s; } - inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { return loaders & (Forge | Fabric | Quilt); } + static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt | LiteLoader); } [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override { diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 4cd88aa6..30fe566d 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -64,7 +64,8 @@ bool ModrinthPackExportTask::abort() if (buildZipFuture.isRunning()) { buildZipFuture.cancel(); - // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately. + // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur + // immediately. return true; } @@ -94,6 +95,7 @@ void ModrinthPackExportTask::collectFiles() void ModrinthPackExportTask::collectHashes() { + setStatus(tr("Finding file hashes...")); for (const QFileInfo& file : files) { QCoreApplication::processEvents(); @@ -157,6 +159,7 @@ void ModrinthPackExportTask::makeApiRequest() if (pendingHashes.isEmpty()) buildZip(); else { + setStatus(tr("Finding versions for hashes...")); auto response = std::make_shared<QByteArray>(); task = api.currentVersions(pendingHashes.values(), "sha512", response); connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); }); @@ -263,13 +266,13 @@ void ModrinthPackExportTask::finish() QByteArray ModrinthPackExportTask::generateIndex() { - QJsonObject obj; - obj["formatVersion"] = 1; - obj["game"] = "minecraft"; - obj["name"] = name; - obj["versionId"] = version; + QJsonObject out; + out["formatVersion"] = 1; + out["game"] = "minecraft"; + out["name"] = name; + out["versionId"] = version; if (!summary.isEmpty()) - obj["summary"] = summary; + out["summary"] = summary; if (mcInstance) { auto profile = mcInstance->getPackProfile(); @@ -290,30 +293,40 @@ QByteArray ModrinthPackExportTask::generateIndex() if (forge != nullptr) dependencies["forge"] = forge->m_version; - obj["dependencies"] = dependencies; + out["dependencies"] = dependencies; } - QJsonArray files; - QMapIterator<QString, ResolvedFile> iterator(resolvedFiles); - while (iterator.hasNext()) { - iterator.next(); + QJsonArray filesOut; + for (auto iterator = resolvedFiles.constBegin(); iterator != resolvedFiles.constEnd(); iterator++) { + QJsonObject fileOut; + QString path = iterator.key(); const ResolvedFile& value = iterator.value(); - QJsonObject file; - file["path"] = iterator.key(); - file["downloads"] = QJsonArray({ iterator.value().url }); + // detect disabled mod + const QFileInfo pathInfo(path); + if (pathInfo.suffix() == "disabled") { + // rename it + path = pathInfo.dir().filePath(pathInfo.completeBaseName()); + // ...and make it optional + QJsonObject env; + env["client"] = "optional"; + env["server"] = "optional"; + fileOut["env"] = env; + } + + fileOut["path"] = path; + fileOut["downloads"] = QJsonArray{ iterator.value().url }; QJsonObject hashes; hashes["sha1"] = value.sha1; hashes["sha512"] = value.sha512; + fileOut["hashes"] = hashes; - file["hashes"] = hashes; - file["fileSize"] = value.size; - - files << file; + fileOut["fileSize"] = value.size; + filesOut << fileOut; } - obj["files"] = files; + out["files"] = filesOut; - return QJsonDocument(obj).toJson(QJsonDocument::Compact); + return QJsonDocument(out).toJson(QJsonDocument::Compact); } diff --git a/launcher/mojang/PackageManifest.cpp b/launcher/mojang/PackageManifest.cpp deleted file mode 100644 index b3dfd7fc..00000000 --- a/launcher/mojang/PackageManifest.cpp +++ /dev/null @@ -1,427 +0,0 @@ -#include "PackageManifest.h" -#include <Json.h> -#include <QDir> -#include <QDirIterator> -#include <QCryptographicHash> -#include <QDebug> - -#ifndef Q_OS_WIN32 -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#endif - -namespace mojang_files { - -const Hash hash_of_empty_string = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; - -int Path::compare(const Path& rhs) const -{ - auto left_cursor = begin(); - auto left_end = end(); - auto right_cursor = rhs.begin(); - auto right_end = rhs.end(); - - while (left_cursor != left_end && right_cursor != right_end) - { - if(*left_cursor < *right_cursor) - { - return -1; - } - else if(*left_cursor > *right_cursor) - { - return 1; - } - left_cursor++; - right_cursor++; - } - - if(left_cursor == left_end) - { - if(right_cursor == right_end) - { - return 0; - } - return -1; - } - return 1; -} - -void Package::addFile(const Path& path, const File& file) { - addFolder(path.parent_path()); - files[path] = file; -} - -void Package::addFolder(Path folder) { - if(!folder.has_parent_path()) { - return; - } - do { - folders.insert(folder); - folder = folder.parent_path(); - } while(folder.has_parent_path()); -} - -void Package::addLink(const Path& path, const Path& target) { - addFolder(path.parent_path()); - symlinks[path] = target; -} - -void Package::addSource(const FileSource& source) { - sources[source.hash] = source; -} - - -namespace { -void fromJson(QJsonDocument & doc, Package & out) { - std::set<Path> seen_paths; - if (!doc.isObject()) - { - throw JSONValidationError("file manifest is not an object"); - } - QJsonObject root = doc.object(); - - auto filesObj = Json::ensureObject(root, "files"); - auto iter = filesObj.begin(); - while (iter != filesObj.end()) - { - Path objectPath = Path(iter.key()); - auto value = iter.value(); - iter++; - if(seen_paths.count(objectPath)) { - throw JSONValidationError("duplicate path inside manifest, the manifest is invalid"); - } - if (!value.isObject()) - { - throw JSONValidationError("file entry inside manifest is not an an object"); - } - seen_paths.insert(objectPath); - - auto fileObject = value.toObject(); - auto type = Json::requireString(fileObject, "type"); - if(type == "directory") { - out.addFolder(objectPath); - continue; - } - else if(type == "file") { - FileSource bestSource; - File file; - file.executable = Json::ensureBoolean(fileObject, QString("executable"), false); - auto downloads = Json::requireObject(fileObject, "downloads"); - for(auto iter2 = downloads.begin(); iter2 != downloads.end(); iter2++) { - FileSource source; - - auto downloadObject = Json::requireObject(iter2.value()); - source.hash = Json::requireString(downloadObject, "sha1"); - source.size = Json::requireInteger(downloadObject, "size"); - source.url = Json::requireString(downloadObject, "url"); - - auto compression = iter2.key(); - if(compression == "raw") { - file.hash = source.hash; - file.size = source.size; - source.compression = Compression::Raw; - } - else if (compression == "lzma") { - source.compression = Compression::Lzma; - } - else { - continue; - } - bestSource.upgrade(source); - } - if(bestSource.isBad()) { - throw JSONValidationError("No valid compression method for file " + iter.key()); - } - out.addFile(objectPath, file); - out.addSource(bestSource); - } - else if(type == "link") { - auto target = Json::requireString(fileObject, "target"); - out.symlinks[objectPath] = target; - out.addLink(objectPath, target); - } - else { - throw JSONValidationError("Invalid item type in manifest: " + type); - } - } - // make sure the containing folder exists - out.folders.insert(Path()); -} -} - -Package Package::fromManifestContents(const QByteArray& contents) -{ - Package out; - try - { - auto doc = Json::requireDocument(contents, "Manifest"); - fromJson(doc, out); - return out; - } - catch (const Exception &e) - { - qDebug() << QString("Unable to parse manifest: %1").arg(e.cause()); - out.valid = false; - return out; - } -} - -Package Package::fromManifestFile(const QString & filename) { - Package out; - try - { - auto doc = Json::requireDocument(filename, filename); - fromJson(doc, out); - return out; - } - catch (const Exception &e) - { - qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause()); - out.valid = false; - return out; - } -} - -#ifndef Q_OS_WIN32 - -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> - -namespace { -// FIXME: Qt obscures symlink targets by making them absolute. that is useless. this is the workaround - we do it ourselves -bool actually_read_symlink_target(const QString & filepath, Path & out) -{ - struct ::stat st; - // FIXME: here, we assume the native filesystem encoding. May the Gods have mercy upon our Souls. - QByteArray nativePath = filepath.toUtf8(); - const char * filepath_cstr = nativePath.data(); - - if (lstat(filepath_cstr, &st) != 0) - { - return false; - } - - auto size = st.st_size ? st.st_size + 1 : PATH_MAX; - std::string temp(size, '\0'); - // because we don't realiably know how long the damn thing actually is, we loop and expand. POSIX is naff - do - { - auto link_length = ::readlink(filepath_cstr, &temp[0], temp.size()); - if(link_length == -1) - { - return false; - } - if(std::string::size_type(link_length) < temp.size()) - { - // buffer was long enough and we managed to read the link target. RETURN here. - temp.resize(link_length); - out = Path(QString::fromUtf8(temp.c_str())); - return true; - } - temp.resize(temp.size() * 2); - } while (true); -} -} -#endif - -// FIXME: Qt filesystem abstraction is bad, but ... let's hope it doesn't break too much? -// FIXME: The error handling is just DEFICIENT -Package Package::fromInspectedFolder(const QString& folderPath) -{ - QDir root(folderPath); - - Package out; - QDirIterator iterator(folderPath, QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden, QDirIterator::Subdirectories); - while(iterator.hasNext()) { - iterator.next(); - - auto fileInfo = iterator.fileInfo(); - auto relPath = root.relativeFilePath(fileInfo.filePath()); - // FIXME: this is probably completely busted on Windows anyway, so just disable it. - // Qt makes shit up and doesn't understand the platform details - // TODO: Actually use a filesystem library that isn't terrible and has decen license. - // I only know one, and I wrote it. Sadly, currently proprietary. PAIN. -#ifndef Q_OS_WIN32 - if(fileInfo.isSymLink()) { - Path targetPath; - if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) { - qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath(); - out.valid = false; - } - out.addLink(relPath, targetPath); - } - else -#endif - if(fileInfo.isDir()) { - out.addFolder(relPath); - } - else if(fileInfo.isFile()) { - File f; - f.executable = fileInfo.isExecutable(); - f.size = fileInfo.size(); - // FIXME: async / optimize the hashing - QFile input(fileInfo.absoluteFilePath()); - if(!input.open(QIODevice::ReadOnly)) { - qCritical() << "Folder inspection: Failed to open file:" << fileInfo.absoluteFilePath(); - out.valid = false; - break; - } - f.hash = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1).toHex().constData(); - out.addFile(relPath, f); - } - else { - // Something else... oh my - qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath(); - out.valid = false; - break; - } - } - out.folders.insert(Path(".")); - out.valid = true; - return out; -} - -namespace { -struct shallow_first_sort -{ - bool operator()(const Path &lhs, const Path &rhs) const - { - auto lhs_depth = lhs.length(); - auto rhs_depth = rhs.length(); - if(lhs_depth < rhs_depth) - { - return true; - } - else if(lhs_depth == rhs_depth) - { - if(lhs < rhs) - { - return true; - } - } - return false; - } -}; - -struct deep_first_sort -{ - bool operator()(const Path &lhs, const Path &rhs) const - { - auto lhs_depth = lhs.length(); - auto rhs_depth = rhs.length(); - if(lhs_depth > rhs_depth) - { - return true; - } - else if(lhs_depth == rhs_depth) - { - if(lhs < rhs) - { - return true; - } - } - return false; - } -}; -} - -UpdateOperations UpdateOperations::resolve(const Package& from, const Package& to) -{ - UpdateOperations out; - - if(!from.valid || !to.valid) { - out.valid = false; - return out; - } - - // Files - for(auto iter = from.files.begin(); iter != from.files.end(); iter++) { - const auto ¤t_hash = iter->second.hash; - const auto ¤t_executable = iter->second.executable; - const auto &path = iter->first; - - auto iter2 = to.files.find(path); - if(iter2 == to.files.end()) { - // removed - out.deletes.push_back(path); - continue; - } - auto new_hash = iter2->second.hash; - auto new_executable = iter2->second.executable; - if (current_hash != new_hash) { - out.deletes.push_back(path); - out.downloads.emplace( - std::pair<Path, FileDownload>{ - path, - FileDownload(to.sources.at(iter2->second.hash), iter2->second.executable) - } - ); - } - else if (current_executable != new_executable) { - out.executable_fixes[path] = new_executable; - } - } - for(auto iter = to.files.begin(); iter != to.files.end(); iter++) { - auto path = iter->first; - if(!from.files.count(path)) { - out.downloads.emplace( - std::pair<Path, FileDownload>{ - path, - FileDownload(to.sources.at(iter->second.hash), iter->second.executable) - } - ); - } - } - - // Folders - std::set<Path, deep_first_sort> remove_folders; - std::set<Path, shallow_first_sort> make_folders; - for(auto from_path: from.folders) { - auto iter = to.folders.find(from_path); - if(iter == to.folders.end()) { - remove_folders.insert(from_path); - } - } - for(auto & rmdir: remove_folders) { - out.rmdirs.push_back(rmdir); - } - for(auto to_path: to.folders) { - auto iter = from.folders.find(to_path); - if(iter == from.folders.end()) { - make_folders.insert(to_path); - } - } - for(auto & mkdir: make_folders) { - out.mkdirs.push_back(mkdir); - } - - // Symlinks - for(auto iter = from.symlinks.begin(); iter != from.symlinks.end(); iter++) { - const auto ¤t_target = iter->second; - const auto &path = iter->first; - - auto iter2 = to.symlinks.find(path); - if(iter2 == to.symlinks.end()) { - // removed - out.deletes.push_back(path); - continue; - } - const auto &new_target = iter2->second; - if (current_target != new_target) { - out.deletes.push_back(path); - out.mklinks[path] = iter2->second; - } - } - for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) { - auto path = iter->first; - if(!from.symlinks.count(path)) { - out.mklinks[path] = iter->second; - } - } - out.valid = true; - return out; -} - -} diff --git a/launcher/mojang/PackageManifest.h b/launcher/mojang/PackageManifest.h deleted file mode 100644 index fd7ab0ad..00000000 --- a/launcher/mojang/PackageManifest.h +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -#include <QString> -#include <map> -#include <set> -#include <QStringList> -#include "tasks/Task.h" - -namespace mojang_files { - -using Hash = QString; -extern const Hash empty_hash; - -// simple-ish path implementation. assumes always relative and does not allow '..' entries -class Path -{ -public: - using parts_type = QStringList; - - Path() = default; - Path(QString string) { - auto parts_in = string.split('/'); - for(auto & part: parts_in) { - if(part.isEmpty() || part == ".") { - continue; - } - if(part == "..") { - if(parts.size()) { - parts.pop_back(); - } - continue; - } - parts.push_back(part); - } - } - - bool has_parent_path() const - { - return parts.size() > 0; - } - - Path parent_path() const - { - if (parts.empty()) - return Path(); - return Path(parts.begin(), std::prev(parts.end())); - } - - bool empty() const - { - return parts.empty(); - } - - int length() const - { - return parts.length(); - } - - bool operator==(const Path & rhs) const { - return parts == rhs.parts; - } - - bool operator!=(const Path & rhs) const { - return parts != rhs.parts; - } - - inline bool operator<(const Path& rhs) const - { - return compare(rhs) < 0; - } - - parts_type::const_iterator begin() const - { - return parts.begin(); - } - - parts_type::const_iterator end() const - { - return parts.end(); - } - - QString toString() const { - return parts.join("/"); - } - -private: - Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) { - auto cursor = start; - while(cursor != end) { - parts.push_back(*cursor); - cursor++; - } - } - int compare(const Path& p) const; - - parts_type parts; -}; - - -enum class Compression { - Raw, - Lzma, - Unknown -}; - - -struct FileSource -{ - Compression compression = Compression::Unknown; - Hash hash; - QString url; - std::size_t size = 0; - void upgrade(const FileSource & other) { - if(compression == Compression::Unknown || other.size < size) { - *this = other; - } - } - bool isBad() const { - return compression == Compression::Unknown; - } -}; - -struct File -{ - Hash hash; - bool executable; - std::uint64_t size = 0; -}; - -struct Package { - static Package fromInspectedFolder(const QString &folderPath); - static Package fromManifestFile(const QString &path); - static Package fromManifestContents(const QByteArray& contents); - - explicit operator bool() const - { - return valid; - } - void addFolder(Path folder); - void addFile(const Path & path, const File & file); - void addLink(const Path & path, const Path & target); - void addSource(const FileSource & source); - - std::map<Hash, FileSource> sources; - bool valid = true; - std::set<Path> folders; - std::map<Path, File> files; - std::map<Path, Path> symlinks; -}; - -struct FileDownload : FileSource -{ - FileDownload(const FileSource& source, bool executable) { - static_cast<FileSource &> (*this) = source; - this->executable = executable; - } - bool executable = false; -}; - -struct UpdateOperations { - static UpdateOperations resolve(const Package & from, const Package & to); - bool valid = false; - std::vector<Path> deletes; - std::vector<Path> rmdirs; - std::vector<Path> mkdirs; - std::map<Path, FileDownload> downloads; - std::map<Path, Path> mklinks; - std::map<Path, bool> executable_fixes; -}; - -} diff --git a/launcher/resources/OSX/OSX.qrc b/launcher/resources/OSX/OSX.qrc index 9d4511d1..49f56b0c 100644 --- a/launcher/resources/OSX/OSX.qrc +++ b/launcher/resources/OSX/OSX.qrc @@ -16,7 +16,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/OSX/scalable/launcher.svg b/launcher/resources/OSX/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/OSX/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/resources/breeze_dark/scalable/launcher.svg b/launcher/resources/breeze_dark/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/breeze_dark/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/resources/flat/flat.qrc b/launcher/resources/flat/flat.qrc index cadf8736..2fd5daef 100644 --- a/launcher/resources/flat/flat.qrc +++ b/launcher/resources/flat/flat.qrc @@ -18,7 +18,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/flat/scalable/launcher.svg b/launcher/resources/flat/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/flat/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/resources/flat_white/flat_white.qrc b/launcher/resources/flat_white/flat_white.qrc index 2701462f..a1c940da 100644 --- a/launcher/resources/flat_white/flat_white.qrc +++ b/launcher/resources/flat_white/flat_white.qrc @@ -18,7 +18,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/flat_white/scalable/launcher.svg b/launcher/resources/flat_white/scalable/launcher.svg deleted file mode 100644 index 54131b65..00000000 --- a/launcher/resources/flat_white/scalable/launcher.svg +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg width="24" height="24" fill="#eeeeee" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m20 4h-16v16h16zm0 18h-16c-1.1046 0-2-0.89543-2-2v-16c0-1.1046 0.89543-2 2-2h16c1.1046 0 2 0.89543 2 2v16c0 1.1046-0.89543 2-2 2z"/><path d="m7.2 18c-0.225 0-0.45-0.075-0.6-0.15-0.375-0.225-0.6-0.6-0.6-1.05v-9.6c0-0.45 0.225-0.825 0.6-1.05 0.225-0.15 0.375-0.15 0.6-0.15 0.15 0 0.375 0.075 0.525 0.15l9.6 4.8c0.375 0.225 0.675 0.6 0.675 1.05 0 0.45-0.225 0.9-0.675 1.05l-9.6 4.8c-0.15 0.075-0.375 0.15-0.525 0.15z" clip-rule="evenodd" fill="#eeeeee" fill-rule="evenodd" stroke-width=".99999"/></svg> diff --git a/launcher/resources/iOS/iOS.qrc b/launcher/resources/iOS/iOS.qrc index 0b79efb2..9b8d84f5 100644 --- a/launcher/resources/iOS/iOS.qrc +++ b/launcher/resources/iOS/iOS.qrc @@ -16,7 +16,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/iOS/scalable/launcher.svg b/launcher/resources/iOS/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/iOS/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/resources/pe_blue/pe_blue.qrc b/launcher/resources/pe_blue/pe_blue.qrc index 1b2b6291..da45ef9a 100644 --- a/launcher/resources/pe_blue/pe_blue.qrc +++ b/launcher/resources/pe_blue/pe_blue.qrc @@ -16,7 +16,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/pe_blue/scalable/launcher.svg b/launcher/resources/pe_blue/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/pe_blue/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/resources/pe_colored/pe_colored.qrc b/launcher/resources/pe_colored/pe_colored.qrc index 084fca93..ba5bd44f 100644 --- a/launcher/resources/pe_colored/pe_colored.qrc +++ b/launcher/resources/pe_colored/pe_colored.qrc @@ -16,7 +16,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/pe_colored/scalable/launcher.svg b/launcher/resources/pe_colored/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/pe_colored/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/resources/pe_dark/pe_dark.qrc b/launcher/resources/pe_dark/pe_dark.qrc index 5c49b75a..2bfec42c 100644 --- a/launcher/resources/pe_dark/pe_dark.qrc +++ b/launcher/resources/pe_dark/pe_dark.qrc @@ -16,7 +16,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/pe_dark/scalable/launcher.svg b/launcher/resources/pe_dark/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/pe_dark/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/resources/pe_light/pe_light.qrc b/launcher/resources/pe_light/pe_light.qrc index a8e3f157..25d5da73 100644 --- a/launcher/resources/pe_light/pe_light.qrc +++ b/launcher/resources/pe_light/pe_light.qrc @@ -16,7 +16,6 @@ <file>scalable/jarmods.svg</file> <file>scalable/java.svg</file> <file>scalable/language.svg</file> - <file>scalable/launcher.svg</file> <file>scalable/loadermods.svg</file> <file>scalable/log.svg</file> <file>scalable/minecraft.svg</file> diff --git a/launcher/resources/pe_light/scalable/launcher.svg b/launcher/resources/pe_light/scalable/launcher.svg deleted file mode 100644 index aeee8433..00000000 --- a/launcher/resources/pe_light/scalable/launcher.svg +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> -<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.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#"> - <title>Prism Launcher Logo</title> - <g stroke-width=".26458"> - <path d="m6.35 6.35" fill="#99cd61"/> - <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/> - <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/> - <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/> - <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/> - <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/> - <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/> - <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/> - <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/> - <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/> - </g> - <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)"> - <g transform="translate(13.26 2.2776)"> - <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/> - </g> - <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/> - </g> - <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/> - <metadata> - <rdf:RDF> - <cc:Work rdf:about=""> - <dc:title>Prism Launcher Logo</dc:title> - <dc:date>19/10/2022</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:creator> - <dc:contributor> - <cc:Agent> - <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title> - </cc:Agent> - </dc:contributor> - <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source> - <dc:publisher> - <cc:Agent> - <dc:title>Prism Launcher</dc:title> - </cc:Agent> - </dc:publisher> - <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/> - </cc:Work> - <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"> - <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/> - <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/> - <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/> - </cc:License> - </rdf:RDF> - </metadata> -</svg> diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 23e55c51..2763cca2 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -42,6 +42,7 @@ #include <QDir> #include <QLibraryInfo> #include <QDebug> +#include <locale> #include "FileSystem.h" #include "net/NetJob.h" @@ -454,6 +455,7 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const return QString("%1%").arg(lang.percentTranslated(), 3, 'f', 1); } } + qWarning("TranslationModel::data not implemented when role is DisplayRole"); } case Qt::ToolTipRole: { @@ -526,34 +528,34 @@ Language * TranslationsModel::findLanguage(const QString& key) } } +void TranslationsModel::setUseSystemLocale(bool useSystemLocale) +{ + APPLICATION->settings()->set("UseSystemLocale", useSystemLocale); + QLocale::setDefault(QLocale(useSystemLocale ? QString::fromStdString(std::locale().name()) : defaultLangCode)); +} + bool TranslationsModel::selectLanguage(QString key) { - QString &langCode = key; + QString& langCode = key; auto langPtr = findLanguage(key); - if (langCode.isEmpty()) - { + if (langCode.isEmpty()) { d->no_language_set = true; } - if(!langPtr) - { + if (!langPtr) { qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; langCode = defaultLangCode; - } - else - { + } else { langCode = langPtr->key; } // uninstall existing translators if there are any - if (d->m_app_translator) - { + if (d->m_app_translator) { QCoreApplication::removeTranslator(d->m_app_translator.get()); d->m_app_translator.reset(); } - if (d->m_qt_translator) - { + if (d->m_qt_translator) { QCoreApplication::removeTranslator(d->m_qt_translator.get()); d->m_qt_translator.reset(); } @@ -563,8 +565,9 @@ bool TranslationsModel::selectLanguage(QString key) * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. * This function is not reentrant. */ - QLocale locale = QLocale(langCode); - QLocale::setDefault(locale); + QLocale::setDefault( + QLocale(APPLICATION->settings()->get("UseSystemLocale").toBool() ? QString::fromStdString(std::locale().name()) : langCode)); + // if it's the default UI language, finish if(langCode == defaultLangCode) diff --git a/launcher/translations/TranslationsModel.h b/launcher/translations/TranslationsModel.h index 3abf84e6..cff23ce7 100644 --- a/launcher/translations/TranslationsModel.h +++ b/launcher/translations/TranslationsModel.h @@ -20,17 +20,16 @@ struct Language; -class TranslationsModel : public QAbstractListModel -{ +class TranslationsModel : public QAbstractListModel { Q_OBJECT -public: - explicit TranslationsModel(QString path, QObject *parent = 0); + public: + explicit TranslationsModel(QString path, QObject* parent = 0); virtual ~TranslationsModel(); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex & parent) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent) const override; bool selectLanguage(QString key); void updateLanguage(QString key); @@ -38,27 +37,27 @@ public: QString selectedLanguage(); void downloadIndex(); + void setUseSystemLocale(bool useSystemLocale); -private: - Language *findLanguage(const QString & key); + private: + Language* findLanguage(const QString& key); void reloadLocalFiles(); void downloadTranslation(QString key); void downloadNext(); // hide copy constructor - TranslationsModel(const TranslationsModel &) = delete; + TranslationsModel(const TranslationsModel&) = delete; // hide assign op - TranslationsModel &operator=(const TranslationsModel &) = delete; + TranslationsModel& operator=(const TranslationsModel&) = delete; -private slots: + private slots: void indexReceived(); void indexFailed(QString reason); void dlFailed(QString reason); void dlGood(); - void translationDirChanged(const QString &path); + void translationDirChanged(const QString& path); - -private: /* data */ + private: /* data */ struct Private; std::unique_ptr<Private> d; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index eeb78c53..da572fc3 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -43,102 +43,100 @@ #include "FileSystem.h" #include "MainWindow.h" +#include "ui/dialogs/ExportToModListDialog.h" #include "ui_MainWindow.h" -#include <QVariant> -#include <QUrl> #include <QDir> #include <QFileInfo> +#include <QUrl> +#include <QVariant> -#include <QKeyEvent> #include <QAction> #include <QActionGroup> #include <QApplication> #include <QButtonGroup> +#include <QFileDialog> #include <QHBoxLayout> #include <QHeaderView> +#include <QInputDialog> +#include <QKeyEvent> +#include <QLabel> #include <QMainWindow> -#include <QStatusBar> -#include <QToolBar> -#include <QWidget> #include <QMenu> #include <QMenuBar> #include <QMessageBox> -#include <QFileDialog> -#include <QInputDialog> -#include <QLabel> -#include <QToolButton> -#include <QWidgetAction> #include <QProgressDialog> #include <QShortcut> +#include <QStatusBar> +#include <QToolBar> +#include <QToolButton> +#include <QWidget> +#include <QWidgetAction> #include <BaseInstance.h> +#include <BuildConfig.h> +#include <DesktopServices.h> #include <InstanceList.h> -#include <minecraft/MinecraftInstance.h> #include <MMCZip.h> +#include <SkinUtils.h> #include <icons/IconList.h> -#include <java/JavaUtils.h> #include <java/JavaInstallList.h> +#include <java/JavaUtils.h> #include <launch/LaunchTask.h> +#include <minecraft/MinecraftInstance.h> #include <minecraft/auth/AccountList.h> -#include <SkinUtils.h> -#include <BuildConfig.h> -#include <net/NetJob.h> #include <net/Download.h> +#include <net/NetJob.h> #include <news/NewsChecker.h> #include <tools/BaseProfiler.h> #include <updater/ExternalUpdater.h> -#include <DesktopServices.h> -#include "InstanceWindow.h" #include "InstancePageProvider.h" +#include "InstanceWindow.h" #include "JavaCommon.h" #include "LaunchController.h" -#include "ui/instanceview/InstanceProxyModel.h" -#include "ui/instanceview/InstanceView.h" -#include "ui/instanceview/InstanceDelegate.h" -#include "ui/widgets/LabeledToolButton.h" -#include "ui/dialogs/NewInstanceDialog.h" -#include "ui/dialogs/NewsDialog.h" -#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/AboutDialog.h" -#include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/IconPickerDialog.h" #include "ui/dialogs/CopyInstanceDialog.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ExportInstanceDialog.h" -#include "ui/dialogs/ExportMrPackDialog.h" +#include "ui/dialogs/ExportPackDialog.h" +#include "ui/dialogs/IconPickerDialog.h" #include "ui/dialogs/ImportResourceDialog.h" +#include "ui/dialogs/NewInstanceDialog.h" +#include "ui/dialogs/NewsDialog.h" +#include "ui/dialogs/ProgressDialog.h" +#include "ui/instanceview/InstanceDelegate.h" +#include "ui/instanceview/InstanceProxyModel.h" +#include "ui/instanceview/InstanceView.h" #include "ui/themes/ITheme.h" #include "ui/themes/ThemeManager.h" +#include "ui/widgets/LabeledToolButton.h" -#include "minecraft/mod/tasks/LocalResourceParse.h" +#include "minecraft/WorldList.h" #include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" -#include "minecraft/WorldList.h" +#include "minecraft/mod/tasks/LocalResourceParse.h" #include "KonamiCode.h" -#include "InstanceImportTask.h" #include "InstanceCopyTask.h" +#include "InstanceImportTask.h" #include "MMCTime.h" namespace { -QString profileInUseFilter(const QString & profile, bool used) +QString profileInUseFilter(const QString& profile, bool used) { - if(used) - { + if (used) { return QObject::tr("%1 (in use)").arg(profile); - } - else - { + } else { return profile; } } -} +} // namespace -MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); @@ -184,7 +182,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction()); ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars); - } // set the menu for the folders help, accounts, and export tool buttons @@ -205,6 +202,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi auto exportInstanceMenu = new QMenu(this); exportInstanceMenu->addAction(ui->actionExportInstanceZip); exportInstanceMenu->addAction(ui->actionExportInstanceMrPack); + exportInstanceMenu->addAction(ui->actionExportInstanceFlamePack); + exportInstanceMenu->addAction(ui->actionExportInstanceToModList); ui->actionExportInstance->setMenu(exportInstanceMenu); } @@ -230,7 +229,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") { ui->mainToolBar->addAction(ui->actionCloseWindow); } - } // add the toolbar toggles to the view menu @@ -300,9 +298,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged); view->setModel(proxymodel); - view->setSourceOfGroupCollapseStatus([](const QString & groupName)->bool { - return APPLICATION->instances()->isGroupCollapsed(groupName); - }); + view->setSourceOfGroupCollapseStatus( + [](const QString& groupName) -> bool { return APPLICATION->instances()->isGroupCollapsed(groupName); }); connect(view, &InstanceView::groupStateChanged, APPLICATION->instances().get(), &InstanceList::on_GroupStateChanged); ui->horizontalLayout->addWidget(view); } @@ -348,7 +345,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi statusBar()->addPermanentWidget(m_statusCenter, 0); // Add "manage accounts" button, right align - QWidget *spacer = new QWidget(); + QWidget* spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); ui->mainToolBar->insertWidget(ui->actionAccountsButton, spacer); @@ -360,21 +357,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // Update the menu when the active account changes. // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. // Template hell sucks... - connect( - APPLICATION->accounts().get(), - &AccountList::defaultAccountChanged, - [this] { - defaultAccountChanged(); - } - ); - connect( - APPLICATION->accounts().get(), - &AccountList::listChanged, - [this] - { - repopulateAccountsMenu(); - } - ); + connect(APPLICATION->accounts().get(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); }); + connect(APPLICATION->accounts().get(), &AccountList::listChanged, [this] { repopulateAccountsMenu(); }); // Show initial account defaultAccountChanged(); @@ -415,9 +399,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // macOS always has a native menu bar, so these fixes are not applicable // Other systems may or may not have a native menu bar (most do not - it seems like only Ubuntu Unity does) #ifndef Q_OS_MAC -void MainWindow::keyReleaseEvent(QKeyEvent *event) +void MainWindow::keyReleaseEvent(QKeyEvent* event) { - if(event->key()==Qt::Key_Alt && !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()) + if (event->key() == Qt::Key_Alt && !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()) ui->menuBar->setVisible(!ui->menuBar->isVisible()); else QMainWindow::keyReleaseEvent(event); @@ -426,7 +410,6 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) void MainWindow::retranslateUi() { - if (m_selectedInstance) { m_statusLeft->setText(m_selectedInstance->getStatusbarDescription()); } else { @@ -436,7 +419,7 @@ void MainWindow::retranslateUi() ui->retranslateUi(this); MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount(); - if(defaultAccount) { + if (defaultAccount) { auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); ui->actionAccountsButton->setText(profileLabel); } @@ -456,14 +439,12 @@ void MainWindow::retranslateUi() } } -MainWindow::~MainWindow() -{ -} +MainWindow::~MainWindow() {} -QMenu * MainWindow::createPopupMenu() +QMenu* MainWindow::createPopupMenu() { QMenu* filteredMenu = QMainWindow::createPopupMenu(); - filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() ); + filteredMenu->removeAction(ui->mainToolBar->toggleViewAction()); filteredMenu->addAction(ui->actionLockToolbars); @@ -477,10 +458,11 @@ void MainWindow::lockToolbars(bool state) APPLICATION->settings()->set("ToolbarsLocked", state); } - void MainWindow::konamiTriggered() { - QString gradient = " stop:0 rgba(125, 0, 0, 255), stop:0.166 rgba(125, 125, 0, 255), stop:0.333 rgba(0, 125, 0, 255), stop:0.5 rgba(0, 125, 125, 255), stop:0.666 rgba(0, 0, 125, 255), stop:0.833 rgba(125, 0, 125, 255), stop:1 rgba(125, 0, 0, 255));"; + QString gradient = + " stop:0 rgba(125, 0, 0, 255), stop:0.166 rgba(125, 125, 0, 255), stop:0.333 rgba(0, 125, 0, 255), stop:0.5 rgba(0, 125, 125, " + "255), stop:0.666 rgba(0, 0, 125, 255), stop:0.833 rgba(125, 0, 125, 255), stop:1 rgba(125, 0, 0, 255));"; QString stylesheet = "background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + gradient; if (ui->mainToolBar->styleSheet() == stylesheet) { ui->mainToolBar->setStyleSheet(""); @@ -499,16 +481,15 @@ void MainWindow::konamiTriggered() } } -void MainWindow::showInstanceContextMenu(const QPoint &pos) +void MainWindow::showInstanceContextMenu(const QPoint& pos) { - QList<QAction *> actions; + QList<QAction*> actions; - QAction *actionSep = new QAction("", this); + QAction* actionSep = new QAction("", this); actionSep->setSeparator(true); bool onInstance = view->indexAt(pos).isValid(); - if (onInstance) - { + if (onInstance) { // reuse the file menu actions actions = ui->fileMenu->actions(); @@ -522,21 +503,18 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) // add header actions.prepend(actionSep); - QAction *actionVoid = new QAction(m_selectedInstance->name(), this); + QAction* actionVoid = new QAction(m_selectedInstance->name(), this); actionVoid->setEnabled(false); actions.prepend(actionVoid); - } - else - { + } else { auto group = view->groupNameAt(pos); - QAction *actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this); + QAction* actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this); actionVoid->setEnabled(false); - QAction *actionCreateInstance = new QAction(tr("Create instance"), this); + QAction* actionCreateInstance = new QAction(tr("Create instance"), this); actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip()); - if(!group.isNull()) - { + if (!group.isNull()) { QVariantMap data; data["group"] = group; actionCreateInstance->setData(data); @@ -547,9 +525,8 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) actions.prepend(actionSep); actions.prepend(actionVoid); actions.append(actionCreateInstance); - if(!group.isNull()) - { - QAction *actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this); + if (!group.isNull()) { + QAction* actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this); QVariantMap data; data["group"] = group; actionDeleteGroup->setData(data); @@ -580,39 +557,27 @@ void MainWindow::updateToolsMenu() ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning); ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); - QMenu *launchMenu = ui->actionLaunchInstance->menu(); - if (launchMenu) - { + QMenu* launchMenu = ui->actionLaunchInstance->menu(); + if (launchMenu) { launchMenu->clear(); - } - else - { + } else { launchMenu = new QMenu(this); } - QAction *normalLaunch = launchMenu->addAction(tr("Launch")); + QAction* normalLaunch = launchMenu->addAction(tr("Launch")); normalLaunch->setShortcut(QKeySequence::Open); - QAction *normalLaunchOffline = launchMenu->addAction(tr("Launch Offline")); + QAction* normalLaunchOffline = launchMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); - QAction *normalLaunchDemo = launchMenu->addAction(tr("Launch Demo")); + QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo")); normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); - if (m_selectedInstance) - { + if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch()); normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch()); - connect(normalLaunch, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, true, false); - }); - connect(normalLaunchOffline, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, false, false); - }); - connect(normalLaunchDemo, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, false, true); - }); - } - else - { + connect(normalLaunch, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, true, false); }); + connect(normalLaunchOffline, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, false); }); + connect(normalLaunchDemo, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, true); }); + } else { normalLaunch->setDisabled(true); normalLaunchOffline->setDisabled(true); normalLaunchDemo->setDisabled(true); @@ -626,35 +591,25 @@ void MainWindow::updateToolsMenu() QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); - for (auto profiler : APPLICATION->profilers().values()) - { - QAction *profilerAction = launchMenu->addAction(profiler->name()); - QAction *profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name())); + for (auto profiler : APPLICATION->profilers().values()) { + QAction* profilerAction = launchMenu->addAction(profiler->name()); + QAction* profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name())); QString error; - if (!profiler->check(&error)) - { + if (!profiler->check(&error)) { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); profilerAction->setToolTip(profilerToolTip); profilerOfflineAction->setToolTip(profilerToolTip); - } - else if (m_selectedInstance) - { + } else if (m_selectedInstance) { profilerAction->setEnabled(m_selectedInstance->canLaunch()); profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); - connect(profilerAction, &QAction::triggered, [this, profiler]() - { - APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); - }); - connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() - { - APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); - }); - } - else - { + connect(profilerAction, &QAction::triggered, + [this, profiler]() { APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); }); + connect(profilerOfflineAction, &QAction::triggered, + [this, profiler]() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); }); + } else { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); } @@ -664,7 +619,7 @@ void MainWindow::updateToolsMenu() void MainWindow::updateThemeMenu() { - QMenu *themeMenu = ui->actionChangeTheme->menu(); + QMenu* themeMenu = ui->actionChangeTheme->menu(); if (themeMenu) { themeMenu->clear(); @@ -674,10 +629,10 @@ void MainWindow::updateThemeMenu() auto themes = APPLICATION->getValidApplicationThemes(); - QActionGroup* themesGroup = new QActionGroup( this ); + QActionGroup* themesGroup = new QActionGroup(this); for (auto* theme : themes) { - QAction * themeAction = themeMenu->addAction(theme->name()); + QAction* themeAction = themeMenu->addAction(theme->name()); themeAction->setCheckable(true); if (APPLICATION->settings()->get("ApplicationTheme").toString() == theme->id()) { @@ -699,7 +654,7 @@ void MainWindow::repopulateAccountsMenu() ui->accountsMenu->clear(); // NOTE: this is done so the accounts button text is not set to the accounts menu title - QMenu *accountsButtonMenu = ui->actionAccountsButton->menu(); + QMenu* accountsButtonMenu = ui->actionAccountsButton->menu(); if (accountsButtonMenu) { accountsButtonMenu->clear(); } else { @@ -711,11 +666,9 @@ void MainWindow::repopulateAccountsMenu() MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); QString active_profileId = ""; - if (defaultAccount) - { + if (defaultAccount) { // this can be called before accountMenuButton exists - if (ui->actionAccountsButton) - { + if (ui->actionAccountsButton) { auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse()); ui->actionAccountsButton->setText(profileLabel); } @@ -723,38 +676,31 @@ void MainWindow::repopulateAccountsMenu() QActionGroup* accountsGroup = new QActionGroup(this); - if (accounts->count() <= 0) - { + if (accounts->count() <= 0) { ui->actionNoAccountsAdded->setEnabled(false); ui->accountsMenu->addAction(ui->actionNoAccountsAdded); - } - else - { + } else { // TODO: Nicer way to iterate? - for (int i = 0; i < accounts->count(); i++) - { + for (int i = 0; i < accounts->count(); i++) { MinecraftAccountPtr account = accounts->at(i); auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); - QAction *action = new QAction(profileLabel, this); + QAction* action = new QAction(profileLabel, this); action->setData(i); action->setCheckable(true); action->setActionGroup(accountsGroup); - if (defaultAccount == account) - { + if (defaultAccount == account) { action->setChecked(true); } auto face = account->getFace(); - if(!face.isNull()) { + if (!face.isNull()) { action->setIcon(face); - } - else { + } else { action->setIcon(APPLICATION->getThemedIcon("noaccount")); } const int highestNumberKey = 9; - if(i<highestNumberKey) - { + if (i < highestNumberKey) { action->setShortcut(QKeySequence(tr("Ctrl+%1").arg(i + 1))); } @@ -781,8 +727,7 @@ void MainWindow::repopulateAccountsMenu() void MainWindow::updatesAllowedChanged(bool allowed) { - if(!BuildConfig.UPDATER_ENABLED) - { + if (!BuildConfig.UPDATER_ENABLED) { return; } ui->actionCheckUpdate->setEnabled(allowed); @@ -793,7 +738,7 @@ void MainWindow::updatesAllowedChanged(bool allowed) */ void MainWindow::changeActiveAccount() { - QAction *sAction = (QAction *)sender(); + QAction* sAction = (QAction*)sender(); // Profile's associated Mojang username if (sAction->data().type() != QVariant::Type::Int) @@ -802,7 +747,7 @@ void MainWindow::changeActiveAccount() QVariant data = sAction->data(); bool valid = false; int index = data.toInt(&valid); - if(!valid) { + if (!valid) { index = -1; } auto accounts = APPLICATION->accounts(); @@ -817,15 +762,13 @@ void MainWindow::defaultAccountChanged() MinecraftAccountPtr account = APPLICATION->accounts()->defaultAccount(); // FIXME: this needs adjustment for MSA - if (account && account->profileName() != "") - { + if (account && account->profileName() != "") { auto profileLabel = profileInUseFilter(account->profileName(), account->isInUse()); ui->actionAccountsButton->setText(profileLabel); auto face = account->getFace(); - if(face.isNull()) { + if (face.isNull()) { ui->actionAccountsButton->setIcon(APPLICATION->getThemedIcon("noaccount")); - } - else { + } else { ui->actionAccountsButton->setIcon(face); } return; @@ -836,33 +779,30 @@ void MainWindow::defaultAccountChanged() ui->actionAccountsButton->setText(tr("Accounts")); } -bool MainWindow::eventFilter(QObject *obj, QEvent *ev) +bool MainWindow::eventFilter(QObject* obj, QEvent* ev) { - if (obj == view) - { - if (ev->type() == QEvent::KeyPress) - { + if (obj == view) { + if (ev->type() == QEvent::KeyPress) { secretEventFilter->input(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; - case Qt::Key_F5: - refreshInstances(); - return true; - case Qt::Key_F2: - on_actionRenameInstance_triggered(); - return true; - default: - break; + 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; + case Qt::Key_F5: + refreshInstances(); + return true; + case Qt::Key_F2: + on_actionRenameInstance_triggered(); + return true; + default: + break; } } } @@ -871,23 +811,17 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev) void MainWindow::updateNewsLabel() { - if (m_newsChecker->isLoadingNews()) - { + if (m_newsChecker->isLoadingNews()) { newsLabel->setText(tr("Loading news...")); newsLabel->setEnabled(false); ui->actionMoreNews->setVisible(false); - } - else - { + } else { QList<NewsEntryPtr> entries = m_newsChecker->getNewsEntries(); - if (entries.length() > 0) - { + if (entries.length() > 0) { newsLabel->setText(entries[0]->title); newsLabel->setEnabled(true); ui->actionMoreNews->setVisible(true); - } - else - { + } else { newsLabel->setText(tr("No news available.")); newsLabel->setEnabled(false); ui->actionMoreNews->setVisible(false); @@ -895,7 +829,7 @@ void MainWindow::updateNewsLabel() } } -QList<int> stringToIntList(const QString &string) +QList<int> stringToIntList(const QString& string) { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QStringList split = string.split(',', Qt::SkipEmptyParts); @@ -903,17 +837,15 @@ QList<int> stringToIntList(const QString &string) QStringList split = string.split(',', QString::SkipEmptyParts); #endif QList<int> out; - for (int i = 0; i < split.size(); ++i) - { + for (int i = 0; i < split.size(); ++i) { out.append(split.at(i).toInt()); } return out; } -QString intListToString(const QList<int> &list) +QString intListToString(const QList<int>& list) { QStringList slist; - for (int i = 0; i < list.size(); ++i) - { + for (int i = 0; i < list.size(); ++i) { slist.append(QString::number(list.at(i))); } return slist.join(','); @@ -927,47 +859,30 @@ void MainWindow::onCatToggled(bool state) void MainWindow::setCatBackground(bool enabled) { - if (enabled) { - view->setStyleSheet(QString(R"( -InstanceView -{ - background-image: url(:/backgrounds/%1); - background-attachment: fixed; - background-clip: padding; - background-position: bottom right; - background-repeat: none; - background-color:palette(base); -})") - .arg(ThemeManager::getCatImage())); - } else { - view->setStyleSheet(QString()); - } + view->setPaintCat(enabled); + view->viewport()->repaint(); } -void MainWindow::runModalTask(Task *task) +void MainWindow::runModalTask(Task* task) { - connect(task, &Task::failed, [this](QString reason) - { - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - }); - connect(task, &Task::succeeded, [this, task]() - { - QStringList warnings = task->warnings(); - if(warnings.count()) - { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); - } - }); - connect(task, &Task::aborted, [this] - { - CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); - }); + connect(task, &Task::failed, + [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); + connect(task, &Task::succeeded, [this, task]() { + QStringList warnings = task->warnings(); + if (warnings.count()) { + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } + }); + connect(task, &Task::aborted, [this] { + CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information) + ->show(); + }); ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); loadDialog.execWithTask(task); } -void MainWindow::instanceFromInstanceTask(InstanceTask *rawTask) +void MainWindow::instanceFromInstanceTask(InstanceTask* rawTask) { unique_qobject_ptr<Task> task(APPLICATION->instances()->wrapInstanceTask(rawTask)); runModalTask(task.get()); @@ -994,52 +909,43 @@ void MainWindow::finalizeInstance(InstancePtr inst) { view->updateGeometries(); setSelectedInstanceById(inst->id()); - if (APPLICATION->accounts()->anyAccountIsValid()) - { + if (APPLICATION->accounts()->anyAccountIsValid()) { ProgressDialog loadDialog(this); auto update = inst->createUpdateTask(Net::Mode::Online); - connect(update.get(), &Task::failed, [this](QString reason) - { - QString error = QString("Instance load failed: %1").arg(reason); - CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); - }); - if(update) - { + connect(update.get(), &Task::failed, [this](QString reason) { + QString error = QString("Instance load failed: %1").arg(reason); + CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show(); + }); + if (update) { loadDialog.setSkipButton(true, tr("Abort")); loadDialog.execWithTask(update.get()); } - } - else - { - CustomMessageBox::selectable( - this, - tr("Error"), - tr("The launcher cannot download Minecraft or update instances unless you have at least " - "one account added.\nPlease add your Mojang or Minecraft account."), - QMessageBox::Warning - )->show(); + } else { + CustomMessageBox::selectable(this, tr("Error"), + tr("The launcher cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning) + ->show(); } } void MainWindow::addInstance(QString url) { QString groupName; - do - { + do { QObject* obj = sender(); - if(!obj) + if (!obj) break; - QAction *action = qobject_cast<QAction *>(obj); - if(!action) + QAction* action = qobject_cast<QAction*>(obj); + if (!action) break; auto map = action->data().toMap(); - if(!map.contains("group")) + if (!map.contains("group")) break; groupName = map["group"].toString(); - } while(0); + } while (0); - if(groupName.isEmpty()) - { + if (groupName.isEmpty()) { groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString(); } @@ -1049,9 +955,8 @@ void MainWindow::addInstance(QString url) APPLICATION->settings()->set("LastUsedGroupForNewInstance", newInstDlg.instGroup()); - InstanceTask * creationTask = newInstDlg.extractTask(); - if(creationTask) - { + InstanceTask* creationTask = newInstDlg.extractTask(); + if (creationTask) { instanceFromInstanceTask(creationTask); } } @@ -1076,7 +981,7 @@ void MainWindow::processURLs(QList<QUrl> urls) break; } - auto localFileName = QDir::toNativeSeparators(url.toLocalFile()) ; + auto localFileName = QDir::toNativeSeparators(url.toLocalFile()); QFileInfo localFileInfo(localFileName); auto type = ResourceUtils::identify(localFileInfo); @@ -1145,8 +1050,7 @@ void MainWindow::on_actionChangeInstIcon_triggered() IconPickerDialog dlg(this); dlg.execWithSelection(m_selectedInstance->iconKey()); - if (dlg.result() == QDialog::Accepted) - { + if (dlg.result() == QDialog::Accepted) { m_selectedInstance->setIconKey(dlg.selectedIconKey); auto icon = APPLICATION->icons()->getIcon(dlg.selectedIconKey); ui->actionChangeInstIcon->setIcon(icon); @@ -1156,8 +1060,7 @@ void MainWindow::on_actionChangeInstIcon_triggered() void MainWindow::iconUpdated(QString icon) { - if (icon == m_currentInstIcon) - { + if (icon == m_currentInstIcon) { auto icon = APPLICATION->icons()->getIcon(m_currentInstIcon); ui->actionChangeInstIcon->setIcon(icon); changeIconButton->setIcon(icon); @@ -1172,13 +1075,12 @@ void MainWindow::updateInstanceToolIcon(QString new_icon) changeIconButton->setIcon(icon); } -void MainWindow::setSelectedInstanceById(const QString &id) +void MainWindow::setSelectedInstanceById(const QString& id) { if (id.isNull()) return; const QModelIndex index = APPLICATION->instances()->getInstanceIndexById(id); - if (index.isValid()) - { + if (index.isValid()) { QModelIndex selectionIndex = proxymodel->mapFromSource(index); view->selectionModel()->setCurrentIndex(selectionIndex, QItemSelectionModel::ClearAndSelect); updateStatusCenter(); @@ -1200,8 +1102,7 @@ void MainWindow::on_actionChangeInstGroup_triggered() name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok); name = name.simplified(); - if (ok) - { + if (ok) { APPLICATION->instances()->setInstanceGroup(instId, name); } } @@ -1209,21 +1110,19 @@ void MainWindow::on_actionChangeInstGroup_triggered() void MainWindow::deleteGroup() { QObject* obj = sender(); - if(!obj) + if (!obj) return; - QAction *action = qobject_cast<QAction *>(obj); - if(!action) + QAction* action = qobject_cast<QAction*>(obj); + if (!action) return; auto map = action->data().toMap(); - if(!map.contains("group")) + if (!map.contains("group")) return; QString groupName = map["group"].toString(); - if(!groupName.isEmpty()) - { - 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) - { + if (!groupName.isEmpty()) { + 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) { APPLICATION->instances()->deleteGroup(groupName); } } @@ -1259,12 +1158,9 @@ void MainWindow::on_actionViewCentralModsFolder_triggered() void MainWindow::checkForUpdates() { - if(BuildConfig.UPDATER_ENABLED) - { + if (BuildConfig.UPDATER_ENABLED) { APPLICATION->triggerUpdateCheck(); - } - else - { + } else { qWarning() << "Updater not set up. Cannot check for updates."; } } @@ -1292,7 +1188,17 @@ void MainWindow::globalSettingsClosed() void MainWindow::on_actionEditInstance_triggered() { - APPLICATION->showInstanceWindow(m_selectedInstance); + if (!m_selectedInstance) + return; + + if (m_selectedInstance->canEdit()) { + APPLICATION->showInstanceWindow(m_selectedInstance); + } else { + CustomMessageBox::selectable(this, tr("Instance not editable"), + tr("This instance is not editable. It may be broken, invalid, or too old. Check logs for details."), + QMessageBox::Critical) + ->show(); + } } void MainWindow::on_actionManageAccounts_triggered() @@ -1354,7 +1260,8 @@ void MainWindow::newsButtonClicked() news_dialog.exec(); } -void MainWindow::onCatChanged(int) { +void MainWindow::onCatChanged(int) +{ setCatBackground(APPLICATION->settings()->get("TheCat").toBool()); } @@ -1385,14 +1292,15 @@ void MainWindow::on_actionDeleteInstance_triggered() auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id); if (!linkedInstances.empty()) { - response = CustomMessageBox::selectable( - this, tr("There are linked instances"), - tr("The following instance(s) might reference files in this instance:\n\n" - "%1\n\n" - "Deleting it could break the other instance(s), \n\n" - "Do you wish to proceed?", nullptr, linkedInstances.count()).arg(linkedInstances.join("\n")), - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No - )->exec(); + response = CustomMessageBox::selectable(this, tr("There are linked instances"), + tr("The following instance(s) might reference files in this instance:\n\n" + "%1\n\n" + "Deleting it could break the other instance(s), \n\n" + "Do you wish to proceed?", + nullptr, linkedInstances.count()) + .arg(linkedInstances.join("\n")), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); if (response != QMessageBox::Yes) return; } @@ -1407,8 +1315,7 @@ void MainWindow::on_actionDeleteInstance_triggered() void MainWindow::on_actionExportInstanceZip_triggered() { - if (m_selectedInstance) - { + if (m_selectedInstance) { ExportInstanceDialog dlg(m_selectedInstance, this); dlg.exec(); } @@ -1416,31 +1323,60 @@ void MainWindow::on_actionExportInstanceZip_triggered() void MainWindow::on_actionExportInstanceMrPack_triggered() { - if (m_selectedInstance) - { - ExportMrPackDialog dlg(m_selectedInstance, this); + if (m_selectedInstance) { + ExportPackDialog dlg(m_selectedInstance, this); dlg.exec(); } } +void MainWindow::on_actionExportInstanceToModList_triggered() +{ + if (m_selectedInstance) { + ExportToModListDialog dlg(m_selectedInstance, this); + dlg.exec(); + } +} + +void MainWindow::on_actionExportInstanceFlamePack_triggered() +{ + if (m_selectedInstance) { + auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get()); + if (instance) { + QString errorMsg; + if (instance->getPackProfile()->getComponent("org.quiltmc.quilt-loader")) { + errorMsg = tr("Quilt is currently not supported by CurseForge modpacks."); + } else if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft"); + cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") { + errorMsg = tr("Snapshots are currently not supported by CurseForge modpacks."); + } + if (!errorMsg.isEmpty()) { + QMessageBox msgBox; + msgBox.setText(errorMsg); + msgBox.exec(); + return; + } + ExportPackDialog dlg(m_selectedInstance, this, ModPlatform::ResourceProvider::FLAME); + dlg.exec(); + } + } +} + void MainWindow::on_actionRenameInstance_triggered() { - if (m_selectedInstance) - { + if (m_selectedInstance) { view->edit(view->currentIndex()); } } void MainWindow::on_actionViewSelectedInstFolder_triggered() { - if (m_selectedInstance) - { + if (m_selectedInstance) { QString str = m_selectedInstance->instanceRoot(); DesktopServices::openDirectory(QDir(str).absolutePath()); } } -void MainWindow::closeEvent(QCloseEvent *event) +void MainWindow::closeEvent(QCloseEvent* event) { // Save the window state and geometry. APPLICATION->settings()->set("MainWindowState", saveState().toBase64()); @@ -1452,8 +1388,7 @@ void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) - { + if (event->type() == QEvent::LanguageChange) { retranslateUi(); } QMainWindow::changeEvent(event); @@ -1473,8 +1408,7 @@ void MainWindow::instanceActivated(QModelIndex index) void MainWindow::on_actionLaunchInstance_triggered() { - if(m_selectedInstance && !m_selectedInstance->isRunning()) - { + if (m_selectedInstance && !m_selectedInstance->isRunning()) { APPLICATION->launch(m_selectedInstance); } } @@ -1486,187 +1420,185 @@ void MainWindow::activateInstance(InstancePtr instance) void MainWindow::on_actionLaunchInstanceOffline_triggered() { - if (m_selectedInstance) - { + if (m_selectedInstance) { APPLICATION->launch(m_selectedInstance, false); } } void MainWindow::on_actionLaunchInstanceDemo_triggered() { - if (m_selectedInstance) - { + if (m_selectedInstance) { APPLICATION->launch(m_selectedInstance, false, true); } } void MainWindow::on_actionKillInstance_triggered() { - if(m_selectedInstance && m_selectedInstance->isRunning()) - { + if (m_selectedInstance && m_selectedInstance->isRunning()) { APPLICATION->kill(m_selectedInstance); } } void MainWindow::on_actionCreateInstanceShortcut_triggered() { - if (m_selectedInstance) - { - auto desktopPath = FS::getDesktopDir(); - if (desktopPath.isEmpty()) { - // TODO come up with an alternative solution (open "save file" dialog) - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); - return; - } + if (!m_selectedInstance) + return; + auto desktopPath = FS::getDesktopDir(); + if (desktopPath.isEmpty()) { + // TODO come up with an alternative solution (open "save file" dialog) + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); + return; + } + QString desktopFilePath; + QString appPath = QApplication::applicationFilePath(); + QString iconPath; + QStringList args; #if defined(Q_OS_MACOS) - QString appPath = QApplication::applicationFilePath(); - if (appPath.startsWith("/private/var/")) { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); - return; - } + appPath = QApplication::applicationFilePath(); + if (appPath.startsWith("/private/var/")) { + QMessageBox::critical(this, tr("Create instance shortcut"), + tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); + return; + } - if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()), - appPath, { "--launch", m_selectedInstance->id() }, - m_selectedInstance->name(), "")) { - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); - } - else - { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); - } + auto pIcon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (pIcon == nullptr) { + pIcon = APPLICATION->icons()->icon("grass"); + } + + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "Icon.icns"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); + return; + } + + QIcon icon = pIcon->icon(); + + bool success = icon.pixmap(1024, 1024).save(iconPath, "ICNS"); + iconFile.close(); + + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance Application"), tr("Failed to create icon for Application.")); + return; + } #elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) - QString appPath = QApplication::applicationFilePath(); - if (appPath.startsWith("/tmp/.mount_")) { - // AppImage! - appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); - if (appPath.isEmpty()) - { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); - } - else if (appPath.endsWith("/")) - { - appPath.chop(1); - } + if (appPath.startsWith("/tmp/.mount_")) { + // AppImage! + appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); + if (appPath.isEmpty()) { + QMessageBox::critical(this, tr("Create instance shortcut"), + tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); + } else if (appPath.endsWith("/")) { + appPath.chop(1); } + } - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) - { - icon = APPLICATION->icons()->icon("grass"); - } + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } - QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) - { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); - iconFile.close(); + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); + iconFile.close(); - if (!success) - { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + + if (DesktopServices::isFlatpak()) { + desktopFilePath = FS::PathCombine(desktopPath, FS::RemoveInvalidFilenameChars(m_selectedInstance->name()) + ".desktop"); + QFileDialog fileDialog; + // workaround to make sure the portal file dialog opens in the desktop directory + fileDialog.setDirectoryUrl(desktopPath); + desktopFilePath = fileDialog.getSaveFileName(this, tr("Create Shortcut"), desktopFilePath, tr("Desktop Entries (*.desktop)")); + if (desktopFilePath.isEmpty()) + return; // file dialog canceled by user + appPath = "flatpak"; + QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME; + flatpakAppId.remove(".desktop"); + args.append({ "run", flatpakAppId }); + } - QString desktopFilePath = FS::PathCombine(desktopPath, m_selectedInstance->name() + ".desktop"); - QStringList args; - if (DesktopServices::isFlatpak()) { - QFileDialog fileDialog; - // workaround to make sure the portal file dialog opens in the desktop directory - fileDialog.setDirectoryUrl(desktopPath); - desktopFilePath = fileDialog.getSaveFileName( - this, tr("Create Shortcut"), desktopFilePath, - tr("Desktop Entries (*.desktop)")); - if (desktopFilePath.isEmpty()) - return; // file dialog canceled by user - appPath = "flatpak"; - QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME; - flatpakAppId.remove(".desktop"); - args.append({ "run", flatpakAppId }); - } - args.append({ "--launch", m_selectedInstance->id() }); - if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) { - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); - } - else - { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); - } #elif defined(Q_OS_WIN) - auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); - if (icon == nullptr) - { - icon = APPLICATION->icons()->icon("grass"); - } + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) { + icon = APPLICATION->icons()->icon("grass"); + } - QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); + iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); - // part of fix for weird bug involving the window icon being replaced - // dunno why it happens, but this 2-line fix seems to be enough, so w/e - auto appIcon = APPLICATION->getThemedIcon("logo"); + // part of fix for weird bug involving the window icon being replaced + // dunno why it happens, but this 2-line fix seems to be enough, so w/e + auto appIcon = APPLICATION->getThemedIcon("logo"); - QFile iconFile(iconPath); - if (!iconFile.open(QFile::WriteOnly)) - { - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } - bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); - iconFile.close(); + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); + iconFile.close(); - // restore original window icon - QGuiApplication::setWindowIcon(appIcon); + // restore original window icon + QGuiApplication::setWindowIcon(appIcon); - if (!success) - { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); - return; - } + if (!success) { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } - if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()), - QApplication::applicationFilePath(), { "--launch", m_selectedInstance->id() }, - m_selectedInstance->name(), iconPath)) { - QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); - } - else - { - iconFile.remove(); - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); - } #else - QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); + return; #endif + args.append({ "--launch", m_selectedInstance->id() }); + if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) { +#if not defined(Q_OS_MACOS) + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); +#else + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance!")); +#endif + } else { +#if not defined(Q_OS_MACOS) + iconFile.remove(); +#endif + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); } } void MainWindow::taskEnd() { - QObject *sender = QObject::sender(); + QObject* sender = QObject::sender(); if (sender == m_versionLoadTask) m_versionLoadTask = NULL; sender->deleteLater(); } -void MainWindow::startTask(Task *task) +void MainWindow::startTask(Task* task) { connect(task, SIGNAL(succeeded()), SLOT(taskEnd())); connect(task, SIGNAL(failed(QString)), SLOT(taskEnd())); task->start(); } -void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex &previous) +void MainWindow::instanceChanged(const QModelIndex& current, const QModelIndex& previous) { - if (!current.isValid()) - { + if (!current.isValid()) { APPLICATION->settings()->set("SelectedInstance", QString()); selectionBad(); return; @@ -1676,8 +1608,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & } QString id = current.data(InstanceList::InstanceIDRole).toString(); m_selectedInstance = APPLICATION->instances()->getInstanceById(id); - if (m_selectedInstance) - { + if (m_selectedInstance) { ui->instanceToolBar->setEnabled(true); setInstanceActionsEnabled(true); ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); @@ -1687,7 +1618,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & // Disable demo-mode if not available. auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get()); if (instance) { - ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo()); + ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo()); } ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); @@ -1702,9 +1633,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); - } - else - { + } else { ui->instanceToolBar->setEnabled(false); setInstanceActionsEnabled(false); ui->actionLaunchInstance->setEnabled(false); @@ -1722,12 +1651,11 @@ void MainWindow::instanceSelectRequest(QString id) setSelectedInstanceById(id); } -void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +void MainWindow::instanceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { auto current = view->selectionModel()->currentIndex(); QItemSelection test(topLeft, bottomRight); - if (test.contains(current)) - { + if (test.contains(current)) { instanceChanged(current, current); } } @@ -1751,34 +1679,28 @@ void MainWindow::selectionBad() void MainWindow::checkInstancePathForProblems() { QString instanceFolder = APPLICATION->settings()->get("InstanceDir").toString(); - if (FS::checkProblemticPathJava(QDir(instanceFolder))) - { + if (FS::checkProblemticPathJava(QDir(instanceFolder))) { QMessageBox warning(this); warning.setText(tr("Your instance folder contains \'!\' and this is known to cause Java problems!")); - warning.setInformativeText( - tr( - "You have now two options: <br/>" - " - change the instance folder in the settings <br/>" - " - move this installation of %1 to a different folder" - ).arg(BuildConfig.LAUNCHER_DISPLAYNAME) - ); + warning.setInformativeText(tr("You have now two options: <br/>" + " - change the instance folder in the settings <br/>" + " - move this installation of %1 to a different folder") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME)); warning.setDefaultButton(QMessageBox::Ok); warning.exec(); } - auto tempFolderText = tr("This is a problem: <br/>" - " - The launcher will likely be deleted without warning by the operating system <br/>" - " - close the launcher now and extract it to a real location, not a temporary folder"); + auto tempFolderText = + tr("This is a problem: <br/>" + " - The launcher will likely be deleted without warning by the operating system <br/>" + " - close the launcher now and extract it to a real location, not a temporary folder"); QString pathfoldername = QDir(instanceFolder).absolutePath(); - if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) - { + if (pathfoldername.contains("Rar$", Qt::CaseInsensitive)) { QMessageBox warning(this); warning.setText(tr("Your instance folder contains \'Rar$\' - that means you haven't extracted the launcher archive!")); warning.setInformativeText(tempFolderText); warning.setDefaultButton(QMessageBox::Ok); warning.exec(); - } - else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/")) - { + } 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())); warning.setInformativeText(tempFolderText); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 3bb20c4a..27c2756f 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -157,6 +157,8 @@ private slots: inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); } void on_actionExportInstanceZip_triggered(); void on_actionExportInstanceMrPack_triggered(); + void on_actionExportInstanceFlamePack_triggered(); + void on_actionExportInstanceToModList_triggered(); void on_actionRenameInstance_triggered(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 113dfc1e..e4421d40 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -479,6 +479,22 @@ <string>Modrinth (mrpack)</string> </property> </action> + <action name="actionExportInstanceFlamePack"> + <property name="icon"> + <iconset theme="flame"/> + </property> + <property name="text"> + <string>CurseForge (zip)</string> + </property> + </action> + <action name="actionExportInstanceToModList"> + <property name="icon"> + <iconset theme="new"/> + </property> + <property name="text"> + <string>Mod List</string> + </property> + </action> <action name="actionCreateInstanceShortcut"> <property name="icon"> <iconset theme="shortcut"> diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 8ecd91a9..cc41c394 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -35,24 +35,26 @@ */ #include "ExportInstanceDialog.h" -#include "ui_ExportInstanceDialog.h" #include <BaseInstance.h> #include <MMCZip.h> #include <QFileDialog> -#include <QMessageBox> #include <QFileSystemModel> +#include <QMessageBox> +#include "FileIgnoreProxy.h" +#include "ui_ExportInstanceDialog.h" -#include <QSortFilterProxyModel> +#include <FileSystem.h> +#include <icons/IconList.h> #include <QDebug> +#include <QFileInfo> #include <QSaveFile> +#include <QSortFilterProxyModel> #include <QStack> -#include <QFileInfo> -#include "SeparatorPrefixTree.h" +#include <functional> #include "Application.h" -#include <icons/IconList.h> -#include <FileSystem.h> +#include "SeparatorPrefixTree.h" -ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent) +ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent) : QDialog(parent), ui(new Ui::ExportInstanceDialog), m_instance(instance) { ui->setupUi(this); @@ -60,8 +62,12 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent model->setIconProvider(&icons); auto root = instance->instanceRoot(); proxyModel = new FileIgnoreProxy(root, this); - loadPackIgnore(); proxyModel->setSourceModel(model); + auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot()); + proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") }); + proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); + loadPackIgnore(); + ui->treeView->setModel(proxyModel); ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root))); ui->treeView->sortByColumn(0, Qt::AscendingOrder); @@ -133,11 +139,9 @@ bool ExportInstanceDialog::doExport() SaveIcon(m_instance); - auto & blocked = proxyModel->blockedPaths(); - using std::placeholders::_1; auto files = QFileInfoList(); if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files, - std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) { + std::bind(&FileIgnoreProxy::filterFile, proxyModel, std::placeholders::_1))) { QMessageBox::warning(this, tr("Error"), tr("Unable to export instance")); return false; } diff --git a/launcher/ui/dialogs/ExportMrPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp index 60ecefd5..2abe2805 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.cpp +++ b/launcher/ui/dialogs/ExportPackDialog.cpp @@ -16,11 +16,13 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -#include "ExportMrPackDialog.h" +#include "ExportPackDialog.h" #include "minecraft/mod/ModFolderModel.h" +#include "modplatform/ModIndex.h" +#include "modplatform/flame/FlamePackExportTask.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/ProgressDialog.h" -#include "ui_ExportMrPackDialog.h" +#include "ui_ExportPackDialog.h" #include <QFileDialog> #include <QFileSystemModel> @@ -32,17 +34,24 @@ #include "MMCZip.h" #include "modplatform/modrinth/ModrinthPackExportTask.h" -ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) - : QDialog(parent), instance(instance), ui(new Ui::ExportMrPackDialog) +ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) + : QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider) { ui->setupUi(this); ui->name->setText(instance->name()); - ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); + if (m_provider == ModPlatform::ResourceProvider::MODRINTH) { + ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); + setWindowTitle("Export Modrinth Pack"); + } else { + setWindowTitle("Export CurseForge Pack"); + ui->version->setText(""); + ui->summaryLabel->setText("Author"); + } // ensure a valid pack is generated // the name and version fields mustn't be empty - connect(ui->name, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate); - connect(ui->version, &QLineEdit::textEdited, this, &ExportMrPackDialog::validate); + connect(ui->name, &QLineEdit::textEdited, this, &ExportPackDialog::validate); + connect(ui->version, &QLineEdit::textEdited, this, &ExportPackDialog::validate); // the instance name can technically be empty validate(); @@ -52,8 +61,9 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) // use the game root - everything outside cannot be exported const QDir root(instance->gameRoot()); proxy = new FileIgnoreProxy(instance->gameRoot(), this); + proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports" }); + proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); proxy->setSourceModel(model); - proxy->setFilterRegularExpression("^(?!(\\.DS_Store)|([tT]humbs\\.db)).+$"); const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden); @@ -65,6 +75,7 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); if (mcInstance) { + mcInstance->loaderModList()->update(); const QDir index = mcInstance->loaderModList()->indexDir(); if (index.exists()) proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath())); @@ -82,43 +93,54 @@ ExportMrPackDialog::ExportMrPackDialog(InstancePtr instance, QWidget* parent) headerView->setSectionResizeMode(0, QHeaderView::Stretch); } -ExportMrPackDialog::~ExportMrPackDialog() +ExportPackDialog::~ExportPackDialog() { delete ui; } -void ExportMrPackDialog::done(int result) +void ExportPackDialog::done(int result) { if (result == Accepted) { const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); - const QString output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), - FS::PathCombine(QDir::homePath(), filename + ".mrpack"), - "Modrinth pack (*.mrpack *.zip)", nullptr); + QString output; + if (m_provider == ModPlatform::ResourceProvider::MODRINTH) + output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), + FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)", + nullptr); + else + output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), + FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr); if (output.isEmpty()) return; - - ModrinthPackExportTask task(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, - [this](const QString& path) { return proxy->blockedPaths().covers(path); }); - - connect(&task, &Task::failed, + Task* task; + if (m_provider == ModPlatform::ResourceProvider::MODRINTH) + task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, + std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); + else + task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, + std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); + + connect(task, &Task::failed, [this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); - connect(&task, &Task::aborted, [this] { + connect(task, &Task::aborted, [this] { CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information) ->show(); }); + connect(task, &Task::finished, [task] { task->deleteLater(); }); ProgressDialog progress(this); progress.setSkipButton(true, tr("Abort")); - if (progress.execWithTask(&task) != QDialog::Accepted) + if (progress.execWithTask(task) != QDialog::Accepted) return; } QDialog::done(result); } -void ExportMrPackDialog::validate() +void ExportPackDialog::validate() { - const bool invalid = ui->name->text().isEmpty() || ui->version->text().isEmpty(); + const bool invalid = + ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty()); ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid); } diff --git a/launcher/ui/dialogs/ExportMrPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h index 1c70c4ae..830c24d2 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.h +++ b/launcher/ui/dialogs/ExportPackDialog.h @@ -22,24 +22,28 @@ #include "BaseInstance.h" #include "FastFileIconProvider.h" #include "FileIgnoreProxy.h" +#include "modplatform/ModIndex.h" namespace Ui { -class ExportMrPackDialog; +class ExportPackDialog; } -class ExportMrPackDialog : public QDialog { +class ExportPackDialog : public QDialog { Q_OBJECT public: - explicit ExportMrPackDialog(InstancePtr instance, QWidget* parent = nullptr); - ~ExportMrPackDialog(); + explicit ExportPackDialog(InstancePtr instance, + QWidget* parent = nullptr, + ModPlatform::ResourceProvider provider = ModPlatform::ResourceProvider::MODRINTH); + ~ExportPackDialog(); void done(int result) override; void validate(); private: const InstancePtr instance; - Ui::ExportMrPackDialog* ui; + Ui::ExportPackDialog* ui; FileIgnoreProxy* proxy; FastFileIconProvider icons; + const ModPlatform::ResourceProvider m_provider; }; diff --git a/launcher/ui/dialogs/ExportMrPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui index 9a789737..3976e28f 100644 --- a/launcher/ui/dialogs/ExportMrPackDialog.ui +++ b/launcher/ui/dialogs/ExportPackDialog.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>ExportMrPackDialog</class> - <widget class="QDialog" name="ExportMrPackDialog"> + <class>ExportPackDialog</class> + <widget class="QDialog" name="ExportPackDialog"> <property name="geometry"> <rect> <x>0</x> @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>Export Modrinth Pack</string> + <string>Export Pack</string> </property> <property name="sizeGripEnabled"> <bool>true</bool> @@ -24,7 +24,7 @@ </property> <layout class="QGridLayout" name="gridLayout"> <item row="3" column="0"> - <widget class="QLabel" name="versionLabel"> + <widget class="QLabel" name="summaryLabel"> <property name="text"> <string>Summary</string> </property> @@ -41,7 +41,7 @@ </widget> </item> <item row="1" column="0"> - <widget class="QLabel" name="summaryLabel"> + <widget class="QLabel" name="versionLabel"> <property name="text"> <string>Version</string> </property> @@ -57,6 +57,7 @@ </property> </widget> </item> + </layout> </widget> </item> @@ -103,7 +104,7 @@ <connection> <sender>buttonBox</sender> <signal>accepted()</signal> - <receiver>ExportMrPackDialog</receiver> + <receiver>ExportPackDialog</receiver> <slot>accept()</slot> <hints> <hint type="sourcelabel"> @@ -119,7 +120,7 @@ <connection> <sender>buttonBox</sender> <signal>rejected()</signal> - <receiver>ExportMrPackDialog</receiver> + <receiver>ExportPackDialog</receiver> <slot>reject()</slot> <hints> <hint type="sourcelabel"> diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp new file mode 100644 index 00000000..c811bfe6 --- /dev/null +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "ExportToModListDialog.h" +#include <QCheckBox> +#include <QComboBox> +#include <QTextEdit> +#include "FileSystem.h" +#include "Markdown.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/mod/ModFolderModel.h" +#include "modplatform/helpers/ExportToModList.h" +#include "ui_ExportToModListDialog.h" + +#include <QFileDialog> +#include <QFileSystemModel> +#include <QJsonDocument> +#include <QMessageBox> +#include <QPushButton> + +const QHash<ExportToModList::Formats, QString> ExportToModListDialog::exampleLines = { + { ExportToModList::HTML, "<li><a href=\"{url}\">{name}</a> [{version}] by {authors}</li>" }, + { ExportToModList::MARKDOWN, "[{name}]({url}) [{version}] by {authors}" }, + { ExportToModList::PLAINTXT, "{name} ({url}) [{version}] by {authors}" }, + { ExportToModList::JSON, "{\"name\":\"{name}\",\"url\":\"{url}\",\"version\":\"{version}\",\"authors\":\"{authors}\"}," }, + { ExportToModList::CSV, "{name},{url},{version},\"{authors}\"" }, +}; + +ExportToModListDialog::ExportToModListDialog(InstancePtr instance, QWidget* parent) + : QDialog(parent), m_template_changed(false), name(instance->name()), ui(new Ui::ExportToModListDialog) +{ + ui->setupUi(this); + enableCustom(false); + + MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); + if (mcInstance) { + mcInstance->loaderModList()->update(); + connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, [this, mcInstance]() { + m_allMods = mcInstance->loaderModList()->allMods(); + triggerImp(); + }); + } + + connect(ui->formatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ExportToModListDialog::formatChanged); + connect(ui->authorsCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); + connect(ui->versionCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); + connect(ui->urlCheckBox, &QCheckBox::stateChanged, this, &ExportToModListDialog::trigger); + connect(ui->authorsButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Authors); }); + connect(ui->versionButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Version); }); + connect(ui->urlButton, &QPushButton::clicked, this, [this](bool) { addExtra(ExportToModList::Url); }); + connect(ui->templateText, &QTextEdit::textChanged, this, [this] { + if (ui->templateText->toPlainText() != exampleLines[format]) + ui->formatComboBox->setCurrentIndex(5); + else + triggerImp(); + }); + connect(ui->copyButton, &QPushButton::clicked, this, [this](bool) { + this->ui->finalText->selectAll(); + this->ui->finalText->copy(); + }); +} + +ExportToModListDialog::~ExportToModListDialog() +{ + delete ui; +} + +void ExportToModListDialog::formatChanged(int index) +{ + switch (index) { + case 0: { + enableCustom(false); + ui->resultText->show(); + format = ExportToModList::HTML; + break; + } + case 1: { + enableCustom(false); + ui->resultText->show(); + format = ExportToModList::MARKDOWN; + break; + } + case 2: { + enableCustom(false); + ui->resultText->hide(); + format = ExportToModList::PLAINTXT; + break; + } + case 3: { + enableCustom(false); + ui->resultText->hide(); + format = ExportToModList::JSON; + break; + } + case 4: { + enableCustom(false); + ui->resultText->hide(); + format = ExportToModList::CSV; + break; + } + case 5: { + m_template_changed = true; + enableCustom(true); + ui->resultText->hide(); + format = ExportToModList::CUSTOM; + break; + } + } + triggerImp(); +} + +void ExportToModListDialog::triggerImp() +{ + if (format == ExportToModList::CUSTOM) { + ui->finalText->setPlainText(ExportToModList::exportToModList(m_allMods, ui->templateText->toPlainText())); + return; + } + auto opt = 0; + if (ui->authorsCheckBox->isChecked()) + opt |= ExportToModList::Authors; + if (ui->versionCheckBox->isChecked()) + opt |= ExportToModList::Version; + if (ui->urlCheckBox->isChecked()) + opt |= ExportToModList::Url; + auto txt = ExportToModList::exportToModList(m_allMods, format, static_cast<ExportToModList::OptionalData>(opt)); + ui->finalText->setPlainText(txt); + switch (format) { + case ExportToModList::CUSTOM: + return; + case ExportToModList::HTML: + ui->resultText->setHtml(txt); + break; + case ExportToModList::MARKDOWN: + ui->resultText->setHtml(markdownToHTML(txt)); + break; + case ExportToModList::PLAINTXT: + break; + case ExportToModList::JSON: + break; + case ExportToModList::CSV: + break; + } + auto exampleLine = exampleLines[format]; + if (!m_template_changed && ui->templateText->toPlainText() != exampleLine) + ui->templateText->setPlainText(exampleLine); +} + +void ExportToModListDialog::done(int result) +{ + if (result == Accepted) { + const QString filename = FS::RemoveInvalidFilenameChars(name); + const QString output = + QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + extension()), + "File (*.txt *.html *.md *.json *.csv)", nullptr); + + if (output.isEmpty()) + return; + FS::write(output, ui->finalText->toPlainText().toUtf8()); + } + + QDialog::done(result); +} + +QString ExportToModListDialog::extension() +{ + switch (format) { + case ExportToModList::HTML: + return ".html"; + case ExportToModList::MARKDOWN: + return ".md"; + case ExportToModList::PLAINTXT: + return ".txt"; + case ExportToModList::CUSTOM: + return ".txt"; + case ExportToModList::JSON: + return ".json"; + case ExportToModList::CSV: + return ".csv"; + } + return ".txt"; +} + +void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) +{ + if (format != ExportToModList::CUSTOM) + return; + switch (option) { + case ExportToModList::Authors: + ui->templateText->insertPlainText("{authors}"); + break; + case ExportToModList::Url: + ui->templateText->insertPlainText("{url}"); + break; + case ExportToModList::Version: + ui->templateText->insertPlainText("{version}"); + break; + } +} +void ExportToModListDialog::enableCustom(bool enabled) +{ + ui->authorsCheckBox->setHidden(enabled); + ui->versionCheckBox->setHidden(enabled); + ui->urlCheckBox->setHidden(enabled); + + ui->authorsButton->setHidden(!enabled); + ui->versionButton->setHidden(!enabled); + ui->urlButton->setHidden(!enabled); +} diff --git a/launcher/ui/dialogs/ExportToModListDialog.h b/launcher/ui/dialogs/ExportToModListDialog.h new file mode 100644 index 00000000..9886ae5a --- /dev/null +++ b/launcher/ui/dialogs/ExportToModListDialog.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QDialog> +#include <QList> +#include "BaseInstance.h" +#include "minecraft/mod/Mod.h" +#include "modplatform/helpers/ExportToModList.h" + +namespace Ui { +class ExportToModListDialog; +} + +class ExportToModListDialog : public QDialog { + Q_OBJECT + + public: + explicit ExportToModListDialog(InstancePtr instance, QWidget* parent = nullptr); + ~ExportToModListDialog(); + + void done(int result) override; + + protected slots: + void formatChanged(int index); + void triggerImp(); + void trigger(int) { triggerImp(); }; + void addExtra(ExportToModList::OptionalData option); + + private: + QString extension(); + void enableCustom(bool enabled); + QList<Mod*> m_allMods; + bool m_template_changed; + QString name; + ExportToModList::Formats format = ExportToModList::Formats::HTML; + Ui::ExportToModListDialog* ui; + static const QHash<ExportToModList::Formats, QString> exampleLines; +}; diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui new file mode 100644 index 00000000..25eb4342 --- /dev/null +++ b/launcher/ui/dialogs/ExportToModListDialog.ui @@ -0,0 +1,240 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ExportToModListDialog</class> + <widget class="QDialog" name="ExportToModListDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>650</width> + <height>446</height> + </rect> + </property> + <property name="windowTitle"> + <string>Export Pack to ModList</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0"> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Settings</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QComboBox" name="formatComboBox"> + <item> + <property name="text"> + <string>HTML</string> + </property> + </item> + <item> + <property name="text"> + <string>Markdown</string> + </property> + </item> + <item> + <property name="text"> + <string>Plaintext</string> + </property> + </item> + <item> + <property name="text"> + <string>JSON</string> + </property> + </item> + <item> + <property name="text"> + <string>CSV</string> + </property> + </item> + <item> + <property name="text"> + <string>Custom</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="templateGroup"> + <property name="title"> + <string>Template</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QTextEdit" name="templateText"/> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QGroupBox" name="optionsGroup"> + <property name="title"> + <string>Optional Info</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <widget class="QCheckBox" name="versionCheckBox"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="authorsCheckBox"> + <property name="text"> + <string>Authors</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="urlCheckBox"> + <property name="text"> + <string>URL</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="versionButton"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="authorsButton"> + <property name="text"> + <string>Authors</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="urlButton"> + <property name="text"> + <string>URL</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="lineWidth"> + <number>1</number> + </property> + <property name="text"> + <string>Format</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>Result</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPlainTextEdit" name="finalText"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>143</height> + </size> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QTextBrowser" name="resultText"> + <property name="openExternalLinks"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="warningLabel"> + <property name="text"> + <string>This depends on the mods' metadata. To ensure it is available, run an update on the instance. Installing the updates isn't necessary.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="copyButton"> + <property name="text"> + <string>Copy</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ExportToModListDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>334</x> + <y>435</y> + </hint> + <hint type="destinationlabel"> + <x>324</x> + <y>206</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ExportToModListDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>324</x> + <y>390</y> + </hint> + <hint type="destinationlabel"> + <x>324</x> + <y>206</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index aafaf220..7b9bb944 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -54,7 +54,7 @@ #include <utility> #include "ui/widgets/PageContainer.h" -#include "ui/pages/modplatform/VanillaPage.h" +#include "ui/pages/modplatform/CustomPage.h" #include "ui/pages/modplatform/atlauncher/AtlPage.h" #include "ui/pages/modplatform/legacy_ftb/Page.h" #include "ui/pages/modplatform/flame/FlamePage.h" @@ -162,7 +162,7 @@ QList<BasePage *> NewInstanceDialog::getPages() importPage = new ImportPage(this); - pages.append(new VanillaPage(this)); + pages.append(new CustomPage(this)); pages.append(importPage); pages.append(new AtlPage(this)); if (APPLICATION->capabilities() & Application::SupportsFlame) diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 246a0fd4..4243e291 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -34,6 +34,7 @@ */ #include "ProgressDialog.h" +#include <QPoint> #include "ui_ProgressDialog.h" #include <limits> @@ -66,8 +67,9 @@ ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Pr ui->taskProgressScrollArea->setHidden(true); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true); - setSkipButton(false); changeProgress(0, 100); + updateSize(true); + setSkipButton(false); } void ProgressDialog::setSkipButton(bool present, QString label) @@ -93,25 +95,39 @@ ProgressDialog::~ProgressDialog() delete ui; } -void ProgressDialog::updateSize() +void ProgressDialog::updateSize(bool recenterParent) { QSize lastSize = this->size(); - QSize qSize = QSize(480, minimumSizeHint().height()); - - // if the current window is too small - if ((lastSize != qSize) && (lastSize.height() < qSize.height())) + QPoint lastPos = this->pos(); + int minHeight = ui->globalStatusDetailsLabel->minimumSize().height() + (ui->verticalLayout->spacing() * 2); + minHeight += ui->globalProgressBar->minimumSize().height() + ui->verticalLayout->spacing(); + if (!ui->taskProgressScrollArea->isHidden()) + minHeight += ui->taskProgressScrollArea->minimumSizeHint().height() + ui->verticalLayout->spacing(); + if (ui->skipButton->isVisible()) + minHeight += ui->skipButton->height() + ui->verticalLayout->spacing(); + minHeight = std::max(minHeight, 60); + QSize minSize = QSize(480, minHeight); + + setMinimumSize(minSize); + adjustSize(); + + QSize newSize = this->size(); + // if the current window is a different size + auto parent = this->parentWidget(); + if (recenterParent && parent) { + auto newX = std::max(0, parent->x() + ((parent->width() - newSize.width()) / 2)); + auto newY = std::max(0, parent->y() + ((parent->height() - newSize.height()) / 2)); + this->move(newX, newY); + } + else if (lastSize != newSize) { - resize(qSize); - - // keep the dialog in the center after a resize - this->move( - this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2, - this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2 - ); + // center on old position after resize + QSize sizeDiff = lastSize - newSize; // last size was smaller, the results should be negative + auto newX = std::max(0, lastPos.x() + (sizeDiff.width() / 2)); + auto newY = std::max(0, lastPos.y() + (sizeDiff.height() / 2)); + this->move(newX, newY); } - setMinimumSize(qSize); - } int ProgressDialog::execWithTask(Task* task) @@ -201,7 +217,9 @@ void ProgressDialog::onTaskSucceeded() void ProgressDialog::changeStatus(const QString& status) { ui->globalStatusLabel->setText(task->getStatus()); + ui->globalStatusLabel->adjustSize(); ui->globalStatusDetailsLabel->setText(task->getDetails()); + ui->globalStatusDetailsLabel->adjustSize(); updateSize(); } diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index fc9a0fbc..f062be08 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -62,7 +62,7 @@ public: explicit ProgressDialog(QWidget *parent = 0); ~ProgressDialog(); - void updateSize(); + void updateSize(bool recenterParent = false); int execWithTask(Task* task); int execWithTask(std::unique_ptr<Task> &&task); diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 4f59f560..b17eced3 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -43,6 +43,8 @@ #include "ui/pages/modplatform/flame/FlameResourcePages.h" #include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" +#include "modplatform/flame/FlameAPI.h" +#include "modplatform/modrinth/ModrinthAPI.h" #include "ui/widgets/PageContainer.h" namespace ResourceDownload { @@ -281,8 +283,11 @@ QList<BasePage*> ModDownloadDialog::getPages() { QList<BasePage*> pages; - pages.append(ModrinthModPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) + auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value(); + + if (ModrinthAPI::validateModLoaders(loaders)) + pages.append(ModrinthModPage::create(this, *m_instance)); + if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) pages.append(FlameModPage::create(this, *m_instance)); m_selectedPage = dynamic_cast<ModPage*>(pages[0]); diff --git a/launcher/ui/instanceview/AccessibleInstanceView.cpp b/launcher/ui/instanceview/AccessibleInstanceView.cpp index 7de3ac72..2e7b8300 100644 --- a/launcher/ui/instanceview/AccessibleInstanceView.cpp +++ b/launcher/ui/instanceview/AccessibleInstanceView.cpp @@ -248,8 +248,8 @@ bool AccessibleInstanceView::selectColumn(int column) if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) { return false; } - // fallthrough intentional } + /* fallthrough */ case QAbstractItemView::ContiguousSelection: { if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { view()->clearSelection(); diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index fbeffe35..1911dd59 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -48,6 +48,7 @@ #include <QAccessible> #include "VisualGroup.h" +#include "ui/themes/ThemeManager.h" #include <QDebug> #include <Application.h> @@ -73,6 +74,7 @@ InstanceView::InstanceView(QWidget *parent) setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setAcceptDrops(true); setAutoScroll(true); + setPaintCat(APPLICATION->settings()->get("TheCat").toBool()); } InstanceView::~InstanceView() @@ -498,12 +500,34 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent *event) } } -void InstanceView::paintEvent(QPaintEvent *event) +void InstanceView::setPaintCat(bool visible) +{ + m_catVisible = visible; + if (visible) + m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage())); + else + m_catPixmap = QPixmap(); +} + +void InstanceView::paintEvent(QPaintEvent* event) { executeDelayedItemsLayout(); QPainter painter(this->viewport()); + if (m_catVisible) { + int widWidth = this->viewport()->width(); + int widHeight = this->viewport()->height(); + if (m_catPixmap.width() < widWidth) + widWidth = m_catPixmap.width(); + if (m_catPixmap.height() < widHeight) + widHeight = m_catPixmap.height(); + auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio); + QRect rectOfPixmap = pixmap.rect(); + rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight()); + painter.drawPixmap(rectOfPixmap.topLeft(), pixmap); + } + #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QStyleOptionViewItem option; initViewItemOption(&option); diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index ac338274..36405675 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -85,10 +85,8 @@ public: virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - int spacing() const - { - return m_spacing; - }; + int spacing() const { return m_spacing; }; + void setPaintCat(bool visible); public slots: virtual void updateGeometries() override; @@ -139,6 +137,8 @@ private: int m_currentItemsPerRow = -1; int m_currentCursorColumn= -1; mutable QCache<int, QRect> geometryCache; + bool m_catVisible = false; + QPixmap m_catPixmap; // point where the currently active mouse action started in geometry coordinates QPoint m_pressedPosition; diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index dca1b3a6..668aa007 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -81,6 +81,8 @@ APIPage::APIPage(QWidget *parent) : connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLPlaceholder); // This function needs to be called even when the ComboBox's index is still in its default state. updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex()); + // NOTE: this allows http://, but we replace that with https later anyway + ui->metaURL->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->metaURL)); ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); ui->msaClientID->setValidator(new QRegularExpressionValidator(validMSAClientID, ui->msaClientID)); ui->flameKey->setValidator(new QRegularExpressionValidator(validFlameKey, ui->flameKey)); @@ -163,7 +165,7 @@ void APIPage::applySettings() QString msaClientID = ui->msaClientID->text(); s->set("MSAClientIDOverride", msaClientID); - QUrl metaURL = ui->metaURL->text(); + QUrl metaURL(ui->metaURL->text()); // Add required trailing slash if (!metaURL.isEmpty() && !metaURL.path().endsWith('/')) { diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index d9116bfc..26408f44 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -169,7 +169,7 @@ <item> <widget class="QCheckBox" name="metadataDisableBtn"> <property name="toolTip"> - <string>Disable using metadata provided by mod providers (like Modrinth or Curseforge) for mods.</string> + <string>Disable using metadata provided by mod providers (like Modrinth or CurseForge) for mods.</string> </property> <property name="text"> <string>Disable using metadata for mods</string> diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 173bcb66..12038f88 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -151,9 +151,6 @@ void ExternalResourcesPage::retranslate() void ExternalResourcesPage::itemActivated(const QModelIndex&) { - if (!m_controlsEnabled) - return; - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); } @@ -197,9 +194,6 @@ bool ExternalResourcesPage::eventFilter(QObject* obj, QEvent* ev) void ExternalResourcesPage::addItem() { - if (!m_controlsEnabled) - return; - auto list = GuiUtil::BrowseForFiles( helpPage(), tr("Select %1", "Select whatever type of files the page contains. Example: 'Loader Mods'").arg(displayName()), m_fileSelectionFilter.arg(displayName()), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); @@ -213,9 +207,6 @@ void ExternalResourcesPage::addItem() void ExternalResourcesPage::removeItem() { - if (!m_controlsEnabled) - return; - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); int count = 0; @@ -259,23 +250,37 @@ void ExternalResourcesPage::removeItem() void ExternalResourcesPage::removeItems(const QItemSelection& selection) { + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = CustomMessageBox::selectable(this, "Confirm Delete", + "If you remove this resource while the game is running it may crash your game.\n" + "Are you sure you want to do this?", + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } m_model->deleteResources(selection.indexes()); } void ExternalResourcesPage::enableItem() { - if (!m_controlsEnabled) - return; - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); m_model->setResourceEnabled(selection.indexes(), EnableAction::ENABLE); } void ExternalResourcesPage::disableItem() { - if (!m_controlsEnabled) - return; + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = CustomMessageBox::selectable(this, "Confirm disable", + "If you disable this resource while the game is running it may crash your game.\n" + "Are you sure you want to do this?", + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + if (response != QMessageBox::Yes) + return; + } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE); } diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index 6c0a12cb..97d922d8 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -73,7 +73,5 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { QString m_fileSelectionFilter; QString m_viewFilter; - bool m_controlsEnabled = true; - std::shared_ptr<Setting> m_wide_bar_setting = nullptr; }; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index f676361c..3c836691 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -157,6 +157,17 @@ <string>Try to check or update all selected resources (all resources if none are selected)</string> </property> </action> + <action name="actionVisitItemPage"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Visit mod's page</string> + </property> + <property name="toolTip"> + <string>Go to mods home page</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 08977841..943ff17f 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -60,17 +60,13 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent) m_settings = inst->settings(); ui->setupUi(this); - // As the signal will (probably) not be triggered once we click edit, let's update it manually instead. - updateRunningStatus(m_instance->isRunning()); - - connect(m_instance, &BaseInstance::runningStatusChanged, this, &InstanceSettingsPage::updateRunningStatus); connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked); connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); - connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &InstanceSettingsPage::changeInstanceAccount); + connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &InstanceSettingsPage::changeInstanceAccount); loadSettings(); - updateThresholds(); } @@ -85,12 +81,12 @@ void InstanceSettingsPage::globalSettingsButtonClicked(bool) case 0: APPLICATION->ShowGlobalSettings(this, "java-settings"); return; - case 1: - APPLICATION->ShowGlobalSettings(this, "minecraft-settings"); - return; case 2: APPLICATION->ShowGlobalSettings(this, "custom-commands"); return; + default: + APPLICATION->ShowGlobalSettings(this, "minecraft-settings"); + return; } } @@ -523,8 +519,3 @@ void InstanceSettingsPage::updateThresholds() ui->labelMaxMemIcon->setPixmap(pix); } } - -void InstanceSettingsPage::updateRunningStatus(bool running) -{ - setEnabled(!running); -} diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 0438fe3b..036b4181 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -79,8 +79,7 @@ public: void updateThresholds(); -private slots: - void updateRunningStatus(bool running); + private slots: void on_javaDetectBtn_clicked(); void on_javaTestBtn_clicked(); void on_javaBrowseBtn_clicked(); diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index e0a7314f..0fc0c986 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -69,7 +69,6 @@ class NoBigComboBoxStyle : public QProxyStyle { private: NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {} - }; ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent) @@ -91,13 +90,13 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi // NOTE: GTK2 themes crash with the proxy style. // This seems like an upstream bug, so there's not much else that can be done. - if (!QStyleFactory::keys().contains("gtk2")){ + if (!QStyleFactory::keys().contains("gtk2")) { auto comboStyle = NoBigComboBoxStyle::getInstance(ui->versionsComboBox->style()); ui->versionsComboBox->setStyle(comboStyle); } ui->reloadButton->setVisible(false); - connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool){ + connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool) { ui->reloadButton->setVisible(false); m_loaded = false; @@ -205,7 +204,7 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin { Q_ASSERT(inst->isManagedPack()); connect(ui->versionsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(suggestVersion())); - connect(ui->updateButton, &QPushButton::pressed, this, &ModrinthManagedPackPage::update); + connect(ui->updateButton, &QPushButton::clicked, this, &ModrinthManagedPackPage::update); } // MODRINTH @@ -226,7 +225,8 @@ void ModrinthManagedPackPage::parseManagedPack() QString id = m_inst->getManagedPackID(); - m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); + m_fetch_job->addNetAction( + Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] { QJsonParseError parse_error{}; @@ -267,7 +267,6 @@ void ModrinthManagedPackPage::parseManagedPack() if (version.version == m_inst->getManagedPackVersionName()) name = tr("%1 (Current)").arg(name); - ui->versionsComboBox->addItem(name, QVariant(version.id)); } @@ -291,6 +290,10 @@ QString ModrinthManagedPackPage::url() const void ModrinthManagedPackPage::suggestVersion() { auto index = ui->versionsComboBox->currentIndex(); + if (m_pack.versions.length() == 0) { + setFailState(); + return; + } auto version = m_pack.versions.at(index); ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8())); @@ -301,6 +304,10 @@ void ModrinthManagedPackPage::suggestVersion() void ModrinthManagedPackPage::update() { auto index = ui->versionsComboBox->currentIndex(); + if (m_pack.versions.length() == 0) { + setFailState(); + return; + } auto version = m_pack.versions.at(index); QMap<QString, QString> extra_info; @@ -332,7 +339,7 @@ FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* i { Q_ASSERT(inst->isManagedPack()); connect(ui->versionsComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(suggestVersion())); - connect(ui->updateButton, &QPushButton::pressed, this, &FlameManagedPackPage::update); + connect(ui->updateButton, &QPushButton::clicked, this, &FlameManagedPackPage::update); } void FlameManagedPackPage::parseManagedPack() @@ -429,6 +436,10 @@ QString FlameManagedPackPage::url() const void FlameManagedPackPage::suggestVersion() { auto index = ui->versionsComboBox->currentIndex(); + if (m_pack.versions.length() == 0) { + setFailState(); + return; + } auto version = m_pack.versions.at(index); ui->changelogTextBrowser->setHtml(m_api.getModFileChangelog(m_inst->getManagedPackID().toInt(), version.fileId)); @@ -439,6 +450,10 @@ void FlameManagedPackPage::suggestVersion() void FlameManagedPackPage::update() { auto index = ui->versionsComboBox->currentIndex(); + if (m_pack.versions.length() == 0) { + setFailState(); + return; + } auto version = m_pack.versions.at(index); QMap<QString, QString> extra_info; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 90e7d0d6..cef292bd 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -45,6 +45,7 @@ #include <QMenu> #include <QMessageBox> #include <QSortFilterProxyModel> +#include <algorithm> #include "Application.h" @@ -60,6 +61,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/ModFolderModel.h" +#include "modplatform/ModIndex.h" #include "modplatform/ResourceAPI.h" #include "Version.h" @@ -86,12 +88,28 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem); connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); - auto check_allow_update = [this] { - return (!m_instance || !m_instance->isRunning()) && (ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); - }; + ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); + ui->actionsToolbar->addAction(ui->actionVisitItemPage); + connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); - connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); }); + auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; + + connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] { + ui->actionUpdateItem->setEnabled(check_allow_update()); + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto mods_list = m_model->selectedMods(selection); + auto selected = std::count_if(mods_list.cbegin(), mods_list.cend(), + [](Mod* v) { return v->metadata() != nullptr || v->homeurl().size() != 0; }); + if (selected <= 1) { + ui->actionVisitItemPage->setText(tr("Visit mod's page")); + ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); + } else { + ui->actionVisitItemPage->setText(tr("Visit mods' pages")); + ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods")); + } + ui->actionVisitItemPage->setEnabled(selected != 0); + }); connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); }); @@ -101,22 +119,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> connect(mods.get(), &ModFolderModel::updateFinished, this, [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); }); - - connect(m_instance, &BaseInstance::runningStatusChanged, this, &ModFolderPage::runningStateChanged); - ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning()); } } -void ModFolderPage::runningStateChanged(bool running) -{ - ui->actionDownloadItem->setEnabled(!running); - ui->actionUpdateItem->setEnabled(!running); - ui->actionAddItem->setEnabled(!running); - ui->actionEnableItem->setEnabled(!running); - ui->actionDisableItem->setEnabled(!running); - ui->actionRemoveItem->setEnabled(!running); -} - bool ModFolderPage::shouldDisplay() const { return true; @@ -133,15 +138,23 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelI return true; } -void ModFolderPage::removeItems(const QItemSelection &selection) +void ModFolderPage::removeItems(const QItemSelection& selection) { + if (m_instance != nullptr && m_instance->isRunning()) { + auto response = CustomMessageBox::selectable(this, "Confirm Delete", + "If you remove mods while the game is running it may crash your game.\n" + "Are you sure you want to do this?", + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response != QMessageBox::Yes) + return; + } m_model->deleteMods(selection.indexes()); } void ModFolderPage::installMods() { - if (!m_controlsEnabled) - return; if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance @@ -207,8 +220,7 @@ void ModFolderPage::updateMods() message = tr("All selected mods are up-to-date! :)"); } } - CustomMessageBox::selectable(this, tr("Update checker"), message) - ->exec(); + CustomMessageBox::selectable(this, tr("Update checker"), message)->exec(); return; } @@ -275,3 +287,13 @@ bool NilModFolderPage::shouldDisplay() const { return m_model->dir().exists(); } + +void ModFolderPage::visitModPages() +{ + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + for (auto mod : m_model->selectedMods(selection)) { + auto url = mod->metaurl(); + if (!url.isEmpty()) + DesktopServices::openUrl(url); + } +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 2fc7b574..a23dcae1 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -4,6 +4,7 @@ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -59,11 +60,11 @@ class ModFolderPage : public ExternalResourcesPage { bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; private slots: - void runningStateChanged(bool running); - void removeItems(const QItemSelection &selection) override; + void removeItems(const QItemSelection& selection) override; void installMods(); void updateMods(); + void visitModPages(); protected: std::shared_ptr<ModFolderModel> m_model; diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 24bfb38d..12b371df 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -67,8 +67,6 @@ bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, const QMod void ResourcePackPage::downloadRPs() { - if (!m_controlsEnabled) - return; if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index ca368d3b..bcce5f57 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -36,6 +36,7 @@ */ #include "ScreenshotsPage.h" +#include "BuildConfig.h" #include "ui_ScreenshotsPage.h" #include <QModelIndex> @@ -96,37 +97,30 @@ public: return; if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) return; - int tries = 5; - while (tries) - { - if (!m_cache->stale(m_path)) - return; - QImage image(m_path); - if (image.isNull()) - { - QThread::msleep(500); - tries--; - continue; - } - 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(); - - QIcon icon(QPixmap::fromImage(square)); - m_cache->add(m_path, icon); - m_resultEmitter.emitResultsReady(m_path); + if (!m_cache->stale(m_path)) + return; + QImage image(m_path); + if (image.isNull()) { + m_resultEmitter.emitResultsFailed(m_path); + qDebug() << "Error loading screenshot: " + m_path + ". Perhaps too large?"; return; } - m_resultEmitter.emitResultsFailed(m_path); + 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(); + + QIcon icon(QPixmap::fromImage(square)); + m_cache->add(m_path, icon); + m_resultEmitter.emitResultsReady(m_path); } QString m_path; SharedIconCachePtr m_cache; @@ -145,9 +139,12 @@ public: m_thumbnailCache = std::make_shared<SharedIconCache>(); m_thumbnailCache->add("placeholder", APPLICATION->getThemedIcon("screenshot-placeholder")); connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); - // FIXME: the watched file set is not updated when files are removed } - virtual ~FilterModel() { m_thumbnailingPool.waitForDone(500); } + virtual ~FilterModel() { + m_thumbnailingPool.clear(); + if (!m_thumbnailingPool.waitForDone(500)) + qDebug() << "Thumbnail pool took longer than 500ms to finish"; + } virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const { auto model = sourceModel(); @@ -214,10 +211,12 @@ private slots: void fileChanged(QString filepath) { m_thumbnailCache->setStale(filepath); - thumbnailImage(filepath); // reinsert the path... watcher.removePath(filepath); - watcher.addPath(filepath); + if (QFile::exists(filepath)) { + watcher.addPath(filepath); + thumbnailImage(filepath); + } } private: @@ -380,16 +379,18 @@ void ScreenshotsPage::on_actionUpload_triggered() if (selection.isEmpty()) return; - QString text; + QUrl baseUrl(BuildConfig.IMGUR_BASE_URL); if (selection.size() > 1) - text = tr("You are about to upload %1 screenshots.\n\n" + text = tr("You are about to upload %1 screenshots to %2.\n" + "You should double-check for personal information.\n\n" "Are you sure?") - .arg(selection.size()); + .arg(QString::number(selection.size()), baseUrl.host()); else - text = - tr("You are about to upload the selected screenshot.\n\n" - "Are you sure?"); + text = tr("You are about to upload the selected screenshot to %1.\n" + "You should double-check for personal information.\n\n" + "Are you sure?") + .arg(baseUrl.host()); auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 2d0c10aa..dc8b0a05 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -46,7 +46,6 @@ #include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ResourceDownloadDialog.h" - ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget* parent) : ExternalResourcesPage(instance, model, parent) { @@ -61,8 +60,6 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<Shad void ShaderPackPage::downloadShaders() { - if (!m_controlsEnabled) - return; if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index 427aba11..e477ceda 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -69,8 +69,6 @@ bool TexturePackPage::onSelectionChanged(const QModelIndex& current, const QMode void TexturePackPage::downloadTPs() { - if (!m_controlsEnabled) - return; if (m_instance->typeName() != "Minecraft") return; // this is a null instance or a legacy instance diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 74b7ec7c..a180c804 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -40,14 +40,13 @@ #include "Application.h" -#include <QMessageBox> -#include <QLabel> +#include <QAbstractItemModel> #include <QEvent> #include <QKeyEvent> +#include <QLabel> +#include <QListView> #include <QMenu> -#include <QAbstractItemModel> #include <QMessageBox> -#include <QListView> #include <QString> #include <QUrl> @@ -55,49 +54,42 @@ #include "ui_VersionPage.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/VersionSelectDialog.h" #include "ui/dialogs/NewComponentDialog.h" #include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/VersionSelectDialog.h" #include "ui/GuiUtil.h" +#include "DesktopServices.h" +#include "Exception.h" +#include "Version.h" +#include "icons/IconList.h" #include "minecraft/PackProfile.h" #include "minecraft/auth/AccountList.h" #include "minecraft/mod/Mod.h" -#include "icons/IconList.h" -#include "Exception.h" -#include "Version.h" -#include "DesktopServices.h" #include "meta/Index.h" #include "meta/VersionList.h" -class IconProxy : public QIdentityProxyModel -{ +class IconProxy : public QIdentityProxyModel { Q_OBJECT -public: - - IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget) + public: + IconProxy(QWidget* parentWidget) : QIdentityProxyModel(parentWidget) { connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone); m_parentWidget = parentWidget; } - virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override + virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const override { QVariant var = QIdentityProxyModel::data(proxyIndex, role); int column = proxyIndex.column(); - if(column == 0 && role == Qt::DecorationRole && m_parentWidget) - { - if(!var.isNull()) - { + if (column == 0 && role == Qt::DecorationRole && m_parentWidget) { + if (!var.isNull()) { auto string = var.toString(); - if(string == "warning") - { + if (string == "warning") { return APPLICATION->getThemedIcon("status-yellow"); - } - else if(string == "error") - { + } else if (string == "error") { return APPLICATION->getThemedIcon("status-bad"); } } @@ -105,14 +97,11 @@ public: } return var; } -private slots: - void widgetGone() - { - m_parentWidget = nullptr; - } + private slots: + void widgetGone() { m_parentWidget = nullptr; } -private: - QWidget *m_parentWidget = nullptr; + private: + QWidget* m_parentWidget = nullptr; }; QIcon VersionPage::icon() const @@ -144,15 +133,14 @@ void VersionPage::closedImpl() m_wide_bar_setting->set(ui->toolBar->getVisibilityState()); } -QMenu * VersionPage::createPopupMenu() +QMenu* VersionPage::createPopupMenu() { QMenu* filteredMenu = QMainWindow::createPopupMenu(); - filteredMenu->removeAction( ui->toolBar->toggleViewAction() ); + filteredMenu->removeAction(ui->toolBar->toggleViewAction()); return filteredMenu; } -VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) - : QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst) +VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst) { ui->setupUi(this); @@ -182,10 +170,8 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent) 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); } @@ -202,18 +188,16 @@ void VersionPage::showContextMenu(const QPoint& pos) delete menu; } -void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &previous) +void VersionPage::packageCurrent(const QModelIndex& current, const QModelIndex& previous) { - if (!current.isValid()) - { + if (!current.isValid()) { ui->frame->clear(); return; } int row = current.row(); auto patch = m_profile->getComponent(row); auto severity = patch->getProblemSeverity(); - switch(severity) - { + switch (severity) { case ProblemSeverity::Warning: ui->frame->setName(tr("%1 possibly has issues.").arg(patch->getName())); break; @@ -226,16 +210,12 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex & return; } - auto &problems = patch->getProblems(); + auto& problems = patch->getProblems(); QString problemOut; - for (auto &problem: problems) - { - if(problem.m_severity == ProblemSeverity::Error) - { + for (auto& problem : problems) { + if (problem.m_severity == ProblemSeverity::Error) { problemOut += tr("Error: "); - } - else if(problem.m_severity == ProblemSeverity::Warning) - { + } else if (problem.m_severity == ProblemSeverity::Warning) { problemOut += tr("Warning: "); } problemOut += problem.m_description; @@ -244,72 +224,47 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex & ui->frame->setDescription(problemOut); } -void VersionPage::updateRunningStatus(bool running) -{ - if(controlsEnabled == running) { - controlsEnabled = !running; - updateVersionControls(); - } -} - void VersionPage::updateVersionControls() { // FIXME: this is a dirty hack auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft")); - ui->actionInstall_Forge->setEnabled(controlsEnabled); - bool supportsFabric = minecraftVersion >= Version("1.14"); - ui->actionInstall_Fabric->setEnabled(controlsEnabled && supportsFabric); + ui->actionInstall_Fabric->setEnabled(supportsFabric); bool supportsQuilt = minecraftVersion >= Version("1.14"); - ui->actionInstall_Quilt->setEnabled(controlsEnabled && supportsQuilt); + ui->actionInstall_Quilt->setEnabled(supportsQuilt); bool supportsLiteLoader = minecraftVersion <= Version("1.12.2"); - ui->actionInstall_LiteLoader->setEnabled(controlsEnabled && supportsLiteLoader); + ui->actionInstall_LiteLoader->setEnabled(supportsLiteLoader); updateButtons(); } void VersionPage::updateButtons(int row) { - if(row == -1) + 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->actionImport_Components->setEnabled(controlsEnabled); - ui->actionReload->setEnabled(controlsEnabled); - ui->actionInstall_mods->setEnabled(controlsEnabled); - ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled); - ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled); - ui->actionAdd_Agents->setEnabled(controlsEnabled); + ui->actionRemove->setEnabled(patch && patch->isRemovable()); + ui->actionMove_down->setEnabled(patch && patch->isMoveable()); + ui->actionMove_up->setEnabled(patch && patch->isMoveable()); + ui->actionChange_version->setEnabled(patch && patch->isVersionChangeable()); + ui->actionEdit->setEnabled(patch && patch->isCustom()); + ui->actionCustomize->setEnabled(patch && patch->isCustomizable()); + ui->actionRevert->setEnabled(patch && patch->isRevertible()); } bool VersionPage::reloadPackProfile() { - try - { + try { m_profile->reload(Net::Mode::Online); return true; - } - catch (const Exception &e) - { + } catch (const Exception& e) { QMessageBox::critical(this, tr("Error"), e.cause()); return false; - } - catch (...) - { - QMessageBox::critical( - this, tr("Error"), - tr("Couldn't load the instance profile.")); + } catch (...) { + QMessageBox::critical(this, tr("Error"), tr("Couldn't load the instance profile.")); return false; } } @@ -322,14 +277,12 @@ void VersionPage::on_actionReload_triggered() void VersionPage::on_actionRemove_triggered() { - if (!ui->packageView->currentIndex().isValid()) - { + if (!ui->packageView->currentIndex().isValid()) { return; } int index = ui->packageView->currentIndex().row(); auto component = m_profile->getComponent(index); - if (component->isCustom()) - { + if (component->isCustom()) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove \"%1\".\n" "This is permanent and will completely remove the custom component.\n\n" @@ -342,8 +295,7 @@ void VersionPage::on_actionRemove_triggered() return; } // FIXME: use actual model, not reloading. - if (!m_profile->remove(index)) - { + if (!m_profile->remove(index)) { QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file")); } updateButtons(); @@ -353,17 +305,16 @@ void VersionPage::on_actionRemove_triggered() void VersionPage::on_actionInstall_mods_triggered() { - if(m_container) - { + if (m_container) { m_container->selectPage("mods"); } } void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() { - auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); - if(!list.empty()) - { + auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), + APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); + if (!list.empty()) { m_profile->installJarMods(list); } updateButtons(); @@ -371,9 +322,9 @@ void VersionPage::on_actionAdd_to_Minecraft_jar_triggered() void VersionPage::on_actionReplace_Minecraft_jar_triggered() { - auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); - if(!jarPath.isEmpty()) - { + auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), + APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget()); + if (!jarPath.isEmpty()) { m_profile->installCustomJar(jarPath); } updateButtons(); @@ -407,12 +358,9 @@ void VersionPage::on_actionAdd_Agents_triggered() void VersionPage::on_actionMove_up_triggered() { - try - { + try { m_profile->move(currentRow(), PackProfile::MoveUp); - } - catch (const Exception &e) - { + } catch (const Exception& e) { QMessageBox::critical(this, tr("Error"), e.cause()); } updateButtons(); @@ -420,12 +368,9 @@ void VersionPage::on_actionMove_up_triggered() void VersionPage::on_actionMove_down_triggered() { - try - { + try { m_profile->move(currentRow(), PackProfile::MoveDown); - } - catch (const Exception &e) - { + } catch (const Exception& e) { QMessageBox::critical(this, tr("Error"), e.cause()); } updateButtons(); @@ -434,39 +379,32 @@ void VersionPage::on_actionMove_down_triggered() void VersionPage::on_actionChange_version_triggered() { auto versionRow = currentRow(); - if(versionRow == -1) - { + if (versionRow == -1) { return; } auto patch = m_profile->getComponent(versionRow); auto name = patch->getName(); auto list = patch->getVersionList(); - if(!list) - { + if (!list) { return; } auto uid = list->uid(); // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata... - if(uid == "net.minecraftforge") - { + if (uid == "net.minecraftforge") { on_actionInstall_Forge_triggered(); return; - } - else if (uid == "com.mumfrey.liteloader") - { + } else if (uid == "com.mumfrey.liteloader") { on_actionInstall_LiteLoader_triggered(); return; } VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); - if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed") - { + if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed") { vselect.setEmptyString(tr("No intermediary mappings versions are currently available.")); vselect.setEmptyErrorString(tr("Couldn't load or download the intermediary mappings version lists!")); vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); } auto currentVersion = patch->getVersion(); - if(!currentVersion.isEmpty()) - { + if (!currentVersion.isEmpty()) { vselect.setCurrentVersion(currentVersion); } if (!vselect.exec() || !vselect.selectedVersion()) @@ -474,8 +412,7 @@ void VersionPage::on_actionChange_version_triggered() qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor(); bool important = false; - if(uid == "net.minecraft") - { + if (uid == "net.minecraft") { important = true; } m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important); @@ -485,19 +422,17 @@ void VersionPage::on_actionChange_version_triggered() void VersionPage::on_actionDownload_All_triggered() { - if (!APPLICATION->accounts()->anyAccountIsValid()) - { - CustomMessageBox::selectable( - this, tr("Error"), - tr("Cannot download Minecraft or update instances unless you have at least " - "one account added.\nPlease add your Mojang or Minecraft account."), - QMessageBox::Warning)->show(); + if (!APPLICATION->accounts()->anyAccountIsValid()) { + CustomMessageBox::selectable(this, tr("Error"), + tr("Cannot download Minecraft or update instances unless you have at least " + "one account added.\nPlease add your Mojang or Minecraft account."), + QMessageBox::Warning) + ->show(); return; } auto updateTask = m_inst->createUpdateTask(Net::Mode::Online); - if (!updateTask) - { + if (!updateTask) { return; } ProgressDialog tDialog(this); @@ -511,28 +446,26 @@ void VersionPage::on_actionDownload_All_triggered() void VersionPage::on_actionInstall_Forge_triggered() { auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge"); - if(!vlist) - { + if (!vlist) { return; } VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this); vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + + m_profile->getComponentVersion("net.minecraft")); vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!")); auto currentVersion = m_profile->getComponentVersion("net.minecraftforge"); - if(!currentVersion.isEmpty()) - { + if (!currentVersion.isEmpty()) { vselect.setCurrentVersion(currentVersion); } - if (vselect.exec() && vselect.selectedVersion()) - { + if (vselect.exec() && vselect.selectedVersion()) { auto vsn = vselect.selectedVersion(); m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor()); m_profile->resolve(Net::Mode::Online); // m_profile->installVersion(); - preselect(m_profile->rowCount(QModelIndex())-1); + preselect(m_profile->rowCount(QModelIndex()) - 1); m_container->refreshContainer(); } } @@ -540,8 +473,7 @@ void VersionPage::on_actionInstall_Forge_triggered() void VersionPage::on_actionInstall_Fabric_triggered() { auto vlist = APPLICATION->metadataIndex()->get("net.fabricmc.fabric-loader"); - if(!vlist) - { + if (!vlist) { return; } VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this); @@ -549,17 +481,15 @@ void VersionPage::on_actionInstall_Fabric_triggered() 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()) - { + if (!currentVersion.isEmpty()) { vselect.setCurrentVersion(currentVersion); } - if (vselect.exec() && vselect.selectedVersion()) - { + 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); + preselect(m_profile->rowCount(QModelIndex()) - 1); m_container->refreshContainer(); } } @@ -567,8 +497,7 @@ void VersionPage::on_actionInstall_Fabric_triggered() void VersionPage::on_actionInstall_Quilt_triggered() { auto vlist = APPLICATION->metadataIndex()->get("org.quiltmc.quilt-loader"); - if(!vlist) - { + if (!vlist) { return; } VersionSelectDialog vselect(vlist.get(), tr("Select Quilt Loader version"), this); @@ -576,17 +505,15 @@ void VersionPage::on_actionInstall_Quilt_triggered() vselect.setEmptyErrorString(tr("Couldn't load or download the Quilt Loader version lists!")); auto currentVersion = m_profile->getComponentVersion("org.quiltmc.quilt-loader"); - if(!currentVersion.isEmpty()) - { + if (!currentVersion.isEmpty()) { vselect.setCurrentVersion(currentVersion); } - if (vselect.exec() && vselect.selectedVersion()) - { + if (vselect.exec() && vselect.selectedVersion()) { auto vsn = vselect.selectedVersion(); m_profile->setComponentVersion("org.quiltmc.quilt-loader", vsn->descriptor()); m_profile->resolve(Net::Mode::Online); - preselect(m_profile->rowCount(QModelIndex())-1); + preselect(m_profile->rowCount(QModelIndex()) - 1); m_container->refreshContainer(); } } @@ -595,14 +522,12 @@ void VersionPage::on_actionAdd_Empty_triggered() { NewComponentDialog compdialog(QString(), QString(), this); QStringList blacklist; - for(int i = 0; i < m_profile->rowCount(); i++) - { + for (int i = 0; i < m_profile->rowCount(); i++) { auto comp = m_profile->getComponent(i); blacklist.push_back(comp->getID()); } compdialog.setBlacklist(blacklist); - if (compdialog.exec()) - { + if (compdialog.exec()) { qDebug() << "name:" << compdialog.name(); qDebug() << "uid:" << compdialog.uid(); m_profile->installEmpty(compdialog.uid(), compdialog.name()); @@ -612,28 +537,26 @@ void VersionPage::on_actionAdd_Empty_triggered() void VersionPage::on_actionInstall_LiteLoader_triggered() { auto vlist = APPLICATION->metadataIndex()->get("com.mumfrey.liteloader"); - if(!vlist) - { + if (!vlist) { return; } VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this); vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); - vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft")); + vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + + m_profile->getComponentVersion("net.minecraft")); vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!")); auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader"); - if(!currentVersion.isEmpty()) - { + if (!currentVersion.isEmpty()) { vselect.setCurrentVersion(currentVersion); } - if (vselect.exec() && vselect.selectedVersion()) - { + if (vselect.exec() && vselect.selectedVersion()) { auto vsn = vselect.selectedVersion(); m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor()); m_profile->resolve(Net::Mode::Online); // m_profile->installVersion(vselect.selectedVersion()); - preselect(m_profile->rowCount(QModelIndex())-1); + preselect(m_profile->rowCount(QModelIndex()) - 1); m_container->refreshContainer(); } } @@ -648,7 +571,7 @@ void VersionPage::on_actionMinecraftFolder_triggered() DesktopServices::openDirectory(m_inst->gameRoot(), true); } -void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) +void VersionPage::versionCurrent(const QModelIndex& current, const QModelIndex& previous) { currentIdx = current.row(); updateButtons(currentIdx); @@ -656,16 +579,13 @@ void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex & void VersionPage::preselect(int row) { - if(row < 0) - { + if (row < 0) { row = 0; } - if(row >= m_profile->rowCount(QModelIndex())) - { + if (row >= m_profile->rowCount(QModelIndex())) { row = m_profile->rowCount(QModelIndex()) - 1; } - if(row < 0) - { + if (row < 0) { return; } auto model_index = m_profile->index(row); @@ -681,8 +601,7 @@ void VersionPage::onGameUpdateError(QString error) ComponentPtr VersionPage::current() { auto row = currentRow(); - if(row < 0) - { + if (row < 0) { return nullptr; } return m_profile->getComponent(row); @@ -690,8 +609,7 @@ ComponentPtr VersionPage::current() int VersionPage::currentRow() { - if (ui->packageView->selectionModel()->selectedRows().isEmpty()) - { + if (ui->packageView->selectionModel()->selectedRows().isEmpty()) { return -1; } return ui->packageView->selectionModel()->selectedRows().first().row(); @@ -700,18 +618,15 @@ int VersionPage::currentRow() void VersionPage::on_actionCustomize_triggered() { auto version = currentRow(); - if(version == -1) - { + if (version == -1) { return; } auto patch = m_profile->getComponent(version); - if(!patch->getVersionFile()) - { + if (!patch->getVersionFile()) { // TODO: wait for the update task to finish here... return; } - if(!m_profile->customize(version)) - { + if (!m_profile->customize(version)) { // TODO: some error box here } updateButtons(); @@ -721,13 +636,11 @@ void VersionPage::on_actionCustomize_triggered() void VersionPage::on_actionEdit_triggered() { auto version = current(); - if(!version) - { + if (!version) { return; } auto filename = version->getFilename(); - if(!QFileInfo::exists(filename)) - { + if (!QFileInfo::exists(filename)) { qWarning() << "file" << filename << "can't be opened for editing, doesn't exist!"; return; } @@ -737,8 +650,7 @@ void VersionPage::on_actionEdit_triggered() void VersionPage::on_actionRevert_triggered() { auto version = currentRow(); - if(version == -1) - { + if (version == -1) { return; } auto component = m_profile->getComponent(version); @@ -754,8 +666,7 @@ void VersionPage::on_actionRevert_triggered() if (response != QMessageBox::Yes) return; - if(!m_profile->revertToBase(version)) - { + if (!m_profile->revertToBase(version)) { // TODO: some error box here } updateButtons(); @@ -763,7 +674,7 @@ void VersionPage::on_actionRevert_triggered() m_container->refreshContainer(); } -void VersionPage::onFilterTextChanged(const QString &newContents) +void VersionPage::onFilterTextChanged(const QString& newContents) { m_filterModel->setFilterFixedString(newContents); } diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index d0087714..45d383f4 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -46,38 +46,27 @@ #include "minecraft/PackProfile.h" #include "ui/pages/BasePage.h" -namespace Ui -{ +namespace Ui { class VersionPage; } -class VersionPage : public QMainWindow, public BasePage -{ +class VersionPage : public QMainWindow, public BasePage { Q_OBJECT -public: - explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0); + public: + explicit VersionPage(MinecraftInstance* inst, QWidget* parent = 0); virtual ~VersionPage(); - virtual QString displayName() const override - { - return tr("Version"); - } + virtual QString displayName() const override { return tr("Version"); } virtual QIcon icon() const override; - virtual QString id() const override - { - return "version"; - } - virtual QString helpPage() const override - { - return "Instance-Version"; - } + virtual QString id() const override { return "version"; } + virtual QString helpPage() const override { return "Instance-Version"; } virtual bool shouldDisplay() const override; void retranslate() override; void openedImpl() override; void closedImpl() override; -private slots: + private slots: void on_actionChange_version_triggered(); void on_actionInstall_Forge_triggered(); void on_actionInstall_Fabric_triggered(); @@ -103,36 +92,34 @@ private slots: void updateVersionControls(); -private: + private: ComponentPtr current(); int currentRow(); void updateButtons(int row = -1); void preselect(int row = 0); int doUpdate(); -protected: - QMenu * createPopupMenu() override; + protected: + QMenu* createPopupMenu() override; /// FIXME: this shouldn't be necessary! bool reloadPackProfile(); -private: - Ui::VersionPage *ui; - QSortFilterProxyModel *m_filterModel; + private: + Ui::VersionPage* ui; + QSortFilterProxyModel* m_filterModel; std::shared_ptr<PackProfile> m_profile; - MinecraftInstance *m_inst; + MinecraftInstance* m_inst; int currentIdx = 0; - bool controlsEnabled = false; std::shared_ptr<Setting> m_wide_bar_setting = nullptr; -public slots: - void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous); + public slots: + void versionCurrent(const QModelIndex& current, const QModelIndex& previous); -private slots: - void updateRunningStatus(bool running); + private slots: void onGameUpdateError(QString error); - void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); - void showContextMenu(const QPoint &pos); - void onFilterTextChanged(const QString & newContents); + void packageCurrent(const QModelIndex& current, const QModelIndex& previous); + void showContextMenu(const QPoint& pos); + void onFilterTextChanged(const QString& newContents); }; diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index 4777eafe..a73c42d6 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -102,7 +102,6 @@ <addaction name="actionInstall_Fabric"/> <addaction name="actionInstall_Quilt"/> <addaction name="actionInstall_LiteLoader"/> - <addaction name="actionInstall_mods"/> <addaction name="separator"/> <addaction name="actionAdd_to_Minecraft_jar"/> <addaction name="actionReplace_Minecraft_jar"/> @@ -112,7 +111,6 @@ <addaction name="separator"/> <addaction name="actionMinecraftFolder"/> <addaction name="actionLibrariesFolder"/> - <addaction name="separator"/> <addaction name="actionReload"/> <addaction name="actionDownload_All"/> </widget> @@ -204,14 +202,6 @@ <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> diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index b6ad159e..b2200b1a 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -339,6 +339,7 @@ void WorldListPage::mceditState(LoggedProcess::State state) { failed = true; } + /* fallthrough */ case LoggedProcess::Running: case LoggedProcess::Finished: { diff --git a/launcher/ui/pages/modplatform/VanillaPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index 29fecb85..e164171a 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -33,8 +33,8 @@ * limitations under the License. */ -#include "VanillaPage.h" -#include "ui_VanillaPage.h" +#include "CustomPage.h" +#include "ui_CustomPage.h" #include <QTabBar> @@ -46,32 +46,32 @@ #include "minecraft/VanillaInstanceCreationTask.h" #include "ui/dialogs/NewInstanceDialog.h" -VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) - : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) +CustomPage::CustomPage(NewInstanceDialog *dialog, QWidget *parent) + : QWidget(parent), dialog(dialog), ui(new Ui::CustomPage) { ui->setupUi(this); ui->tabWidget->tabBar()->hide(); - connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion); + connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedVersion); filterChanged(); - connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); - 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); - - connect(ui->loaderVersionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedLoaderVersion); - connect(ui->noneFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); - connect(ui->forgeFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); - connect(ui->fabricFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); - connect(ui->quiltFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); - connect(ui->liteLoaderFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); - connect(ui->loaderRefreshBtn, &QPushButton::clicked, this, &VanillaPage::loaderRefresh); + connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged); + connect(ui->betaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged); + connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged); + connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged); + connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged); + connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged); + connect(ui->refreshBtn, &QPushButton::clicked, this, &CustomPage::refresh); + + connect(ui->loaderVersionList, &VersionSelectWidget::selectedVersionChanged, this, &CustomPage::setSelectedLoaderVersion); + connect(ui->noneFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged); + connect(ui->forgeFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged); + connect(ui->fabricFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged); + connect(ui->quiltFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged); + connect(ui->liteLoaderFilter, &QRadioButton::toggled, this, &CustomPage::loaderFilterChanged); + connect(ui->loaderRefreshBtn, &QPushButton::clicked, this, &CustomPage::loaderRefresh); } -void VanillaPage::openedImpl() +void CustomPage::openedImpl() { if(!initialized) { @@ -85,19 +85,19 @@ void VanillaPage::openedImpl() } } -void VanillaPage::refresh() +void CustomPage::refresh() { ui->versionList->loadList(); } -void VanillaPage::loaderRefresh() +void CustomPage::loaderRefresh() { if(ui->noneFilter->isChecked()) return; ui->loaderVersionList->loadList(); } -void VanillaPage::filterChanged() +void CustomPage::filterChanged() { QStringList out; if(ui->alphaFilter->isChecked()) @@ -116,7 +116,7 @@ void VanillaPage::filterChanged() ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); } -void VanillaPage::loaderFilterChanged() +void CustomPage::loaderFilterChanged() { QString minecraftVersion; if (m_selectedVersion) @@ -172,37 +172,37 @@ void VanillaPage::loaderFilterChanged() ui->loaderVersionList->setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion)); } -VanillaPage::~VanillaPage() +CustomPage::~CustomPage() { delete ui; } -bool VanillaPage::shouldDisplay() const +bool CustomPage::shouldDisplay() const { return true; } -void VanillaPage::retranslate() +void CustomPage::retranslate() { ui->retranslateUi(this); } -BaseVersion::Ptr VanillaPage::selectedVersion() const +BaseVersion::Ptr CustomPage::selectedVersion() const { return m_selectedVersion; } -BaseVersion::Ptr VanillaPage::selectedLoaderVersion() const +BaseVersion::Ptr CustomPage::selectedLoaderVersion() const { return m_selectedLoaderVersion; } -QString VanillaPage::selectedLoader() const +QString CustomPage::selectedLoader() const { return m_selectedLoader; } -void VanillaPage::suggestCurrent() +void CustomPage::suggestCurrent() { if (!isOpened) { @@ -227,14 +227,14 @@ void VanillaPage::suggestCurrent() dialog->setSuggestedIcon("default"); } -void VanillaPage::setSelectedVersion(BaseVersion::Ptr version) +void CustomPage::setSelectedVersion(BaseVersion::Ptr version) { m_selectedVersion = version; suggestCurrent(); loaderFilterChanged(); } -void VanillaPage::setSelectedLoaderVersion(BaseVersion::Ptr version) +void CustomPage::setSelectedLoaderVersion(BaseVersion::Ptr version) { m_selectedLoaderVersion = version; suggestCurrent(); diff --git a/launcher/ui/pages/modplatform/VanillaPage.h b/launcher/ui/pages/modplatform/CustomPage.h index 39aba760..8b5a5011 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.h +++ b/launcher/ui/pages/modplatform/CustomPage.h @@ -43,21 +43,21 @@ namespace Ui { -class VanillaPage; +class CustomPage; } class NewInstanceDialog; -class VanillaPage : public QWidget, public BasePage +class CustomPage : public QWidget, public BasePage { Q_OBJECT public: - explicit VanillaPage(NewInstanceDialog *dialog, QWidget *parent = 0); - virtual ~VanillaPage(); + explicit CustomPage(NewInstanceDialog *dialog, QWidget *parent = 0); + virtual ~CustomPage(); virtual QString displayName() const override { - return tr("Vanilla"); + return tr("Custom"); } virtual QIcon icon() const override { @@ -96,7 +96,7 @@ private: private: bool initialized = false; NewInstanceDialog *dialog = nullptr; - Ui::VanillaPage *ui = nullptr; + Ui::CustomPage *ui = nullptr; bool m_versionSetByUser = false; BaseVersion::Ptr m_selectedVersion; BaseVersion::Ptr m_selectedLoaderVersion; diff --git a/launcher/ui/pages/modplatform/VanillaPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui index 43110927..0d89b595 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.ui +++ b/launcher/ui/pages/modplatform/CustomPage.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>VanillaPage</class> - <widget class="QWidget" name="VanillaPage"> + <class>CustomPage</class> + <widget class="QWidget" name="CustomPage"> <property name="geometry"> <rect> <x>0</x> diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index aab2ee89..48afbd90 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -104,6 +104,7 @@ void ResourcePage::openedImpl() updateSelectionButton(); triggerSearch(); + m_ui->searchEdit->setFocus(); } auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 98ab8799..b3f6261f 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -116,8 +116,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget *parent) connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged); connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged); - connect(ui->addPackBtn, &QPushButton::pressed, this, &Page::onAddPackClicked); - connect(ui->removePackBtn, &QPushButton::pressed, this, &Page::onRemovePackClicked); + connect(ui->addPackBtn, &QPushButton::clicked, this, &Page::onAddPackClicked); + connect(ui->removePackBtn, &QPushButton::clicked, this, &Page::onRemovePackClicked); connect(ui->tabWidget, &QTabWidget::currentChanged, this, &Page::onTabChanged); diff --git a/launcher/ui/setupwizard/JavaWizardPage.cpp b/launcher/ui/setupwizard/JavaWizardPage.cpp index 14683778..2b70c47c 100644 --- a/launcher/ui/setupwizard/JavaWizardPage.cpp +++ b/launcher/ui/setupwizard/JavaWizardPage.cpp @@ -69,6 +69,7 @@ bool JavaWizardPage::validatePage() case JavaSettingsWidget::ValidationStatus::AllOK: { settings->set("JavaPath", m_java_widget->javaPath()); + return true; } case JavaSettingsWidget::ValidationStatus::JavaBad: { diff --git a/launcher/ui/setupwizard/SetupWizard.cpp b/launcher/ui/setupwizard/SetupWizard.cpp index 3fd9bb23..0a47334f 100644 --- a/launcher/ui/setupwizard/SetupWizard.cpp +++ b/launcher/ui/setupwizard/SetupWizard.cpp @@ -59,7 +59,7 @@ void SetupWizard::pageChanged(int id) { setButtonLayout({QWizard::CustomButton1, QWizard::Stretch, QWizard::BackButton, QWizard::NextButton, QWizard::FinishButton}); auto customButton = button(QWizard::CustomButton1); - connect(customButton, &QAbstractButton::pressed, [&](){ + connect(customButton, &QAbstractButton::clicked, [&](){ auto basePagePtr = getCurrentBasePage(); if(basePagePtr) { diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 9c041bfe..a0fda952 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -1,54 +1,70 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln <flowlnlnln@gmail.com> -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see <https://www.gnu.org/licenses/>. -* -* This file incorporates work covered by the following copyright and -* permission notice: -* -* Copyright 2013-2021 MultiMC Contributors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <QLabel> #include <QMessageBox> +#include <QToolTip> #include "InfoFrame.h" #include "ui_InfoFrame.h" #include "ui/dialogs/CustomMessageBox.h" -InfoFrame::InfoFrame(QWidget *parent) : - QFrame(parent), - ui(new Ui::InfoFrame) +void setupLinkToolTip(QLabel* label) +{ + QObject::connect(label, &QLabel::linkHovered, [label](const QString& link) { + if (auto url = QUrl(link); !url.isValid() || (url.scheme() != "http" && url.scheme() != "https")) + return; + label->setToolTip(link); + }); +} + +InfoFrame::InfoFrame(QWidget* parent) : QFrame(parent), ui(new Ui::InfoFrame) { ui->setupUi(this); ui->descriptionLabel->setHidden(true); ui->nameLabel->setHidden(true); ui->licenseLabel->setHidden(true); ui->issueTrackerLabel->setHidden(true); + + setupLinkToolTip(ui->iconLabel); + setupLinkToolTip(ui->descriptionLabel); + setupLinkToolTip(ui->nameLabel); + setupLinkToolTip(ui->licenseLabel); + setupLinkToolTip(ui->issueTrackerLabel); updateHiddenState(); } @@ -59,45 +75,43 @@ InfoFrame::~InfoFrame() void InfoFrame::updateWithMod(Mod const& m) { - if (m.type() == ResourceType::FOLDER) - { + if (m.type() == ResourceType::FOLDER) { clear(); return; } QString text = ""; QString name = ""; + QString link = m.metaurl(); if (m.name().isEmpty()) name = m.internal_id(); else name = m.name(); - if (m.homeurl().isEmpty()) + if (link.isEmpty()) text = name; - else - text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>"; + else { + text = "<a href=\"" + link + "\">" + name + "</a>"; + } if (!m.authors().isEmpty()) text += " by " + m.authors().join(", "); setName(text); - if (m.description().isEmpty()) - { + if (m.description().isEmpty()) { setDescription(QString()); - } - else - { + } else { setDescription(m.description()); } - setImage(m.icon({64,64})); + setImage(m.icon({ 64, 64 })); auto licenses = m.licenses(); QString licenseText = ""; if (!licenses.empty()) { for (auto l : licenses) { if (!licenseText.isEmpty()) { - licenseText += "\n"; // add newline between licenses + licenseText += "\n"; // add newline between licenses } if (!l.name.isEmpty()) { if (l.url.isEmpty()) { @@ -109,9 +123,9 @@ void InfoFrame::updateWithMod(Mod const& m) licenseText += "<a href=\"" + l.url + "\">" + l.url + "</a>"; } if (!l.description.isEmpty() && l.description != l.name) { - licenseText += " " + l.description; + licenseText += " " + l.description; } - } + } } if (!licenseText.isEmpty()) { setLicense(tr("License: %1").arg(licenseText)); @@ -123,7 +137,7 @@ void InfoFrame::updateWithMod(Mod const& m) if (!m.issueTracker().isEmpty()) { issueTracker += tr("Report issues to: "); issueTracker += "<a href=\"" + m.issueTracker() + "\">" + m.issueTracker() + "</a>"; - } + } setIssueTracker(issueTracker); } @@ -133,7 +147,8 @@ void InfoFrame::updateWithResource(const Resource& resource) setImage(); } -QString InfoFrame::renderColorCodes(QString input) { +QString InfoFrame::renderColorCodes(QString input) +{ // We have to manually set the colors for use. // // A color is set using §x, with x = a hex number from 0 to f. @@ -144,16 +159,12 @@ QString InfoFrame::renderColorCodes(QString input) { // TODO: Wrap links inside <a> tags // https://minecraft.fandom.com/wiki/Formatting_codes#Color_codes - const QMap<QChar, QString> color_codes_map = { - {'0', "#000000"}, {'1', "#0000AA"}, {'2', "#00AA00"}, {'3', "#00AAAA"}, {'4', "#AA0000"}, - {'5', "#AA00AA"}, {'6', "#FFAA00"}, {'7', "#AAAAAA"}, {'8', "#555555"}, {'9', "#5555FF"}, - {'a', "#55FF55"}, {'b', "#55FFFF"}, {'c', "#FF5555"}, {'d', "#FF55FF"}, {'e', "#FFFF55"}, - {'f', "#FFFFFF"} - }; + const QMap<QChar, QString> color_codes_map = { { '0', "#000000" }, { '1', "#0000AA" }, { '2', "#00AA00" }, { '3', "#00AAAA" }, + { '4', "#AA0000" }, { '5', "#AA00AA" }, { '6', "#FFAA00" }, { '7', "#AAAAAA" }, + { '8', "#555555" }, { '9', "#5555FF" }, { 'a', "#55FF55" }, { 'b', "#55FFFF" }, + { 'c', "#FF5555" }, { 'd', "#FF55FF" }, { 'e', "#FFFF55" }, { 'f', "#FFFFFF" } }; // https://minecraft.fandom.com/wiki/Formatting_codes#Formatting_codes - const QMap<QChar, QString> formatting_codes_map = { - {'l', "b"}, {'m', "s"}, {'n', "u"}, {'o', "i"} - }; + const QMap<QChar, QString> formatting_codes_map = { { 'l', "b" }, { 'm', "s" }, { 'n', "u" }, { 'o', "i" } }; QString html("<html>"); QList<QString> tags{}; @@ -198,14 +209,14 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) { setName(renderColorCodes(resource_pack.name())); setDescription(renderColorCodes(resource_pack.description())); - setImage(resource_pack.image({64, 64})); + setImage(resource_pack.image({ 64, 64 })); } void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) { setName(renderColorCodes(texture_pack.name())); setDescription(renderColorCodes(texture_pack.description())); - setImage(texture_pack.image({64, 64})); + setImage(texture_pack.image({ 64, 64 })); } void InfoFrame::clear() @@ -229,12 +240,9 @@ void InfoFrame::updateHiddenState() void InfoFrame::setName(QString text) { - if(text.isEmpty()) - { + if (text.isEmpty()) { ui->nameLabel->setHidden(true); - } - else - { + } else { ui->nameLabel->setText(text); ui->nameLabel->setHidden(false); } @@ -243,14 +251,11 @@ void InfoFrame::setName(QString text) void InfoFrame::setDescription(QString text) { - if(text.isEmpty()) - { + if (text.isEmpty()) { ui->descriptionLabel->setHidden(true); updateHiddenState(); return; - } - else - { + } else { ui->descriptionLabel->setHidden(false); updateHiddenState(); } @@ -260,9 +265,8 @@ void InfoFrame::setDescription(QString text) QChar rem('\n'); QString finaltext; finaltext.reserve(intermediatetext.size()); - foreach(const QChar& c, intermediatetext) - { - if(c == rem && prev){ + foreach (const QChar& c, intermediatetext) { + if (c == rem && prev) { continue; } prev = c == rem; @@ -270,17 +274,14 @@ void InfoFrame::setDescription(QString text) } QString labeltext; labeltext.reserve(300); - if(finaltext.length() > 290) - { + if (finaltext.length() > 290) { ui->descriptionLabel->setOpenExternalLinks(false); ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); m_description = text; // This allows injecting HTML here. labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); - } - else - { + } else { ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText); labeltext.append(finaltext); } @@ -289,14 +290,11 @@ void InfoFrame::setDescription(QString text) void InfoFrame::setLicense(QString text) { - if(text.isEmpty()) - { + if (text.isEmpty()) { ui->licenseLabel->setHidden(true); updateHiddenState(); return; - } - else - { + } else { ui->licenseLabel->setHidden(false); updateHiddenState(); } @@ -306,9 +304,8 @@ void InfoFrame::setLicense(QString text) QChar rem('\n'); QString finaltext; finaltext.reserve(intermediatetext.size()); - foreach(const QChar& c, intermediatetext) - { - if(c == rem && prev){ + foreach (const QChar& c, intermediatetext) { + if (c == rem && prev) { continue; } prev = c == rem; @@ -316,17 +313,14 @@ void InfoFrame::setLicense(QString text) } QString labeltext; labeltext.reserve(300); - if(finaltext.length() > 290) - { + if (finaltext.length() > 290) { ui->licenseLabel->setOpenExternalLinks(false); ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText); m_description = text; // This allows injecting HTML here. labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>"); QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler); - } - else - { + } else { ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText); labeltext.append(finaltext); } @@ -335,12 +329,9 @@ void InfoFrame::setLicense(QString text) void InfoFrame::setIssueTracker(QString text) { - if(text.isEmpty()) - { + if (text.isEmpty()) { ui->issueTrackerLabel->setHidden(true); - } - else - { + } else { ui->issueTrackerLabel->setText(text); ui->issueTrackerLabel->setHidden(false); } @@ -359,28 +350,22 @@ void InfoFrame::setImage(QPixmap img) void InfoFrame::descriptionEllipsisHandler(QString link) { - if(!m_current_box) - { + if (!m_current_box) { m_current_box = CustomMessageBox::selectable(this, "", m_description); connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed); m_current_box->show(); - } - else - { + } else { m_current_box->setText(m_description); } } void InfoFrame::licenseEllipsisHandler(QString link) { - if(!m_current_box) - { + if (!m_current_box) { m_current_box = CustomMessageBox::selectable(this, "", m_license); connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed); m_current_box->show(); - } - else - { + } else { m_current_box->setText(m_license); } } diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index 7eb679a9..d6764baa 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once @@ -21,8 +41,7 @@ #include "minecraft/mod/ResourcePack.h" #include "minecraft/mod/TexturePack.h" -namespace Ui -{ +namespace Ui { class InfoFrame; } diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp index 256b09da..37d05347 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.cpp +++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp @@ -1,16 +1,16 @@ #include "LanguageSelectionWidget.h" -#include <QVBoxLayout> -#include <QTreeView> +#include <QCheckBox> #include <QHeaderView> #include <QLabel> +#include <QTreeView> +#include <QVBoxLayout> #include "Application.h" #include "BuildConfig.h" -#include "translations/TranslationsModel.h" #include "settings/Setting.h" +#include "translations/TranslationsModel.h" -LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : - QWidget(parent) +LanguageSelectionWidget::LanguageSelectionWidget(QWidget* parent) : QWidget(parent) { verticalLayout = new QVBoxLayout(this); verticalLayout->setObjectName(QStringLiteral("verticalLayout")); @@ -31,6 +31,13 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : helpUsLabel->setWordWrap(true); verticalLayout->addWidget(helpUsLabel); + formatCheckbox = new QCheckBox(this); + formatCheckbox->setObjectName(QStringLiteral("formatCheckbox")); + formatCheckbox->setCheckState(APPLICATION->settings()->get("UseSystemLocale").toBool() ? Qt::Checked : Qt::Unchecked); + connect(formatCheckbox, &QCheckBox::stateChanged, + [this]() { APPLICATION->translations()->setUseSystemLocale(formatCheckbox->isChecked()); }); + verticalLayout->addWidget(formatCheckbox); + auto translations = APPLICATION->translations(); auto index = translations->selectedIndex(); languageView->setModel(translations.get()); @@ -38,7 +45,7 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) : 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); + verticalLayout->setContentsMargins(0, 0, 0, 0); auto language_setting = APPLICATION->settings()->getSetting("Language"); connect(language_setting.get(), &Setting::SettingChanged, this, &LanguageSelectionWidget::languageSettingChanged); @@ -53,15 +60,14 @@ QString LanguageSelectionWidget::getSelectedLanguageKey() const 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(BuildConfig.TRANSLATIONS_URL); + .arg(BuildConfig.TRANSLATIONS_URL); helpUsLabel->setText(text); - + formatCheckbox->setText(tr("Use system locales")); } void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous) { - if (current == previous) - { + if (current == previous) { return; } auto translations = APPLICATION->translations(); @@ -70,7 +76,7 @@ void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, con translations->updateLanguage(key); } -void LanguageSelectionWidget::languageSettingChanged(const Setting &, const QVariant) +void LanguageSelectionWidget::languageSettingChanged(const Setting&, const QVariant) { auto translations = APPLICATION->translations(); auto index = translations->selectedIndex(); diff --git a/launcher/ui/widgets/LanguageSelectionWidget.h b/launcher/ui/widgets/LanguageSelectionWidget.h index 4a88924c..5e86a288 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.h +++ b/launcher/ui/widgets/LanguageSelectionWidget.h @@ -21,23 +21,24 @@ class QVBoxLayout; class QTreeView; class QLabel; class Setting; +class QCheckBox; -class LanguageSelectionWidget: public QWidget -{ +class LanguageSelectionWidget : public QWidget { Q_OBJECT -public: - explicit LanguageSelectionWidget(QWidget *parent = 0); - virtual ~LanguageSelectionWidget() { }; + public: + explicit LanguageSelectionWidget(QWidget* parent = 0); + virtual ~LanguageSelectionWidget(){}; QString getSelectedLanguageKey() const; void retranslate(); -protected slots: - void languageRowChanged(const QModelIndex ¤t, const QModelIndex &previous); - void languageSettingChanged(const Setting &, const QVariant); + protected slots: + void languageRowChanged(const QModelIndex& current, const QModelIndex& previous); + void languageSettingChanged(const Setting&, const QVariant); -private: - QVBoxLayout *verticalLayout = nullptr; - QTreeView *languageView = nullptr; - QLabel *helpUsLabel = nullptr; + private: + QVBoxLayout* verticalLayout = nullptr; + QTreeView* languageView = nullptr; + QLabel* helpUsLabel = nullptr; + QCheckBox* formatCheckbox = nullptr; }; diff --git a/launcher/ui/widgets/ModListView.cpp b/launcher/ui/widgets/ModListView.cpp index 893cd120..80a918b6 100644 --- a/launcher/ui/widgets/ModListView.cpp +++ b/launcher/ui/widgets/ModListView.cpp @@ -14,9 +14,6 @@ */ #include "ModListView.h" - -#include "minecraft/mod/ModFolderModel.h" - #include <QHeaderView> #include <QMouseEvent> #include <QPainter> @@ -65,19 +62,6 @@ void ModListView::setModel ( QAbstractItemModel* model ) for(int i = 1; i < head->count(); i++) head->setSectionResizeMode(i, QHeaderView::ResizeToContents); } - - auto real_model = model; - if (auto proxy_model = dynamic_cast<QSortFilterProxyModel*>(model); proxy_model) - real_model = proxy_model->sourceModel(); - - if (auto mod_model = dynamic_cast<ModFolderModel*>(real_model); mod_model) { - connect(mod_model, &ModFolderModel::updateFinished, this, [this, mod_model]{ - auto mods = mod_model->allMods(); - // Hide the 'Provider' column if no mod has a defined provider! - setColumnHidden(ModFolderModel::Columns::ProviderColumn, - std::none_of(mods.constBegin(), mods.constEnd(), [](auto const mod){ return mod->provider().has_value(); })); - }); - } } void ModListView::setResizeModes(const QList<QHeaderView::ResizeMode> &modes) diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp index ac34e3aa..a77c45fe 100644 --- a/launcher/ui/widgets/WideBar.cpp +++ b/launcher/ui/widgets/WideBar.cpp @@ -116,12 +116,21 @@ void WideBar::insertActionAfter(QAction* after, QAction* action) if (iter == m_entries.end()) return; + iter++; + // the action to insert after is present + // however, the element after it isn't valid + if (iter == m_entries.end()) { + // append the action instead of inserting it + addAction(action); + return; + } + BarEntry entry; - entry.bar_action = insertWidget((iter + 1)->bar_action, new ActionButton(action, this, m_use_default_action)); + entry.bar_action = insertWidget(iter->bar_action, new ActionButton(action, this, m_use_default_action)); entry.menu_action = action; entry.type = BarEntry::Type::Action; - m_entries.insert(iter + 1, entry); + m_entries.insert(iter, entry); m_menu_state = MenuState::Dirty; } |