aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/ui')
-rw-r--r--launcher/ui/MainWindow.cpp923
-rw-r--r--launcher/ui/MainWindow.h11
-rw-r--r--launcher/ui/MainWindow.ui50
-rw-r--r--launcher/ui/dialogs/AboutDialog.cpp11
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp39
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.cpp125
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.h33
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.ui358
-rw-r--r--launcher/ui/dialogs/ExportInstanceDialog.cpp333
-rw-r--r--launcher/ui/dialogs/ExportInstanceDialog.h46
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.cpp146
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.h49
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.ui137
-rw-r--r--launcher/ui/dialogs/ExportToModListDialog.cpp223
-rw-r--r--launcher/ui/dialogs/ExportToModListDialog.h55
-rw-r--r--launcher/ui/dialogs/ExportToModListDialog.ui240
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp8
-rw-r--r--launcher/ui/dialogs/NewsDialog.cpp2
-rw-r--r--launcher/ui/dialogs/ProgressDialog.cpp160
-rw-r--r--launcher/ui/dialogs/ProgressDialog.h56
-rw-r--r--launcher/ui/dialogs/ProgressDialog.ui150
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp171
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.h21
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp29
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h8
-rw-r--r--launcher/ui/dialogs/VersionSelectDialog.cpp47
-rw-r--r--launcher/ui/instanceview/AccessibleInstanceView.cpp2
-rw-r--r--launcher/ui/instanceview/InstanceView.cpp26
-rw-r--r--launcher/ui/instanceview/InstanceView.h8
-rw-r--r--launcher/ui/pages/BasePage.h24
-rw-r--r--launcher/ui/pages/global/APIPage.cpp6
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui10
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp1
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui135
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp104
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.h4
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui14
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp65
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.h6
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui17
-rw-r--r--launcher/ui/pages/instance/ManagedPackPage.cpp66
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp81
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h5
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.cpp79
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.cpp3
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp311
-rw-r--r--launcher/ui/pages/instance/VersionPage.h55
-rw-r--r--launcher/ui/pages/instance/VersionPage.ui10
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp2
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.cpp (renamed from launcher/ui/pages/modplatform/VanillaPage.cpp)68
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.h (renamed from launcher/ui/pages/modplatform/VanillaPage.h)12
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.ui (renamed from launcher/ui/pages/modplatform/VanillaPage.ui)4
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.h2
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp20
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h6
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp43
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h17
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp91
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h16
-rw-r--r--launcher/ui/pages/modplatform/ResourcePackModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/ResourcePackPage.h2
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp108
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.h18
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.cpp16
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.h6
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp99
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.h29
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp8
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp2
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h8
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp8
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.h53
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp7
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp9
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.h3
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp93
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbFilterModel.h51
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbListModel.cpp304
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbListModel.h83
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbPage.cpp199
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbPage.h105
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbPage.ui86
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp111
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.cpp4
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp20
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h4
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp20
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp13
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h3
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp118
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.h27
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.cpp15
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.h2
-rw-r--r--launcher/ui/setupwizard/JavaWizardPage.cpp1
-rw-r--r--launcher/ui/setupwizard/SetupWizard.cpp2
-rw-r--r--launcher/ui/themes/SystemTheme.cpp2
-rw-r--r--launcher/ui/widgets/InfoFrame.cpp267
-rw-r--r--launcher/ui/widgets/InfoFrame.h47
-rw-r--r--launcher/ui/widgets/InfoFrame.ui92
-rw-r--r--launcher/ui/widgets/JavaSettingsWidget.cpp2
-rw-r--r--launcher/ui/widgets/LanguageSelectionWidget.cpp28
-rw-r--r--launcher/ui/widgets/LanguageSelectionWidget.h25
-rw-r--r--launcher/ui/widgets/ModListView.cpp21
-rw-r--r--launcher/ui/widgets/ModListView.h2
-rw-r--r--launcher/ui/widgets/PageContainer.cpp13
-rw-r--r--launcher/ui/widgets/PageContainer.h1
-rw-r--r--launcher/ui/widgets/ProgressWidget.cpp1
-rw-r--r--launcher/ui/widgets/ProjectItem.cpp11
-rw-r--r--launcher/ui/widgets/ProjectItem.h3
-rw-r--r--launcher/ui/widgets/SubTaskProgressBar.cpp58
-rw-r--r--launcher/ui/widgets/SubTaskProgressBar.h48
-rw-r--r--launcher/ui/widgets/SubTaskProgressBar.ui94
-rw-r--r--launcher/ui/widgets/VersionListView.cpp8
-rw-r--r--launcher/ui/widgets/VersionSelectWidget.cpp46
-rw-r--r--launcher/ui/widgets/VersionSelectWidget.h48
-rw-r--r--launcher/ui/widgets/WideBar.cpp13
120 files changed, 4040 insertions, 3187 deletions
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 8490b292..da572fc3 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -43,101 +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/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);
@@ -183,10 +182,9 @@ 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, and accounts tool buttons
+ // set the menu for the folders help, accounts, and export tool buttons
{
auto foldersMenuButton = dynamic_cast<QToolButton*>(ui->mainToolBar->widgetForAction(ui->actionFoldersButton));
ui->actionFoldersButton->setMenu(ui->foldersMenu);
@@ -199,8 +197,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
helpMenuButton->setPopupMode(QToolButton::InstantPopup);
auto accountMenuButton = dynamic_cast<QToolButton*>(ui->mainToolBar->widgetForAction(ui->actionAccountsButton));
- ui->actionAccountsButton->setMenu(ui->accountsMenu);
accountMenuButton->setPopupMode(QToolButton::InstantPopup);
+
+ 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);
}
// hide, disable and show stuff
@@ -219,6 +223,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
// disabled until we have an instance selected
ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false);
+
+ // add a close button at the end of the main toolbar when running on gamescope / steam deck
+ // FIXME: detect if we don't have server side decorations instead
+ if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
+ ui->mainToolBar->addAction(ui->actionCloseWindow);
+ }
}
// add the toolbar toggles to the view menu
@@ -288,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);
}
@@ -336,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);
@@ -348,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();
@@ -403,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);
@@ -414,16 +410,6 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event)
void MainWindow::retranslateUi()
{
- auto accounts = APPLICATION->accounts();
- MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
- if(defaultAccount) {
- auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse());
- ui->actionAccountsButton->setText(profileLabel);
- }
- else {
- ui->actionAccountsButton->setText(tr("Accounts"));
- }
-
if (m_selectedInstance) {
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
} else {
@@ -432,6 +418,12 @@ void MainWindow::retranslateUi()
ui->retranslateUi(this);
+ MinecraftAccountPtr defaultAccount = APPLICATION->accounts()->defaultAccount();
+ if (defaultAccount) {
+ auto profileLabel = profileInUseFilter(defaultAccount->profileName(), defaultAccount->isInUse());
+ ui->actionAccountsButton->setText(profileLabel);
+ }
+
changeIconButton->setToolTip(ui->actionChangeInstIcon->toolTip());
renameButton->setToolTip(ui->actionRenameInstance->toolTip());
@@ -447,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);
@@ -468,22 +458,38 @@ void MainWindow::lockToolbars(bool state)
APPLICATION->settings()->set("ToolbarsLocked", state);
}
-
void MainWindow::konamiTriggered()
{
- qDebug() << "Super Secret Mode ACTIVATED!";
+ 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("");
+ ui->instanceToolBar->setStyleSheet("");
+ ui->centralWidget->setStyleSheet("");
+ ui->newsToolBar->setStyleSheet("");
+ ui->statusBar->setStyleSheet("");
+ qDebug() << "Super Secret Mode DEACTIVATED!";
+ } else {
+ ui->mainToolBar->setStyleSheet(stylesheet);
+ ui->instanceToolBar->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1," + gradient);
+ ui->centralWidget->setStyleSheet("background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1," + gradient);
+ ui->newsToolBar->setStyleSheet(stylesheet);
+ ui->statusBar->setStyleSheet(stylesheet);
+ qDebug() << "Super Secret Mode ACTIVATED!";
+ }
}
-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();
@@ -497,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);
@@ -522,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);
@@ -555,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);
@@ -601,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);
}
@@ -639,7 +619,7 @@ void MainWindow::updateToolsMenu()
void MainWindow::updateThemeMenu()
{
- QMenu *themeMenu = ui->actionChangeTheme->menu();
+ QMenu* themeMenu = ui->actionChangeTheme->menu();
if (themeMenu) {
themeMenu->clear();
@@ -649,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()) {
@@ -673,51 +653,54 @@ 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();
+ if (accountsButtonMenu) {
+ accountsButtonMenu->clear();
+ } else {
+ accountsButtonMenu = new QMenu(this);
+ ui->actionAccountsButton->setMenu(accountsButtonMenu);
+ }
+
auto accounts = APPLICATION->accounts();
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);
}
}
- if (accounts->count() <= 0)
- {
+ QActionGroup* accountsGroup = new QActionGroup(this);
+
+ 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);
- if (defaultAccount == account)
- {
+ action->setActionGroup(accountsGroup);
+ 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)));
}
@@ -730,6 +713,7 @@ void MainWindow::repopulateAccountsMenu()
ui->actionNoDefaultAccount->setData(-1);
ui->actionNoDefaultAccount->setChecked(!defaultAccount);
+ ui->actionNoDefaultAccount->setActionGroup(accountsGroup);
ui->accountsMenu->addAction(ui->actionNoDefaultAccount);
@@ -737,12 +721,13 @@ void MainWindow::repopulateAccountsMenu()
ui->accountsMenu->addSeparator();
ui->accountsMenu->addAction(ui->actionManageAccounts);
+
+ accountsButtonMenu->addActions(ui->accountsMenu->actions());
}
void MainWindow::updatesAllowedChanged(bool allowed)
{
- if(!BuildConfig.UPDATER_ENABLED)
- {
+ if (!BuildConfig.UPDATER_ENABLED) {
return;
}
ui->actionCheckUpdate->setEnabled(allowed);
@@ -753,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)
@@ -762,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();
@@ -777,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;
@@ -796,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;
}
}
}
@@ -831,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);
@@ -855,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);
@@ -863,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(',');
@@ -887,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());
@@ -954,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();
}
@@ -1009,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);
}
}
@@ -1036,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);
@@ -1105,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);
@@ -1116,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);
@@ -1132,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();
@@ -1160,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);
}
}
@@ -1169,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);
}
}
@@ -1201,6 +1140,12 @@ void MainWindow::on_actionViewInstanceFolder_triggered()
DesktopServices::openDirectory(str);
}
+void MainWindow::on_actionViewLauncherRootFolder_triggered()
+{
+ const QString dataPath = QDir::currentPath();
+ DesktopServices::openDirectory(dataPath);
+}
+
void MainWindow::refreshInstances()
{
APPLICATION->instances()->loadList();
@@ -1213,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.";
}
}
@@ -1246,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()
@@ -1308,7 +1260,8 @@ void MainWindow::newsButtonClicked()
news_dialog.exec();
}
-void MainWindow::onCatChanged(int) {
+void MainWindow::onCatChanged(int)
+{
setCatBackground(APPLICATION->settings()->get("TheCat").toBool());
}
@@ -1337,6 +1290,21 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (response != QMessageBox::Yes)
return;
+ 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();
+ if (response != QMessageBox::Yes)
+ return;
+ }
+
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
return;
@@ -1345,33 +1313,70 @@ void MainWindow::on_actionDeleteInstance_triggered()
APPLICATION->instances()->deleteInstance(id);
}
-void MainWindow::on_actionExportInstance_triggered()
+void MainWindow::on_actionExportInstanceZip_triggered()
{
- if (m_selectedInstance)
- {
+ if (m_selectedInstance) {
ExportInstanceDialog dlg(m_selectedInstance, this);
dlg.exec();
}
}
+void MainWindow::on_actionExportInstanceMrPack_triggered()
+{
+ 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());
@@ -1383,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);
@@ -1404,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);
}
}
@@ -1417,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 &current, 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;
@@ -1607,8 +1608,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, 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());
@@ -1618,7 +1618,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, 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());
@@ -1633,9 +1633,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, 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);
@@ -1653,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);
}
}
@@ -1682,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 3a42c34e..27c2756f 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -112,6 +113,8 @@ private slots:
void on_actionViewInstanceFolder_triggered();
+ void on_actionViewLauncherRootFolder_triggered();
+
void on_actionViewSelectedInstFolder_triggered();
void refreshInstances();
@@ -151,7 +154,11 @@ private slots:
void deleteGroup();
void undoTrashInstance();
- void on_actionExportInstance_triggered();
+ 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 2b6a10b1..e4421d40 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -187,6 +187,7 @@
<bool>true</bool>
</property>
<addaction name="actionViewInstanceFolder"/>
+ <addaction name="actionViewLauncherRootFolder"/>
<addaction name="actionViewCentralModsFolder"/>
</widget>
<widget class="QMenu" name="accountsMenu">
@@ -459,10 +460,39 @@
<string>E&amp;xport...</string>
</property>
<property name="toolTip">
- <string>Export the selected instance as a zip file.</string>
+ <string>Export the selected instance to supported formats.</string>
</property>
- <property name="shortcut">
- <string>Ctrl+E</string>
+ </action>
+ <action name="actionExportInstanceZip">
+ <property name="icon">
+ <iconset theme="launcher"/>
+ </property>
+ <property name="text">
+ <string>Prism Launcher (zip)</string>
+ </property>
+ </action>
+ <action name="actionExportInstanceMrPack">
+ <property name="icon">
+ <iconset theme="modrinth"/>
+ </property>
+ <property name="text">
+ <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">
@@ -528,6 +558,18 @@
<string>Open the instance folder in a file browser.</string>
</property>
</action>
+ <action name="actionViewLauncherRootFolder">
+ <property name="icon">
+ <iconset theme="viewfolder">
+ <normaloff>.</normaloff>.</iconset>
+ </property>
+ <property name="text">
+ <string>&amp;View Launcher Root Folder</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the launcher's root folder in a file browser.</string>
+ </property>
+ </action>
<action name="actionViewCentralModsFolder">
<property name="icon">
<iconset theme="centralmods">
@@ -551,7 +593,7 @@
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
- <string>Report a &amp;Bug...</string>
+ <string>Report a Bug or Suggest a Feature</string>
</property>
<property name="toolTip">
<string>Open the bug tracker to report a bug with %1.</string>
diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp
index 76e3d8ed..88739463 100644
--- a/launcher/ui/dialogs/AboutDialog.cpp
+++ b/launcher/ui/dialogs/AboutDialog.cpp
@@ -71,13 +71,18 @@ QString getCreditsHtml()
//: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers"
stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n";
stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net"));
- stream << QString("<p>dada513 %1</p>\n") .arg(getGitHub("dada513"));
+ stream << QString("<p>d-513 %1</p>\n") .arg(getGitHub("d-513"));
stream << QString("<p>txtsd %1</p>\n") .arg(getWebsite("https://ihavea.quest"));
stream << QString("<p>timoreo %1</p>\n") .arg(getGitHub("timoreo22"));
stream << QString("<p>Ezekiel Smith (ZekeSmith) %1</p>\n") .arg(getGitHub("ZekeSmith"));
stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism"));
- stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio"));
- stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln"));
+ stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio"));
+ stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln"));
+ stream << QString("<p>ViRb3 %1</p>\n") .arg(getGitHub("ViRb3"));
+ stream << QString("<p>Rachel Powers (Ryex) %1</p>\n") .arg(getGitHub("Ryex"));
+ stream << QString("<p>TayouVR %1</p>\n") .arg(getGitHub("TayouVR"));
+ stream << QString("<p>TheKodeToad %1</p>\n") .arg(getGitHub("TheKodeToad"));
+ stream << QString("<p>getchoo %1</p>\n") .arg(getGitHub("getchoo"));
stream << "<br />\n";
// TODO: possibly retrieve from git history at build time?
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index ba453df6..fdfae597 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -195,7 +195,7 @@ void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
auto to_watch = QFileInfo(path);
auto to_watch_path = to_watch.canonicalFilePath();
if (m_watcher.directories().contains(to_watch_path))
- return; // don't watch the same path twice (no loops!)
+ return; // don't watch the same path twice (no loops!)
qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path;
m_watcher.addPath(to_watch_path);
@@ -203,10 +203,9 @@ void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
if (!to_watch.isDir() || !watch_recursive)
return;
-
QDirIterator it(to_watch_path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
- QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths
+ QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths
watchPath(watch_dir, watch_recursive);
}
}
@@ -302,11 +301,35 @@ bool BlockedModsDialog::checkValidPath(QString path)
{
const QFileInfo file = QFileInfo(path);
const QString filename = file.fileName();
- QString laxFilename(filename);
- laxFilename.replace('+', ' ');
- auto compare = [](QString fsfilename, QString metadataFilename) {
- return metadataFilename.compare(fsfilename, Qt::CaseInsensitive) == 0;
+ auto compare = [](QString fsFilename, QString metadataFilename) {
+ return metadataFilename.compare(fsFilename, Qt::CaseInsensitive) == 0;
+ };
+
+ // super lax compare (but not fuzzy)
+ // convert to lowercase
+ // convert all speratores to whitespace
+ // simplify sequence of internal whitespace to a single space
+ // efectivly compare two strings ignoring all separators and case
+ auto laxCompare = [](QString fsfilename, QString metadataFilename) {
+ // allowed character seperators
+ QList<QChar> allowedSeperators = { '-', '+', '.' , '_'};
+
+ // copy in lowercase
+ auto fsName = fsfilename.toLower();
+ auto metaName = metadataFilename.toLower();
+
+ // replace all potential allowed seperatores with whitespace
+ for (auto sep : allowedSeperators) {
+ fsName = fsName.replace(sep, ' ');
+ metaName = metaName.replace(sep, ' ');
+ }
+
+ // remove extraneous whitespace
+ fsName = fsName.simplified();
+ metaName = metaName.simplified();
+
+ return fsName.compare(metaName) == 0;
};
for (auto& mod : m_mods) {
@@ -314,7 +337,7 @@ bool BlockedModsDialog::checkValidPath(QString path)
qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
return true;
}
- if (compare(laxFilename, mod.name)) {
+ if (laxCompare(filename, mod.name)) {
qDebug() << "[Blocked Mods Dialog] Lax name match found:" << mod.name << "| From path:" << path;
return true;
}
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 3f5122f6..d75bb5fe 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -37,18 +37,21 @@
#include <QPushButton>
#include "Application.h"
+#include "BuildConfig.h"
#include "CopyInstanceDialog.h"
#include "ui_CopyInstanceDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
-#include "BaseVersion.h"
-#include "icons/IconList.h"
#include "BaseInstance.h"
+#include "BaseVersion.h"
+#include "DesktopServices.h"
+#include "FileSystem.h"
#include "InstanceList.h"
+#include "icons/IconList.h"
-CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
- :QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
+CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent)
+ : QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
{
ui->setupUi(this);
resize(minimumSizeHint());
@@ -71,8 +74,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
groupList.push_front("");
ui->groupBox->addItems(groupList);
int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
- if(index == -1)
- {
+ if (index == -1) {
index = 0;
}
ui->groupBox->setCurrentIndex(index);
@@ -85,6 +87,35 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled());
ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
+
+ ui->symbolicLinksCheckbox->setChecked(m_selectedOptions.isUseSymLinksEnabled());
+ ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
+
+ ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
+ ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
+
+ auto detectedFS = FS::statFS(m_original->instanceRoot()).fsType;
+
+ m_cloneSupported = FS::canCloneOnFS(detectedFS);
+ m_linkSupported = FS::canLinkOnFS(detectedFS);
+
+ if (m_cloneSupported) {
+ ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedFS)));
+ } else {
+ ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedFS)));
+ }
+
+#if defined(Q_OS_WIN)
+ ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield));
+ ui->symbolicLinksCheckbox->setToolTip(tr("Use symbolic links instead of copying files.") +
+ "\n" + tr("On Windows, symbolic links may require admin permission to create."));
+#endif
+
+ updateLinkOptions();
+ updateUseCloneCheckbox();
+
+ auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help);
+ connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help);
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -96,8 +127,7 @@ void CopyInstanceDialog::updateDialogState()
{
auto allowOK = !instName().isEmpty();
auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok);
- if(OkButton->isEnabled() != allowOK)
- {
+ if (OkButton->isEnabled() != allowOK) {
OkButton->setEnabled(allowOK);
}
}
@@ -105,8 +135,7 @@ void CopyInstanceDialog::updateDialogState()
QString CopyInstanceDialog::instName() const
{
auto result = ui->instNameTextBox->text().trimmed();
- if(result.size())
- {
+ if (result.size()) {
return result;
}
return QString();
@@ -127,6 +156,11 @@ const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const
return m_selectedOptions;
}
+void CopyInstanceDialog::help()
+{
+ DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("instance-copy")));
+}
+
void CopyInstanceDialog::checkAllCheckboxes(const bool& b)
{
ui->keepPlaytimeCheckbox->setChecked(b);
@@ -147,20 +181,46 @@ void CopyInstanceDialog::updateSelectAllCheckbox()
ui->selectAllCheckbox->blockSignals(false);
}
+void CopyInstanceDialog::updateUseCloneCheckbox()
+{
+ ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->hardLinksCheckbox->isChecked());
+ ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled() && !ui->symbolicLinksCheckbox->isChecked() &&
+ !ui->hardLinksCheckbox->isChecked());
+}
+
+void CopyInstanceDialog::updateLinkOptions()
+{
+ ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
+ ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
+
+ ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() &&
+ !ui->useCloneCheckbox->isChecked());
+ ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked());
+
+ bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked());
+ ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked());
+ ui->dontLinkSavesCheckbox->setEnabled(m_linkSupported && linksInUse);
+ ui->recursiveLinkCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isLinkRecursivelyEnabled());
+ ui->dontLinkSavesCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isDontLinkSavesEnabled());
+
+#if defined(Q_OS_WIN)
+ auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok);
+ OkButton->setIcon(m_selectedOptions.isUseSymLinksEnabled() ? style()->standardIcon(QStyle::SP_VistaShield) : QIcon());
+#endif
+}
+
void CopyInstanceDialog::on_iconButton_clicked()
{
IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey);
- if (dlg.result() == QDialog::Accepted)
- {
+ if (dlg.result() == QDialog::Accepted) {
InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
}
}
-
-void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
+void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString& arg1)
{
updateDialogState();
}
@@ -175,10 +235,10 @@ void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state)
void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableCopySaves(state == Qt::Checked);
+ ui->dontLinkSavesCheckbox->setChecked((state == Qt::Checked) && ui->dontLinkSavesCheckbox->isChecked());
updateSelectAllCheckbox();
}
-
void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
{
m_selectedOptions.enableKeepPlaytime(state == Qt::Checked);
@@ -220,3 +280,38 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
m_selectedOptions.enableCopyScreenshots(state == Qt::Checked);
updateSelectAllCheckbox();
}
+
+void CopyInstanceDialog::on_symbolicLinksCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableUseSymLinks(state == Qt::Checked);
+ updateUseCloneCheckbox();
+ updateLinkOptions();
+}
+
+void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
+ if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) {
+ ui->recursiveLinkCheckbox->setChecked(true);
+ }
+ updateUseCloneCheckbox();
+ updateLinkOptions();
+}
+
+void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableLinkRecursively(state == Qt::Checked);
+ updateLinkOptions();
+}
+
+void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
+}
+
+void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked));
+ updateUseCloneCheckbox();
+ updateLinkOptions();
+}
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index 884501d1..698c6e93 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -16,22 +16,21 @@
#pragma once
#include <QDialog>
+#include "BaseInstance.h"
#include "BaseVersion.h"
#include "InstanceCopyPrefs.h"
class BaseInstance;
-namespace Ui
-{
+namespace Ui {
class CopyInstanceDialog;
}
-class CopyInstanceDialog : public QDialog
-{
+class CopyInstanceDialog : public QDialog {
Q_OBJECT
-public:
- explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0);
+ public:
+ explicit CopyInstanceDialog(InstancePtr original, QWidget* parent = 0);
~CopyInstanceDialog();
void updateDialogState();
@@ -41,10 +40,12 @@ public:
QString iconKey() const;
const InstanceCopyPrefs& getChosenOptions() const;
-private
-slots:
+ public slots:
+ void help();
+
+ private slots:
void on_iconButton_clicked();
- void on_instNameTextBox_textChanged(const QString &arg1);
+ void on_instNameTextBox_textChanged(const QString& arg1);
// Checkboxes
void on_selectAllCheckbox_stateChanged(int state);
void on_copySavesCheckbox_stateChanged(int state);
@@ -55,13 +56,23 @@ slots:
void on_copyServersCheckbox_stateChanged(int state);
void on_copyModsCheckbox_stateChanged(int state);
void on_copyScreenshotsCheckbox_stateChanged(int state);
+ void on_symbolicLinksCheckbox_stateChanged(int state);
+ void on_hardLinksCheckbox_stateChanged(int state);
+ void on_recursiveLinkCheckbox_stateChanged(int state);
+ void on_dontLinkSavesCheckbox_stateChanged(int state);
+ void on_useCloneCheckbox_stateChanged(int state);
-private:
+ private:
void checkAllCheckboxes(const bool& b);
void updateSelectAllCheckbox();
+ void updateUseCloneCheckbox();
+ void updateLinkOptions();
+
/* data */
- Ui::CopyInstanceDialog *ui;
+ Ui::CopyInstanceDialog* ui;
QString InstIconKey;
InstancePtr m_original;
InstanceCopyPrefs m_selectedOptions;
+ bool m_cloneSupported = false;
+ bool m_linkSupported = false;
};
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index b7828fe3..5060debc 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>341</width>
- <height>399</height>
+ <width>575</width>
+ <height>695</height>
</rect>
</property>
<property name="windowTitle">
@@ -113,93 +113,268 @@
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="selectAllButtonLayout">
- <item>
- <widget class="QCheckBox" name="selectAllCheckbox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Select all</string>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QGroupBox" name="copyOptionsGroup">
+ <property name="title">
+ <string>Instance Copy Options</string>
+ </property>
+ <layout class="QGridLayout" name="copyOptionsLayout">
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="keepPlaytimeCheckbox">
+ <property name="text">
+ <string>Keep play time</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QCheckBox" name="copyModsCheckbox">
+ <property name="toolTip">
+ <string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string>
+ </property>
+ <property name="text">
+ <string>Copy mods</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="copyResPacksCheckbox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Copy resource packs</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="copyGameOptionsCheckbox">
+ <property name="toolTip">
+ <string>Copy the in-game options like FOV, max framerate, etc.</string>
+ </property>
+ <property name="text">
+ <string>Copy game options</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QCheckBox" name="copyShaderPacksCheckbox">
+ <property name="text">
+ <string>Copy shader packs</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QCheckBox" name="copyServersCheckbox">
+ <property name="text">
+ <string>Copy servers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="copySavesCheckbox">
+ <property name="text">
+ <string>Copy saves</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="copyScreenshotsCheckbox">
+ <property name="text">
+ <string>Copy screenshots</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QCheckBox" name="selectAllCheckbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Select all</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
- <layout class="QGridLayout" name="copyOptionsLayout">
- <item row="6" column="1">
- <widget class="QCheckBox" name="copyModsCheckbox">
- <property name="toolTip">
- <string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string>
- </property>
- <property name="text">
- <string>Copy mods</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0">
- <widget class="QCheckBox" name="copyGameOptionsCheckbox">
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="advancedOptionsLabel">
+ <property name="text">
+ <string>Advanced Copy Options</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="copyModeLayout">
+ <item>
+ <widget class="QGroupBox" name="linkFilesGroup">
<property name="toolTip">
- <string>Copy the in-game options like FOV, max framerate, etc.</string>
+ <string>Use symbolic or hard links instead of copying files.</string>
</property>
- <property name="text">
- <string>Copy game options</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QCheckBox" name="copySavesCheckbox">
- <property name="text">
- <string>Copy saves</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QCheckBox" name="copyShaderPacksCheckbox">
- <property name="text">
- <string>Copy shader packs</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QCheckBox" name="copyServersCheckbox">
- <property name="text">
- <string>Copy servers</string>
+ <property name="title">
+ <string>Symbolic and Hard Link Options</string>
</property>
- </widget>
- </item>
- <item row="6" column="0">
- <widget class="QCheckBox" name="copyResPacksCheckbox">
- <property name="enabled">
- <bool>true</bool>
+ <property name="flat">
+ <bool>false</bool>
</property>
- <property name="text">
- <string>Copy resource packs</string>
+ <property name="checkable">
+ <bool>false</bool>
</property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QCheckBox" name="keepPlaytimeCheckbox">
- <property name="text">
- <string>Keep play time</string>
+ <property name="checked">
+ <bool>false</bool>
</property>
+ <layout class="QVBoxLayout" name="linkOptionsLayout">
+ <item>
+ <widget class="QLabel" name="linkOptionsLabel">
+ <property name="text">
+ <string>Links are supported on most filesystems except FAT</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="linkOptionsGridLayout" rowstretch="0,0,0,0" columnstretch="0,0" rowminimumheight="0,0,0,0" columnminimumwidth="0,0">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="topMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <property name="bottomMargin">
+ <number>6</number>
+ </property>
+ <item row="2" column="1">
+ <widget class="QCheckBox" name="recursiveLinkCheckbox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Link each resource individually instead of linking whole folders at once</string>
+ </property>
+ <property name="text">
+ <string>Link files recursively</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QCheckBox" name="dontLinkSavesCheckbox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>If &quot;copy saves&quot; is selected world save data will be copied instead of linked and thus not shared between instances.</string>
+ </property>
+ <property name="text">
+ <string>Don't link saves</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="hardLinksCheckbox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>Use hard links instead of copying files.</string>
+ </property>
+ <property name="text">
+ <string>Use hard links</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="symbolicLinksCheckbox">
+ <property name="toolTip">
+ <string>Use symbolic links instead of copying files.</string>
+ </property>
+ <property name="text">
+ <string>Use symbolic links</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QCheckBox" name="copyScreenshotsCheckbox">
- <property name="text">
- <string>Copy screenshots</string>
+ <item>
+ <widget class="QGroupBox" name="horizontalGroupBox">
+ <property name="title">
+ <string>CoW (Copy-on-Write) Options</string>
</property>
+ <layout class="QHBoxLayout" name="useCloneLayout">
+ <item>
+ <widget class="QCheckBox" name="useCloneCheckbox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Files cloned with reflinks take up no extra space until they are modified.</string>
+ </property>
+ <property name="text">
+ <string>Clone instead of copying</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="CoWSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="cloneSupportedLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Your filesystem and/or OS doesn't support reflinks</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="margin">
+ <number>4</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</layout>
@@ -210,7 +385,7 @@
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
@@ -220,10 +395,21 @@
<tabstop>iconButton</tabstop>
<tabstop>instNameTextBox</tabstop>
<tabstop>groupBox</tabstop>
+ <tabstop>keepPlaytimeCheckbox</tabstop>
+ <tabstop>copyScreenshotsCheckbox</tabstop>
+ <tabstop>copySavesCheckbox</tabstop>
+ <tabstop>copyShaderPacksCheckbox</tabstop>
+ <tabstop>copyGameOptionsCheckbox</tabstop>
+ <tabstop>copyServersCheckbox</tabstop>
+ <tabstop>copyResPacksCheckbox</tabstop>
+ <tabstop>copyModsCheckbox</tabstop>
+ <tabstop>symbolicLinksCheckbox</tabstop>
+ <tabstop>recursiveLinkCheckbox</tabstop>
+ <tabstop>hardLinksCheckbox</tabstop>
+ <tabstop>dontLinkSavesCheckbox</tabstop>
+ <tabstop>useCloneCheckbox</tabstop>
</tabstops>
- <resources>
- <include location="../../graphics.qrc"/>
- </resources>
+ <resources/>
<connections>
<connection>
<sender>buttonBox</sender>
@@ -232,8 +418,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
- <x>254</x>
- <y>316</y>
+ <x>269</x>
+ <y>692</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@@ -248,8 +434,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
- <x>322</x>
- <y>316</y>
+ <x>337</x>
+ <y>692</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp
index f13e36e8..cc41c394 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.cpp
+++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -34,311 +35,39 @@
*/
#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 "StringUtils.h"
-#include "SeparatorPrefixTree.h"
+#include <functional>
#include "Application.h"
-#include <icons/IconList.h>
-#include <FileSystem.h>
-
-class PackIgnoreProxy : public QSortFilterProxyModel
-{
- Q_OBJECT
-
-public:
- PackIgnoreProxy(InstancePtr instance, QObject *parent) : QSortFilterProxyModel(parent)
- {
- m_instance = instance;
- }
- // NOTE: Sadly, we have to do sorting ourselves.
- bool lessThan(const QModelIndex &left, const QModelIndex &right) const
- {
- QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
- if (!fsm)
- {
- return QSortFilterProxyModel::lessThan(left, right);
- }
- bool asc = sortOrder() == Qt::AscendingOrder ? true : false;
-
- QFileInfo leftFileInfo = fsm->fileInfo(left);
- QFileInfo rightFileInfo = fsm->fileInfo(right);
-
- if (!leftFileInfo.isDir() && rightFileInfo.isDir())
- {
- return !asc;
- }
- if (leftFileInfo.isDir() && !rightFileInfo.isDir())
- {
- return asc;
- }
-
- // sort and proxy model breaks the original model...
- if (sortColumn() == 0)
- {
- return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(),
- Qt::CaseInsensitive) < 0;
- }
- if (sortColumn() == 1)
- {
- auto leftSize = leftFileInfo.size();
- auto rightSize = rightFileInfo.size();
- if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir()))
- {
- return StringUtils::naturalCompare(leftFileInfo.fileName(),
- rightFileInfo.fileName(),
- Qt::CaseInsensitive) < 0
- ? asc
- : !asc;
- }
- return leftSize < rightSize;
- }
- return QSortFilterProxyModel::lessThan(left, right);
- }
-
- virtual Qt::ItemFlags flags(const QModelIndex &index) const
- {
- if (!index.isValid())
- return Qt::NoItemFlags;
-
- auto sourceIndex = mapToSource(index);
- Qt::ItemFlags flags = sourceIndex.flags();
- if (index.column() == 0)
- {
- flags |= Qt::ItemIsUserCheckable;
- if (sourceIndex.model()->hasChildren(sourceIndex))
- {
- flags |= Qt::ItemIsAutoTristate;
- }
- }
-
- return flags;
- }
-
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
- {
- QModelIndex sourceIndex = mapToSource(index);
-
- if (index.column() == 0 && role == Qt::CheckStateRole)
- {
- QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
- auto blockedPath = relPath(fsm->filePath(sourceIndex));
- auto cover = blocked.cover(blockedPath);
- if (!cover.isNull())
- {
- return QVariant(Qt::Unchecked);
- }
- else if (blocked.exists(blockedPath))
- {
- return QVariant(Qt::PartiallyChecked);
- }
- else
- {
- return QVariant(Qt::Checked);
- }
- }
-
- return sourceIndex.data(role);
- }
-
- virtual bool setData(const QModelIndex &index, const QVariant &value,
- int role = Qt::EditRole)
- {
- if (index.column() == 0 && role == Qt::CheckStateRole)
- {
- Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
- return setFilterState(index, state);
- }
-
- QModelIndex sourceIndex = mapToSource(index);
- return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role);
- }
-
- QString relPath(const QString &path) const
- {
- QString prefix = QDir().absoluteFilePath(m_instance->instanceRoot());
- prefix += '/';
- if (!path.startsWith(prefix))
- {
- return QString();
- }
- return path.mid(prefix.size());
- }
-
- bool setFilterState(QModelIndex index, Qt::CheckState state)
- {
- QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
-
- if (!fsm)
- {
- return false;
- }
-
- QModelIndex sourceIndex = mapToSource(index);
- auto blockedPath = relPath(fsm->filePath(sourceIndex));
- bool changed = false;
- if (state == Qt::Unchecked)
- {
- // blocking a path
- auto &node = blocked.insert(blockedPath);
- // get rid of all blocked nodes below
- node.clear();
- changed = true;
- }
- else if (state == Qt::Checked || state == Qt::PartiallyChecked)
- {
- if (!blocked.remove(blockedPath))
- {
- auto cover = blocked.cover(blockedPath);
- qDebug() << "Blocked by cover" << cover;
- // uncover
- blocked.remove(cover);
- // block all contents, except for any cover
- QModelIndex rootIndex =
- fsm->index(FS::PathCombine(m_instance->instanceRoot(), cover));
- QModelIndex doing = rootIndex;
- int row = 0;
- QStack<QModelIndex> todo;
- while (1)
- {
- auto node = fsm->index(row, 0, doing);
- if (!node.isValid())
- {
- if (!todo.size())
- {
- break;
- }
- else
- {
- doing = todo.pop();
- row = 0;
- continue;
- }
- }
- auto relpath = relPath(fsm->filePath(node));
- if (blockedPath.startsWith(relpath)) // cover found?
- {
- // continue processing cover later
- todo.push(node);
- }
- else
- {
- // or just block this one.
- blocked.insert(relpath);
- }
- row++;
- }
- }
- changed = true;
- }
- if (changed)
- {
- // update the thing
- emit dataChanged(index, index, {Qt::CheckStateRole});
- // update everything above index
- QModelIndex up = index.parent();
- while (1)
- {
- if (!up.isValid())
- break;
- emit dataChanged(up, up, {Qt::CheckStateRole});
- up = up.parent();
- }
- // and everything below the index
- QModelIndex doing = index;
- int row = 0;
- QStack<QModelIndex> todo;
- while (1)
- {
- auto node = this->index(row, 0, doing);
- if (!node.isValid())
- {
- if (!todo.size())
- {
- break;
- }
- else
- {
- doing = todo.pop();
- row = 0;
- continue;
- }
- }
- emit dataChanged(node, node, {Qt::CheckStateRole});
- todo.push(node);
- row++;
- }
- // siblings and unrelated nodes are ignored
- }
- return true;
- }
-
- bool shouldExpand(QModelIndex index)
- {
- QModelIndex sourceIndex = mapToSource(index);
- QFileSystemModel *fsm = qobject_cast<QFileSystemModel *>(sourceModel());
- if (!fsm)
- {
- return false;
- }
- auto blockedPath = relPath(fsm->filePath(sourceIndex));
- auto found = blocked.find(blockedPath);
- if(found)
- {
- return !found->leaf();
- }
- return false;
- }
-
- void setBlockedPaths(QStringList paths)
- {
- beginResetModel();
- blocked.clear();
- blocked.insert(paths);
- endResetModel();
- }
-
- const SeparatorPrefixTree<'/'> & blockedPaths() const
- {
- return blocked;
- }
-
-protected:
- bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
- {
- Q_UNUSED(source_parent)
-
- // adjust the columns you want to filter out here
- // return false for those that will be hidden
- if (source_column == 2 || source_column == 3)
- return false;
-
- return true;
- }
-
-private:
- InstancePtr m_instance;
- SeparatorPrefixTree<'/'> blocked;
-};
+#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);
auto model = new QFileSystemModel(this);
- proxyModel = new PackIgnoreProxy(m_instance, this);
- loadPackIgnore();
- proxyModel->setSourceModel(model);
+ model->setIconProvider(&icons);
auto root = instance->instanceRoot();
+ proxyModel = new FileIgnoreProxy(root, this);
+ 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);
@@ -402,34 +131,22 @@ bool ExportInstanceDialog::doExport()
const QString output = QFileDialog::getSaveFileName(
this, tr("Export %1").arg(m_instance->name()),
- FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite);
+ FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr);
if (output.isEmpty())
{
return false;
}
- if (QFile::exists(output))
- {
- int ret =
- QMessageBox::question(this, tr("Overwrite?"),
- tr("This file already exists. Do you want to overwrite it?"),
- QMessageBox::No, QMessageBox::Yes);
- if (ret == QMessageBox::No)
- {
- return false;
- }
- }
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;
}
- if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files))
+
+ if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true))
{
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;
@@ -508,5 +225,3 @@ void ExportInstanceDialog::savePackIgnore()
qWarning() << e.cause();
}
}
-
-#include "ExportInstanceDialog.moc"
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.h b/launcher/ui/dialogs/ExportInstanceDialog.h
index dea02d1b..5e801875 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.h
+++ b/launcher/ui/dialogs/ExportInstanceDialog.h
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
- * 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
@@ -18,9 +38,10 @@
#include <QDialog>
#include <QModelIndex>
#include <memory>
+#include "FileIgnoreProxy.h"
+#include "FastFileIconProvider.h"
class BaseInstance;
-class PackIgnoreProxy;
typedef std::shared_ptr<BaseInstance> InstancePtr;
namespace Ui
@@ -47,7 +68,8 @@ private:
private:
Ui::ExportInstanceDialog *ui;
InstancePtr m_instance;
- PackIgnoreProxy * proxyModel;
+ FileIgnoreProxy * proxyModel;
+ FastFileIconProvider icons;
private slots:
void rowsInserted(QModelIndex parent, int top, int bottom);
diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp
new file mode 100644
index 00000000..2abe2805
--- /dev/null
+++ b/launcher/ui/dialogs/ExportPackDialog.cpp
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * 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 "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_ExportPackDialog.h"
+
+#include <QFileDialog>
+#include <QFileSystemModel>
+#include <QJsonDocument>
+#include <QMessageBox>
+#include <QPushButton>
+#include "FastFileIconProvider.h"
+#include "FileSystem.h"
+#include "MMCZip.h"
+#include "modplatform/modrinth/ModrinthPackExportTask.h"
+
+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());
+ 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, &ExportPackDialog::validate);
+ connect(ui->version, &QLineEdit::textEdited, this, &ExportPackDialog::validate);
+ // the instance name can technically be empty
+ validate();
+
+ QFileSystemModel* model = new QFileSystemModel(this);
+ model->setIconProvider(&icons);
+
+ // 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);
+
+ const QDir::Filters filter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);
+
+ for (const QString& file : root.entryList(filter)) {
+ if (!(file == "mods" || file == "coremods" || file == "datapacks" || file == "config" || file == "options.txt" ||
+ file == "servers.dat"))
+ proxy->blockedPaths().insert(file);
+ }
+
+ 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()));
+ }
+
+ ui->treeView->setModel(proxy);
+ ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
+ ui->treeView->sortByColumn(0, Qt::AscendingOrder);
+
+ model->setFilter(filter);
+ model->setRootPath(instance->gameRoot());
+
+ QHeaderView* headerView = ui->treeView->header();
+ headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
+ headerView->setSectionResizeMode(0, QHeaderView::Stretch);
+}
+
+ExportPackDialog::~ExportPackDialog()
+{
+ delete ui;
+}
+
+void ExportPackDialog::done(int result)
+{
+ if (result == Accepted) {
+ const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
+ 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;
+ 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] {
+ 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)
+ return;
+ }
+
+ QDialog::done(result);
+}
+
+void ExportPackDialog::validate()
+{
+ 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/ExportPackDialog.h b/launcher/ui/dialogs/ExportPackDialog.h
new file mode 100644
index 00000000..830c24d2
--- /dev/null
+++ b/launcher/ui/dialogs/ExportPackDialog.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * 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 "BaseInstance.h"
+#include "FastFileIconProvider.h"
+#include "FileIgnoreProxy.h"
+#include "modplatform/ModIndex.h"
+
+namespace Ui {
+class ExportPackDialog;
+}
+
+class ExportPackDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ 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::ExportPackDialog* ui;
+ FileIgnoreProxy* proxy;
+ FastFileIconProvider icons;
+ const ModPlatform::ResourceProvider m_provider;
+};
diff --git a/launcher/ui/dialogs/ExportPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui
new file mode 100644
index 00000000..3976e28f
--- /dev/null
+++ b/launcher/ui/dialogs/ExportPackDialog.ui
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportPackDialog</class>
+ <widget class="QDialog" name="ExportPackDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>650</width>
+ <height>413</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Export Pack</string>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="information">
+ <property name="title">
+ <string>Information</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <widget class="QLabel" name="summaryLabel">
+ <property name="text">
+ <string>Summary</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="summary"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="versionLabel">
+ <property name="text">
+ <string>Version</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="name"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="version">
+ <property name="text">
+ <string>1.0.0</string>
+ </property>
+ </widget>
+ </item>
+
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="filesLabel">
+ <property name="text">
+ <string>Files</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="treeView">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>name</tabstop>
+ <tabstop>version</tabstop>
+ <tabstop>summary</tabstop>
+ <tabstop>treeView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ExportPackDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>324</x>
+ <y>390</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>324</x>
+ <y>206</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ExportPackDialog</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/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 df182f09..7b9bb944 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -54,9 +54,8 @@
#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/ftb/FtbPage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h"
@@ -100,7 +99,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
// NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below.
m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- m_container = new PageContainer(this);
+ m_container = new PageContainer(this, {}, this);
m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding);
m_container->layout()->setContentsMargins(0, 0, 0, 0);
ui->verticalLayout->insertWidget(2, m_container);
@@ -163,12 +162,11 @@ 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)
pages.append(new FlamePage(this));
- pages.append(new FtbPage(this));
pages.append(new LegacyFTB::Page(this));
pages.append(new ModrinthPage(this));
pages.append(new TechnicPage(this));
diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp
index e1b5dd74..b646e391 100644
--- a/launcher/ui/dialogs/NewsDialog.cpp
+++ b/launcher/ui/dialogs/NewsDialog.cpp
@@ -32,7 +32,7 @@ NewsDialog::~NewsDialog()
void NewsDialog::selectedArticleChanged(const QString& new_title)
{
- auto const& article_entry = m_entries.constFind(new_title).value();
+ auto article_entry = m_entries.constFind(new_title).value();
ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title));
diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp
index da73a449..4243e291 100644
--- a/launcher/ui/dialogs/ProgressDialog.cpp
+++ b/launcher/ui/dialogs/ProgressDialog.cpp
@@ -1,33 +1,75 @@
-/* Copyright 2013-2021 MultiMC Contributors
+/// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PrismLaucher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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.
*/
#include "ProgressDialog.h"
+#include <QPoint>
#include "ui_ProgressDialog.h"
+#include <limits>
#include <QDebug>
#include <QKeyEvent>
#include "tasks/Task.h"
+#include "ui/widgets/SubTaskProgressBar.h"
+
+
+// map a value in a numeric range of an arbitrary type to between 0 and INT_MAX
+// for getting the best precision out of the qt progress bar
+template<typename T, std::enable_if_t<std::is_arithmetic_v<T>, bool> = true>
+std::tuple<int, int> map_int_zero_max(T current, T range_max, T range_min)
+{
+ int int_max = std::numeric_limits<int>::max();
+
+ auto type_range = range_max - range_min;
+ double percentage = static_cast<double>(current - range_min) / static_cast<double>(type_range);
+ int mapped_current = percentage * int_max;
+
+ return {mapped_current, int_max};
+}
+
+
ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog)
{
ui->setupUi(this);
+ 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)
@@ -53,11 +95,39 @@ ProgressDialog::~ProgressDialog()
delete ui;
}
-void ProgressDialog::updateSize()
-{
- QSize qSize = QSize(480, minimumSizeHint().height());
- resize(qSize);
- setFixedSize(qSize);
+void ProgressDialog::updateSize(bool recenterParent)
+{
+ QSize lastSize = this->size();
+ 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)
+ {
+ // 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);
+ }
+
}
int ProgressDialog::execWithTask(Task* task)
@@ -79,17 +149,15 @@ int ProgressDialog::execWithTask(Task* task)
connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed);
connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded);
connect(task, &Task::status, this, &ProgressDialog::changeStatus);
- connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus);
+ connect(task, &Task::details, this, &ProgressDialog::changeStatus);
+ connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress);
connect(task, &Task::progress, this, &ProgressDialog::changeProgress);
-
connect(task, &Task::aborted, this, &ProgressDialog::hide);
connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled);
m_is_multi_step = task->isMultiStep();
- if (!m_is_multi_step) {
- ui->globalStatusLabel->setHidden(true);
- ui->globalProgressBar->setHidden(true);
- }
+ ui->taskProgressScrollArea->setHidden(!m_is_multi_step);
+ updateSize();
// It's a good idea to start the task after we entered the dialog's event loop :^)
if (!task->isRunning()) {
@@ -149,23 +217,55 @@ void ProgressDialog::onTaskSucceeded()
void ProgressDialog::changeStatus(const QString& status)
{
ui->globalStatusLabel->setText(task->getStatus());
- ui->statusLabel->setText(task->getStepStatus());
+ ui->globalStatusLabel->adjustSize();
+ ui->globalStatusDetailsLabel->setText(task->getDetails());
+ ui->globalStatusDetailsLabel->adjustSize();
updateSize();
}
+void ProgressDialog::addTaskProgress(TaskStepProgress const& progress)
+{
+ SubTaskProgressBar* task_bar = new SubTaskProgressBar(this);
+ taskProgress.insert(progress.uid, task_bar);
+ ui->taskProgressLayout->addWidget(task_bar);
+}
+
+void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress)
+{
+ m_is_multi_step = true;
+ if(ui->taskProgressScrollArea->isHidden()) {
+ ui->taskProgressScrollArea->setHidden(false);
+ updateSize();
+ }
+
+ if (!taskProgress.contains(task_progress.uid))
+ addTaskProgress(task_progress);
+ auto task_bar = taskProgress.value(task_progress.uid);
+
+
+ auto const [mapped_current, mapped_total] = map_int_zero_max<qint64>(task_progress.current, task_progress.total, 0);
+ if (task_progress.total <= 0) {
+ task_bar->setRange(0, 0);
+ } else {
+ task_bar->setRange(0, mapped_total);
+ }
+
+ task_bar->setValue(mapped_current);
+ task_bar->setStatus(task_progress.status);
+ task_bar->setDetails(task_progress.details);
+
+ if (task_progress.isDone()) {
+ task_bar->setVisible(false);
+ }
+
+}
+
void ProgressDialog::changeProgress(qint64 current, qint64 total)
{
ui->globalProgressBar->setMaximum(total);
ui->globalProgressBar->setValue(current);
- if (!m_is_multi_step) {
- ui->taskProgressBar->setMaximum(total);
- ui->taskProgressBar->setValue(current);
- } else {
- ui->taskProgressBar->setMaximum(task->getStepProgress());
- ui->taskProgressBar->setValue(task->getStepTotalProgress());
- }
}
void ProgressDialog::keyPressEvent(QKeyEvent* e)
diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h
index 0b4b78a4..f062be08 100644
--- a/launcher/ui/dialogs/ProgressDialog.h
+++ b/launcher/ui/dialogs/ProgressDialog.h
@@ -1,22 +1,50 @@
-/* Copyright 2013-2021 MultiMC Contributors
+/// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PrismLaucher - Minecraft Launcher
+ * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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
#include <QDialog>
#include <memory>
+#include <QHash>
+#include <QUuid>
+
+#include "QObjectPtr.h"
+#include "tasks/Task.h"
+
+#include "ui/widgets/SubTaskProgressBar.h"
class Task;
class SequentialTask;
@@ -34,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);
@@ -52,6 +80,7 @@ slots:
void changeStatus(const QString &status);
void changeProgress(qint64 current, qint64 total);
+ void changeStepProgress(TaskStepProgress const& task_progress);
private
@@ -64,6 +93,7 @@ protected:
private:
bool handleImmediateResult(QDialog::DialogCode &result);
+ void addTaskProgress(TaskStepProgress const& progress);
private:
Ui::ProgressDialog *ui;
@@ -71,4 +101,8 @@ private:
Task *task;
bool m_is_multi_step = false;
+ QHash<QUuid, SubTaskProgressBar*> taskProgress;
+
+
};
+
diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui
index 34ab71e3..a4d08124 100644
--- a/launcher/ui/dialogs/ProgressDialog.ui
+++ b/launcher/ui/dialogs/ProgressDialog.ui
@@ -2,75 +2,135 @@
<ui version="4.0">
<class>ProgressDialog</class>
<widget class="QDialog" name="ProgressDialog">
- <property name="minimumSize">
- <size>
- <width>400</width>
- <height>0</height>
- </size>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>480</width>
+ <height>210</height>
+ </rect>
</property>
- <property name="maximumSize">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>1</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
<size>
- <width>600</width>
- <height>16777215</height>
+ <width>480</width>
+ <height>210</height>
</size>
</property>
<property name="windowTitle">
<string>Please wait...</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="4" column="0">
- <widget class="QPushButton" name="skipButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <property name="sizeGripEnabled">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
+ <item>
+ <widget class="QLabel" name="globalStatusLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>15</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Global Task Status...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="globalStatusDetailsLabel">
+ <property name="text">
+ <string>Global Status Details...</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="globalProgressBar">
+ <property name="enabled">
+ <bool>true</bool>
</property>
- <property name="text">
- <string>Skip</string>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>24</height>
+ </size>
</property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="globalStatusLabel">
- <property name="text">
- <string>Global Task Status...</string>
+ <property name="value">
+ <number>24</number>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="statusLabel">
+ <item>
+ <widget class="QScrollArea" name="taskProgressScrollArea">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="text">
- <string>Task Status...</string>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>100</height>
+ </size>
</property>
- <property name="wordWrap">
- <bool>true</bool>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
</property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QProgressBar" name="taskProgressBar">
- <property name="value">
- <number>24</number>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
- <property name="textVisible">
- <bool>false</bool>
+ <property name="widgetResizable">
+ <bool>true</bool>
</property>
+ <widget class="QWidget" name="taskProgressContainer">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>464</width>
+ <height>96</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="taskProgressLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ </layout>
+ </widget>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QProgressBar" name="globalProgressBar">
- <property name="enabled">
- <bool>true</bool>
+ <item>
+ <widget class="QPushButton" name="skipButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="value">
- <number>24</number>
+ <property name="text">
+ <string>Skip</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
index edb7d063..b17eced3 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
@@ -18,17 +18,24 @@
*/
#include "ResourceDownloadDialog.h"
+#include <QEventLoop>
+#include <QList>
#include <QPushButton>
+#include <algorithm>
#include "Application.h"
#include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourcePackFolderModel.h"
-#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h"
+#include "minecraft/mod/TexturePackFolderModel.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
+#include "modplatform/ModIndex.h"
+#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ReviewMessageBox.h"
#include "ui/pages/modplatform/ResourcePage.h"
@@ -36,12 +43,17 @@
#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 {
ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::shared_ptr<ResourceFolderModel> base_model)
- : QDialog(parent), m_base_model(base_model), m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel), m_vertical_layout(this)
+ : QDialog(parent)
+ , m_base_model(base_model)
+ , m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
+ , m_vertical_layout(this)
{
setObjectName(QStringLiteral("ResourceDownloadDialog"));
@@ -89,7 +101,7 @@ void ResourceDownloadDialog::reject()
// won't work with subclasses if we put it in this ctor.
void ResourceDownloadDialog::initializeContainer()
{
- m_container = new PageContainer(this);
+ m_container = new PageContainer(this, {}, this);
m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding);
m_container->layout()->setContentsMargins(0, 0, 0, 0);
m_vertical_layout.addWidget(m_container);
@@ -102,7 +114,8 @@ void ResourceDownloadDialog::initializeContainer()
void ResourceDownloadDialog::connectButtons()
{
auto OkButton = m_buttons.button(QDialogButtonBox::Ok);
- OkButton->setToolTip(tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString()));
+ OkButton->setToolTip(
+ tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString()));
connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm);
auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel);
@@ -112,23 +125,79 @@ void ResourceDownloadDialog::connectButtons()
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
}
-void ResourceDownloadDialog::confirm()
+static ModPlatform::ProviderCapabilities ProviderCaps;
+
+QStringList getRequiredBy(QList<ResourceDownloadDialog::DownloadTaskPtr> tasks, ResourceDownloadDialog::DownloadTaskPtr pack)
{
- auto keys = m_selected.keys();
- keys.sort(Qt::CaseInsensitive);
+ auto addonId = pack->getPack()->addonId;
+ auto provider = pack->getPack()->provider;
+ auto version = pack->getVersionID();
+ auto req = QStringList();
+ for (auto& task : tasks) {
+ if (provider != task->getPack()->provider)
+ continue;
+ auto deps = task->getVersion().dependencies;
+ if (auto dep = std::find_if(deps.begin(), deps.end(),
+ [addonId, provider, version](const ModPlatform::Dependency& d) {
+ return d.type == ModPlatform::DependencyType::REQUIRED &&
+ (provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty()
+ ? version == d.version
+ : d.addonId == addonId);
+ });
+ dep != deps.end()) {
+ req.append(task->getName());
+ }
+ }
+ return req;
+}
+void ResourceDownloadDialog::confirm()
+{
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
- for (auto& task : keys) {
- auto selected = m_selected.constFind(task).value();
- confirm_dialog->appendResource({ task, selected->getFilename(), selected->getCustomPath() });
+ if (auto task = getModDependenciesTask(); task) {
+ connect(task.get(), &Task::failed, this,
+ [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
+
+ connect(task.get(), &Task::succeeded, this, [&]() {
+ QStringList warnings = task->warnings();
+ if (warnings.count()) {
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
+ }
+ });
+
+ // Check for updates
+ ProgressDialog progress_dialog(this);
+ progress_dialog.setSkipButton(true, tr("Abort"));
+ progress_dialog.setWindowTitle(tr("Checking for dependencies..."));
+ auto ret = progress_dialog.execWithTask(task.get());
+
+ // If the dialog was skipped / some download error happened
+ if (ret == QDialog::DialogCode::Rejected) {
+ QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
+ return;
+ } else {
+ for (auto dep : task->getDependecies())
+ addResource(dep->pack, dep->version);
+ }
+ }
+
+ auto selected = getTasks();
+ std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) {
+ return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
+ });
+ for (auto& task : selected) {
+ confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
+ ProviderCaps.name(task->getProvider()), getRequiredBy(selected, task) });
}
if (confirm_dialog->exec()) {
auto deselected = confirm_dialog->deselectedResources();
- for (auto name : deselected) {
- m_selected.remove(name);
+ for (auto page : m_container->getPages()) {
+ auto res = static_cast<ResourcePage*>(page);
+ for (auto name : deselected)
+ res->removeResourceFromPage(name);
}
this->accept();
@@ -145,46 +214,39 @@ ResourcePage* ResourceDownloadDialog::getSelectedPage()
return m_selectedPage;
}
-void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, bool is_indexed)
+void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver)
{
- removeResource(pack, ver);
-
- ver.is_currently_selected = true;
- m_selected.insert(pack.name, makeShared<ResourceDownloadTask>(pack, ver, getBaseModel(), is_indexed));
-
- m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty());
+ removeResource(pack->name);
+ m_selectedPage->addResourceToPage(pack, ver, getBaseModel());
+ setButtonStatus();
}
-static ModPlatform::IndexedVersion& getVersionWithID(ModPlatform::IndexedPack& pack, QVariant id)
+void ResourceDownloadDialog::removeResource(const QString& pack_name)
{
- Q_ASSERT(pack.versionsLoaded);
- auto it = std::find_if(pack.versions.begin(), pack.versions.end(), [id](auto const& v) { return v.fileId == id; });
- Q_ASSERT(it != pack.versions.end());
- return *it;
+ for (auto page : m_container->getPages()) {
+ static_cast<ResourcePage*>(page)->removeResourceFromPage(pack_name);
+ }
+ setButtonStatus();
}
-void ResourceDownloadDialog::removeResource(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver)
+void ResourceDownloadDialog::setButtonStatus()
{
- if (auto selected_task_it = m_selected.find(pack.name); selected_task_it != m_selected.end()) {
- auto selected_task = *selected_task_it;
- auto old_version_id = selected_task->getVersionID();
-
- // If the new and old version IDs don't match, search for the old one and deselect it.
- if (ver.fileId != old_version_id)
- getVersionWithID(pack, old_version_id).is_currently_selected = false;
+ auto selected = false;
+ for (auto page : m_container->getPages()) {
+ auto res = static_cast<ResourcePage*>(page);
+ selected = selected || res->hasSelectedPacks();
}
-
- // Deselect the new version too, since all versions of that pack got removed.
- ver.is_currently_selected = false;
-
- m_selected.remove(pack.name);
-
- m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty());
+ m_buttons.button(QDialogButtonBox::Ok)->setEnabled(selected);
}
const QList<ResourceDownloadDialog::DownloadTaskPtr> ResourceDownloadDialog::getTasks()
{
- return m_selected.values();
+ QList<DownloadTaskPtr> selected;
+ for (auto page : m_container->getPages()) {
+ auto res = static_cast<ResourcePage*>(page);
+ selected.append(res->selectedPacks());
+ }
+ return selected;
}
void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected)
@@ -205,8 +267,6 @@ void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* s
m_selectedPage->setSearchTerm(prev_page->getSearchTerm());
}
-
-
ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr<ModFolderModel>& mods, BaseInstance* instance)
: ResourceDownloadDialog(parent, mods), m_instance(instance)
{
@@ -223,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]);
@@ -232,6 +295,18 @@ QList<BasePage*> ModDownloadDialog::getPages()
return pages;
}
+GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask()
+{
+ if (auto model = dynamic_cast<ModFolderModel*>(getBaseModel().get()); model) {
+ QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers;
+ for (auto& selected : getTasks()) {
+ selectedVers.append(std::make_shared<GetModDependenciesTask::PackDependency>(selected->getPack(), selected->getVersion()));
+ }
+
+ return makeShared<GetModDependenciesTask>(this, m_instance, model, selectedVers);
+ }
+ return nullptr;
+};
ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent,
const std::shared_ptr<ResourcePackFolderModel>& resource_packs,
@@ -255,10 +330,11 @@ QList<BasePage*> ResourcePackDownloadDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(FlameResourcePackPage::create(this, *m_instance));
+ m_selectedPage = dynamic_cast<ResourcePackResourcePage*>(pages[0]);
+
return pages;
}
-
TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent,
const std::shared_ptr<TexturePackFolderModel>& resource_packs,
BaseInstance* instance)
@@ -281,10 +357,11 @@ QList<BasePage*> TexturePackDownloadDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(FlameTexturePackPage::create(this, *m_instance));
+ m_selectedPage = dynamic_cast<TexturePackResourcePage*>(pages[0]);
+
return pages;
}
-
ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent,
const std::shared_ptr<ShaderPackFolderModel>& shaders,
BaseInstance* instance)
@@ -305,6 +382,8 @@ QList<BasePage*> ShaderPackDownloadDialog::getPages()
pages.append(ModrinthShaderPackPage::create(this, *m_instance));
+ m_selectedPage = dynamic_cast<ShaderPackResourcePage*>(pages[0]);
+
return pages;
}
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h
index 5678dc8b..f65daaa3 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.h
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.h
@@ -25,6 +25,7 @@
#include <QLayout>
#include "QObjectPtr.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "ui/pages/BasePageProvider.h"
@@ -62,8 +63,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
bool selectPage(QString pageId);
ResourcePage* getSelectedPage();
- void addResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&, bool is_indexed = false);
- void removeResource(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+ void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&);
+ void removeResource(const QString&);
const QList<DownloadTaskPtr> getTasks();
[[nodiscard]] const std::shared_ptr<ResourceFolderModel> getBaseModel() const { return m_base_model; }
@@ -79,6 +80,9 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
protected:
[[nodiscard]] virtual QString geometrySaveKey() const { return ""; }
+ void setButtonStatus();
+
+ [[nodiscard]] virtual GetModDependenciesTask::Ptr getModDependenciesTask() { return nullptr; }
protected:
const std::shared_ptr<ResourceFolderModel> m_base_model;
@@ -88,12 +92,8 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider {
QDialogButtonBox m_buttons;
QVBoxLayout m_vertical_layout;
-
- QHash<QString, DownloadTaskPtr> m_selected;
};
-
-
class ModDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
@@ -106,6 +106,7 @@ class ModDownloadDialog final : public ResourceDownloadDialog {
[[nodiscard]] QString geometrySaveKey() const override { return "ModDownloadGeometry"; }
QList<BasePage*> getPages() override;
+ GetModDependenciesTask::Ptr getModDependenciesTask() override;
private:
BaseInstance* m_instance;
@@ -135,8 +136,8 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog {
public:
explicit TexturePackDownloadDialog(QWidget* parent,
- const std::shared_ptr<TexturePackFolderModel>& resource_packs,
- BaseInstance* instance);
+ const std::shared_ptr<TexturePackFolderModel>& resource_packs,
+ BaseInstance* instance);
~TexturePackDownloadDialog() override = default;
//: String that gets appended to the texture pack download dialog title ("Download " + resourcesString())
@@ -153,9 +154,7 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog {
Q_OBJECT
public:
- explicit ShaderPackDownloadDialog(QWidget* parent,
- const std::shared_ptr<ShaderPackFolderModel>& shader_packs,
- BaseInstance* instance);
+ explicit ShaderPackDownloadDialog(QWidget* parent, const std::shared_ptr<ShaderPackFolderModel>& shader_packs, BaseInstance* instance);
~ShaderPackDownloadDialog() override = default;
//: String that gets appended to the shader pack download dialog title ("Download " + resourcesString())
diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp
index 7b2df278..7b33765f 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.cpp
+++ b/launcher/ui/dialogs/ReviewMessageBox.cpp
@@ -40,7 +40,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
auto filenameItem = new QTreeWidgetItem(itemTop);
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
- itemTop->insertChildren(0, { filenameItem });
+ auto childIndx = 0;
+ itemTop->insertChildren(childIndx++, { filenameItem });
if (!info.custom_file_path.isEmpty()) {
auto customPathItem = new QTreeWidgetItem(itemTop);
@@ -49,7 +50,31 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
itemTop->insertChildren(1, { customPathItem });
itemTop->setIcon(1, QIcon(APPLICATION->getThemedIcon("status-yellow")));
- itemTop->setToolTip(1, tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it."));
+ itemTop->setToolTip(
+ childIndx++,
+ tr("This file will be downloaded to a folder location different from the default, possibly due to its loader requiring it."));
+ }
+
+ auto providerItem = new QTreeWidgetItem(itemTop);
+ providerItem->setText(0, tr("Provider: %1").arg(info.provider));
+
+ itemTop->insertChildren(childIndx++, { providerItem });
+
+ if (!info.required_by.isEmpty()) {
+ auto requiredByItem = new QTreeWidgetItem(itemTop);
+ if (info.required_by.length() == 1) {
+ requiredByItem->setText(0, tr("Required by: %1").arg(info.required_by.back()));
+ } else {
+ requiredByItem->setText(0, tr("Required by:"));
+ auto i = 0;
+ for (auto req : info.required_by) {
+ auto reqItem = new QTreeWidgetItem(requiredByItem);
+ reqItem->setText(0, req);
+ reqItem->insertChildren(i++, { reqItem });
+ }
+ }
+
+ itemTop->insertChildren(childIndx++, { requiredByItem });
}
ui->modTreeWidget->addTopLevelItem(itemTop);
diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h
index 5ec2bc23..a520cc2a 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.h
+++ b/launcher/ui/dialogs/ReviewMessageBox.h
@@ -13,9 +13,11 @@ class ReviewMessageBox : public QDialog {
static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*;
using ResourceInformation = struct res_info {
- QString name;
- QString filename;
- QString custom_file_path {};
+ QString name;
+ QString filename;
+ QString custom_file_path{};
+ QString provider;
+ QStringList required_by;
};
void appendResource(ResourceInformation&& info);
diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp
index d7880334..5feb70d2 100644
--- a/launcher/ui/dialogs/VersionSelectDialog.cpp
+++ b/launcher/ui/dialogs/VersionSelectDialog.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
- * 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.
*/
#include "VersionSelectDialog.h"
@@ -22,15 +42,10 @@
#include <QtWidgets/QVBoxLayout>
#include <QDebug>
-#include "ui/dialogs/ProgressDialog.h"
#include "ui/widgets/VersionSelectWidget.h"
-#include "ui/dialogs/CustomMessageBox.h"
#include "BaseVersion.h"
#include "BaseVersionList.h"
-#include "tasks/Task.h"
-#include "Application.h"
-#include "VersionProxyModel.h"
VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, bool cancelable)
: QDialog(parent)
@@ -40,7 +55,7 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title,
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
- m_versionWidget = new VersionSelectWidget(parent);
+ m_versionWidget = new VersionSelectWidget(true, parent);
m_verticalLayout->addWidget(m_versionWidget);
m_horizontalLayout = new QHBoxLayout();
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/BasePage.h b/launcher/ui/pages/BasePage.h
index ceb24040..dc2bde99 100644
--- a/launcher/ui/pages/BasePage.h
+++ b/launcher/ui/pages/BasePage.h
@@ -35,15 +35,16 @@
#pragma once
-#include <QString>
#include <QIcon>
+#include <QString>
+#include <functional>
#include <memory>
#include "BasePageContainer.h"
-class BasePage
-{
-public:
+class BasePage {
+ public:
+ using updateExtraInfoFunc = std::function<void(QString, QString)>;
virtual ~BasePage() {}
virtual QString id() const = 0;
virtual QString displayName() const = 0;
@@ -63,17 +64,16 @@ public:
}
virtual void openedImpl() {}
virtual void closedImpl() {}
- virtual void setParentContainer(BasePageContainer * container)
- {
- m_container = container;
- };
- virtual void retranslate() { }
+ virtual void setParentContainer(BasePageContainer* container) { m_container = container; };
+ virtual void retranslate() {}
-public:
+ public:
int stackIndex = -1;
int listIndex = -1;
-protected:
- BasePageContainer * m_container = nullptr;
+ updateExtraInfoFunc updateExtraInfo;
+
+ protected:
+ BasePageContainer* m_container = nullptr;
bool isOpened = false;
};
diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp
index f662ee1c..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('/'))
{
@@ -177,7 +179,7 @@ void APIPage::applySettings()
metaURL.setScheme("https");
}
- s->set("MetaURLOverride", metaURL);
+ s->set("MetaURLOverride", metaURL.toString());
QString flameKey = ui->flameKey->text();
s->set("FlameKeyOverride", flameKey);
QString modrinthToken = ui->modrinthToken->text();
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 55bd3eea..26408f44 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -169,10 +169,10 @@
<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>
+ <string>Disable using metadata for mods</string>
</property>
</widget>
</item>
@@ -307,21 +307,21 @@
<item>
<widget class="QCheckBox" name="showConsoleCheck">
<property name="text">
- <string>Show console while the game is &amp;running?</string>
+ <string>Show console while the game is &amp;running</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoCloseConsoleCheck">
<property name="text">
- <string>&amp;Automatically close console when the game quits?</string>
+ <string>&amp;Automatically close console when the game quits</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showConsoleErrorCheck">
<property name="text">
- <string>Show console when the game &amp;crashes?</string>
+ <string>Show console when the game &amp;crashes</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index cc597fe0..eca3e865 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -46,7 +46,6 @@
MinecraftPage::MinecraftPage(QWidget *parent) : QWidget(parent), ui(new Ui::MinecraftPage)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
loadSettings();
updateCheckboxStuff();
}
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 640f436d..8f5de725 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>936</width>
- <height>1134</height>
+ <height>541</height>
</rect>
</property>
<property name="sizePolicy">
@@ -39,7 +39,7 @@
</property>
<widget class="QWidget" name="minecraftTab">
<attribute name="title">
- <string notr="true">Minecraft</string>
+ <string notr="true">General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
@@ -51,7 +51,7 @@
<item>
<widget class="QCheckBox" name="maximizedCheckBox">
<property name="text">
- <string>Start Minecraft &amp;maximized?</string>
+ <string>Start Minecraft &amp;maximized</string>
</property>
</widget>
</item>
@@ -112,22 +112,29 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
+ <widget class="QGroupBox" name="gameTimeGroupBox">
<property name="title">
- <string>Native library workarounds</string>
+ <string>Game time</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <widget class="QCheckBox" name="useNativeGLFWCheck">
+ <widget class="QCheckBox" name="showGameTime">
<property name="text">
- <string>Use system installation of &amp;GLFW</string>
+ <string>Show time spent &amp;playing instances</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <widget class="QCheckBox" name="showGlobalGameTime">
<property name="text">
- <string>Use system installation of &amp;OpenAL</string>
+ <string>Show time spent playing across &amp;all instances</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="recordGameTime">
+ <property name="text">
+ <string>&amp;Record time spent playing instances</string>
</property>
</widget>
</item>
@@ -135,38 +142,28 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="perfomanceGroupBox">
+ <widget class="QGroupBox" name="groupBox">
<property name="title">
- <string>Performance</string>
+ <string>Miscellaneous</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QCheckBox" name="enableFeralGamemodeCheck">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>Enable Feral GameMode</string>
- </property>
- </widget>
- </item>
+ <layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QCheckBox" name="enableMangoHud">
+ <widget class="QCheckBox" name="closeAfterLaunchCheck">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically reopen when the game crashes or exits.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Enable MangoHud</string>
+ <string>&amp;Close the launcher after game window opens</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <widget class="QCheckBox" name="quitAfterGameStopCheck">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Use discrete GPU</string>
+ <string>&amp;Quit the launcher after game window closes</string>
</property>
</widget>
</item>
@@ -174,29 +171,42 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="gameTimeGroupBox">
+ <spacer name="verticalSpacerMinecraft">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Tweaks</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title">
- <string>Game time</string>
+ <string>Native library workarounds</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
+ <layout class="QVBoxLayout" name="verticalLayout_11">
<item>
- <widget class="QCheckBox" name="showGameTime">
- <property name="text">
- <string>Show time spent &amp;playing instances</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="showGlobalGameTime">
+ <widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
- <string>Show time spent playing across &amp;all instances</string>
+ <string>Use system installation of &amp;GLFW</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="recordGameTime">
+ <widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
- <string>&amp;Record time spent playing instances</string>
+ <string>Use system installation of &amp;OpenAL</string>
</property>
</widget>
</item>
@@ -204,28 +214,38 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox">
+ <widget class="QGroupBox" name="perfomanceGroupBox">
<property name="title">
- <string>Miscellaneous</string>
+ <string>Performance</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <widget class="QCheckBox" name="closeAfterLaunchCheck">
+ <widget class="QCheckBox" name="enableFeralGamemodeCheck">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically reopen when the game crashes or exits.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>&amp;Close the launcher after game window opens</string>
+ <string>Enable Feral GameMode</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="quitAfterGameStopCheck">
+ <widget class="QCheckBox" name="enableMangoHud">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>&amp;Quit the launcher after game window closes</string>
+ <string>Enable MangoHud</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Use discrete GPU</string>
</property>
</widget>
</item>
@@ -233,14 +253,14 @@
</widget>
</item>
<item>
- <spacer name="verticalSpacerMinecraft">
+ <spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>0</width>
- <height>0</height>
+ <width>20</width>
+ <height>40</height>
</size>
</property>
</spacer>
@@ -255,11 +275,6 @@
<tabstop>maximizedCheckBox</tabstop>
<tabstop>windowWidthSpinBox</tabstop>
<tabstop>windowHeightSpinBox</tabstop>
- <tabstop>useNativeGLFWCheck</tabstop>
- <tabstop>useNativeOpenALCheck</tabstop>
- <tabstop>enableFeralGamemodeCheck</tabstop>
- <tabstop>enableMangoHud</tabstop>
- <tabstop>useDiscreteGpuCheck</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index 1115ddc3..12038f88 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -1,3 +1,38 @@
+// 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/>.
+ *
+ * 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 "ExternalResourcesPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ExternalResourcesPage.h"
@@ -9,6 +44,7 @@
#include <QKeyEvent>
#include <QMenu>
+#include <algorithm>
ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr<ResourceFolderModel> model, QWidget* parent)
: QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model)
@@ -24,6 +60,8 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
m_filterModel->setSourceModel(m_model.get());
m_filterModel->setFilterKeyColumn(-1);
ui->treeView->setModel(m_filterModel);
+ // must come after setModel
+ ui->treeView->setResizeModes(m_model->columnResizeModes());
ui->treeView->installEventFilter(this);
ui->treeView->sortByColumn(1, Qt::AscendingOrder);
@@ -43,7 +81,21 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
auto selection_model = ui->treeView->selectionModel();
connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
+ auto updateExtra = [this]() {
+ if (updateExtraInfo)
+ updateExtraInfo(id(), extraHeaderInfoString());
+ };
+ connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
+ connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
+
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
+
+ auto viewHeader = ui->treeView->header();
+ viewHeader->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ connect(viewHeader, &QHeaderView::customContextMenuRequested, this, &ExternalResourcesPage::ShowHeaderContextMenu);
+
+ m_model->loadHiddenColumns(ui->treeView);
}
ExternalResourcesPage::~ExternalResourcesPage()
@@ -65,6 +117,13 @@ void ExternalResourcesPage::ShowContextMenu(const QPoint& pos)
delete menu;
}
+void ExternalResourcesPage::ShowHeaderContextMenu(const QPoint& pos)
+{
+ auto menu = m_model->createHeaderContextMenu(ui->treeView);
+ menu->exec(ui->treeView->mapToGlobal(pos));
+ menu->deleteLater();
+}
+
void ExternalResourcesPage::openedImpl()
{
m_model->startWatching();
@@ -92,11 +151,7 @@ void ExternalResourcesPage::retranslate()
void ExternalResourcesPage::itemActivated(const QModelIndex&)
{
- if (!m_controlsEnabled)
- return;
-
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
- m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
}
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
@@ -139,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());
@@ -155,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;
@@ -201,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);
}
@@ -248,6 +311,15 @@ bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, const
int row = sourceCurrent.row();
Resource const& resource = m_model->at(row);
ui->frame->updateWithResource(resource);
-
return true;
}
+
+QString ExternalResourcesPage::extraHeaderInfoString()
+{
+ if (ui && ui->treeView && ui->treeView->selectionModel()) {
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
+ if (auto count = std::count_if(selection.cbegin(), selection.cend(), [](auto v) { return v.column() == 0; }); count != 0)
+ return tr(" (%1 installed, %2 selected)").arg(m_model->size()).arg(count);
+ }
+ return tr(" (%1 installed)").arg(m_model->size());
+}
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h
index d17fbb7f..97d922d8 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.h
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.h
@@ -29,6 +29,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
virtual QString helpPage() const override = 0;
virtual bool shouldDisplay() const override = 0;
+ QString extraHeaderInfoString();
void openedImpl() override;
void closedImpl() override;
@@ -60,6 +61,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
virtual void viewConfigs();
void ShowContextMenu(const QPoint& pos);
+ void ShowHeaderContextMenu(const QPoint& pos);
protected:
BaseInstance* m_instance = nullptr;
@@ -71,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 33a03336..3c836691 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -62,6 +62,9 @@
<property name="dragDropMode">
<enum>QAbstractItemView::DropOnly</enum>
</property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
@@ -154,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 4b4c73dc..943ff17f 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -60,21 +60,14 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent)
m_settings = inst->settings();
ui->setupUi(this);
- accountMenu = new QMenu(this);
- // Use undocumented property... https://stackoverflow.com/questions/7121718/create-a-scrollbar-in-a-submenu-qt
- accountMenu->setStyleSheet("QMenu { menu-scrollable: 1; }");
- ui->instanceAccountSelector->setMenu(accountMenu);
-
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);
loadSettings();
- updateThresholds();
-}
-bool InstanceSettingsPage::shouldDisplay() const
-{
- return !m_instance->isRunning();
+ updateThresholds();
}
InstanceSettingsPage::~InstanceSettingsPage()
@@ -88,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;
}
}
@@ -454,36 +447,17 @@ void InstanceSettingsPage::on_javaTestBtn_clicked()
void InstanceSettingsPage::updateAccountsMenu()
{
- accountMenu->clear();
-
+ ui->instanceAccountSelector->clear();
auto accounts = APPLICATION->accounts();
int accountIndex = accounts->findAccountByProfileId(m_settings->get("InstanceAccountId").toString());
- MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
-
- if (accountIndex != -1 && accounts->at(accountIndex)) {
- defaultAccount = accounts->at(accountIndex);
- }
-
- if (defaultAccount) {
- ui->instanceAccountSelector->setText(defaultAccount->profileName());
- ui->instanceAccountSelector->setIcon(getFaceForAccount(defaultAccount));
- } else {
- ui->instanceAccountSelector->setText(tr("No default account"));
- ui->instanceAccountSelector->setIcon(APPLICATION->getThemedIcon("noaccount"));
- }
for (int i = 0; i < accounts->count(); i++) {
MinecraftAccountPtr account = accounts->at(i);
- QAction* action = new QAction(account->profileName(), this);
- action->setData(i);
- action->setCheckable(true);
- if (accountIndex == i) {
- action->setChecked(true);
- }
- action->setIcon(getFaceForAccount(account));
- accountMenu->addAction(action);
- connect(action, SIGNAL(triggered(bool)), this, SLOT(changeInstanceAccount()));
+ ui->instanceAccountSelector->addItem(getFaceForAccount(account), account->profileName(), i);
+ if (i == accountIndex)
+ ui->instanceAccountSelector->setCurrentIndex(i);
}
+
}
QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account)
@@ -495,20 +469,13 @@ QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account)
return APPLICATION->getThemedIcon("noaccount");
}
-void InstanceSettingsPage::changeInstanceAccount()
+void InstanceSettingsPage::changeInstanceAccount(int index)
{
- QAction* sAction = (QAction*)sender();
-
- Q_ASSERT(sAction->data().type() == QVariant::Type::Int);
-
- QVariant data = sAction->data();
- int index = data.toInt();
auto accounts = APPLICATION->accounts();
- auto account = accounts->at(index);
- m_settings->set("InstanceAccountId", account->profileId());
-
- ui->instanceAccountSelector->setText(account->profileName());
- ui->instanceAccountSelector->setIcon(getFaceForAccount(account));
+ if (index != -1 && accounts->at(index) && ui->instanceAccountGroupBox->isChecked()) {
+ auto account = accounts->at(index);
+ m_settings->set("InstanceAccountId", account->profileId());
+ }
}
void InstanceSettingsPage::on_maxMemSpinBox_valueChanged(int i)
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h
index cb6fbae0..036b4181 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.h
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.h
@@ -75,12 +75,11 @@ public:
{
return "Instance-settings";
}
- virtual bool shouldDisplay() const override;
void retranslate() override;
void updateThresholds();
-private slots:
+ private slots:
void on_javaDetectBtn_clicked();
void on_javaTestBtn_clicked();
void on_javaBrowseBtn_clicked();
@@ -95,12 +94,11 @@ private slots:
void updateAccountsMenu();
QIcon getFaceForAccount(MinecraftAccountPtr account);
- void changeInstanceAccount();
+ void changeInstanceAccount(int index);
private:
Ui::InstanceSettingsPage *ui;
BaseInstance *m_instance;
SettingsObjectPtr m_settings;
unique_qobject_ptr<JavaCommon::TestCheck> checker;
- QMenu *accountMenu = nullptr;
};
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index 1b986184..8427965d 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -269,7 +269,7 @@
<item>
<widget class="QCheckBox" name="maximizedCheckBox">
<property name="text">
- <string>Start Minecraft maximized?</string>
+ <string>Start Minecraft maximized</string>
</property>
</widget>
</item>
@@ -341,21 +341,21 @@
<item>
<widget class="QCheckBox" name="showConsoleCheck">
<property name="text">
- <string>Show console while the game is running?</string>
+ <string>Show console while the game is running</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoCloseConsoleCheck">
<property name="text">
- <string>Automatically close console when the game quits?</string>
+ <string>Automatically close console when the game quits</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showConsoleErrorCheck">
<property name="text">
- <string>Show console when the game crashes?</string>
+ <string>Show console when the game crashes</string>
</property>
</widget>
</item>
@@ -636,14 +636,7 @@
</widget>
</item>
<item row="0" column="1">
- <widget class="QToolButton" name="instanceAccountSelector">
- <property name="popupMode">
- <enum>QToolButton::InstantPopup</enum>
- </property>
- <property name="toolButtonStyle">
- <enum>Qt::ToolButtonTextBesideIcon</enum>
- </property>
- </widget>
+ <widget class="QComboBox" name="instanceAccountSelector"/>
</item>
</layout>
</item>
diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp
index dc983d9a..0fc0c986 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.cpp
+++ b/launcher/ui/pages/instance/ManagedPackPage.cpp
@@ -30,8 +30,6 @@ class NoBigComboBoxStyle : public QProxyStyle {
Q_OBJECT
public:
- NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
-
// clang-format off
int styleHint(QStyle::StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override
{
@@ -41,6 +39,36 @@ class NoBigComboBoxStyle : public QProxyStyle {
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
// clang-format on
+
+ /**
+ * Something about QProxyStyle and QStyle objects means they can't be free'd just
+ * because all the widgets using them are gone.
+ * They seems to be tied to the QApplicaiton lifecycle.
+ * So make singletons tied to the lifetime of the application to clean them up and ensure they aren't
+ * being remade over and over again, thus leaking memory.
+ */
+ public:
+ static NoBigComboBoxStyle* getInstance(QStyle* style)
+ {
+ static QHash<QStyle*, NoBigComboBoxStyle*> s_singleton_instances_ = {};
+ static std::mutex s_singleton_instances_mutex_;
+
+ std::lock_guard<std::mutex> lock(s_singleton_instances_mutex_);
+ auto inst_iter = s_singleton_instances_.constFind(style);
+ NoBigComboBoxStyle* inst = nullptr;
+ if (inst_iter == s_singleton_instances_.constEnd() || *inst_iter == nullptr) {
+ inst = new NoBigComboBoxStyle(style);
+ inst->setParent(APPLICATION);
+ s_singleton_instances_.insert(style, inst);
+ qDebug() << "QProxyStyle NoBigComboBox created for" << style->objectName() << style;
+ } else {
+ inst = *inst_iter;
+ }
+ return inst;
+ }
+
+ private:
+ NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
};
ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent)
@@ -62,11 +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"))
- ui->versionsComboBox->setStyle(new NoBigComboBoxStyle(ui->versionsComboBox->style()));
+ 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;
@@ -174,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
@@ -195,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.get()));
+ 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{};
@@ -236,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));
}
@@ -260,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()));
@@ -270,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;
@@ -301,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()
@@ -338,7 +376,7 @@ void FlameManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID();
- m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response.get()));
+ m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{};
@@ -398,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));
@@ -408,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 4548af59..cef292bd 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -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
@@ -44,6 +45,7 @@
#include <QMenu>
#include <QMessageBox>
#include <QSortFilterProxyModel>
+#include <algorithm>
#include "Application.h"
@@ -59,6 +61,7 @@
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/ModFolderModel.h"
+#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "Version.h"
@@ -85,45 +88,40 @@ 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(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] {
+ connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
ui->actionUpdateItem->setEnabled(check_allow_update());
- });
- connect(mods.get(), &ModFolderModel::rowsRemoved, 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::updateFinished, this, [this, check_allow_update, mods] {
- ui->actionUpdateItem->setEnabled(check_allow_update());
+ connect(mods.get(), &ModFolderModel::rowsInserted, this,
+ [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
- // Prevent a weird crash when trying to open the mods page twice in a session o.O
- disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0);
- });
+ connect(mods.get(), &ModFolderModel::rowsRemoved, 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());
+ connect(mods.get(), &ModFolderModel::updateFinished, this,
+ [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
}
}
-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;
@@ -140,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
@@ -214,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;
}
@@ -282,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 7fff3b93..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);
@@ -165,7 +153,7 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
auto proxy = new IconProxy(ui->packageView);
proxy->setSourceModel(m_profile.get());
- m_filterModel = new QSortFilterProxyModel();
+ m_filterModel = new QSortFilterProxyModel(this);
m_filterModel->setDynamicSortFilter(true);
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
@@ -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 &current, 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 &current, 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 &current, 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,23 +422,21 @@ 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);
- connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
+ connect(updateTask.get(), &Task::failed, this, &VersionPage::onGameUpdateError);
// FIXME: unused return value
tDialog.execWithTask(updateTask.get());
updateButtons();
@@ -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 &current, 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 &current, 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 &current, 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 &current, 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 d4a395d9..b2200b1a 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -107,6 +107,7 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worl
auto head = ui->worldTreeView->header();
head->setSectionResizeMode(0, QHeaderView::Stretch);
head->setSectionResizeMode(1, QHeaderView::ResizeToContents);
+ head->setSectionResizeMode(4, QHeaderView::ResizeToContents);
connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged);
worldChanged(QModelIndex(), QModelIndex());
@@ -338,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/ImportPage.h b/launcher/ui/pages/modplatform/ImportPage.h
index 8d13ac10..c2acb92d 100644
--- a/launcher/ui/pages/modplatform/ImportPage.h
+++ b/launcher/ui/pages/modplatform/ImportPage.h
@@ -57,7 +57,7 @@ public:
virtual ~ImportPage();
virtual QString displayName() const override
{
- return tr("Import from zip");
+ return tr("Import");
}
virtual QIcon icon() const override
{
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index 3ffe6cb0..b7537890 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -6,12 +6,14 @@
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
+#include "minecraft/mod/ModFolderModel.h"
#include <QMessageBox>
+#include <algorithm>
namespace ResourceDownload {
-ModModel::ModModel(BaseInstance const& base_inst, ResourceAPI* api) : ResourceModel(api), m_base_instance(base_inst) {}
+ModModel::ModModel(BaseInstance& base_inst, ResourceAPI* api) : ResourceModel(api), m_base_instance(base_inst) {}
/******** Make data requests ********/
@@ -24,7 +26,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
std::optional<std::list<Version>> versions{};
- { // Version filter
+ { // Version filter
if (!m_filter->versions.empty())
versions = m_filter->versions;
}
@@ -36,7 +38,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
{
- auto& pack = m_packs[entry.row()];
+ auto& pack = *m_packs[entry.row()];
auto profile = static_cast<MinecraftInstance const&>(m_base_instance).getPackProfile();
Q_ASSERT(profile);
@@ -51,7 +53,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
{
- auto& pack = m_packs[entry.row()];
+ auto& pack = *m_packs[entry.row()];
return { pack };
}
@@ -67,4 +69,14 @@ void ModModel::searchWithTerm(const QString& term, unsigned int sort, bool filte
refresh();
}
+bool ModModel::isPackInstalled(ModPlatform::IndexedPack::Ptr pack) const
+{
+ auto allMods = static_cast<MinecraftInstance&>(m_base_instance).loaderModList()->allMods();
+ return std::any_of(allMods.cbegin(), allMods.cend(), [pack](Mod* mod) {
+ if (auto meta = mod->metadata(); meta)
+ return meta->provider == pack->provider && meta->project_id == pack->addonId;
+ return false;
+ });
+}
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h
index 5d4a7785..dd187aa8 100644
--- a/launcher/ui/pages/modplatform/ModModel.h
+++ b/launcher/ui/pages/modplatform/ModModel.h
@@ -24,7 +24,7 @@ class ModModel : public ResourceModel {
Q_OBJECT
public:
- ModModel(const BaseInstance&, ResourceAPI* api);
+ ModModel(BaseInstance&, ResourceAPI* api);
/* Ask the API for more information */
void searchWithTerm(const QString& term, unsigned int sort, bool filter_changed);
@@ -32,6 +32,7 @@ class ModModel : public ResourceModel {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override = 0;
+ virtual ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) = 0;
void setFilter(std::shared_ptr<ModFilterWidget::Filter> filter) { m_filter = filter; }
@@ -42,9 +43,10 @@ class ModModel : public ResourceModel {
protected:
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0;
+ virtual bool isPackInstalled(ModPlatform::IndexedPack::Ptr) const override;
protected:
- const BaseInstance& m_base_instance;
+ BaseInstance& m_base_instance;
std::shared_ptr<ModFilterWidget::Filter> m_filter = nullptr;
};
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 04be43ad..60a43128 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -55,8 +55,7 @@
namespace ResourceDownload {
-ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance)
- : ResourcePage(dialog, instance)
+ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance)
{
connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch);
connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
@@ -75,12 +74,10 @@ void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& widget)
m_filter_widget->setInstance(&static_cast<MinecraftInstance&>(m_base_instance));
m_filter = m_filter_widget->getFilter();
- connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{
- m_ui->searchButton->setStyleSheet("text-decoration: underline");
- });
- connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{
- m_ui->searchButton->setStyleSheet("text-decoration: none");
- });
+ connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this,
+ [&] { m_ui->searchButton->setStyleSheet("text-decoration: underline"); });
+ connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this,
+ [&] { m_ui->searchButton->setStyleSheet("text-decoration: none"); });
}
/******** Callbacks to events in the UI (set up in the derived classes) ********/
@@ -92,17 +89,13 @@ void ModPage::filterMods()
void ModPage::triggerSearch()
{
- auto changed = m_filter_widget->changed();
m_filter = m_filter_widget->getFilter();
+ m_ui->packView->clearSelection();
+ m_ui->packDescription->clear();
+ m_ui->versionSelectionBox->clear();
+ updateSelectionButton();
- if (changed) {
- m_ui->packView->clearSelection();
- m_ui->packDescription->clear();
- m_ui->versionSelectionBox->clear();
- updateSelectionButton();
- }
-
- static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed);
+ static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), m_filter_widget->changed());
m_fetch_progress.watch(m_model->activeSearchJob().get());
}
@@ -125,11 +118,13 @@ void ModPage::updateVersionList()
QString mcVersion = packProfile->getComponentVersion("net.minecraft");
auto current_pack = getCurrentPack();
- for (int i = 0; i < current_pack.versions.size(); i++) {
- auto version = current_pack.versions[i];
+ if (!current_pack)
+ return;
+ for (int i = 0; i < current_pack->versions.size(); i++) {
+ auto version = current_pack->versions[i];
bool valid = false;
- for(auto& mcVer : m_filter->versions){
- //NOTE: Flame doesn't care about loader, so passing it changes nothing.
+ for (auto& mcVer : m_filter->versions) {
+ // NOTE: Flame doesn't care about loader, so passing it changes nothing.
if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
valid = true;
break;
@@ -148,10 +143,12 @@ void ModPage::updateVersionList()
updateSelectionButton();
}
-void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> base_model)
{
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- m_parent_dialog->addResource(pack, version, is_indexed);
+ m_model->addPack(pack, version, base_model, is_indexed);
}
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index c3b58cd6..5510c191 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -8,8 +8,8 @@
#include "modplatform/ModIndex.h"
-#include "ui/pages/modplatform/ResourcePage.h"
#include "ui/pages/modplatform/ModModel.h"
+#include "ui/pages/modplatform/ResourcePage.h"
#include "ui/widgets/ModFilterWidget.h"
namespace Ui {
@@ -25,13 +25,14 @@ class ModPage : public ResourcePage {
Q_OBJECT
public:
- template<typename T>
+ template <typename T>
static T* create(ModDownloadDialog* dialog, BaseInstance& instance)
{
auto page = new T(dialog, instance);
auto model = static_cast<ModModel*>(page->getModel());
- auto filter_widget = ModFilterWidget::create(static_cast<MinecraftInstance&>(instance).getPackProfile()->getComponentVersion("net.minecraft"), page);
+ auto filter_widget =
+ ModFilterWidget::create(static_cast<MinecraftInstance&>(instance).getPackProfile()->getComponentVersion("net.minecraft"), page);
page->setFilterWidget(filter_widget);
model->setFilter(page->getFilter());
@@ -41,8 +42,6 @@ class ModPage : public ResourcePage {
return page;
}
- ~ModPage() override = default;
-
//: The plural version of 'mod'
[[nodiscard]] inline QString resourcesString() const override { return tr("mods"); }
//: The singular version of 'mods'
@@ -50,9 +49,13 @@ class ModPage : public ResourcePage {
[[nodiscard]] QMap<QString, QString> urlHandlers() const override;
- void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override;
+ void addResourceToPage(ModPlatform::IndexedPack::Ptr,
+ ModPlatform::IndexedVersion&,
+ const std::shared_ptr<ResourceFolderModel>) override;
- virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
+ virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
+ QString mineVer,
+ std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
[[nodiscard]] bool supportsFiltering() const override { return true; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
index db7d26f8..49405a02 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -6,9 +6,12 @@
#include <QCryptographicHash>
#include <QIcon>
+#include <QList>
#include <QMessageBox>
#include <QPixmapCache>
#include <QUrl>
+#include <algorithm>
+#include <memory>
#include "Application.h"
#include "BuildConfig.h"
@@ -45,16 +48,16 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant
auto pack = m_packs.at(pos);
switch (role) {
case Qt::ToolTipRole: {
- if (pack.description.length() > 100) {
+ if (pack->description.length() > 100) {
// some magic to prevent to long tooltips and replace html linebreaks
- QString edit = pack.description.left(97);
+ QString edit = pack->description.left(97);
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
return edit;
}
- return pack.description;
+ return pack->description;
}
case Qt::DecorationRole: {
- if (auto icon_or_none = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(index), pack.logoUrl);
+ if (auto icon_or_none = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(index), pack->logoUrl);
icon_or_none.has_value())
return icon_or_none.value();
@@ -69,11 +72,13 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant
}
// Custom data
case UserDataTypes::TITLE:
- return pack.name;
+ return pack->name;
case UserDataTypes::DESCRIPTION:
- return pack.description;
+ return pack->description;
case UserDataTypes::SELECTED:
- return pack.isAnyVersionSelected();
+ return pack->isAnyVersionSelected();
+ case UserDataTypes::INSTALLED:
+ return this->isPackInstalled(pack);
default:
break;
}
@@ -92,6 +97,7 @@ QHash<int, QByteArray> ResourceModel::roleNames() const
roles[UserDataTypes::TITLE] = "title";
roles[UserDataTypes::DESCRIPTION] = "description";
roles[UserDataTypes::SELECTED] = "selected";
+ roles[UserDataTypes::INSTALLED] = "installed";
return roles;
}
@@ -102,7 +108,7 @@ bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int
if (pos >= m_packs.size() || pos < 0 || !index.isValid())
return false;
- m_packs[pos] = value.value<ModPlatform::IndexedPack>();
+ m_packs[pos] = value.value<ModPlatform::IndexedPack::Ptr>();
emit dataChanged(index, index);
return true;
@@ -161,7 +167,7 @@ void ResourceModel::loadEntry(QModelIndex& entry)
if (!hasActiveInfoJob())
m_current_info_job.clear();
- if (!pack.versionsLoaded) {
+ if (!pack->versionsLoaded) {
auto args{ createVersionsArguments(entry) };
auto callbacks{ createVersionsCallbacks(entry) };
@@ -177,7 +183,7 @@ void ResourceModel::loadEntry(QModelIndex& entry)
runInfoJob(job);
}
- if (!pack.extraDataLoaded) {
+ if (!pack->extraDataLoaded) {
auto args{ createInfoArguments(entry) };
auto callbacks{ createInfoCallbacks(entry) };
@@ -229,7 +235,7 @@ void ResourceModel::clearData()
void ResourceModel::runSearchJob(Task::Ptr ptr)
{
- m_current_search_job = ptr;
+ m_current_search_job.reset(ptr); // clean up first
m_current_search_job->start();
}
void ResourceModel::runInfoJob(Task::Ptr ptr)
@@ -326,16 +332,24 @@ void ResourceModel::loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArra
void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
{
- QList<ModPlatform::IndexedPack> newList;
+ QList<ModPlatform::IndexedPack::Ptr> newList;
auto packs = documentToArray(doc);
for (auto packRaw : packs) {
auto packObj = packRaw.toObject();
- ModPlatform::IndexedPack pack;
+ ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
try {
- loadIndexedPack(pack, packObj);
- newList.append(pack);
+ loadIndexedPack(*pack, packObj);
+ if (auto sel = std::find_if(m_selected.begin(), m_selected.end(),
+ [&pack](const DownloadTaskPtr i) {
+ const auto ipack = i->getPack();
+ return ipack->provider == pack->provider && ipack->addonId == pack->addonId;
+ });
+ sel != m_selected.end()) {
+ newList.append(sel->get()->getPack());
+ } else
+ newList.append(pack);
} catch (const JSONValidationError& e) {
qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause();
continue;
@@ -389,15 +403,15 @@ void ResourceModel::searchRequestAborted()
void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
{
- auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
+ auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
// Check if the index is still valid for this resource or not
- if (pack.addonId != current_pack.addonId)
+ if (pack.addonId != current_pack->addonId)
return;
try {
auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
- loadIndexedPackVersions(current_pack, arr);
+ loadIndexedPackVersions(*current_pack, arr);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading " << debugName() << " resource version: " << e.cause();
@@ -416,15 +430,15 @@ void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::Ind
void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
{
- auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
+ auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
// Check if the index is still valid for this resource or not
- if (pack.addonId != current_pack.addonId)
+ if (pack.addonId != current_pack->addonId)
return;
try {
auto obj = Json::requireObject(doc);
- loadExtraPackInfo(current_pack, obj);
+ loadExtraPackInfo(*current_pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
@@ -441,4 +455,39 @@ void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::Indexe
emit projectInfoUpdated();
}
+void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> packs,
+ bool is_indexed,
+ QString custom_target_folder)
+{
+ version.is_currently_selected = true;
+ m_selected.append(makeShared<ResourceDownloadTask>(pack, version, packs, is_indexed, custom_target_folder));
+}
+
+void ResourceModel::removePack(const QString& rem)
+{
+ auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); };
+#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
+ m_selected.removeIf(pred);
+#else
+ {
+ for (auto it = m_selected.begin(); it != m_selected.end();)
+ if (pred(*it))
+ it = m_selected.erase(it);
+ else
+ ++it;
+ }
+#endif
+ auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack::Ptr i) { return rem == i->name; });
+ if (pack == m_packs.end()) { // ignore it if is not in the current search
+ return;
+ }
+ if (!pack->get()->versionsLoaded) {
+ return;
+ }
+ for (auto& ver : pack->get()->versions)
+ ver.is_currently_selected = false;
+}
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h
index 46a02d6e..6533d9c6 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.h
+++ b/launcher/ui/pages/modplatform/ResourceModel.h
@@ -10,6 +10,7 @@
#include "QObjectPtr.h"
+#include "ResourceDownloadTask.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/ConcurrentTask.h"
@@ -29,6 +30,8 @@ class ResourceModel : public QAbstractListModel {
Q_PROPERTY(QString search_term MEMBER m_search_term WRITE setSearchTerm)
public:
+ using DownloadTaskPtr = shared_qobject_ptr<ResourceDownloadTask>;
+
ResourceModel(ResourceAPI* api);
~ResourceModel() override;
@@ -80,6 +83,14 @@ class ResourceModel : public QAbstractListModel {
/** Gets the icon at the URL for the given index. If it's not fetched yet, fetch it and update when fisinhed. */
std::optional<QIcon> getIcon(QModelIndex&, const QUrl&);
+ void addPack(ModPlatform::IndexedPack::Ptr pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> packs,
+ bool is_indexed = false,
+ QString custom_target_folder = {});
+ void removePack(const QString& rem);
+ QList<DownloadTaskPtr> selectedPacks() { return m_selected; }
+
protected:
/** Resets the model's data. */
void clearData();
@@ -105,6 +116,8 @@ class ResourceModel : public QAbstractListModel {
virtual void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&);
virtual void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&);
+ virtual bool isPackInstalled(ModPlatform::IndexedPack::Ptr) const { return false; }
+
protected:
/* Basic search parameters */
enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None;
@@ -123,7 +136,8 @@ class ResourceModel : public QAbstractListModel {
QSet<QUrl> m_currently_running_icon_actions;
QSet<QUrl> m_failed_icon_actions;
- QList<ModPlatform::IndexedPack> m_packs;
+ QList<ModPlatform::IndexedPack::Ptr> m_packs;
+ QList<DownloadTaskPtr> m_selected;
// HACK: We need this to prevent callbacks from calling the model after it has already been deleted.
// This leaks a tiny bit of memory per time the user has opened a resource dialog. How to make this better?
diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp
index 3df9a787..18c14bf8 100644
--- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp
@@ -22,13 +22,13 @@ ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments()
ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
- return { pack };
+ return { *pack };
}
ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
- return { pack };
+ return { *pack };
}
void ResourcePackResourceModel::searchWithTerm(const QString& term, unsigned int sort)
diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h
index c01c89c4..8c5cf08b 100644
--- a/launcher/ui/pages/modplatform/ResourcePackPage.h
+++ b/launcher/ui/pages/modplatform/ResourcePackPage.h
@@ -31,8 +31,6 @@ class ResourcePackResourcePage : public ResourcePage {
return page;
}
- ~ResourcePackResourcePage() override = default;
-
//: The plural version of 'resource pack'
[[nodiscard]] inline QString resourcesString() const override { return tr("resource packs"); }
//: The singular version of 'resource packs'
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index bbd465bc..48afbd90 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -37,6 +37,7 @@
*/
#include "ResourcePage.h"
+#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h"
#include <QDesktopServices>
@@ -83,6 +84,8 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in
ResourcePage::~ResourcePage()
{
delete m_ui;
+ if (m_model)
+ delete m_model;
}
void ResourcePage::retranslate()
@@ -101,6 +104,7 @@ void ResourcePage::openedImpl()
updateSelectionButton();
triggerSearch();
+ m_ui->searchEdit->setFocus();
}
auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool
@@ -156,31 +160,35 @@ void ResourcePage::addSortings()
m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index));
}
-bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack)
+bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack::Ptr pack)
{
QVariant v;
v.setValue(pack);
return m_model->setData(m_ui->packView->currentIndex(), v, Qt::UserRole);
}
-ModPlatform::IndexedPack ResourcePage::getCurrentPack() const
+ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const
{
- return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value<ModPlatform::IndexedPack>();
+ return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
}
void ResourcePage::updateUi()
{
auto current_pack = getCurrentPack();
-
+ if (!current_pack) {
+ m_ui->packDescription->setHtml({});
+ m_ui->packDescription->flush();
+ return;
+ }
QString text = "";
- QString name = current_pack.name;
+ QString name = current_pack->name;
- if (current_pack.websiteUrl.isEmpty())
+ if (current_pack->websiteUrl.isEmpty())
text = name;
else
- text = "<a href=\"" + current_pack.websiteUrl + "\">" + name + "</a>";
+ text = "<a href=\"" + current_pack->websiteUrl + "\">" + name + "</a>";
- if (!current_pack.authors.empty()) {
+ if (!current_pack->authors.empty()) {
auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
if (author.url.isEmpty()) {
return author.name;
@@ -188,44 +196,44 @@ void ResourcePage::updateUi()
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
};
QStringList authorStrs;
- for (auto& author : current_pack.authors) {
+ for (auto& author : current_pack->authors) {
authorStrs.push_back(authorToStr(author));
}
text += "<br>" + tr(" by ") + authorStrs.join(", ");
}
- if (current_pack.extraDataLoaded) {
- if (!current_pack.extraData.donate.isEmpty()) {
+ if (current_pack->extraDataLoaded) {
+ if (!current_pack->extraData.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform);
};
QStringList donates;
- for (auto& donate : current_pack.extraData.donate) {
+ for (auto& donate : current_pack->extraData.donate) {
donates.append(donateToStr(donate));
}
text += donates.join(", ");
}
- if (!current_pack.extraData.issuesUrl.isEmpty() || !current_pack.extraData.sourceUrl.isEmpty() ||
- !current_pack.extraData.wikiUrl.isEmpty() || !current_pack.extraData.discordUrl.isEmpty()) {
+ if (!current_pack->extraData.issuesUrl.isEmpty() || !current_pack->extraData.sourceUrl.isEmpty() ||
+ !current_pack->extraData.wikiUrl.isEmpty() || !current_pack->extraData.discordUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>";
}
- if (!current_pack.extraData.issuesUrl.isEmpty())
- text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current_pack.extraData.issuesUrl) + "<br>";
- if (!current_pack.extraData.wikiUrl.isEmpty())
- text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current_pack.extraData.wikiUrl) + "<br>";
- if (!current_pack.extraData.sourceUrl.isEmpty())
- text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current_pack.extraData.sourceUrl) + "<br>";
- if (!current_pack.extraData.discordUrl.isEmpty())
- text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current_pack.extraData.discordUrl) + "<br>";
+ if (!current_pack->extraData.issuesUrl.isEmpty())
+ text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current_pack->extraData.issuesUrl) + "<br>";
+ if (!current_pack->extraData.wikiUrl.isEmpty())
+ text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current_pack->extraData.wikiUrl) + "<br>";
+ if (!current_pack->extraData.sourceUrl.isEmpty())
+ text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current_pack->extraData.sourceUrl) + "<br>";
+ if (!current_pack->extraData.discordUrl.isEmpty())
+ text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current_pack->extraData.discordUrl) + "<br>";
}
text += "<hr>";
m_ui->packDescription->setHtml(
- text + (current_pack.extraData.body.isEmpty() ? current_pack.description : markdownToHTML(current_pack.extraData.body)));
+ text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body)));
m_ui->packDescription->flush();
}
@@ -237,10 +245,13 @@ void ResourcePage::updateSelectionButton()
}
m_ui->resourceSelectionButton->setEnabled(true);
- if (!getCurrentPack().isVersionSelected(m_selected_version_index)) {
- m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString()));
+ if (auto current_pack = getCurrentPack(); current_pack) {
+ if (!current_pack->isVersionSelected(m_selected_version_index))
+ m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString()));
+ else
+ m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString()));
} else {
- m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString()));
+ qWarning() << "Tried to update the selected button but there is not a pack selected";
}
}
@@ -252,13 +263,14 @@ void ResourcePage::updateVersionList()
m_ui->versionSelectionBox->clear();
m_ui->versionSelectionBox->blockSignals(false);
- for (int i = 0; i < current_pack.versions.size(); i++) {
- auto& version = current_pack.versions[i];
- if (optedOut(version))
- continue;
+ if (current_pack)
+ for (int i = 0; i < current_pack->versions.size(); i++) {
+ auto& version = current_pack->versions[i];
+ if (optedOut(version))
+ continue;
- m_ui->versionSelectionBox->addItem(current_pack.versions[i].version, QVariant(i));
- }
+ m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i));
+ }
if (m_ui->versionSelectionBox->count() == 0) {
m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1));
@@ -277,7 +289,7 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
auto current_pack = getCurrentPack();
bool request_load = false;
- if (!current_pack.versionsLoaded) {
+ if (!current_pack || !current_pack->versionsLoaded) {
m_ui->resourceSelectionButton->setText(tr("Loading versions..."));
m_ui->resourceSelectionButton->setEnabled(false);
@@ -286,7 +298,7 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
updateVersionList();
}
- if (!current_pack.extraDataLoaded)
+ if (current_pack && !current_pack->extraDataLoaded)
request_load = true;
if (request_load)
@@ -306,14 +318,26 @@ void ResourcePage::onVersionSelectionChanged(QString data)
updateSelectionButton();
}
-void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version)
{
m_parent_dialog->addResource(pack, version);
}
-void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+void ResourcePage::removeResourceFromDialog(const QString& pack_name)
+{
+ m_parent_dialog->removeResource(pack_name);
+}
+
+void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
+ ModPlatform::IndexedVersion& ver,
+ const std::shared_ptr<ResourceFolderModel> base_model)
+{
+ m_model->addPack(pack, ver, base_model);
+}
+
+void ResourcePage::removeResourceFromPage(const QString& name)
{
- m_parent_dialog->removeResource(pack, version);
+ m_model->removePack(name);
}
void ResourcePage::onResourceSelected()
@@ -322,12 +346,12 @@ void ResourcePage::onResourceSelected()
return;
auto current_pack = getCurrentPack();
- if (!current_pack.versionsLoaded)
+ if (!current_pack || !current_pack->versionsLoaded)
return;
- auto& version = current_pack.versions[m_selected_version_index];
+ auto& version = current_pack->versions[m_selected_version_index];
if (version.is_currently_selected)
- removeResourceFromDialog(current_pack, version);
+ removeResourceFromDialog(current_pack->name);
else
addResourceToDialog(current_pack, version);
@@ -338,7 +362,7 @@ void ResourcePage::onResourceSelected()
updateSelectionButton();
/* Force redraw on the resource list when the selection changes */
- m_ui->packView->adjustSize();
+ m_ui->packView->repaint();
}
void ResourcePage::openUrl(const QUrl& url)
@@ -368,7 +392,7 @@ void ResourcePage::openUrl(const QUrl& url)
const QString slug = match.captured(1);
// ensure the user isn't opening the same mod
- if (slug != getCurrentPack().slug) {
+ if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) {
m_parent_dialog->selectPage(page);
auto newPage = m_parent_dialog->getSelectedPage();
diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h
index 1896d53e..b4a87f57 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.h
+++ b/launcher/ui/pages/modplatform/ResourcePage.h
@@ -7,10 +7,12 @@
#include <QTimer>
#include <QWidget>
+#include "ResourceDownloadTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "ui/pages/BasePage.h"
+#include "ui/pages/modplatform/ResourceModel.h"
#include "ui/widgets/ProgressWidget.h"
namespace Ui {
@@ -27,6 +29,7 @@ class ResourceModel;
class ResourcePage : public QWidget, public BasePage {
Q_OBJECT
public:
+ using DownloadTaskPtr = shared_qobject_ptr<ResourceDownloadTask>;
~ResourcePage() override;
/* Affects what the user sees */
@@ -57,8 +60,8 @@ class ResourcePage : public QWidget, public BasePage {
/** Programatically set the term in the search bar. */
void setSearchTerm(QString);
- [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack);
- [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack;
+ [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack::Ptr);
+ [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr;
[[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; }
[[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; }
@@ -72,12 +75,17 @@ class ResourcePage : public QWidget, public BasePage {
virtual void updateSelectionButton();
virtual void updateVersionList();
- virtual void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
- virtual void removeResourceFromDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+ void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&);
+ void removeResourceFromDialog(const QString& pack_name);
+ virtual void removeResourceFromPage(const QString& name);
+ virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, const std::shared_ptr<ResourceFolderModel>);
+
+ QList<DownloadTaskPtr> selectedPacks() { return m_model->selectedPacks(); }
+ bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); }
protected slots:
virtual void triggerSearch() {}
-
+
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data);
void onResourceSelected();
diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp
index 2101b394..aabd3be6 100644
--- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp
+++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp
@@ -22,13 +22,13 @@ ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments()
ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
- return { pack };
+ return { *pack };
}
ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(QModelIndex& entry)
{
auto& pack = m_packs[entry.row()];
- return { pack };
+ return { *pack };
}
void ShaderPackResourceModel::searchWithTerm(const QString& term, unsigned int sort)
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
index 251c07e7..fbf94e84 100644
--- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
@@ -13,8 +13,7 @@
namespace ResourceDownload {
-ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
- : ResourcePage(dialog, instance)
+ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) : ResourcePage(dialog, instance)
{
connect(m_ui->searchButton, &QPushButton::clicked, this, &ShaderPackResourcePage::triggerSearch);
connect(m_ui->packView, &QListView::doubleClicked, this, &ShaderPackResourcePage::onResourceSelected);
@@ -38,17 +37,20 @@ QMap<QString, QString> ShaderPackResourcePage::urlHandlers() const
{
QMap<QString, QString> map;
map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth");
- map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), "curseforge");
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"),
+ "curseforge");
map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
return map;
}
-void ShaderPackResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack,
+ ModPlatform::IndexedVersion& version,
+ const std::shared_ptr<ResourceFolderModel> base_model)
{
+ QString custom_target_folder;
if (version.loaders.contains(QStringLiteral("canvas")))
- version.custom_target_folder = QStringLiteral("resourcepacks");
-
- m_parent_dialog->addResource(pack, version);
+ custom_target_folder = QStringLiteral("resourcepacks");
+ m_model->addPack(pack, version, base_model, false, custom_target_folder);
}
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h
index 972419a8..fcf6d4a7 100644
--- a/launcher/ui/pages/modplatform/ShaderPackPage.h
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.h
@@ -31,8 +31,6 @@ class ShaderPackResourcePage : public ResourcePage {
return page;
}
- ~ShaderPackResourcePage() override = default;
-
//: The plural version of 'shader pack'
[[nodiscard]] inline QString resourcesString() const override { return tr("shader packs"); }
//: The singular version of 'shader packs'
@@ -40,7 +38,9 @@ class ShaderPackResourcePage : public ResourcePage {
[[nodiscard]] bool supportsFiltering() const override { return false; };
- void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override;
+ void addResourceToPage(ModPlatform::IndexedPack::Ptr,
+ ModPlatform::IndexedVersion&,
+ const std::shared_ptr<ResourceFolderModel>) override;
[[nodiscard]] QMap<QString, QString> urlHandlers() const override;
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index 9ad26f47..c6b087d6 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -16,62 +16,49 @@
#include "AtlListModel.h"
-#include <BuildConfig.h>
#include <Application.h>
+#include <BuildConfig.h>
#include <Json.h>
namespace Atl {
-ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
-{
-}
+ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
-ListModel::~ListModel()
-{
-}
+ListModel::~ListModel() {}
-int ListModel::rowCount(const QModelIndex &parent) const
+int ListModel::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : modpacks.size();
}
-int ListModel::columnCount(const QModelIndex &parent) const
+int ListModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : 1;
}
-QVariant ListModel::data(const QModelIndex &index, int role) const
+QVariant ListModel::data(const QModelIndex& index, int role) const
{
int pos = index.row();
- if(pos >= modpacks.size() || pos < 0 || !index.isValid())
- {
+ if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
return QString("INVALID INDEX %1").arg(pos);
}
ATLauncher::IndexedPack pack = modpacks.at(pos);
- if(role == Qt::DisplayRole)
- {
+ if (role == Qt::DisplayRole) {
return pack.name;
- }
- else if (role == Qt::ToolTipRole)
- {
+ } else if (role == Qt::ToolTipRole) {
return pack.name;
- }
- else if(role == Qt::DecorationRole)
- {
- if(m_logoMap.contains(pack.safeName))
- {
+ } else if (role == Qt::DecorationRole) {
+ if (m_logoMap.contains(pack.safeName)) {
return (m_logoMap.value(pack.safeName));
}
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
- ((ListModel *)this)->requestLogo(pack.safeName, url);
+ ((ListModel*)this)->requestLogo(pack.safeName, url);
return icon;
- }
- else if(role == Qt::UserRole)
- {
+ } else if (role == Qt::UserRole) {
QVariant v;
v.setValue(pack);
return v;
@@ -88,7 +75,7 @@ void ListModel::request()
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
jobPtr = netJob;
jobPtr->start();
@@ -101,36 +88,38 @@ void ListModel::requestFinished()
jobPtr.reset();
QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError) {
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
+ qWarning() << *response;
return;
}
QList<ATLauncher::IndexedPack> newList;
auto packs = doc.array();
- for(auto packRaw : packs) {
+ for (auto packRaw : packs) {
auto packObj = packRaw.toObject();
ATLauncher::IndexedPack pack;
try {
ATLauncher::loadIndexedPack(pack, packObj);
- }
- catch (const JSONValidationError &e) {
- qDebug() << QString::fromUtf8(response);
+ } catch (const JSONValidationError& e) {
+ qDebug() << QString::fromUtf8(*response);
qWarning() << "Error while reading pack manifest from ATLauncher: " << e.cause();
return;
}
// ignore packs without a published version
- if(pack.versions.length() == 0) continue;
+ if (pack.versions.length() == 0)
+ continue;
// only display public packs (for now)
- if(pack.type != ATLauncher::PackType::Public) continue;
+ if (pack.type != ATLauncher::PackType::Public)
+ continue;
// ignore "system" packs (Vanilla, Vanilla with Forge, etc)
- if(pack.system) continue;
+ if (pack.system)
+ continue;
newList.append(pack);
}
@@ -145,14 +134,12 @@ void ListModel::requestFailed(QString reason)
jobPtr.reset();
}
-void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
+void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
- if(m_logoMap.contains(logo))
- {
- callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
- }
- else
- {
+ if (m_logoMap.contains(logo)) {
+ callback(
+ APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ } else {
requestLogo(logo, logoUrl);
}
}
@@ -168,36 +155,34 @@ void ListModel::logoLoaded(QString logo, QIcon out)
m_loadingLogos.removeAll(logo);
m_logoMap.insert(logo, out);
- for(int i = 0; i < modpacks.size(); i++) {
- if(modpacks[i].safeName == logo) {
- emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ for (int i = 0; i < modpacks.size(); i++) {
+ if (modpacks[i].safeName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
}
}
}
void ListModel::requestLogo(QString file, QString url)
{
- if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
- {
+ if (m_loadingLogos.contains(file) || m_failedLogos.contains(file)) {
return;
}
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
- NetJob *job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
+ auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
- QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath]
- {
+ QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {
+ job->deleteLater();
emit logoLoaded(file, QIcon(fullPath));
- if(waitingCallbacks.contains(file))
- {
+ if (waitingCallbacks.contains(file)) {
waitingCallbacks.value(file)(fullPath);
}
});
- QObject::connect(job, &NetJob::failed, this, [this, file]
- {
+ QObject::connect(job, &NetJob::failed, this, [this, file, job] {
+ job->deleteLater();
emit logoFailed(file);
});
@@ -206,4 +191,4 @@ void ListModel::requestLogo(QString file, QString url)
m_loadingLogos.append(file);
}
-}
+} // namespace Atl
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h
index 2574c48d..ed1fdc9f 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h
@@ -18,42 +18,41 @@
#include <QAbstractListModel>
-#include "net/NetJob.h"
-#include <QIcon>
#include <modplatform/atlauncher/ATLPackIndex.h>
+#include <QIcon>
+#include "net/NetJob.h"
namespace Atl {
typedef QMap<QString, QIcon> LogoMap;
typedef std::function<void(QString)> LogoCallback;
-class ListModel : public QAbstractListModel
-{
+class ListModel : public QAbstractListModel {
Q_OBJECT
-public:
- ListModel(QObject *parent);
+ public:
+ ListModel(QObject* parent);
virtual ~ListModel();
- int rowCount(const QModelIndex &parent) const override;
- int columnCount(const QModelIndex &parent) const override;
- QVariant data(const QModelIndex &index, int role) const override;
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QVariant data(const QModelIndex& index, int role) const override;
void request();
- void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+ void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
-private slots:
+ private slots:
void requestFinished();
void requestFailed(QString reason);
void logoFailed(QString logo);
void logoLoaded(QString logo, QIcon out);
-private:
+ private:
void requestLogo(QString file, QString url);
-private:
+ private:
QList<ATLauncher::IndexedPack> modpacks;
QStringList m_failedLogos;
@@ -62,7 +61,7 @@ private:
QMap<QString, LogoCallback> waitingCallbacks;
NetJob::Ptr jobPtr;
- QByteArray response;
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
-}
+} // namespace Atl
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index cdb4532c..7b61daa7 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -152,7 +152,7 @@ Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
void AtlOptionalModListModel::useShareCode(const QString& code) {
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
- m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), &m_response));
+ m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response));
connect(m_jobPtr.get(), &NetJob::succeeded,
this, &AtlOptionalModListModel::shareCodeSuccess);
@@ -166,10 +166,10 @@ void AtlOptionalModListModel::shareCodeSuccess() {
m_jobPtr.reset();
QJsonParseError parse_error {};
- auto doc = QJsonDocument::fromJson(m_response, &parse_error);
+ auto doc = QJsonDocument::fromJson(*m_response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << m_response;
+ qWarning() << *m_response;
return;
}
auto obj = doc.object();
@@ -179,7 +179,7 @@ void AtlOptionalModListModel::shareCodeSuccess() {
ATLauncher::loadShareCodeResponse(response, obj);
}
catch (const JSONValidationError& e) {
- qDebug() << QString::fromUtf8(m_response);
+ qDebug() << QString::fromUtf8(*m_response);
qWarning() << "Error while reading response from ATLauncher: " << e.cause();
return;
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
index 8e02444e..639f0d48 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
@@ -82,9 +82,9 @@ private:
void toggleMod(ATLauncher::VersionMod mod, int index);
void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
-private:
+ private:
NetJob::Ptr m_jobPtr;
- QByteArray m_response;
+ std::shared_ptr<QByteArray> m_response = std::make_shared<QByteArray>();
ATLauncher::PackVersion m_version;
QVector<ATLauncher::VersionMod> m_mods;
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
index f5f50cae..3d2d568a 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
@@ -68,7 +68,7 @@ QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionList::Ptr vlis
// select recommended build
for (int i = 0; i < vlist->versions().size(); i++) {
auto version = vlist->versions().at(i);
- auto reqs = version->requires();
+ auto reqs = version->requiredSet();
// filter by minecraft version, if the loader depends on a certain version.
if (minecraftVersion != nullptr) {
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
index 37010b3f..adeb53cb 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
@@ -42,15 +42,15 @@
class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport {
Q_OBJECT
-public:
+ public:
AtlUserInteractionSupportImpl(QWidget* parent);
+ virtual ~AtlUserInteractionSupportImpl() = default;
-private:
+ private:
QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override;
std::optional<QVector<QString>> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
void displayMessage(QString message) override;
-private:
+ private:
QWidget* m_parent;
-
};
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index 5961ea02..fa55aa68 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -60,6 +60,8 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
return pack.description;
case UserDataTypes::SELECTED:
return false;
+ case UserDataTypes::INSTALLED:
+ return false;
default:
break;
}
@@ -169,7 +171,7 @@ void ListModel::performPaginatedSearch()
.arg(currentSearchTerm)
.arg(currentSort + 1);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@@ -202,11 +204,11 @@ void Flame::ListModel::searchRequestFinished()
jobPtr.reset();
QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset
<< " reason: " << parse_error.errorString();
- qWarning() << response;
+ qWarning() << *response;
return;
}
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h
index cab666cc..b3bc96b8 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.h
@@ -3,46 +3,44 @@
#include <RWStorage.h>
#include <QAbstractListModel>
-#include <QSortFilterProxyModel>
-#include <QThreadPool>
#include <QIcon>
-#include <QStyledItemDelegate>
#include <QList>
+#include <QMetaType>
+#include <QSortFilterProxyModel>
#include <QString>
#include <QStringList>
-#include <QMetaType>
+#include <QStyledItemDelegate>
+#include <QThreadPool>
-#include <functional>
#include <net/NetJob.h>
+#include <functional>
#include <modplatform/flame/FlamePackIndex.h>
namespace Flame {
-
typedef QMap<QString, QIcon> LogoMap;
typedef std::function<void(QString)> LogoCallback;
-class ListModel : public QAbstractListModel
-{
+class ListModel : public QAbstractListModel {
Q_OBJECT
-public:
- ListModel(QObject *parent);
+ public:
+ ListModel(QObject* parent);
virtual ~ListModel();
- int rowCount(const QModelIndex &parent) const override;
- int columnCount(const QModelIndex &parent) const override;
- QVariant data(const QModelIndex &index, int role) const override;
- bool setData(const QModelIndex &index, const QVariant &value, int role) override;
- Qt::ItemFlags flags(const QModelIndex &index) const override;
- bool canFetchMore(const QModelIndex & parent) const override;
- void fetchMore(const QModelIndex & parent) override;
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QVariant data(const QModelIndex& index, int role) const override;
+ bool setData(const QModelIndex& index, const QVariant& value, int role) override;
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+ bool canFetchMore(const QModelIndex& parent) const override;
+ void fetchMore(const QModelIndex& parent) override;
- void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
- void searchWithTerm(const QString & term, const int sort);
+ void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
+ void searchWithTerm(const QString& term, const int sort);
-private slots:
+ private slots:
void performPaginatedSearch();
void logoFailed(QString logo);
@@ -51,10 +49,10 @@ private slots:
void searchRequestFinished();
void searchRequestFailed(QString reason);
-private:
+ private:
void requestLogo(QString file, QString url);
-private:
+ private:
QList<IndexedPack> modpacks;
QStringList m_failedLogos;
QStringList m_loadingLogos;
@@ -64,14 +62,9 @@ private:
QString currentSearchTerm;
int currentSort = 0;
int nextSearchOffset = 0;
- enum SearchState {
- None,
- CanPossiblyFetchMore,
- ResetRequested,
- Finished
- } searchState = None;
+ enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
NetJob::Ptr jobPtr;
- QByteArray response;
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
-}
+} // namespace Flame
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index f9ac4a78..cef26bb6 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -130,7 +130,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
if (current.versionsLoaded == false) {
qDebug() << "Loading flame modpack versions";
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
- auto response = new QByteArray();
+ auto response = std::make_shared<QByteArray>();
int addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
@@ -170,10 +170,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
}
suggestCurrent();
});
- QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
- netJob->deleteLater();
- delete response;
- });
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
netJob->start();
} else {
for (auto version : current.versions) {
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
index e3d0bc14..0fb67c50 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -11,7 +11,7 @@
namespace ResourceDownload {
-FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {}
+FlameModModel::FlameModModel(BaseInstance& base) : ModModel(base, new FlameAPI) {}
void FlameModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
@@ -29,6 +29,11 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
+auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
+{
+ return FlameMod::loadDependencyVersions(m, arr);
+};
+
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return Json::ensureArray(obj.object(), "data");
@@ -81,7 +86,7 @@ void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m,
auto const& mc_versions = version.mcVersion;
if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(),
- [this](auto const& mc_version){ return Version(mc_version) <= maximumTexturePackVersion(); }))
+ [this](auto const& mc_version) { return Version(mc_version) <= maximumTexturePackVersion(); }))
filtered_versions.push_back(version);
}
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
index 0252ac40..6cfd6a6f 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
@@ -14,7 +14,7 @@ class FlameModModel : public ModModel {
Q_OBJECT
public:
- FlameModModel(const BaseInstance&);
+ FlameModModel(BaseInstance&);
~FlameModModel() override = default;
private:
@@ -24,6 +24,7 @@ class FlameModModel : public ModModel {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+ auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
deleted file mode 100644
index e2b548f2..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * 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 "FtbFilterModel.h"
-
-#include <QDebug>
-
-#include "modplatform/modpacksch/FTBPackManifest.h"
-
-#include "StringUtils.h"
-
-namespace Ftb {
-
-FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
-{
- currentSorting = Sorting::ByPlays;
- sortings.insert(tr("Sort by Plays"), Sorting::ByPlays);
- sortings.insert(tr("Sort by Installs"), Sorting::ByInstalls);
- sortings.insert(tr("Sort by Name"), Sorting::ByName);
-}
-
-const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
-{
- return sortings;
-}
-
-QString FilterModel::translateCurrentSorting()
-{
- return sortings.key(currentSorting);
-}
-
-void FilterModel::setSorting(Sorting sorting)
-{
- currentSorting = sorting;
- invalidate();
-}
-
-FilterModel::Sorting FilterModel::getCurrentSorting()
-{
- return currentSorting;
-}
-
-void FilterModel::setSearchTerm(const QString& term)
-{
- searchTerm = term.trimmed();
- invalidate();
-}
-
-bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
-{
- if (searchTerm.isEmpty()) {
- return true;
- }
-
- auto index = sourceModel()->index(sourceRow, 0, sourceParent);
- auto pack = sourceModel()->data(index, Qt::UserRole).value<ModpacksCH::Modpack>();
- return pack.name.contains(searchTerm, Qt::CaseInsensitive);
-}
-
-bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
-{
- ModpacksCH::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<ModpacksCH::Modpack>();
- ModpacksCH::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<ModpacksCH::Modpack>();
-
- if (currentSorting == ByPlays) {
- return leftPack.plays < rightPack.plays;
- }
- else if (currentSorting == ByInstalls) {
- return leftPack.installs < rightPack.installs;
- }
- else if (currentSorting == ByName) {
- return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
- }
-
- // Invalid sorting set, somehow...
- qWarning() << "Invalid sorting set!";
- return true;
-}
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
deleted file mode 100644
index 1be28e99..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QtCore/QSortFilterProxyModel>
-
-namespace Ftb {
-
-class FilterModel : public QSortFilterProxyModel
-{
- Q_OBJECT
-
-public:
- FilterModel(QObject* parent = Q_NULLPTR);
- enum Sorting {
- ByPlays,
- ByInstalls,
- ByName,
- };
- const QMap<QString, Sorting> getAvailableSortings();
- QString translateCurrentSorting();
- void setSorting(Sorting sorting);
- Sorting getCurrentSorting();
- void setSearchTerm(const QString& term);
-
-protected:
- bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
- bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
-
-private:
- QMap<QString, Sorting> sortings;
- Sorting currentSorting;
- QString searchTerm { "" };
-
-};
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
deleted file mode 100644
index e8065415..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * 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 "FtbListModel.h"
-
-#include "BuildConfig.h"
-#include "Application.h"
-#include "Json.h"
-
-#include <QPainter>
-
-namespace Ftb {
-
-ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
-{
-}
-
-ListModel::~ListModel()
-{
-}
-
-int ListModel::rowCount(const QModelIndex &parent) const
-{
- return parent.isValid() ? 0 : modpacks.size();
-}
-
-int ListModel::columnCount(const QModelIndex &parent) const
-{
- return parent.isValid() ? 0 : 1;
-}
-
-QVariant ListModel::data(const QModelIndex &index, int role) const
-{
- int pos = index.row();
- if(pos >= modpacks.size() || pos < 0 || !index.isValid())
- {
- return QString("INVALID INDEX %1").arg(pos);
- }
-
- ModpacksCH::Modpack pack = modpacks.at(pos);
- if(role == Qt::DisplayRole)
- {
- return pack.name;
- }
- else if (role == Qt::ToolTipRole)
- {
- return pack.synopsis;
- }
- else if(role == Qt::DecorationRole)
- {
- QIcon placeholder = APPLICATION->getThemedIcon("screenshot-placeholder");
-
- auto iter = m_logoMap.find(pack.name);
- if (iter != m_logoMap.end()) {
- auto & logo = *iter;
- if(!logo.result.isNull()) {
- return logo.result;
- }
- return placeholder;
- }
-
- for(auto art : pack.art) {
- if(art.type == "square") {
- ((ListModel *)this)->requestLogo(pack.name, art.url);
- }
- }
- return placeholder;
- }
- else if(role == Qt::UserRole)
- {
- QVariant v;
- v.setValue(pack);
- return v;
- }
-
- return QVariant();
-}
-
-void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
-{
- if(m_logoMap.contains(logo))
- {
- callback(APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
- }
- else
- {
- requestLogo(logo, logoUrl);
- }
-}
-
-void ListModel::request()
-{
- m_aborted = false;
-
- beginResetModel();
- modpacks.clear();
- endResetModel();
-
- auto netJob = makeShared<NetJob>("Ftb::Request", APPLICATION->network());
- auto url = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all");
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
- jobPtr = netJob;
- jobPtr->start();
-
- QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::requestFinished);
- QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed);
-}
-
-void ListModel::abortRequest()
-{
- m_aborted = jobPtr->abort();
- jobPtr.reset();
-}
-
-void ListModel::requestFinished()
-{
- jobPtr.reset();
- remainingPacks.clear();
-
- QJsonParseError parse_error {};
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
- return;
- }
-
- auto packs = doc.object().value("packs").toArray();
- for(auto pack : packs) {
- auto packId = pack.toInt();
- remainingPacks.append(packId);
- }
-
- if(!remainingPacks.isEmpty()) {
- currentPack = remainingPacks.at(0);
- requestPack();
- }
-}
-
-void ListModel::requestFailed(QString reason)
-{
- jobPtr.reset();
- remainingPacks.clear();
-}
-
-void ListModel::requestPack()
-{
- auto netJob = makeShared<NetJob>("Ftb::Search", APPLICATION->network());
- auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1").arg(currentPack);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
- jobPtr = netJob;
- jobPtr->start();
-
- QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::packRequestFinished);
- QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::packRequestFailed);
-}
-
-void ListModel::packRequestFinished()
-{
- if (!jobPtr || m_aborted)
- return;
-
- jobPtr.reset();
- remainingPacks.removeOne(currentPack);
-
- QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
-
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
- return;
- }
-
- auto obj = doc.object();
-
- ModpacksCH::Modpack pack;
- try
- {
- ModpacksCH::loadModpack(pack, obj);
- }
- catch (const JSONValidationError &e)
- {
- qDebug() << QString::fromUtf8(response);
- qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause();
- return;
- }
-
- // Since there is no guarantee that packs have a version, this will just
- // ignore those "dud" packs.
- if (pack.versions.empty())
- {
- qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions";
- }
- else
- {
- beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size());
- modpacks.append(pack);
- endInsertRows();
- }
-
- if(!remainingPacks.isEmpty()) {
- currentPack = remainingPacks.at(0);
- requestPack();
- }
-}
-
-void ListModel::packRequestFailed(QString reason)
-{
- jobPtr.reset();
- remainingPacks.removeOne(currentPack);
-}
-
-void ListModel::logoLoaded(QString logo, bool stale)
-{
- auto & logoObj = m_logoMap[logo];
- logoObj.downloadJob.reset();
- QString smallPath = logoObj.fullpath + ".small";
-
- QFileInfo smallInfo(smallPath);
-
- if(stale || !smallInfo.exists()) {
- QImage image(logoObj.fullpath);
- if (image.isNull())
- {
- logoObj.failed = true;
- return;
- }
- QImage small;
- if (image.width() > image.height()) {
- small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
- }
- else {
- small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
- }
- QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
- QImage square(QSize(256, 256), QImage::Format_ARGB32);
- square.fill(Qt::transparent);
-
- QPainter painter(&square);
- painter.drawImage(offset, small);
- painter.end();
-
- square.save(logoObj.fullpath + ".small", "PNG");
- }
-
- logoObj.result = QIcon(logoObj.fullpath + ".small");
- for(int i = 0; i < modpacks.size(); i++) {
- if(modpacks[i].name == logo) {
- emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
- }
- }
-}
-
-void ListModel::logoFailed(QString logo)
-{
- m_logoMap[logo].failed = true;
- m_logoMap[logo].downloadJob.reset();
-}
-
-void ListModel::requestLogo(QString logo, QString url)
-{
- if(m_logoMap.contains(logo)) {
- return;
- }
-
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
-
- bool stale = entry->isStale();
-
- auto job = makeShared<NetJob>(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
-
- auto fullPath = entry->getFullPath();
- QObject::connect(job.get(), &NetJob::finished, this, [this, logo, fullPath, stale]
- {
- logoLoaded(logo, stale);
- });
-
- QObject::connect(job.get(), &NetJob::failed, this, [this, logo]
- {
- logoFailed(logo);
- });
-
- auto &newLogoEntry = m_logoMap[logo];
- newLogoEntry.downloadJob = job;
- newLogoEntry.fullpath = fullPath;
- job->start();
-}
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.h b/launcher/ui/pages/modplatform/ftb/FtbListModel.h
deleted file mode 100644
index d7a120f0..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QAbstractListModel>
-
-#include "modplatform/modpacksch/FTBPackManifest.h"
-#include "net/NetJob.h"
-#include <QIcon>
-
-namespace Ftb {
-
-struct Logo {
- QString fullpath;
- NetJob::Ptr downloadJob;
- QIcon result;
- bool failed = false;
-};
-
-typedef QMap<QString, Logo> LogoMap;
-typedef std::function<void(QString)> LogoCallback;
-
-class ListModel : public QAbstractListModel
-{
- Q_OBJECT
-
-public:
- ListModel(QObject *parent);
- virtual ~ListModel();
-
- int rowCount(const QModelIndex &parent) const override;
- int columnCount(const QModelIndex &parent) const override;
- QVariant data(const QModelIndex &index, int role) const override;
-
- void request();
- void abortRequest();
-
- void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
-
- [[nodiscard]] bool isMakingRequest() const { return jobPtr.get(); }
- [[nodiscard]] bool wasAborted() const { return m_aborted; }
-
-private slots:
- void requestFinished();
- void requestFailed(QString reason);
-
- void requestPack();
- void packRequestFinished();
- void packRequestFailed(QString reason);
-
- void logoFailed(QString logo);
- void logoLoaded(QString logo, bool stale);
-
-private:
- void requestLogo(QString file, QString url);
-
-private:
- bool m_aborted = false;
-
- QList<ModpacksCH::Modpack> modpacks;
- LogoMap m_logoMap;
-
- NetJob::Ptr jobPtr;
- int currentPack;
- QList<int> remainingPacks;
- QByteArray response;
-};
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
deleted file mode 100644
index 7d59a6ae..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- *
- * 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Philip T <me@phit.link>
- *
- * 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 "FtbPage.h"
-#include "ui_FtbPage.h"
-
-#include <QKeyEvent>
-
-#include "ui/dialogs/NewInstanceDialog.h"
-#include "modplatform/modpacksch/FTBPackInstallTask.h"
-
-#include "Markdown.h"
-
-FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::FtbPage), dialog(dialog)
-{
- ui->setupUi(this);
-
- filterModel = new Ftb::FilterModel(this);
- listModel = new Ftb::ListModel(this);
- filterModel->setSourceModel(listModel);
- ui->packView->setModel(filterModel);
- ui->packView->setSortingEnabled(true);
- ui->packView->header()->hide();
- ui->packView->setIndentation(0);
-
- ui->searchEdit->installEventFilter(this);
-
- ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
-
- for(int i = 0; i < filterModel->getAvailableSortings().size(); i++)
- {
- ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i));
- }
- ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting());
-
- connect(ui->searchEdit, &QLineEdit::textChanged, this, &FtbPage::triggerSearch);
- connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged);
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged);
-
- ui->packDescription->setMetaEntry("FTBPacks");
-}
-
-FtbPage::~FtbPage()
-{
- delete ui;
-}
-
-bool FtbPage::eventFilter(QObject* watched, QEvent* event)
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- }
- }
- return QWidget::eventFilter(watched, event);
-}
-
-bool FtbPage::shouldDisplay() const
-{
- return true;
-}
-
-void FtbPage::retranslate()
-{
- ui->retranslateUi(this);
-}
-
-void FtbPage::openedImpl()
-{
- if(!initialised || listModel->wasAborted())
- {
- listModel->request();
- initialised = true;
- }
-
- suggestCurrent();
-}
-
-void FtbPage::closedImpl()
-{
- if (listModel->isMakingRequest())
- listModel->abortRequest();
-}
-
-void FtbPage::suggestCurrent()
-{
- if(!isOpened)
- {
- return;
- }
-
- if (selectedVersion.isEmpty())
- {
- dialog->setSuggestedPack();
- return;
- }
-
- dialog->setSuggestedPack(selected.name, selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this));
- for(auto art : selected.art) {
- if(art.type == "square") {
- QString editedLogoName;
- editedLogoName = selected.name;
-
- listModel->getLogo(selected.name, art.url, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName);
- });
- }
- }
-}
-
-void FtbPage::triggerSearch()
-{
- filterModel->setSearchTerm(ui->searchEdit->text());
-}
-
-void FtbPage::onSortingSelectionChanged(QString data)
-{
- auto toSet = filterModel->getAvailableSortings().value(data);
- filterModel->setSorting(toSet);
-}
-
-void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second)
-{
- ui->versionSelectionBox->clear();
-
- if(!first.isValid())
- {
- if(isOpened)
- {
- dialog->setSuggestedPack();
- }
- return;
- }
-
- selected = filterModel->data(first, Qt::UserRole).value<ModpacksCH::Modpack>();
-
- QString output = markdownToHTML(selected.description.toUtf8());
- ui->packDescription->setHtml(output);
-
- // reverse foreach, so that the newest versions are first
- for (auto i = selected.versions.size(); i--;) {
- ui->versionSelectionBox->addItem(selected.versions.at(i).name);
- }
-
- suggestCurrent();
-}
-
-void FtbPage::onVersionSelectionChanged(QString data)
-{
- if(data.isNull() || data.isEmpty())
- {
- selectedVersion = "";
- return;
- }
-
- selectedVersion = data;
- suggestCurrent();
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.h b/launcher/ui/pages/modplatform/ftb/FtbPage.h
deleted file mode 100644
index 631ae7f5..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * 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.
- */
-
-#pragma once
-
-#include "FtbFilterModel.h"
-#include "FtbListModel.h"
-
-#include <QWidget>
-
-#include "Application.h"
-#include "ui/pages/BasePage.h"
-#include "tasks/Task.h"
-
-namespace Ui
-{
- class FtbPage;
-}
-
-class NewInstanceDialog;
-
-class FtbPage : public QWidget, public BasePage
-{
-Q_OBJECT
-
-public:
- explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0);
- virtual ~FtbPage();
- virtual QString displayName() const override
- {
- return "FTB";
- }
- virtual QIcon icon() const override
- {
- return APPLICATION->getThemedIcon("ftb_logo");
- }
- virtual QString id() const override
- {
- return "ftb";
- }
- virtual QString helpPage() const override
- {
- return "FTB-platform";
- }
- virtual bool shouldDisplay() const override;
- void retranslate() override;
-
- void openedImpl() override;
- void closedImpl() override;
-
- bool eventFilter(QObject * watched, QEvent * event) override;
-
-private:
- void suggestCurrent();
-
-private slots:
- void triggerSearch();
-
- void onSortingSelectionChanged(QString data);
- void onSelectionChanged(QModelIndex first, QModelIndex second);
- void onVersionSelectionChanged(QString data);
-
-private:
- Ui::FtbPage *ui = nullptr;
- NewInstanceDialog* dialog = nullptr;
- Ftb::ListModel* listModel = nullptr;
- Ftb::FilterModel* filterModel = nullptr;
-
- ModpacksCH::Modpack selected;
- QString selectedVersion;
-
- bool initialised { false };
-};
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.ui b/launcher/ui/pages/modplatform/ftb/FtbPage.ui
deleted file mode 100644
index 8de0f4e6..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.ui
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>FtbPage</class>
- <widget class="QWidget" name="FtbPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>875</width>
- <height>745</height>
- </rect>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
- </layout>
- </item>
- <item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit">
- <property name="placeholderText">
- <string>Search and filter...</string>
- </property>
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <widget class="QTreeView" name="packView">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="iconSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="ProjectDescriptionPage" name="packDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- <property name="openLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>ProjectDescriptionPage</class>
- <extends>QTextBrowser</extends>
- <header>ui/widgets/ProjectDescriptionPage.h</header>
- </customwidget>
- </customwidgets>
- <tabstops>
- <tabstop>searchEdit</tabstop>
- <tabstop>versionSelectionBox</tabstop>
- </tabstops>
- <resources/>
- <connections/>
-</ui>
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 2343b79f..330dd4fb 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -38,11 +38,11 @@
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
-#include "StringUtils.h"
#include <Version.h>
+#include "StringUtils.h"
-#include <QtMath>
#include <QLabel>
+#include <QtMath>
#include <RWStorage.h>
@@ -50,33 +50,33 @@
namespace LegacyFTB {
-FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
+FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
{
currentSorting = Sorting::ByGameVersion;
sortings.insert(tr("Sort by Name"), Sorting::ByName);
sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
}
-bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
{
Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
- if(currentSorting == Sorting::ByGameVersion) {
+ if (currentSorting == Sorting::ByGameVersion) {
Version lv(leftPack.mcVersion);
Version rv(rightPack.mcVersion);
return lv < rv;
- } else if(currentSorting == Sorting::ByName) {
+ } else if (currentSorting == Sorting::ByName) {
return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
}
- //UHM, some inavlid value set?!
+ // UHM, some inavlid value set?!
qWarning() << "Invalid sorting set!";
return true;
}
-bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
return true;
}
@@ -102,18 +102,13 @@ FilterModel::Sorting FilterModel::getCurrentSorting()
return currentSorting;
}
-ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
-{
-}
+ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
-ListModel::~ListModel()
-{
-}
+ListModel::~ListModel() {}
QString ListModel::translatePackType(PackType type) const
{
- switch(type)
- {
+ switch (type) {
case PackType::Public:
return tr("Public Modpack");
case PackType::ThirdParty:
@@ -125,67 +120,51 @@ QString ListModel::translatePackType(PackType type) const
return QString();
}
-int ListModel::rowCount(const QModelIndex &parent) const
+int ListModel::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : modpacks.size();
}
-int ListModel::columnCount(const QModelIndex &parent) const
+int ListModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : 1;
}
-QVariant ListModel::data(const QModelIndex &index, int role) const
+QVariant ListModel::data(const QModelIndex& index, int role) const
{
int pos = index.row();
- if(pos >= modpacks.size() || pos < 0 || !index.isValid())
- {
+ if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
return QString("INVALID INDEX %1").arg(pos);
}
Modpack pack = modpacks.at(pos);
- if(role == Qt::DisplayRole)
- {
+ if (role == Qt::DisplayRole) {
return pack.name + "\n" + translatePackType(pack.type);
- }
- else if (role == Qt::ToolTipRole)
- {
- if(pack.description.length() > 100)
- {
- //some magic to prevent to long tooltips and replace html linebreaks
+ } else if (role == Qt::ToolTipRole) {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
QString edit = pack.description.left(97);
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
return edit;
-
}
return pack.description;
- }
- else if(role == Qt::DecorationRole)
- {
- if(m_logoMap.contains(pack.logo))
- {
+ } else if (role == Qt::DecorationRole) {
+ if (m_logoMap.contains(pack.logo)) {
return (m_logoMap.value(pack.logo));
}
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel *)this)->requestLogo(pack.logo);
+ ((ListModel*)this)->requestLogo(pack.logo);
return icon;
- }
- else if(role == Qt::ForegroundRole)
- {
- if(pack.broken)
- {
- //FIXME: Hardcoded color
+ } else if (role == Qt::ForegroundRole) {
+ if (pack.broken) {
+ // FIXME: Hardcoded color
return QColor(255, 0, 50);
- }
- else if(pack.bugged)
- {
- //FIXME: Hardcoded color
- //bugged pack, currently only indicates bugged xml
+ } else if (pack.bugged) {
+ // FIXME: Hardcoded color
+ // bugged pack, currently only indicates bugged xml
return QColor(244, 229, 66);
}
- }
- else if(role == Qt::UserRole)
- {
+ } else if (role == Qt::UserRole) {
QVariant v;
v.setValue(pack);
return v;
@@ -222,8 +201,7 @@ Modpack ListModel::at(int row)
void ListModel::remove(int row)
{
- if(row < 0 || row >= modpacks.size())
- {
+ if (row < 0 || row >= modpacks.size()) {
qWarning() << "Attempt to remove FTB modpacks with invalid row" << row;
return;
}
@@ -247,27 +225,25 @@ void ListModel::logoFailed(QString logo)
void ListModel::requestLogo(QString file)
{
- if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
- {
+ if (m_loadingLogos.contains(file) || m_failedLogos.contains(file)) {
return;
}
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
- NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
+ NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
auto fullPath = entry->getFullPath();
- QObject::connect(job, &NetJob::finished, this, [this, file, fullPath]
- {
+ QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {
+ job->deleteLater();
emit logoLoaded(file, QIcon(fullPath));
- if(waitingCallbacks.contains(file))
- {
+ if (waitingCallbacks.contains(file)) {
waitingCallbacks.value(file)(fullPath);
}
});
- QObject::connect(job, &NetJob::failed, this, [this, file]
- {
+ QObject::connect(job, &NetJob::failed, this, [this, file, job] {
+ job->deleteLater();
emit logoFailed(file);
});
@@ -276,21 +252,18 @@ void ListModel::requestLogo(QString file)
m_loadingLogos.append(file);
}
-void ListModel::getLogo(const QString &logo, LogoCallback callback)
+void ListModel::getLogo(const QString& logo, LogoCallback callback)
{
- if(m_logoMap.contains(logo))
- {
+ if (m_logoMap.contains(logo)) {
callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
- }
- else
- {
+ } else {
requestLogo(logo);
}
}
-Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
+Qt::ItemFlags ListModel::flags(const QModelIndex& index) const
{
return QAbstractListModel::flags(index);
}
-}
+} // namespace LegacyFTB
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/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index 346a00b0..e0046d88 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -106,6 +106,8 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
return pack.description;
case UserDataTypes::SELECTED:
return false;
+ case UserDataTypes::INSTALLED:
+ return false;
default:
break;
}
@@ -129,27 +131,27 @@ void ModpackListModel::performPaginatedSearch()
// TODO: Move to standalone API
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
- "/search?"
- "offset=%1&"
- "limit=%2&"
- "query=%3&"
- "index=%4&"
- "facets=[[\"project_type:modpack\"]]")
+ "/search?"
+ "offset=%1&"
+ "limit=%2&"
+ "query=%3&"
+ "index=%4&"
+ "facets=[[\"project_type:modpack\"]]")
.arg(nextSearchOffset)
.arg(m_modpacks_per_page)
.arg(currentSearchTerm)
.arg(currentSort);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response));
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
QJsonParseError parse_error_all{};
- QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all);
+ QJsonDocument doc_all = QJsonDocument::fromJson(*m_all_response, &parse_error_all);
if (parse_error_all.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset
<< " reason: " << parse_error_all.errorString();
- qWarning() << m_all_response;
+ qWarning() << *m_all_response;
return;
}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 6e6be4b9..b9e9c3da 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -110,9 +110,9 @@ class ModpackListModel : public QAbstractListModel {
NetJob::Ptr jobPtr;
- QByteArray m_all_response;
+ std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
QByteArray m_specific_response;
int m_modpacks_per_page = 20;
};
-} // namespace ModPlatform
+} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 0bb11d83..c71dd903 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -123,7 +123,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
qDebug() << "Loading modrinth modpack information";
auto netJob = new NetJob(QString("Modrinth::PackInformation(%1)").arg(current.name), APPLICATION->network());
- auto response = new QByteArray();
+ auto response = std::make_shared<QByteArray>();
QString id = current.id;
@@ -162,10 +162,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
suggestCurrent();
});
- QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
- netJob->deleteLater();
- delete response;
- });
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
netJob->start();
} else
updateUI();
@@ -174,7 +171,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
qDebug() << "Loading modrinth modpack versions";
auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network());
- auto response = new QByteArray();
+ auto response = std::make_shared<QByteArray>();
QString id = current.id;
@@ -217,10 +214,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
suggestCurrent();
});
- QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
- netJob->deleteLater();
- delete response;
- });
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
netJob->start();
} else {
@@ -260,10 +254,8 @@ void ModrinthPage::updateUI()
text += donates.join(", ");
}
- if (!current.extra.issuesUrl.isEmpty()
- || !current.extra.sourceUrl.isEmpty()
- || !current.extra.wikiUrl.isEmpty()
- || !current.extra.discordUrl.isEmpty()) {
+ if (!current.extra.issuesUrl.isEmpty() || !current.extra.sourceUrl.isEmpty() || !current.extra.wikiUrl.isEmpty() ||
+ !current.extra.discordUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>";
}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
index f5d1cc28..8aa64989 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -25,7 +25,7 @@
namespace ResourceDownload {
-ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI) {}
+ModrinthModModel::ModrinthModModel(BaseInstance& base) : ModModel(base, new ModrinthAPI) {}
void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
@@ -42,12 +42,17 @@ void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJso
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
}
+auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
+{
+ return ::Modrinth::loadDependencyVersions(m, arr);
+};
+
auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
{
return obj.object().value("hits").toArray();
}
-ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI){}
+ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI) {}
void ModrinthResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
@@ -69,7 +74,7 @@ auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJs
return obj.object().value("hits").toArray();
}
-ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI){}
+ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI) {}
void ModrinthTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
@@ -91,7 +96,7 @@ auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJso
return obj.object().value("hits").toArray();
}
-ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI){}
+ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI) {}
void ModrinthShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
{
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
index b351b19b..d7c858f8 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
@@ -30,7 +30,7 @@ class ModrinthModModel : public ModModel {
Q_OBJECT
public:
- ModrinthModModel(const BaseInstance&);
+ ModrinthModModel(BaseInstance&);
~ModrinthModModel() override = default;
private:
@@ -40,6 +40,7 @@ class ModrinthModModel : public ModModel {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+ auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion override;
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index 50f0c72d..f08eb289 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -40,39 +40,28 @@
#include <QIcon>
-Technic::ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
-{
-}
+Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
-Technic::ListModel::~ListModel()
-{
-}
+Technic::ListModel::~ListModel() {}
QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
{
int pos = index.row();
- if(pos >= modpacks.size() || pos < 0 || !index.isValid())
- {
+ if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
return QString("INVALID INDEX %1").arg(pos);
}
Modpack pack = modpacks.at(pos);
- if(role == Qt::DisplayRole)
- {
+ if (role == Qt::DisplayRole) {
return pack.name;
- }
- else if(role == Qt::DecorationRole)
- {
- if(m_logoMap.contains(pack.logoName))
- {
+ } else if (role == Qt::DecorationRole) {
+ if (m_logoMap.contains(pack.logoName)) {
return (m_logoMap.value(pack.logoName));
}
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl);
+ ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
return icon;
- }
- else if(role == Qt::UserRole)
- {
+ } else if (role == Qt::UserRole) {
QVariant v;
v.setValue(pack);
return v;
@@ -92,16 +81,15 @@ int Technic::ListModel::rowCount(const QModelIndex& parent) const
void Technic::ListModel::searchWithTerm(const QString& term)
{
- if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) {
+ if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) {
return;
}
currentSearchTerm = term;
- if(jobPtr) {
+ if (jobPtr) {
jobPtr->abort();
searchState = ResetRequested;
return;
- }
- else {
+ } else {
beginResetModel();
modpacks.clear();
endResetModel();
@@ -115,26 +103,20 @@ void Technic::ListModel::performSearch()
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
- searchUrl = QString("%1trending?build=%2")
- .arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD);
+ searchUrl = QString("%1trending?build=%2").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD);
searchMode = List;
- }
- else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) {
- searchUrl = QString("https://%1?build=%2")
- .arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD);
+ } else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) {
+ searchUrl = QString("https://%1?build=%2").arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
- }
- else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
+ } else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
- }
- else {
- searchUrl = QString(
- "%1search?build=%2&q=%3"
- ).arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
+ } else {
+ searchUrl =
+ QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
searchMode = List;
}
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@@ -146,11 +128,11 @@ void Technic::ListModel::searchRequestFinished()
jobPtr.reset();
QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError)
- {
- qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
return;
}
@@ -161,7 +143,7 @@ void Technic::ListModel::searchRequestFinished()
switch (searchMode) {
case List: {
auto objs = Json::requireArray(root, "modpacks");
- for (auto technicPack: objs) {
+ for (auto technicPack : objs) {
Modpack pack;
auto technicPackObject = Json::requireObject(technicPack);
pack.name = Json::requireString(technicPackObject, "name");
@@ -170,11 +152,10 @@ void Technic::ListModel::searchRequestFinished()
continue;
auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null");
- if(rawURL == "null") {
+ if (rawURL == "null") {
pack.logoUrl = "null";
pack.logoName = "null";
- }
- else {
+ } else {
pack.logoUrl = rawURL;
pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
}
@@ -199,8 +180,7 @@ void Technic::ListModel::searchRequestFinished()
pack.logoUrl = iconUrl;
pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
- }
- else {
+ } else {
pack.logoUrl = "null";
pack.logoName = "null";
}
@@ -210,10 +190,8 @@ void Technic::ListModel::searchRequestFinished()
break;
}
}
- }
- catch (const JSONValidationError &err)
- {
- qCritical() << "Couldn't parse technic search results:" << err.cause() ;
+ } catch (const JSONValidationError& err) {
+ qCritical() << "Couldn't parse technic search results:" << err.cause();
return;
}
searchState = Finished;
@@ -229,12 +207,9 @@ void Technic::ListModel::searchRequestFinished()
void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl, Technic::LogoCallback callback)
{
- if(m_logoMap.contains(logo))
- {
+ if (m_logoMap.contains(logo)) {
callback(APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))->getFullPath());
- }
- else
- {
+ } else {
requestLogo(logo, logoUrl);
}
}
@@ -243,30 +218,24 @@ void Technic::ListModel::searchRequestFailed()
{
jobPtr.reset();
- if(searchState == ResetRequested)
- {
+ if (searchState == ResetRequested) {
beginResetModel();
modpacks.clear();
endResetModel();
performSearch();
- }
- else
- {
+ } else {
searchState = Finished;
}
}
-
void Technic::ListModel::logoLoaded(QString logo, QString out)
{
m_loadingLogos.removeAll(logo);
m_logoMap.insert(logo, QIcon(out));
- for(int i = 0; i < modpacks.size(); i++)
- {
- if(modpacks[i].logoName == logo)
- {
- emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ for (int i = 0; i < modpacks.size(); i++) {
+ if (modpacks[i].logoName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
}
}
}
@@ -279,24 +248,23 @@ void Technic::ListModel::logoFailed(QString logo)
void Technic::ListModel::requestLogo(QString logo, QString url)
{
- if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null")
- {
+ if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null") {
return;
}
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
- NetJob *job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
+ auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
- QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath]
- {
+ QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
+ job->deleteLater();
logoLoaded(logo, fullPath);
});
- QObject::connect(job, &NetJob::failed, this, [this, logo]
- {
+ QObject::connect(job, &NetJob::failed, this, [this, logo, job] {
+ job->deleteLater();
logoFailed(logo);
});
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h
index 5eea124c..0f1a814e 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h
@@ -44,33 +44,32 @@ namespace Technic {
typedef std::function<void(QString)> LogoCallback;
-class ListModel : public QAbstractListModel
-{
+class ListModel : public QAbstractListModel {
Q_OBJECT
-public:
- ListModel(QObject *parent);
+ public:
+ ListModel(QObject* parent);
virtual ~ListModel();
virtual QVariant data(const QModelIndex& index, int role) const;
virtual int columnCount(const QModelIndex& parent) const;
virtual int rowCount(const QModelIndex& parent) const;
- void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
- void searchWithTerm(const QString & term);
+ void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
+ void searchWithTerm(const QString& term);
-private slots:
+ private slots:
void searchRequestFinished();
void searchRequestFailed();
void logoFailed(QString logo);
void logoLoaded(QString logo, QString out);
-private:
+ private:
void performSearch();
void requestLogo(QString logo, QString url);
-private:
+ private:
QList<Modpack> modpacks;
QStringList m_failedLogos;
QStringList m_loadingLogos;
@@ -78,17 +77,13 @@ private:
QMap<QString, LogoCallback> waitingCallbacks;
QString currentSearchTerm;
- enum SearchState {
- None,
- ResetRequested,
- Finished
- } searchState = None;
+ enum SearchState { None, ResetRequested, Finished } searchState = None;
enum SearchMode {
List,
Single,
} searchMode = List;
NetJob::Ptr jobPtr;
- QByteArray response;
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
-}
+} // namespace Technic
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
index 859da97e..fc678fa2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
@@ -143,7 +143,7 @@ void TechnicPage::suggestCurrent()
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
QString slug = current.slug;
- netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response));
+ netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug]
{
jobPtr.reset();
@@ -154,7 +154,7 @@ void TechnicPage::suggestCurrent()
}
QJsonParseError parse_error {};
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
QJsonObject obj = doc.object();
if(parse_error.error != QJsonParseError::NoError)
{
@@ -249,7 +249,7 @@ void TechnicPage::metadataLoaded()
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
@@ -291,11 +291,11 @@ void TechnicPage::onSolderLoaded() {
current.versions.clear();
- QJsonParseError parse_error {};
- auto doc = QJsonDocument::fromJson(response, &parse_error);
+ QJsonParseError parse_error{};
+ auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
+ qWarning() << *response;
fallback();
return;
}
@@ -304,8 +304,7 @@ void TechnicPage::onSolderLoaded() {
TechnicSolder::Pack pack;
try {
TechnicSolder::loadPack(pack, obj);
- }
- catch (const JSONValidationError& err) {
+ } catch (const JSONValidationError& err) {
qCritical() << "Couldn't parse Solder pack metadata:" << err.cause();
fallback();
return;
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h
index f4a3b61d..753261b3 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h
@@ -104,5 +104,5 @@ private:
QString selectedVersion;
NetJob::Ptr jobPtr;
- QByteArray response;
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
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/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp
index a95bc875..3a746d02 100644
--- a/launcher/ui/themes/SystemTheme.cpp
+++ b/launcher/ui/themes/SystemTheme.cpp
@@ -43,7 +43,7 @@ SystemTheme::SystemTheme()
{
themeDebugLog() << "Determining System Theme...";
const auto& style = QApplication::style();
- systemPalette = style->standardPalette();
+ systemPalette = QApplication::palette();
QString lowerThemeName = style->objectName();
themeDebugLog() << "System theme seems to be:" << lowerThemeName;
QStringList styles = QStyleFactory::keys();
diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp
index fdc581b4..a0fda952 100644
--- a/launcher/ui/widgets/InfoFrame.cpp
+++ b/launcher/ui/widgets/InfoFrame.cpp
@@ -1,52 +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();
}
@@ -57,38 +75,70 @@ 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();
+ 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
+ }
+ if (!l.name.isEmpty()) {
+ if (l.url.isEmpty()) {
+ licenseText += l.name;
+ } else {
+ licenseText += "<a href=\"" + l.url + "\">" + l.name + "</a>";
+ }
+ } else if (!l.url.isEmpty()) {
+ licenseText += "<a href=\"" + l.url + "\">" + l.url + "</a>";
+ }
+ if (!l.description.isEmpty() && l.description != l.name) {
+ licenseText += " " + l.description;
+ }
+ }
+ }
+ if (!licenseText.isEmpty()) {
+ setLicense(tr("License: %1").arg(licenseText));
+ } else {
+ setLicense();
+ }
+
+ QString issueTracker = "";
+ if (!m.issueTracker().isEmpty()) {
+ issueTracker += tr("Report issues to: ");
+ issueTracker += "<a href=\"" + m.issueTracker() + "\">" + m.issueTracker() + "</a>";
+ }
+ setIssueTracker(issueTracker);
}
void InfoFrame::updateWithResource(const Resource& resource)
@@ -97,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.
@@ -108,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{};
@@ -162,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()
@@ -177,28 +224,25 @@ void InfoFrame::clear()
setName();
setDescription();
setImage();
+ setLicense();
+ setIssueTracker();
}
void InfoFrame::updateHiddenState()
{
- if(ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden())
- {
+ if (ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden() && ui->licenseLabel->isHidden() &&
+ ui->issueTrackerLabel->isHidden()) {
setHidden(true);
- }
- else
- {
+ } else {
setHidden(false);
}
}
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);
}
@@ -207,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();
}
@@ -224,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;
@@ -234,23 +274,70 @@ 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);
}
ui->descriptionLabel->setText(labeltext);
}
+void InfoFrame::setLicense(QString text)
+{
+ if (text.isEmpty()) {
+ ui->licenseLabel->setHidden(true);
+ updateHiddenState();
+ return;
+ } else {
+ ui->licenseLabel->setHidden(false);
+ updateHiddenState();
+ }
+ ui->licenseLabel->setToolTip("");
+ QString intermediatetext = text.trimmed();
+ bool prev(false);
+ QChar rem('\n');
+ QString finaltext;
+ finaltext.reserve(intermediatetext.size());
+ foreach (const QChar& c, intermediatetext) {
+ if (c == rem && prev) {
+ continue;
+ }
+ prev = c == rem;
+ finaltext += c;
+ }
+ QString labeltext;
+ labeltext.reserve(300);
+ 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 {
+ ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText);
+ labeltext.append(finaltext);
+ }
+ ui->licenseLabel->setText(labeltext);
+}
+
+void InfoFrame::setIssueTracker(QString text)
+{
+ if (text.isEmpty()) {
+ ui->issueTrackerLabel->setHidden(true);
+ } else {
+ ui->issueTrackerLabel->setText(text);
+ ui->issueTrackerLabel->setHidden(false);
+ }
+ updateHiddenState();
+}
+
void InfoFrame::setImage(QPixmap img)
{
if (img.isNull()) {
@@ -263,18 +350,26 @@ 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) {
+ m_current_box = CustomMessageBox::selectable(this, "", m_license);
+ connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed);
+ m_current_box->show();
+ } else {
+ m_current_box->setText(m_license);
+ }
+}
+
void InfoFrame::boxClosed(int result)
{
m_current_box = nullptr;
diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h
index 84523e28..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;
}
@@ -36,6 +55,8 @@ class InfoFrame : public QFrame {
void setName(QString text = {});
void setDescription(QString text = {});
void setImage(QPixmap img = {});
+ void setLicense(QString text = {});
+ void setIssueTracker(QString text = {});
void clear();
@@ -48,6 +69,7 @@ class InfoFrame : public QFrame {
public slots:
void descriptionEllipsisHandler(QString link);
+ void licenseEllipsisHandler(QString link);
void boxClosed(int result);
private:
@@ -56,5 +78,6 @@ class InfoFrame : public QFrame {
private:
Ui::InfoFrame* ui;
QString m_description;
+ QString m_license;
class QMessageBox* m_current_box = nullptr;
};
diff --git a/launcher/ui/widgets/InfoFrame.ui b/launcher/ui/widgets/InfoFrame.ui
index 9e407ce9..c4d8c83d 100644
--- a/launcher/ui/widgets/InfoFrame.ui
+++ b/launcher/ui/widgets/InfoFrame.ui
@@ -35,8 +35,36 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item row="0" column="1">
- <widget class="QLabel" name="nameLabel">
+ <item row="0" column="0" rowspan="2">
+ <widget class="QLabel" name="iconLabel">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="descriptionLabel">
+ <property name="toolTip">
+ <string notr="true"/>
+ </property>
<property name="text">
<string notr="true"/>
</property>
@@ -57,11 +85,8 @@
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QLabel" name="descriptionLabel">
- <property name="toolTip">
- <string notr="true"/>
- </property>
+ <item row="0" column="1">
+ <widget class="QLabel" name="nameLabel">
<property name="text">
<string notr="true"/>
</property>
@@ -82,28 +107,47 @@
</property>
</widget>
</item>
- <item row="0" column="0" rowspan="2">
- <widget class="QLabel" name="iconLabel">
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
+ <item row="2" column="1">
+ <widget class="QLabel" name="licenseLabel">
+ <property name="text">
+ <string/>
</property>
- <property name="maximumSize">
- <size>
- <width>64</width>
- <height>64</height>
- </size>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
</property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="issueTrackerLabel">
<property name="text">
- <string notr="true"/>
+ <string/>
</property>
- <property name="scaledContents">
- <bool>false</bool>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
</property>
- <property name="margin">
- <number>0</number>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp
index 15994319..c94fdd8d 100644
--- a/launcher/ui/widgets/JavaSettingsWidget.cpp
+++ b/launcher/ui/widgets/JavaSettingsWidget.cpp
@@ -46,7 +46,7 @@ void JavaSettingsWidget::setupUi()
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
- m_versionWidget = new VersionSelectWidget(this);
+ m_versionWidget = new VersionSelectWidget(true, this);
m_verticalLayout->addWidget(m_versionWidget);
m_horizontalLayout = new QHBoxLayout();
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 &current, 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 09b03a76..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,17 +62,13 @@ 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)
+{
+ auto head = header();
+ for(int i = 0; i < modes.count(); i++) {
+ head->setSectionResizeMode(i, modes[i]);
}
}
+
diff --git a/launcher/ui/widgets/ModListView.h b/launcher/ui/widgets/ModListView.h
index 881e092f..3f0b3b0e 100644
--- a/launcher/ui/widgets/ModListView.h
+++ b/launcher/ui/widgets/ModListView.h
@@ -14,6 +14,7 @@
*/
#pragma once
+#include <QHeaderView>
#include <QTreeView>
class ModListView: public QTreeView
@@ -22,4 +23,5 @@ class ModListView: public QTreeView
public:
explicit ModListView ( QWidget* parent = 0 );
virtual void setModel ( QAbstractItemModel* model );
+ virtual void setResizeModes (const QList<QHeaderView::ResizeMode>& modes);
};
diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp
index 0a06a351..b98c9796 100644
--- a/launcher/ui/widgets/PageContainer.cpp
+++ b/launcher/ui/widgets/PageContainer.cpp
@@ -87,10 +87,16 @@ PageContainer::PageContainer(BasePageProvider *pageProvider, QString defaultId,
auto pages = pageProvider->getPages();
for (auto page : pages)
{
- page->stackIndex = m_pageStack->addWidget(dynamic_cast<QWidget *>(page));
+ auto widget = dynamic_cast<QWidget *>(page);
+ widget->setParent(this);
+ page->stackIndex = m_pageStack->addWidget(widget);
page->listIndex = counter;
page->setParentContainer(this);
counter++;
+ page->updateExtraInfo = [this](QString id, QString info) {
+ if (m_currentPage && id == m_currentPage->id())
+ m_header->setText(m_currentPage->displayName() + info);
+ };
}
m_model->setPages(pages);
@@ -135,6 +141,11 @@ BasePage* PageContainer::getPage(QString pageId)
return m_model->findPageEntryById(pageId);
}
+const QList<BasePage*> PageContainer::getPages() const
+{
+ return m_model->pages();
+}
+
void PageContainer::refreshContainer()
{
m_proxyModel->invalidate();
diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h
index 97e294dc..ad74d43a 100644
--- a/launcher/ui/widgets/PageContainer.h
+++ b/launcher/ui/widgets/PageContainer.h
@@ -80,6 +80,7 @@ public:
virtual bool selectPage(QString pageId) override;
BasePage* getPage(QString pageId) override;
+ const QList<BasePage*> getPages() const;
void refreshContainer() override;
virtual void setParentContainer(BasePageContainer * container)
diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp
index f736af08..9181de7f 100644
--- a/launcher/ui/widgets/ProgressWidget.cpp
+++ b/launcher/ui/widgets/ProgressWidget.cpp
@@ -51,6 +51,7 @@ void ProgressWidget::watch(const Task* task)
connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish);
connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus);
+ // TODO: should we connect &Task::details
connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress);
connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed);
diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp
index d1ff9dbc..0085d6b2 100644
--- a/launcher/ui/widgets/ProjectItem.cpp
+++ b/launcher/ui/widgets/ProjectItem.cpp
@@ -64,6 +64,17 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
font.setBold(true);
font.setUnderline(true);
}
+ if (index.data(UserDataTypes::INSTALLED).toBool()) {
+ auto hRect = opt.rect;
+ hRect.setX(hRect.x() + 1);
+ hRect.setY(hRect.y() + 1);
+ hRect.setHeight(hRect.height() - 2);
+ hRect.setWidth(hRect.width() - 2);
+ // Set nice font
+ font.setItalic(true);
+ font.setOverline(true);
+ painter->drawRect(hRect);
+ }
font.setPointSize(font.pointSize() + 2);
painter->setFont(font);
diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h
index f668edf6..196055ea 100644
--- a/launcher/ui/widgets/ProjectItem.h
+++ b/launcher/ui/widgets/ProjectItem.h
@@ -6,7 +6,8 @@
enum UserDataTypes {
TITLE = 257, // QString
DESCRIPTION = 258, // QString
- SELECTED = 259 // bool
+ SELECTED = 259, // bool
+ INSTALLED = 260 // bool
};
/** This is an item delegate composed of:
diff --git a/launcher/ui/widgets/SubTaskProgressBar.cpp b/launcher/ui/widgets/SubTaskProgressBar.cpp
new file mode 100644
index 00000000..84ea5f20
--- /dev/null
+++ b/launcher/ui/widgets/SubTaskProgressBar.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PrismLaucher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.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 "SubTaskProgressBar.h"
+#include "ui_SubTaskProgressBar.h"
+
+unique_qobject_ptr<SubTaskProgressBar> SubTaskProgressBar::create(QWidget* parent)
+{
+ auto progress_bar = new SubTaskProgressBar(parent);
+ return unique_qobject_ptr<SubTaskProgressBar>(progress_bar);
+}
+
+SubTaskProgressBar::SubTaskProgressBar(QWidget* parent)
+ : ui(new Ui::SubTaskProgressBar)
+{
+ ui->setupUi(this);
+}
+SubTaskProgressBar::~SubTaskProgressBar()
+{
+ delete ui;
+}
+
+void SubTaskProgressBar::setRange(int min, int max)
+{
+ ui->progressBar->setRange(min, max);
+}
+
+void SubTaskProgressBar::setValue(int value)
+{
+ ui->progressBar->setValue(value);
+}
+
+void SubTaskProgressBar::setStatus(QString status)
+{
+ ui->statusLabel->setText(status);
+}
+
+void SubTaskProgressBar::setDetails(QString details)
+{
+ ui->statusDetailsLabel->setText(details);
+}
+
diff --git a/launcher/ui/widgets/SubTaskProgressBar.h b/launcher/ui/widgets/SubTaskProgressBar.h
new file mode 100644
index 00000000..8f8aeea2
--- /dev/null
+++ b/launcher/ui/widgets/SubTaskProgressBar.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PrismLaucher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.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 <QWidget>
+#include "QObjectPtr.h"
+
+namespace Ui {
+class SubTaskProgressBar;
+}
+
+class SubTaskProgressBar : public QWidget
+{
+ Q_OBJECT
+
+public:
+ static unique_qobject_ptr<SubTaskProgressBar> create(QWidget* parent = nullptr);
+
+ SubTaskProgressBar(QWidget* parent = nullptr);
+ ~SubTaskProgressBar();
+
+ void setRange(int min, int max);
+ void setValue(int value);
+ void setStatus(QString status);
+ void setDetails(QString details);
+
+
+
+private:
+ Ui::SubTaskProgressBar* ui;
+
+};
diff --git a/launcher/ui/widgets/SubTaskProgressBar.ui b/launcher/ui/widgets/SubTaskProgressBar.ui
new file mode 100644
index 00000000..5431eab6
--- /dev/null
+++ b/launcher/ui/widgets/SubTaskProgressBar.ui
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SubTaskProgressBar</class>
+ <widget class="QWidget" name="SubTaskProgressBar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>312</width>
+ <height>86</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
+ <property name="spacing">
+ <number>8</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="statusLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>8</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Sub Task Status...</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="statusDetailsLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>8</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Status Details</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="font">
+ <font>
+ <pointsize>8</pointsize>
+ </font>
+ </property>
+ <property name="value">
+ <number>24</number>
+ </property>
+ <property name="textVisible">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/launcher/ui/widgets/VersionListView.cpp b/launcher/ui/widgets/VersionListView.cpp
index 0e126c65..06e58d22 100644
--- a/launcher/ui/widgets/VersionListView.cpp
+++ b/launcher/ui/widgets/VersionListView.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* 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
@@ -125,14 +126,9 @@ void VersionListView::paintEvent(QPaintEvent *event)
QString VersionListView::currentEmptyString() const
{
- if(m_itemCount) {
- return QString();
- }
switch(m_emptyMode)
{
default:
- case VersionListView::Empty:
- return QString();
case VersionListView::String:
return m_emptyString;
case VersionListView::ErrorString:
diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp
index 404860d9..a956ddb3 100644
--- a/launcher/ui/widgets/VersionSelectWidget.cpp
+++ b/launcher/ui/widgets/VersionSelectWidget.cpp
@@ -1,15 +1,20 @@
#include "VersionSelectWidget.h"
+#include <QApplication>
+#include <QEvent>
+#include <QHeaderView>
+#include <QKeyEvent>
#include <QProgressBar>
#include <QVBoxLayout>
-#include <QHeaderView>
#include "VersionProxyModel.h"
#include "ui/dialogs/CustomMessageBox.h"
-VersionSelectWidget::VersionSelectWidget(QWidget* parent)
- : QWidget(parent)
+VersionSelectWidget::VersionSelectWidget(QWidget* parent) : VersionSelectWidget(false, parent) {}
+
+VersionSelectWidget::VersionSelectWidget(bool focusSearch, QWidget* parent)
+ : QWidget(parent), focusSearch(focusSearch)
{
setObjectName(QStringLiteral("VersionSelectWidget"));
verticalLayout = new QVBoxLayout(this);
@@ -30,6 +35,21 @@ VersionSelectWidget::VersionSelectWidget(QWidget* parent)
listView->setModel(m_proxyModel);
verticalLayout->addWidget(listView);
+ search = new QLineEdit(this);
+ search->setPlaceholderText(tr("Search"));
+ search->setClearButtonEnabled(true);
+ verticalLayout->addWidget(search);
+ connect(search, &QLineEdit::textEdited, [this](const QString& value) {
+ m_proxyModel->setSearch(value);
+ if (!value.isEmpty() || !listView->selectionModel()->hasSelection()) {
+ const QModelIndex first = listView->model()->index(0, 0);
+ listView->selectionModel()->setCurrentIndex(first, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
+ listView->scrollToTop();
+ } else
+ listView->scrollTo(listView->selectionModel()->currentIndex(), QAbstractItemView::PositionAtCenter);
+ });
+ search->installEventFilter(this);
+
sneakyProgressBar = new QProgressBar(this);
sneakyProgressBar->setObjectName(QStringLiteral("sneakyProgressBar"));
sneakyProgressBar->setFormat(QStringLiteral("%p%"));
@@ -72,6 +92,23 @@ void VersionSelectWidget::setResizeOn(int column)
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
}
+bool VersionSelectWidget::eventFilter(QObject *watched, QEvent *event) {
+ if (watched == search && event->type() == QEvent::KeyPress) {
+ const QKeyEvent* keyEvent = (QKeyEvent*)event;
+ const bool up = keyEvent->key() == Qt::Key_Up;
+ const bool down = keyEvent->key() == Qt::Key_Down;
+ if (up || down) {
+ const QModelIndex index = listView->model()->index(listView->currentIndex().row() + (up ? -1 : 1), 0);
+ if (index.row() >= 0 && index.row() < listView->model()->rowCount()) {
+ listView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
+ return true;
+ }
+ }
+ }
+
+ return QObject::eventFilter(watched, event);
+}
+
void VersionSelectWidget::initialize(BaseVersionList *vlist)
{
m_vlist = vlist;
@@ -79,6 +116,9 @@ void VersionSelectWidget::initialize(BaseVersionList *vlist)
listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
listView->header()->setSectionResizeMode(resizeOnColumn, QHeaderView::Stretch);
+ if (focusSearch)
+ search->setFocus();
+
if (!m_vlist->isLoaded())
{
loadList();
diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h
index e75efc6f..be4ba768 100644
--- a/launcher/ui/widgets/VersionSelectWidget.h
+++ b/launcher/ui/widgets/VersionSelectWidget.h
@@ -1,22 +1,43 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
- * 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
#include <QWidget>
#include <QSortFilterProxyModel>
+#include <QLineEdit>
#include "BaseVersionList.h"
#include "VersionListView.h"
@@ -30,7 +51,8 @@ class VersionSelectWidget: public QWidget
{
Q_OBJECT
public:
- explicit VersionSelectWidget(QWidget *parent = 0);
+ explicit VersionSelectWidget(QWidget *parent);
+ explicit VersionSelectWidget(bool focusSearch = false, QWidget *parent = 0);
~VersionSelectWidget();
//! loads the list if needed.
@@ -52,6 +74,7 @@ public:
void setEmptyErrorString(QString emptyErrorString);
void setEmptyMode(VersionListView::EmptyMode mode);
void setResizeOn(int column);
+ bool eventFilter(QObject* watched, QEvent* event) override;
signals:
void selectedVersionChanged(BaseVersion::Ptr version);
@@ -75,9 +98,10 @@ private:
int resizeOnColumn = 0;
Task * loadTask;
bool preselectedAlready = false;
+ bool focusSearch;
-private:
QVBoxLayout *verticalLayout = nullptr;
VersionListView *listView = nullptr;
+ QLineEdit *search;
QProgressBar *sneakyProgressBar = nullptr;
};
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;
}