aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/ui')
-rw-r--r--launcher/ui/InstanceWindow.cpp117
-rw-r--r--launcher/ui/InstanceWindow.h13
-rw-r--r--launcher/ui/MainWindow.cpp151
-rw-r--r--launcher/ui/MainWindow.h13
-rw-r--r--launcher/ui/MainWindow.ui16
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp3
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.cpp81
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.ui86
-rw-r--r--launcher/ui/dialogs/InstallLoaderDialog.cpp4
-rw-r--r--launcher/ui/dialogs/LoginDialog.cpp115
-rw-r--r--launcher/ui/dialogs/LoginDialog.h56
-rw-r--r--launcher/ui/dialogs/LoginDialog.ui77
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.cpp85
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.h2
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp33
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp4
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h1
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.ui6
-rw-r--r--launcher/ui/pages/global/AccountListPage.cpp21
-rw-r--r--launcher/ui/pages/global/AccountListPage.h1
-rw-r--r--launcher/ui/pages/global/AccountListPage.ui6
-rw-r--r--launcher/ui/pages/global/JavaPage.cpp4
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp6
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui37
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp8
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui26
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp1
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui11
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp17
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui25
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp42
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.cpp3
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp24
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.cpp3
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp9
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp9
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h2
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp59
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h1
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp56
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.ui89
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp51
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.h6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp23
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.h8
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.ui4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp25
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.h17
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp49
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.h30
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp29
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h3
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui52
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.cpp105
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.h22
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp90
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.h2
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.cpp13
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.h5
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.ui49
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp58
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h6
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp27
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h7
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp10
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp16
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h2
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp72
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.h3
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.cpp23
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.h8
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.ui4
-rw-r--r--launcher/ui/themes/CatPack.cpp10
-rw-r--r--launcher/ui/themes/CatPack.h9
-rw-r--r--launcher/ui/widgets/InfoFrame.cpp4
-rw-r--r--launcher/ui/widgets/JavaSettingsWidget.cpp27
-rw-r--r--launcher/ui/widgets/ProjectItem.cpp2
87 files changed, 1320 insertions, 920 deletions
diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp
index 7025cb79..bf83a56c 100644
--- a/launcher/ui/InstanceWindow.cpp
+++ b/launcher/ui/InstanceWindow.cpp
@@ -44,8 +44,6 @@
#include <QPushButton>
#include <QScrollBar>
-#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ProgressDialog.h"
#include "ui/widgets/PageContainer.h"
#include "InstancePageProvider.h"
@@ -76,40 +74,44 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWin
// Add custom buttons to the page container layout.
{
- auto horizontalLayout = new QHBoxLayout();
+ auto horizontalLayout = new QHBoxLayout(this);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setContentsMargins(6, -1, 6, -1);
- auto btnHelp = new QPushButton();
+ auto btnHelp = new QPushButton(this);
btnHelp->setText(tr("Help"));
horizontalLayout->addWidget(btnHelp);
- connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help()));
+ connect(btnHelp, &QPushButton::clicked, m_container, &PageContainer::help);
auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addSpacerItem(spacer);
- m_killButton = new QPushButton();
- horizontalLayout->addWidget(m_killButton);
- connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked()));
-
- m_launchOfflineButton = new QPushButton();
- horizontalLayout->addWidget(m_launchOfflineButton);
- m_launchOfflineButton->setText(tr("Launch Offline"));
+ m_launchButton = new QToolButton(this);
+ m_launchButton->setText(tr("&Launch"));
+ m_launchButton->setToolTip(tr("Launch the instance"));
+ m_launchButton->setPopupMode(QToolButton::MenuButtonPopup);
+ m_launchButton->setMinimumWidth(80); // HACK!!
+ horizontalLayout->addWidget(m_launchButton);
+ connect(m_launchButton, &QPushButton::clicked, this, [this] { APPLICATION->launch(m_instance); });
- m_launchDemoButton = new QPushButton();
- horizontalLayout->addWidget(m_launchDemoButton);
- m_launchDemoButton->setText(tr("Launch Demo"));
+ m_killButton = new QPushButton(this);
+ m_killButton->setText(tr("&Kill"));
+ m_killButton->setToolTip(tr("Kill the running instance"));
+ m_killButton->setShortcut(QKeySequence(tr("Ctrl+K")));
+ horizontalLayout->addWidget(m_killButton);
+ connect(m_killButton, &QPushButton::clicked, this, [this] { APPLICATION->kill(m_instance); });
- updateLaunchButtons();
- connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked()));
- connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked()));
+ updateButtons();
- m_closeButton = new QPushButton();
+ m_closeButton = new QPushButton(this);
m_closeButton->setText(tr("Close"));
horizontalLayout->addWidget(m_closeButton);
- connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked()));
+ connect(m_closeButton, &QPushButton::clicked, this, &QMainWindow::close);
m_container->addButtons(horizontalLayout);
+
+ connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons);
+ connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceWindow::updateButtons);
}
// restore window state
@@ -149,47 +151,18 @@ void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance
}
}
-void InstanceWindow::updateLaunchButtons()
-{
- if (m_instance->isRunning()) {
- m_launchOfflineButton->setEnabled(false);
- m_launchDemoButton->setEnabled(false);
- m_killButton->setText(tr("Kill"));
- m_killButton->setObjectName("killButton");
- m_killButton->setToolTip(tr("Kill the running instance"));
- } else if (!m_instance->canLaunch()) {
- m_launchOfflineButton->setEnabled(false);
- m_launchDemoButton->setEnabled(false);
- m_killButton->setText(tr("Launch"));
- m_killButton->setObjectName("launchButton");
- m_killButton->setToolTip(tr("Launch the instance"));
- m_killButton->setEnabled(false);
- } else {
- m_launchOfflineButton->setEnabled(true);
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get());
- if (instance) {
- m_launchDemoButton->setEnabled(instance->supportsDemo());
- }
-
- m_killButton->setText(tr("Launch"));
- m_killButton->setObjectName("launchButton");
- m_killButton->setToolTip(tr("Launch the instance"));
- }
- // NOTE: this is a hack to force the button to recalculate its style
- m_killButton->setStyleSheet("/* */");
- m_killButton->setStyleSheet(QString());
-}
-
-void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
+void InstanceWindow::updateButtons()
{
- APPLICATION->launch(m_instance, false, false, nullptr);
-}
+ m_launchButton->setEnabled(m_instance->canLaunch());
+ m_killButton->setEnabled(m_instance->isRunning());
-void InstanceWindow::on_btnLaunchMinecraftDemo_clicked()
-{
- APPLICATION->launch(m_instance, false, true, nullptr);
+ QMenu* launchMenu = m_launchButton->menu();
+ if (launchMenu)
+ launchMenu->clear();
+ else
+ launchMenu = new QMenu(this);
+ m_instance->populateLaunchMenu(launchMenu);
+ m_launchButton->setMenu(launchMenu);
}
void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)
@@ -199,18 +172,13 @@ void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> pr
void InstanceWindow::runningStateChanged(bool running)
{
- updateLaunchButtons();
+ updateButtons();
m_container->refreshContainer();
if (running) {
selectPage("log");
}
}
-void InstanceWindow::on_closeButton_clicked()
-{
- close();
-}
-
void InstanceWindow::closeEvent(QCloseEvent* event)
{
bool proceed = true;
@@ -233,15 +201,6 @@ bool InstanceWindow::saveAll()
return m_container->saveAll();
}
-void InstanceWindow::on_btnKillMinecraft_clicked()
-{
- if (m_instance->isRunning()) {
- APPLICATION->kill(m_instance);
- } else {
- APPLICATION->launch(m_instance, true, false, nullptr);
- }
-}
-
QString InstanceWindow::instanceId()
{
return m_instance->id();
@@ -252,17 +211,15 @@ bool InstanceWindow::selectPage(QString pageId)
return m_container->selectPage(pageId);
}
-BasePage* InstanceWindow::selectedPage() const
-{
- return m_container->selectedPage();
-}
-
void InstanceWindow::refreshContainer()
{
m_container->refreshContainer();
}
-InstanceWindow::~InstanceWindow() {}
+BasePage* InstanceWindow::selectedPage() const
+{
+ return m_container->selectedPage();
+}
bool InstanceWindow::requestClose()
{
diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h
index 70f206f2..e5bc24d4 100644
--- a/launcher/ui/InstanceWindow.h
+++ b/launcher/ui/InstanceWindow.h
@@ -38,6 +38,7 @@
#include <QMainWindow>
#include <QSystemTrayIcon>
+#include <QToolButton>
#include "LaunchController.h"
#include "launch/LaunchTask.h"
@@ -53,7 +54,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
public:
explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0);
- virtual ~InstanceWindow();
+ virtual ~InstanceWindow() = default;
bool selectPage(QString pageId) override;
BasePage* selectedPage() const override;
@@ -71,11 +72,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void isClosing();
private slots:
- void on_closeButton_clicked();
- void on_btnKillMinecraft_clicked();
- void on_btnLaunchMinecraftOffline_clicked();
- void on_btnLaunchMinecraftDemo_clicked();
-
void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
void runningStateChanged(bool running);
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
@@ -84,7 +80,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void closeEvent(QCloseEvent*) override;
private:
- void updateLaunchButtons();
+ void updateButtons();
private:
shared_qobject_ptr<LaunchTask> m_proc;
@@ -92,7 +88,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
bool m_doNotSave = false;
PageContainer* m_container = nullptr;
QPushButton* m_closeButton = nullptr;
+ QToolButton* m_launchButton = nullptr;
QPushButton* m_killButton = nullptr;
- QPushButton* m_launchOfflineButton = nullptr;
- QPushButton* m_launchDemoButton = nullptr;
};
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 067108f2..1202c331 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -43,7 +43,6 @@
#include "FileSystem.h"
#include "MainWindow.h"
-#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h"
#include <QDir>
@@ -90,17 +89,14 @@
#include <news/NewsChecker.h>
#include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h>
-#include "InstancePageProvider.h"
#include "InstanceWindow.h"
-#include "JavaCommon.h"
-#include "LaunchController.h"
#include "ui/dialogs/AboutDialog.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/ExportToModListDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/ImportResourceDialog.h"
#include "ui/dialogs/NewInstanceDialog.h"
@@ -113,17 +109,22 @@
#include "ui/themes/ThemeManager.h"
#include "ui/widgets/LabeledToolButton.h"
+#include "minecraft/PackProfile.h"
+#include "minecraft/VersionFile.h"
#include "minecraft/WorldList.h"
#include "minecraft/mod/ModFolderModel.h"
+#include "minecraft/mod/ResourcePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h"
+#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
+#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
-#include "InstanceImportTask.h"
#include "Json.h"
@@ -362,7 +363,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
// 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::listChanged, [this] { defaultAccountChanged(); });
// Show initial account
defaultAccountChanged();
@@ -553,71 +554,15 @@ void MainWindow::updateMainToolBar()
ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool());
}
-void MainWindow::updateToolsMenu()
+void MainWindow::updateLaunchButton()
{
- bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning();
-
- ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning);
- ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning);
- ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning);
-
QMenu* launchMenu = ui->actionLaunchInstance->menu();
- if (launchMenu) {
+ if (launchMenu)
launchMenu->clear();
- } else {
+ else
launchMenu = new QMenu(this);
- }
- QAction* normalLaunch = launchMenu->addAction(tr("Launch"));
- normalLaunch->setShortcut(QKeySequence::Open);
- QAction* normalLaunchOffline = launchMenu->addAction(tr("Launch Offline"));
- normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
- QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo"));
- normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
- 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 {
- normalLaunch->setDisabled(true);
- normalLaunchOffline->setDisabled(true);
- normalLaunchDemo->setDisabled(true);
- }
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
- if (instance) {
- normalLaunchDemo->setEnabled(instance->supportsDemo());
- }
-
- 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()));
- QString 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) {
- 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 {
- profilerAction->setDisabled(true);
- profilerOfflineAction->setDisabled(true);
- }
- }
+ if (m_selectedInstance)
+ m_selectedInstance->populateLaunchMenu(launchMenu);
ui->actionLaunchInstance->setMenu(launchMenu);
}
@@ -927,7 +872,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
} 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."),
+ "one account added.\nPlease add a Microsoft account."),
QMessageBox::Warning)
->show();
}
@@ -980,6 +925,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty())
url.setScheme("file");
+ ModPlatform::IndexedVersion version;
QMap<QString, QString> extra_info;
QUrl local_url;
if (!url.isLocalFile()) { // download the remote resource and identify
@@ -989,6 +935,11 @@ void MainWindow::processURLs(QList<QUrl> urls)
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
QUrlQuery query(url);
+ if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) {
+ qDebug() << "Invalid curseforge link:" << url;
+ continue;
+ }
+
auto addonId = query.allQueryItemValues("addonId")[0];
auto fileId = query.allQueryItemValues("fileId")[0];
@@ -1000,20 +951,19 @@ void MainWindow::processURLs(QList<QUrl> urls)
auto api = FlameAPI();
auto job = api.getFile(addonId, fileId, array);
- QString resource_name;
-
connect(job.get(), &Task::failed, this,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
- connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &resource_name] {
+ connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &version] {
qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
auto doc = Json::requireDocument(*array);
auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
// No way to find out if it's a mod or a modpack before here
// And also we need to check if it ends with .zip, instead of any better way
- auto fileName = Json::ensureString(data, "fileName");
+ version = FlameMod::loadIndexedPackVersion(data);
+ auto fileName = version.fileName;
// Have to use ensureString then use QUrl to get proper url encoding
- dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
+ dl_url = QUrl(version.downloadUrl);
if (!dl_url.isValid()) {
CustomMessageBox::selectable(
this, tr("Error"),
@@ -1024,7 +974,6 @@ void MainWindow::processURLs(QList<QUrl> urls)
}
QFileInfo dl_file(dl_url.fileName());
- resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
});
{ // drop stack
@@ -1099,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
break;
case PackedResourceType::Mod:
- minecraftInst->loaderModList()->installMod(localFileName);
+ minecraftInst->loaderModList()->installMod(localFileName, version);
break;
case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName);
@@ -1278,7 +1227,7 @@ void MainWindow::globalSettingsClosed()
proxymodel->invalidate();
proxymodel->sort(0);
updateMainToolBar();
- updateToolsMenu();
+ updateLaunchButton();
updateThemeMenu();
updateStatusCenter();
// This needs to be done to prevent UI elements disappearing in the event the config is changed
@@ -1408,10 +1357,11 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
- return;
+ } else {
+ APPLICATION->instances()->deleteInstance(id);
}
-
- APPLICATION->instances()->deleteInstance(id);
+ APPLICATION->settings()->set("SelectedInstance", QString());
+ selectionBad();
}
void MainWindow::on_actionExportInstanceZip_triggered()
@@ -1513,20 +1463,6 @@ void MainWindow::activateInstance(InstancePtr instance)
APPLICATION->launch(instance);
}
-void MainWindow::on_actionLaunchInstanceOffline_triggered()
-{
- if (m_selectedInstance) {
- APPLICATION->launch(m_selectedInstance, false);
- }
-}
-
-void MainWindow::on_actionLaunchInstanceDemo_triggered()
-{
- if (m_selectedInstance) {
- APPLICATION->launch(m_selectedInstance, false, true);
- }
-}
-
void MainWindow::on_actionKillInstance_triggered()
{
if (m_selectedInstance && m_selectedInstance->isRunning()) {
@@ -1700,6 +1636,7 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
}
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ disconnect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
@@ -1707,14 +1644,6 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
ui->instanceToolBar->setEnabled(true);
setInstanceActionsEnabled(true);
ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
- ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch());
- ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch());
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
- if (instance) {
- ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo());
- }
ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning());
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
@@ -1723,18 +1652,13 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
updateStatusCenter();
updateInstanceToolIcon(m_selectedInstance->iconKey());
- updateToolsMenu();
+ updateLaunchButton();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
} else {
- ui->instanceToolBar->setEnabled(false);
- setInstanceActionsEnabled(false);
- ui->actionLaunchInstance->setEnabled(false);
- ui->actionLaunchInstanceOffline->setEnabled(false);
- ui->actionLaunchInstanceDemo->setEnabled(false);
- ui->actionKillInstance->setEnabled(false);
APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad();
return;
@@ -1759,11 +1683,12 @@ void MainWindow::selectionBad()
{
// start by reseting everything...
m_selectedInstance = nullptr;
+ m_statusLeft->setText(tr("No instance selected"));
statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false);
- updateToolsMenu();
+ updateLaunchButton();
renameButton->setText(tr("Rename Instance"));
updateInstanceToolIcon("grass");
@@ -1810,7 +1735,9 @@ void MainWindow::updateStatusCenter()
int timePlayed = APPLICATION->instances()->getTotalPlayTime();
if (timePlayed > 0) {
- m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
+ m_statusCenter->setText(
+ tr("Total playtime: %1")
+ .arg(Time::prettifyDuration(timePlayed, APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
// "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)
@@ -1826,7 +1753,7 @@ void MainWindow::setInstanceActionsEnabled(bool enabled)
ui->actionCreateInstanceShortcut->setEnabled(enabled);
}
-void MainWindow::refreshCurrentInstance([[maybe_unused]] bool running)
+void MainWindow::refreshCurrentInstance()
{
auto current = view->selectionModel()->currentIndex();
instanceChanged(current, current);
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 9fd72d25..0b614452 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -144,10 +144,6 @@ class MainWindow : public QMainWindow {
void on_actionLaunchInstance_triggered();
- void on_actionLaunchInstanceOffline_triggered();
-
- void on_actionLaunchInstanceDemo_triggered();
-
void on_actionKillInstance_triggered();
void on_actionDeleteInstance_triggered();
@@ -155,10 +151,7 @@ class MainWindow : public QMainWindow {
void deleteGroup();
void undoTrashInstance();
- inline void on_actionExportInstance_triggered()
- {
- on_actionExportInstanceZip_triggered();
- }
+ inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered();
@@ -181,7 +174,7 @@ class MainWindow : public QMainWindow {
void updateMainToolBar();
- void updateToolsMenu();
+ void updateLaunchButton();
void updateThemeMenu();
@@ -215,7 +208,7 @@ class MainWindow : public QMainWindow {
void keyReleaseEvent(QKeyEvent* event) override;
#endif
- void refreshCurrentInstance(bool running);
+ void refreshCurrentInstance();
private:
void retranslateUi();
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index 6ef32099..91b2c270 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -440,22 +440,6 @@
<string>Ctrl+D</string>
</property>
</action>
- <action name="actionLaunchInstanceOffline">
- <property name="text">
- <string>Launch &amp;Offline</string>
- </property>
- <property name="toolTip">
- <string>Launch the selected instance in offline mode.</string>
- </property>
- </action>
- <action name="actionLaunchInstanceDemo">
- <property name="text">
- <string>Launch &amp;Demo</string>
- </property>
- <property name="toolTip">
- <string>Launch the selected instance in demo mode.</string>
- </property>
- </action>
<action name="actionExportInstance">
<property name="icon">
<iconset theme="export">
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 727c0614..5a1a2f80 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -44,7 +44,8 @@
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods)
{
- m_hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10));
+ m_hashing_task = shared_qobject_ptr<ConcurrentTask>(
+ new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished);
ui->setupUi(this);
diff --git a/launcher/ui/dialogs/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp
index ad8db5ff..5af24b1b 100644
--- a/launcher/ui/dialogs/ExportPackDialog.cpp
+++ b/launcher/ui/dialogs/ExportPackDialog.cpp
@@ -37,15 +37,21 @@
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
: QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider)
{
+ Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
+
ui->setupUi(this);
- ui->name->setText(instance->name());
+ ui->name->setPlaceholderText(instance->name());
+ ui->name->setText(instance->settings()->get("ExportName").toString());
+ ui->version->setText(instance->settings()->get("ExportVersion").toString());
+ ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
+
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
- ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
- setWindowTitle("Export Modrinth Pack");
+ setWindowTitle(tr("Export Modrinth Pack"));
+ ui->summary->setText(instance->settings()->get("ExportSummary").toString());
} else {
- setWindowTitle("Export CurseForge Pack");
- ui->version->setText("");
- ui->summaryLabel->setText("Author");
+ setWindowTitle(tr("Export CurseForge Pack"));
+ ui->summaryLabel->setText(tr("&Author"));
+ ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
}
// ensure a valid pack is generated
@@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
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()));
+ proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath()));
}
- ui->treeView->setModel(proxy);
- ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
- ui->treeView->sortByColumn(0, Qt::AscendingOrder);
+ ui->files->setModel(proxy);
+ ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
+ ui->files->sortByColumn(0, Qt::AscendingOrder);
model->setFilter(filter);
model->setRootPath(instance->gameRoot());
- QHeaderView* headerView = ui->treeView->header();
+ QHeaderView* headerView = ui->files->header();
headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
}
@@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog()
void ExportPackDialog::done(int result)
{
+ auto settings = instance->settings();
+ settings->set("ExportName", ui->name->text());
+ settings->set("ExportVersion", ui->version->text());
+ settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
+ settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
+
if (result == Accepted) {
- const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
+ const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
+ const QString filename = FS::RemoveInvalidFilenameChars(name);
+
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;
+ if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
+ output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"),
+ "Modrinth pack (*.mrpack *.zip)", nullptr);
+ if (output.isEmpty())
+ return;
+ if (!(output.endsWith(".zip") || output.endsWith(".mrpack")))
+ output.append(".mrpack");
+ } else {
+ output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"),
+ "CurseForge pack (*.zip)", nullptr);
+ if (output.isEmpty())
+ return;
+ if (!output.endsWith(".zip"))
+ output.append(".zip");
+ }
+
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,
+ if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
+ task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
+ output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
+ } else {
+ task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), 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(); });
@@ -140,7 +160,6 @@ void ExportPackDialog::done(int 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);
+ ui->buttonBox->button(QDialogButtonBox::Ok)
+ ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty());
}
diff --git a/launcher/ui/dialogs/ExportPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui
index 3976e28f..09dea72a 100644
--- a/launcher/ui/dialogs/ExportPackDialog.ui
+++ b/launcher/ui/dialogs/ExportPackDialog.ui
@@ -7,12 +7,9 @@
<x>0</x>
<y>0</y>
<width>650</width>
- <height>413</height>
+ <height>510</height>
</rect>
</property>
- <property name="windowTitle">
- <string>Export Pack</string>
- </property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
@@ -20,13 +17,16 @@
<item>
<widget class="QGroupBox" name="information">
<property name="title">
- <string>Information</string>
+ <string>&amp;Description</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="summaryLabel">
<property name="text">
- <string>Summary</string>
+ <string>&amp;Summary</string>
+ </property>
+ <property name="buddy">
+ <cstring>summary</cstring>
</property>
</widget>
</item>
@@ -36,14 +36,20 @@
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
- <string>Name</string>
+ <string>&amp;Name</string>
+ </property>
+ <property name="buddy">
+ <cstring>name</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="versionLabel">
<property name="text">
- <string>Version</string>
+ <string>&amp;Version</string>
+ </property>
+ <property name="buddy">
+ <cstring>version</cstring>
</property>
</widget>
</item>
@@ -57,31 +63,52 @@
</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>
+ <widget class="QGroupBox" name="options">
+ <property name="title">
+ <string>&amp;Options</string>
</property>
- <attribute name="headerStretchLastSection">
- <bool>false</bool>
- </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="filesLabel">
+ <property name="text">
+ <string>&amp;Files</string>
+ </property>
+ <property name="buddy">
+ <cstring>files</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="files">
+ <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="QCheckBox" name="optionalFiles">
+ <property name="text">
+ <string>&amp;Mark disabled files as optional</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -97,7 +124,8 @@
<tabstop>name</tabstop>
<tabstop>version</tabstop>
<tabstop>summary</tabstop>
- <tabstop>treeView</tabstop>
+ <tabstop>files</tabstop>
+ <tabstop>optionalFiles</tabstop>
</tabstops>
<resources/>
<connections>
diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp
index 840a328f..541119d1 100644
--- a/launcher/ui/dialogs/InstallLoaderDialog.cpp
+++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp
@@ -129,7 +129,9 @@ InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr<PackProfile> profile, c
QList<BasePage*> InstallLoaderDialog::getPages()
{
- return { // Forge
+ return { // NeoForge
+ new InstallLoaderPage("net.neoforged", "neoforged", tr("NeoForge"), {}, profile),
+ // Forge
new InstallLoaderPage("net.minecraftforge", "forge", tr("Forge"), {}, profile),
// Fabric
new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile),
diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp
deleted file mode 100644
index 7296a13e..00000000
--- a/launcher/ui/dialogs/LoginDialog.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/* 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 "LoginDialog.h"
-#include "ui_LoginDialog.h"
-
-#include "minecraft/auth/AccountTask.h"
-
-#include <QtWidgets/QPushButton>
-
-LoginDialog::LoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LoginDialog)
-{
- ui->setupUi(this);
- ui->progressBar->setVisible(false);
- ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
-
- connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
- connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
-}
-
-LoginDialog::~LoginDialog()
-{
- delete ui;
-}
-
-// Stage 1: User interaction
-void LoginDialog::accept()
-{
- setUserInputsEnabled(false);
- ui->progressBar->setVisible(true);
-
- // Setup the login task and start it
- m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
- m_loginTask = m_account->login(ui->passTextBox->text());
- connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
- connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded);
- connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus);
- connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress);
- m_loginTask->start();
-}
-
-void LoginDialog::setUserInputsEnabled(bool enable)
-{
- ui->userTextBox->setEnabled(enable);
- ui->passTextBox->setEnabled(enable);
- ui->buttonBox->setEnabled(enable);
-}
-
-// Enable the OK button only when both textboxes contain something.
-void LoginDialog::on_userTextBox_textEdited(const QString& newText)
-{
- ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty());
-}
-void LoginDialog::on_passTextBox_textEdited(const QString& newText)
-{
- ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty());
-}
-
-void LoginDialog::onTaskFailed(const QString& reason)
-{
- // Set message
- auto lines = reason.split('\n');
- QString processed;
- for (auto line : lines) {
- if (line.size()) {
- processed += "<font color='red'>" + line + "</font><br />";
- } else {
- processed += "<br />";
- }
- }
- ui->label->setText(processed);
-
- // Re-enable user-interaction
- setUserInputsEnabled(true);
- ui->progressBar->setVisible(false);
-}
-
-void LoginDialog::onTaskSucceeded()
-{
- QDialog::accept();
-}
-
-void LoginDialog::onTaskStatus(const QString& status)
-{
- ui->label->setText(status);
-}
-
-void LoginDialog::onTaskProgress(qint64 current, qint64 total)
-{
- ui->progressBar->setMaximum(total);
- ui->progressBar->setValue(current);
-}
-
-// Public interface
-MinecraftAccountPtr LoginDialog::newAccount(QWidget* parent, QString msg)
-{
- LoginDialog dlg(parent);
- dlg.ui->label->setText(msg);
- if (dlg.exec() == QDialog::Accepted) {
- return dlg.m_account;
- }
- return nullptr;
-}
diff --git a/launcher/ui/dialogs/LoginDialog.h b/launcher/ui/dialogs/LoginDialog.h
deleted file mode 100644
index 601b5fa7..00000000
--- a/launcher/ui/dialogs/LoginDialog.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/* 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 <QtCore/QEventLoop>
-#include <QtWidgets/QDialog>
-
-#include "minecraft/auth/MinecraftAccount.h"
-#include "tasks/Task.h"
-
-namespace Ui {
-class LoginDialog;
-}
-
-class LoginDialog : public QDialog {
- Q_OBJECT
-
- public:
- ~LoginDialog();
-
- static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
-
- private:
- explicit LoginDialog(QWidget* parent = 0);
-
- void setUserInputsEnabled(bool enable);
-
- protected slots:
- void accept();
-
- void onTaskFailed(const QString& reason);
- void onTaskSucceeded();
- void onTaskStatus(const QString& status);
- void onTaskProgress(qint64 current, qint64 total);
-
- void on_userTextBox_textEdited(const QString& newText);
- void on_passTextBox_textEdited(const QString& newText);
-
- private:
- Ui::LoginDialog* ui;
- MinecraftAccountPtr m_account;
- Task::Ptr m_loginTask;
-};
diff --git a/launcher/ui/dialogs/LoginDialog.ui b/launcher/ui/dialogs/LoginDialog.ui
deleted file mode 100644
index 8fa4a45d..00000000
--- a/launcher/ui/dialogs/LoginDialog.ui
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>LoginDialog</class>
- <widget class="QDialog" name="LoginDialog">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>421</width>
- <height>198</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="windowTitle">
- <string>Add Account</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string notr="true">Message label placeholder.</string>
- </property>
- <property name="textFormat">
- <enum>Qt::RichText</enum>
- </property>
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="userTextBox">
- <property name="placeholderText">
- <string>Email</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="passTextBox">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
- <property name="placeholderText">
- <string>Password</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QProgressBar" name="progressBar">
- <property name="value">
- <number>24</number>
- </property>
- <property name="textVisible">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp
index 0af1ec59..1a70ea59 100644
--- a/launcher/ui/dialogs/ModUpdateDialog.cpp
+++ b/launcher/ui/dialogs/ModUpdateDialog.cpp
@@ -3,10 +3,11 @@
#include "CustomMessageBox.h"
#include "ProgressDialog.h"
#include "ScrollMessageBox.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameAPI.h"
#include "ui_ReviewMessageBox.h"
-#include "FileSystem.h"
-#include "Json.h"
#include "Markdown.h"
#include "tasks/ConcurrentTask.h"
@@ -30,9 +31,9 @@ static std::list<Version> mcVersions(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
}
-static std::optional<ResourceAPI::ModLoaderTypes> mcLoaders(BaseInstance* inst)
+static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst)
{
- return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() };
+ return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() };
}
ModUpdateDialog::ModUpdateDialog(QWidget* parent,
@@ -43,7 +44,8 @@ ModUpdateDialog::ModUpdateDialog(QWidget* parent,
, m_parent(parent)
, m_mod_model(mods)
, m_candidates(search_for)
- , m_second_try_metadata(new ConcurrentTask())
+ , m_second_try_metadata(
+ new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()))
, m_instance(instance)
{
ReviewMessageBox::setGeometry(0, 0, 800, 600);
@@ -126,6 +128,8 @@ void ModUpdateDialog::checkCandidates()
return;
}
+ QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers;
+
// Add found updates for Modrinth
if (m_modrinth_check_task) {
auto modrinth_updates = m_modrinth_check_task->getUpdatable();
@@ -135,6 +139,7 @@ void ModUpdateDialog::checkCandidates()
appendMod(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
+ selectedVers.append(m_modrinth_check_task->getDependencies());
}
// Add found updated for Flame
@@ -146,6 +151,7 @@ void ModUpdateDialog::checkCandidates()
appendMod(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
+ selectedVers.append(m_flame_check_task->getDependencies());
}
// Report failed update checking
@@ -180,6 +186,49 @@ void ModUpdateDialog::checkCandidates()
}
}
+ { // dependencies
+ auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, m_mod_model.get(), selectedVers);
+
+ connect(depTask.get(), &Task::failed, this,
+ [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
+
+ connect(depTask.get(), &Task::succeeded, this, [&]() {
+ QStringList warnings = depTask->warnings();
+ if (warnings.count()) {
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
+ }
+ });
+
+ ProgressDialog progress_dialog_deps(m_parent);
+ progress_dialog_deps.setSkipButton(true, tr("Abort"));
+ progress_dialog_deps.setWindowTitle(tr("Checking for dependencies..."));
+ auto dret = progress_dialog_deps.execWithTask(depTask.get());
+
+ // If the dialog was skipped / some download error happened
+ if (dret == QDialog::DialogCode::Rejected) {
+ m_aborted = true;
+ QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
+ return;
+ }
+ static FlameAPI api;
+
+ auto getRequiredBy = depTask->getRequiredBy();
+
+ for (auto dep : depTask->getDependecies()) {
+ auto changelog = dep->version.changelog;
+ if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
+ changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
+ auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
+ CheckUpdateTask::UpdatableMod updatable = {
+ dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type,
+ changelog, dep->pack->provider, download_task
+ };
+
+ appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
+ m_tasks.insert(updatable.name, updatable.download);
+ }
+ }
+
// If there's no mod to be updated
if (ui->modTreeWidget->topLevelItemCount() == 0) {
m_no_updates = true;
@@ -238,6 +287,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (skip_rest)
continue;
+ if (candidate->type() == ResourceType::FOLDER) {
+ continue;
+ }
+
if (confirm_rest) {
addToTmp(candidate, provider_rest);
should_try_others.insert(candidate->internal_id(), try_others_rest);
@@ -348,7 +401,7 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
}
}
-void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info)
+void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
{
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
item_top->setCheckState(0, Qt::CheckState::Checked);
@@ -364,6 +417,26 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info)
auto new_version_item = new QTreeWidgetItem(item_top);
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
+ if (info.new_version_type.has_value()) {
+ auto new_version_type_itme = new QTreeWidgetItem(item_top);
+ new_version_type_itme->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString()));
+ }
+
+ if (!requiredBy.isEmpty()) {
+ auto requiredByItem = new QTreeWidgetItem(item_top);
+ if (requiredBy.length() == 1) {
+ requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back()));
+ } else {
+ requiredByItem->setText(0, tr("Required by:"));
+ auto i = 0;
+ for (auto req : requiredBy) {
+ auto reqItem = new QTreeWidgetItem(requiredByItem);
+ reqItem->setText(0, req);
+ reqItem->insertChildren(i++, { reqItem });
+ }
+ }
+ }
+
auto changelog_item = new QTreeWidgetItem(item_top);
changelog_item->setText(0, tr("Changelog of the latest version"));
diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h
index 12dddf5e..b79aa494 100644
--- a/launcher/ui/dialogs/ModUpdateDialog.h
+++ b/launcher/ui/dialogs/ModUpdateDialog.h
@@ -23,7 +23,7 @@ class ModUpdateDialog final : public ReviewMessageBox {
void checkCandidates();
- void appendMod(const CheckUpdateTask::UpdatableMod& info);
+ void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {});
const QList<ResourceDownloadTask::Ptr> getTasks();
auto indexDir() const -> QDir { return m_mod_model->indexDir(); }
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
index 4b82c0c5..dc7cfff0 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
@@ -127,35 +127,12 @@ void ResourceDownloadDialog::connectButtons()
static ModPlatform::ProviderCapabilities ProviderCaps;
-QStringList getRequiredBy(QList<ResourceDownloadDialog::DownloadTaskPtr> tasks, ResourceDownloadDialog::DownloadTaskPtr pack)
-{
- 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());
+ QHash<QString, QStringList> getRequiredBy;
if (auto task = getModDependenciesTask(); task) {
connect(task.get(), &Task::failed, this,
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
@@ -180,6 +157,7 @@ void ResourceDownloadDialog::confirm()
} else {
for (auto dep : task->getDependecies())
addResource(dep->pack, dep->version);
+ getRequiredBy = task->getRequiredBy();
}
}
@@ -189,7 +167,8 @@ void ResourceDownloadDialog::confirm()
});
for (auto& task : selected) {
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
- ProviderCaps.name(task->getProvider()), getRequiredBy(selected, task) });
+ ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()),
+ task->getVersion().version_type.toString() });
}
if (confirm_dialog->exec()) {
@@ -279,7 +258,7 @@ QList<BasePage*> ModDownloadDialog::getPages()
{
QList<BasePage*> pages;
- auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value();
+ auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getSupportedModLoaders().value();
if (ModrinthAPI::validateModLoaders(loaders))
pages.append(ModrinthModPage::create(this, *m_instance));
@@ -370,6 +349,8 @@ QList<BasePage*> ShaderPackDownloadDialog::getPages()
{
QList<BasePage*> pages;
pages.append(ModrinthShaderPackPage::create(this, *m_instance));
+ if (APPLICATION->capabilities() & Application::SupportsFlame)
+ pages.append(FlameShaderPackPage::create(this, *m_instance));
return pages;
}
diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp
index 78c2542f..aa668f8c 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.cpp
+++ b/launcher/ui/dialogs/ReviewMessageBox.cpp
@@ -77,6 +77,10 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
itemTop->insertChildren(childIndx++, { requiredByItem });
}
+ auto versionTypeItem = new QTreeWidgetItem(itemTop);
+ versionTypeItem->setText(0, tr("Version Type: %1").arg(info.version_type));
+ itemTop->insertChildren(childIndx++, { versionTypeItem });
+
ui->modTreeWidget->addTopLevelItem(itemTop);
}
diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h
index a520cc2a..596f39c8 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.h
+++ b/launcher/ui/dialogs/ReviewMessageBox.h
@@ -18,6 +18,7 @@ class ReviewMessageBox : public QDialog {
QString custom_file_path{};
QString provider;
QStringList required_by;
+ QString version_type;
};
void appendResource(ResourceInformation&& info);
diff --git a/launcher/ui/dialogs/SkinUploadDialog.ui b/launcher/ui/dialogs/SkinUploadDialog.ui
index 2c81a7fe..c6df92df 100644
--- a/launcher/ui/dialogs/SkinUploadDialog.ui
+++ b/launcher/ui/dialogs/SkinUploadDialog.ui
@@ -35,12 +35,6 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="maximumSize">
- <size>
- <width>28</width>
- <height>16777215</height>
- </size>
- </property>
<property name="text">
<string>Browse</string>
</property>
diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp
index 5c6fb092..50f3ff2b 100644
--- a/launcher/ui/pages/global/AccountListPage.cpp
+++ b/launcher/ui/pages/global/AccountListPage.cpp
@@ -45,7 +45,6 @@
#include "net/NetJob.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/LoginDialog.h"
#include "ui/dialogs/MSALoginDialog.h"
#include "ui/dialogs/OfflineLoginDialog.h"
#include "ui/dialogs/ProgressDialog.h"
@@ -64,7 +63,7 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
ui->setupUi(this);
ui->listView->setEmptyString(
tr("Welcome!\n"
- "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account."));
+ "If you're new here, you can select the \"Add Microsoft\" button to link your Microsoft account."));
ui->listView->setEmptyMode(VersionListView::String);
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -73,7 +72,6 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
ui->listView->setModel(m_accounts.get());
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::ProfileNameColumn, QHeaderView::Stretch);
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::NameColumn, QHeaderView::Stretch);
- ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::MigrationColumn, QHeaderView::ResizeToContents);
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::TypeColumn, QHeaderView::ResizeToContents);
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::StatusColumn, QHeaderView::ResizeToContents);
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
@@ -85,6 +83,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
connect(selectionModel, &QItemSelectionModel::selectionChanged,
[this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); });
connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu);
+ connect(ui->listView, &VersionListView::activated, this,
+ [this](const QModelIndex& index) { m_accounts->setDefaultAccount(m_accounts->at(index.row())); });
connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged);
connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged);
@@ -136,19 +136,6 @@ void AccountListPage::listChanged()
updateButtonStates();
}
-void AccountListPage::on_actionAddMojang_triggered()
-{
- MinecraftAccountPtr account =
- LoginDialog::newAccount(this, tr("Please enter your Mojang account email and password to add your account."));
-
- if (account) {
- m_accounts->addAccount(account);
- if (m_accounts->count() == 1) {
- m_accounts->setDefaultAccount(account);
- }
- }
-}
-
void AccountListPage::on_actionAddMicrosoft_triggered()
{
MinecraftAccountPtr account =
@@ -166,7 +153,7 @@ void AccountListPage::on_actionAddOffline_triggered()
{
if (!m_accounts->anyAccountIsValid()) {
QMessageBox::warning(this, tr("Error"),
- tr("You must add a Microsoft or Mojang account that owns Minecraft before you can add an offline account."
+ tr("You must add a Microsoft account that owns Minecraft before you can add an offline account."
"<br><br>"
"If you have lost your account you can contact Microsoft for support."));
return;
diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h
index add0f4aa..f3b80191 100644
--- a/launcher/ui/pages/global/AccountListPage.h
+++ b/launcher/ui/pages/global/AccountListPage.h
@@ -70,7 +70,6 @@ class AccountListPage : public QMainWindow, public BasePage {
void retranslate() override;
public slots:
- void on_actionAddMojang_triggered();
void on_actionAddMicrosoft_triggered();
void on_actionAddOffline_triggered();
void on_actionRemove_triggered();
diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui
index 469955b5..d8cf3ac0 100644
--- a/launcher/ui/pages/global/AccountListPage.ui
+++ b/launcher/ui/pages/global/AccountListPage.ui
@@ -53,7 +53,6 @@
<bool>false</bool>
</attribute>
<addaction name="actionAddMicrosoft"/>
- <addaction name="actionAddMojang"/>
<addaction name="actionAddOffline"/>
<addaction name="actionRefresh"/>
<addaction name="actionRemove"/>
@@ -63,11 +62,6 @@
<addaction name="actionUploadSkin"/>
<addaction name="actionDeleteSkin"/>
</widget>
- <action name="actionAddMojang">
- <property name="text">
- <string>Add &amp;Mojang</string>
- </property>
- </action>
<action name="actionRemove">
<property name="text">
<string>Remo&amp;ve</string>
diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp
index 45d8f018..ac50319e 100644
--- a/launcher/ui/pages/global/JavaPage.cpp
+++ b/launcher/ui/pages/global/JavaPage.cpp
@@ -185,6 +185,7 @@ void JavaPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
+ unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@@ -194,6 +195,9 @@ void JavaPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (maxMem < minMem) {
+ iconName = "status-yellow";
+ ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 7f22fdb5..6d8c65ec 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -189,6 +189,9 @@ void LauncherPage::applySettings()
s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked());
+ s->set("NumberOfConcurrentTasks", ui->numberOfConcurrentTasksSpinBox->value());
+ s->set("NumberOfConcurrentDownloads", ui->numberOfConcurrentDownloadsSpinBox->value());
+
// Console settings
s->set("ShowConsole", ui->showConsoleCheck->isChecked());
s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked());
@@ -236,6 +239,9 @@ void LauncherPage::loadSettings()
#endif
ui->preferMenuBarCheckBox->setChecked(s->get("MenuBarInsteadOfToolBar").toBool());
+ ui->numberOfConcurrentTasksSpinBox->setValue(s->get("NumberOfConcurrentTasks").toInt());
+ ui->numberOfConcurrentDownloadsSpinBox->setValue(s->get("NumberOfConcurrentDownloads").toInt());
+
// Console settings
ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool());
ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool());
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index bc259a9b..250a8bc8 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -190,6 +190,43 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="miscellaneousGroupBox">
+ <property name="title">
+ <string>Miscellaneous</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="numberOfConcurrentTasksLabel">
+ <property name="text">
+ <string>Number of concurrent tasks</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="numberOfConcurrentTasksSpinBox">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="numberOfConcurrentDownloadsLabel">
+ <property name="text">
+ <string>Number of concurrent downloads</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="numberOfConcurrentDownloadsSpinBox">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index 1c774721..553cefd8 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -2,7 +2,6 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* 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
@@ -115,13 +114,11 @@ void MinecraftPage::applySettings()
s->set("ShowGameTime", ui->showGameTime->isChecked());
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
s->set("RecordGameTime", ui->recordGameTime->isChecked());
+ s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked());
// Miscellaneous
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
-
- // Mod loader settings
- s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
}
void MinecraftPage::loadSettings()
@@ -169,11 +166,10 @@ void MinecraftPage::loadSettings()
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
+ ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool());
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
-
- ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool());
}
void MinecraftPage::retranslate()
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 98e90e4e..b5cfa659 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -138,6 +138,13 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="showGameTimeWithoutDays">
+ <property name="text">
+ <string>Show time spent playing in hours</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -191,25 +198,6 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
- <widget class="QGroupBox" name="modLoaderSettingsGroupBox">
- <property name="title">
- <string>Mod loader settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_13">
- <item>
- <widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
- <property name="text">
- <string>Disable Quilt Loader Beacon</string>
- </property>
- <property name="toolTip">
- <string>Disable Quilt loader's beacon for counting monthly active users</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title">
<string>Native library workarounds</string>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index 719db879..1a8fafa9 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -152,6 +152,7 @@ void ExternalResourcesPage::retranslate()
void ExternalResourcesPage::itemActivated(const QModelIndex&)
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
}
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index 3c836691..ba703f77 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -168,6 +168,17 @@
<string>Go to mods home page</string>
</property>
</action>
+ <action name="actionRemoveItemMetadata">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove metadata</string>
+ </property>
+ <property name="toolTip">
+ <string>Remove mod's metadata</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index 108f2c27..7aa6bd32 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -3,7 +3,6 @@
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* 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
@@ -254,14 +253,6 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("InstanceAccountId");
}
- bool overrideModLoaderSettings = ui->modLoaderSettingsGroupBox->isChecked();
- m_settings->set("OverrideModLoaderSettings", overrideModLoaderSettings);
- if (overrideModLoaderSettings) {
- m_settings->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
- } else {
- m_settings->reset("DisableQuiltBeacon");
- }
-
// FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext();
}
@@ -365,10 +356,6 @@ void InstanceSettingsPage::loadSettings()
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
updateAccountsMenu();
-
- // Mod loader specific settings
- ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool());
- ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool());
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()
@@ -491,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
+ unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@@ -500,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (maxMem < minMem) {
+ iconName = "status-yellow";
+ ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index fcc2a5d0..81cf7093 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -584,31 +584,6 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
- <widget class="QGroupBox" name="modLoaderSettingsGroupBox">
- <property name="checkable">
- <bool>true</bool>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- <property name="title">
- <string>Mod loader settings</string>
- </property>
- <layout class="QVBoxLayout" name="VerticalLayout_16">
- <item>
- <widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
- <property name="text">
- <string>Disable Quilt Loader Beacon</string>
- </property>
- <property name="toolTip">
- <string>Disable Quilt loader's beacon for counting monthly active users</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="gameTimeGroupBox">
<property name="enabled">
<bool>true</bool>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 0f5e29cb..625d3793 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -92,6 +92,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
+ ui->actionsToolbar->insertActionAfter(ui->actionRemoveItem, ui->actionRemoveItemMetadata);
+ connect(ui->actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
+
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
@@ -104,11 +108,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
if (selected <= 1) {
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
} else {
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mods' metadata"));
}
ui->actionVisitItemPage->setEnabled(selected != 0);
+ ui->actionRemoveItemMetadata->setEnabled(selected != 0);
});
connect(mods.get(), &ModFolderModel::rowsInserted, this,
@@ -166,7 +175,7 @@ void ModFolderPage::installMods()
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
- ConcurrentTask* tasks = new ConcurrentTask(this);
+ auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@@ -197,6 +206,14 @@ void ModFolderPage::installMods()
void ModFolderPage::updateMods()
{
+ if (m_instance->typeName() != "Minecraft")
+ return; // this is a null instance or a legacy instance
+
+ auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
+ if (!profile->getModLoaders().has_value()) {
+ QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
+ return;
+ }
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
@@ -225,7 +242,7 @@ void ModFolderPage::updateMods()
}
if (update_dialog.exec()) {
- ConcurrentTask* tasks = new ConcurrentTask(this);
+ auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@@ -297,3 +314,24 @@ void ModFolderPage::visitModPages()
DesktopServices::openUrl(url);
}
}
+
+void ModFolderPage::deleteModMetadata()
+{
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
+ auto selectionCount = m_model->selectedMods(selection).length();
+ if (selectionCount == 0)
+ return;
+ if (selectionCount > 1) {
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
+ tr("You are about to remove the metadata for %1 mods.\n"
+ "Are you sure?")
+ .arg(selectionCount),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+ }
+
+ m_model->deleteModsMetadata(selection);
+}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index a23dcae1..0c654d0d 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -61,6 +61,7 @@ class ModFolderPage : public ExternalResourcesPage {
private slots:
void removeItems(const QItemSelection& selection) override;
+ void deleteModMetadata();
void installMods();
void updateMods();
diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp
index 26c14ca4..85be6425 100644
--- a/launcher/ui/pages/instance/ResourcePackPage.cpp
+++ b/launcher/ui/pages/instance/ResourcePackPage.cpp
@@ -72,7 +72,8 @@ void ResourcePackPage::downloadRPs()
ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast<ResourcePackFolderModel>(m_model), m_instance);
if (mdownload.exec()) {
- auto tasks = new ConcurrentTask(this);
+ auto tasks =
+ new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index da49f4a7..2142e6c9 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -3,7 +3,7 @@
* Prism Launcher - Minecraft Launcher
* 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 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
@@ -354,14 +354,8 @@ class ServersModel : public QAbstractListModel {
}
}
- virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override
- {
- return parent.isValid() ? 0 : m_servers.size();
- }
- int columnCount(const QModelIndex& parent) const override
- {
- return parent.isValid() ? 0 : COLUMN_COUNT;
- }
+ virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : m_servers.size(); }
+ int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : COLUMN_COUNT; }
Server* at(int index)
{
@@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {
qDebug() << "Changed:" << path;
load();
}
- void fileChanged(const QString& path)
- {
- qDebug() << "Changed:" << path;
- }
+ void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }
private slots:
void save_internal()
@@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {
m_saveTimer.stop();
}
- bool saveIsScheduled() const
- {
- return m_dirty;
- }
+ bool saveIsScheduled() const { return m_dirty; }
void updateFSObserver()
{
@@ -743,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered()
void ServersPage::on_actionJoin_triggered()
{
const auto& address = m_model->at(currentServer)->m_address;
- APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
+ APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
}
#include "ServersPage.moc"
diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp
index dc8b0a05..40366a1b 100644
--- a/launcher/ui/pages/instance/ShaderPackPage.cpp
+++ b/launcher/ui/pages/instance/ShaderPackPage.cpp
@@ -65,7 +65,7 @@ void ShaderPackPage::downloadShaders()
ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast<ShaderPackFolderModel>(m_model), m_instance);
if (mdownload.exec()) {
- auto tasks = new ConcurrentTask(this);
+ auto tasks = new ConcurrentTask(this, "Download Shaders", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp
index fa478a9a..7c8d7e06 100644
--- a/launcher/ui/pages/instance/TexturePackPage.cpp
+++ b/launcher/ui/pages/instance/TexturePackPage.cpp
@@ -74,7 +74,8 @@ void TexturePackPage::downloadTPs()
ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast<TexturePackFolderModel>(m_model), m_instance);
if (mdownload.exec()) {
- auto tasks = new ConcurrentTask(this);
+ auto tasks =
+ new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index ef029d1d..2918261d 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -166,14 +166,17 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel();
+ connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
-
connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
updateVersionControls();
preselect(0);
connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
+ connect(ui->packageView, &QAbstractItemView::activated, this, [this](const QModelIndex& index) {
+ auto component = m_profile->getComponent(index.row());
+ component->setEnabled(!component->isEnabled());
+ });
connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
}
@@ -408,7 +411,7 @@ 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."),
+ "one account added.\nPlease add a Microsoft account."),
QMessageBox::Warning)
->show();
return;
diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp
index 4ac21b01..068fb3a3 100644
--- a/launcher/ui/pages/modplatform/CustomPage.cpp
+++ b/launcher/ui/pages/modplatform/CustomPage.cpp
@@ -127,6 +127,9 @@ void CustomPage::loaderFilterChanged()
ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));
ui->loaderVersionList->setEmptyMode(VersionListView::String);
return;
+ } else if (ui->neoForgeFilter->isChecked()) {
+ ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+ m_selectedLoader = "net.neoforged";
} else if (ui->forgeFilter->isChecked()) {
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
m_selectedLoader = "net.minecraftforge";
diff --git a/launcher/ui/pages/modplatform/CustomPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui
index 0d89b595..23351ccd 100644
--- a/launcher/ui/pages/modplatform/CustomPage.ui
+++ b/launcher/ui/pages/modplatform/CustomPage.ui
@@ -195,6 +195,16 @@
</widget>
</item>
<item>
+ <widget class="QRadioButton" name="neoForgeFilter">
+ <property name="text">
+ <string>NeoForge</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">loaderBtnGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
<widget class="QRadioButton" name="forgeFilter">
<property name="text">
<string>Forge</string>
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index b7537890..c628f74a 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
auto sort = getCurrentSortingMethodByIndex();
- return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
+ return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };
}
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
@@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
if (!m_filter->versions.empty())
versions = m_filter->versions;
- return { pack, versions, profile->getModLoaders() };
+ return { pack, versions, profile->getSupportedModLoaders() };
}
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 60a43128..d6cc1fdc 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -124,16 +124,17 @@ void ModPage::updateVersionList()
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.
- if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
+ if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {
valid = true;
break;
}
}
// Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out
- if ((valid || m_filter->versions.empty()) && !optedOut(version))
- m_ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ if ((valid || m_filter->versions.empty()) && !optedOut(version)) {
+ auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
+ m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i));
+ }
}
if (m_ui->versionSelectionBox->count() == 0) {
m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1));
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index 5510c191..5a43e49a 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
+ std::optional<ModPlatform::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 0a7edb7b..48e66efc 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -31,6 +31,9 @@ QHash<ResourceModel*, bool> ResourceModel::s_running_models;
ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api)
{
s_running_models.insert(this, true);
+#ifndef LAUNCHER_TEST
+ m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
+#endif
}
ResourceModel::~ResourceModel()
@@ -132,6 +135,32 @@ void ResourceModel::search()
if (hasActiveSearchJob())
return;
+ if (m_search_term.startsWith("#")) {
+ auto projectId = m_search_term.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestFailed(reason, -1);
+ };
+ callbacks.on_abort = [this] {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestAborted();
+ };
+
+ callbacks.on_succeed = [this](auto& doc, auto& pack) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestForOneSucceeded(doc);
+ };
+ if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job)
+ runSearchJob(job);
+ return;
+ }
+ }
auto args{ createSearchArguments() };
auto callbacks{ createSearchCallbacks() };
@@ -189,11 +218,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)
// Use default if no callbacks are set
if (!callbacks.on_succeed)
- callbacks.on_succeed = [this, entry](auto& doc, auto pack) {
+ callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) {
if (!s_running_models.constFind(this).value())
return;
+ auto pack = newpack;
infoRequestSucceeded(doc, pack, entry);
};
+ if (!callbacks.on_fail)
+ callbacks.on_fail = [this](QString reason) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
+ };
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
runInfoJob(job);
@@ -372,6 +408,27 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
endInsertRows();
}
+void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
+
+ try {
+ auto obj = Json::requireObject(doc);
+ if (obj.contains("data"))
+ obj = Json::requireObject(obj, "data");
+ loadIndexedPack(*pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
+ }
+
+ m_search_state = SearchState::Finished;
+
+ beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1);
+ m_packs.append(pack);
+ endInsertRows();
+}
+
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
{
switch (network_error_code) {
diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h
index cc813d6e..ecf4f8f7 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.h
+++ b/launcher/ui/pages/modplatform/ResourceModel.h
@@ -149,6 +149,7 @@ class ResourceModel : public QAbstractListModel {
private:
/* Default search request callbacks */
void searchRequestSucceeded(QJsonDocument&);
+ void searchRequestForOneSucceeded(QJsonDocument&);
void searchRequestFailed(QString reason, int network_error_code);
void searchRequestAborted();
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index c087e2be..44a91003 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -44,9 +44,6 @@
#include <QKeyEvent>
#include "Markdown.h"
-#include "ResourceDownloadTask.h"
-
-#include "minecraft/MinecraftInstance.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h"
@@ -269,6 +266,9 @@ void ResourcePage::updateVersionList()
if (optedOut(version))
continue;
+ auto release_type = current_pack->versions[i].version_type.isValid()
+ ? QString(" [%1]").arg(current_pack->versions[i].version_type.toString())
+ : "";
m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i));
}
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
index fbf94e84..586dffc5 100644
--- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "ShaderPackPage.h"
+#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h"
#include "ShaderPackModel.h"
@@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
const std::shared_ptr<ResourceFolderModel> base_model)
{
QString custom_target_folder;
- if (version.loaders.contains(QStringLiteral("canvas")))
+ if (version.loaders & ModPlatform::Cauldron)
custom_target_folder = QStringLiteral("resourcepacks");
m_model->addPack(pack, version, base_model, false, custom_target_folder);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
index 9cd5eed5..dee3784e 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
@@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen
if (searchTerm.isEmpty()) {
return true;
}
-
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ if (searchTerm.startsWith("#"))
+ return QString::number(pack.id) == searchTerm.mid(1);
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index 39f4f346..b6fb7153 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -21,6 +21,7 @@
#include <Json.h>
#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
namespace Atl {
@@ -46,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
ATLauncher::IndexedPack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::ToolTipRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.safeName)) {
- return (m_logoMap.value(pack.safeName));
+ switch (role) {
+ case 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;
}
- auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
+ case 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);
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
+ ((ListModel*)this)->requestLogo(pack.safeName, url);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
void ListModel::request()
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
index 5e3b9ecf..c7e80027 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -35,11 +35,11 @@
*/
#include "AtlPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_AtlPage.h"
#include "BuildConfig.h"
-#include "AtlOptionalModDialog.h"
#include "AtlUserInteractionSupportImpl.h"
#include "modplatform/atlauncher/ATLPackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
@@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
AtlPage::~AtlPage()
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
index 746aa6d1..8b674733 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
@@ -11,21 +11,28 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QTreeView" name="packView">
- <property name="alternatingRowColors">
- <bool>true</bool>
+ <item row="3" 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="iconSize">
- <size>
- <width>96</width>
- <height>48</height>
- </size>
+ <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="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
@@ -36,39 +43,22 @@
</property>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
- </property>
- <property name="wordWrap">
+ <item row="1" column="0">
+ <widget class="QTreeView" name="packView">
+ <property name="alternatingRowColors">
<bool>true</bool>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ <property name="iconSize">
+ <size>
+ <width>96</width>
+ <height>48</height>
+ </size>
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
</layout>
</item>
- <item row="0" column="0">
+ <item row="1" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
@@ -78,6 +68,31 @@
</property>
</widget>
</item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<tabstops>
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index ff21d010..8875a945 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -1,6 +1,8 @@
#include "FlameModel.h"
#include <Json.h>
#include "Application.h"
+#include "modplatform/ResourceAPI.h"
+#include "modplatform/flame/FlameAPI.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@@ -161,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch()
{
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const FlameAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ }
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
auto searchUrl = QString(
"https://api.curseforge.com/v1/mods/search?"
@@ -189,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
}
currentSearchTerm = term;
currentSort = sort;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
void Flame::ListModel::searchRequestFinished()
{
- jobPtr.reset();
+ if (hasActiveSearchJob())
+ return;
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
@@ -246,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
endInsertRows();
}
+void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = Json::ensureObject(doc.object(), "data");
+
+ Flame::IndexedPack pack;
+ try {
+ Flame::loadIndexedPack(pack, packObj);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading pack from CurseForge: " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void Flame::ListModel::searchRequestFailed(QString reason)
{
jobPtr.reset();
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h
index b3bc96b8..fd8496df 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.h
@@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void performPaginatedSearch();
@@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished();
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
private:
void requestLogo(QString file, QString url);
@@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
int currentSort = 0;
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 183e16f9..584d94ad 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -50,7 +50,8 @@
static FlameAPI api;
-FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
+FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
@@ -61,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
// index is used to set the sorting with the curseforge api
ui->sortByBox->addItem(tr("Sort by Featured"));
ui->sortByBox->addItem(tr("Sort by Popularity"));
@@ -90,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -114,6 +131,7 @@ void FlamePage::openedImpl()
void FlamePage::triggerSearch()
{
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(listModel->activeSearchJob().get());
}
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
@@ -158,7 +176,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
}
for (auto version : current.versions) {
- ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));
+ auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
+ ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(version.downloadUrl));
}
QVariant current_updated;
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h
index ff5c7975..d35858fb 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.h
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.h
@@ -39,8 +39,9 @@
#include <Application.h>
#include <modplatform/flame/FlamePackIndex.h>
-#include "tasks/Task.h"
+#include <QTimer>
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class FlamePage;
@@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
Flame::IndexedPack current;
int m_selected_version_index = -1;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui
index 71d19513..f9e1fe67 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item row="2" column="0">
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item row="3" column="0">
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
index 2b020c48..7d18e72a 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -31,7 +31,7 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
- return FlameMod::loadDependencyVersions(m, arr);
+ return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
}
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -121,4 +121,27 @@ auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonAr
return Json::ensureArray(obj.object(), "data");
}
+FlameShaderPackModel::FlameShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new FlameAPI) {}
+
+void FlameShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadIndexedPack(m, obj);
+}
+
+// We already deal with the URLs when initializing the pack, due to the API response's structure
+void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadBody(m, obj);
+}
+
+void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return Json::ensureArray(obj.object(), "data");
+}
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
index 6cfd6a6f..76dbd7b3 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
@@ -68,4 +68,21 @@ class FlameTexturePackModel : public TexturePackResourceModel {
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
+class FlameShaderPackModel : public ShaderPackResourceModel {
+ Q_OBJECT
+
+ public:
+ FlameShaderPackModel(const BaseInstance&);
+ ~FlameShaderPackModel() override = default;
+
+ private:
+ [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
+ [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
+
+ 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 documentToArray(QJsonDocument& obj) const -> QJsonArray override;
+};
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
index dc17e705..23373ec9 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
@@ -68,10 +68,10 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
+ std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{
- Q_UNUSED(loaders);
- return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
+ return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() &&
+ (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
}
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
@@ -173,6 +173,45 @@ void FlameTexturePackPage::openUrl(const QUrl& url)
TexturePackResourcePage::openUrl(url);
}
+FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ : ShaderPackResourcePage(dialog, instance)
+{
+ m_model = new FlameShaderPackModel(instance);
+ m_ui->packView->setModel(m_model);
+
+ addSortings();
+
+ // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
+ // so it's best not to connect them in the parent's constructor...
+ connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameShaderPackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameShaderPackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+bool FlameShaderPackPage::optedOut(ModPlatform::IndexedVersion& ver) const
+{
+ return isOptedOut(ver);
+}
+
+void FlameShaderPackPage::openUrl(const QUrl& url)
+{
+ if (url.scheme().isEmpty()) {
+ QString query = url.query(QUrl::FullyDecoded);
+
+ if (query.startsWith("remoteUrl=")) {
+ // attempt to resolve url from warning page
+ query.remove(0, 10);
+ ShaderPackResourcePage::openUrl({ QUrl::fromPercentEncoding(query.toUtf8()) }); // double decoding is necessary
+ return;
+ }
+ }
+
+ ShaderPackResourcePage::openUrl(url);
+}
+
// I don't know why, but doing this on the parent class makes it so that
// other mod providers start loading before being selected, at least with
// my Qt, so we need to implement this in every derived class...
@@ -188,5 +227,9 @@ auto FlameTexturePackPage::shouldDisplay() const -> bool
{
return true;
}
+auto FlameShaderPackPage::shouldDisplay() const -> bool
+{
+ return true;
+}
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
index c6ebc1ea..f2f5ceca 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
@@ -44,6 +44,7 @@
#include "ui/pages/modplatform/ModPage.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
+#include "ui/pages/modplatform/ShaderPackPage.h"
#include "ui/pages/modplatform/TexturePackPage.h"
namespace ResourceDownload {
@@ -95,7 +96,7 @@ class FlameModPage : public ModPage {
bool validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override;
+ std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
void openUrl(const QUrl& url) override;
@@ -155,4 +156,31 @@ class FlameTexturePackPage : public TexturePackResourcePage {
void openUrl(const QUrl& url) override;
};
+class FlameShaderPackPage : public ShaderPackResourcePage {
+ Q_OBJECT
+
+ public:
+ static FlameShaderPackPage* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ShaderPackResourcePage::create<FlameShaderPackPage>(dialog, instance);
+ }
+
+ FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);
+ ~FlameShaderPackPage() override = default;
+
+ [[nodiscard]] bool shouldDisplay() const override;
+
+ [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); }
+ [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); }
+ [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); }
+ [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); }
+ [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); }
+
+ [[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
+
+ bool optedOut(ModPlatform::IndexedVersion& ver) const override;
+
+ void openUrl(const QUrl& url) override;
+};
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
index 5c9ff63b..d3ead083 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
@@ -17,6 +17,7 @@
*/
#include "ImportFTBPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QWidget>
@@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
ui->setupUi(this);
{
+ currentModel = new FilterModel(this);
listModel = new ListModel(this);
+ currentModel->setSourceModel(listModel);
- ui->modpackList->setModel(listModel);
+ ui->modpackList->setModel(currentModel);
ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0);
ui->modpackList->setIconSize(QSize(42, 42));
+
+ for (int i = 0; i < currentModel->getAvailableSortings().size(); i++) {
+ ui->sortByBox->addItem(currentModel->getAvailableSortings().keys().at(i));
+ }
+
+ ui->sortByBox->setCurrentText(currentModel->translateCurrentSorting());
}
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged);
+
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
+
+ ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}
@@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr
onPackSelectionChanged();
return;
}
- Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
+ Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
@@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
dialog->setSuggestedPack();
}
+void ImportFTBPage::onSortingSelectionChanged(QString sort)
+{
+ FilterModel::Sorting toSet = currentModel->getAvailableSortings().value(sort);
+ currentModel->setSorting(toSet);
+}
+
+void ImportFTBPage::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace FTBImportAPP
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
index 54c49f7b..8e966127 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
@@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage {
void suggestCurrent();
void onPackSelectionChanged(Modpack* pack = nullptr);
private slots:
+ void onSortingSelectionChanged(QString data);
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
+ void triggerSearch();
private:
bool initialized = false;
Modpack selected;
ListModel* listModel = nullptr;
+ FilterModel* currentModel = nullptr;
NewInstanceDialog* dialog = nullptr;
Ui::ImportFTBPage* ui = nullptr;
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
index 32d548b0..5e09fb6d 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
@@ -10,8 +10,8 @@
<height>1011</height>
</rect>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@@ -21,6 +21,54 @@
</property>
</widget>
</item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="sortByBox">
+ <property name="minimumSize">
+ <size>
+ <width>265</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
index dc78f451..134bdc0c 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
@@ -23,7 +23,9 @@
#include <QIcon>
#include <QProcessEnvironment>
#include "FileSystem.h"
+#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
+#include "ui/widgets/ProjectItem.h"
namespace FTBImportAPP {
@@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
auto pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- return pack.icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
- } else if (role == Qt::ToolTipRole) {
- return tr("Minecraft %1").arg(pack.mcVersion);
+ if (role == Qt::ToolTipRole) {
}
- return QVariant();
+ switch (role) {
+ case Qt::ToolTipRole:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case Qt::DecorationRole:
+ return pack.icon;
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+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
+{
+ Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
+ Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
+
+ if (currentSorting == Sorting::ByGameVersion) {
+ Version lv(leftPack.mcVersion);
+ Version rv(rightPack.mcVersion);
+ return lv < rv;
+
+ } else if (currentSorting == Sorting::ByName) {
+ return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ }
+
+ // UHM, some inavlid value set?!
+ qWarning() << "Invalid sorting set!";
+ return true;
+}
+
+bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
+{
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
+}
+
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
+{
+ return sortings;
+}
+
+QString FilterModel::translateCurrentSorting()
+{
+ return sortings.key(currentSorting);
+}
+
+void FilterModel::setSorting(Sorting s)
+{
+ currentSorting = s;
+ invalidate();
+}
+
+FilterModel::Sorting FilterModel::getCurrentSorting()
+{
+ return currentSorting;
}
} // namespace FTBImportAPP \ No newline at end of file
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
index c67aa896..11192827 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
@@ -20,11 +20,33 @@
#include <QAbstractListModel>
#include <QIcon>
+#include <QSortFilterProxyModel>
#include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
+class FilterModel : public QSortFilterProxyModel {
+ Q_OBJECT
+ public:
+ FilterModel(QObject* parent = Q_NULLPTR);
+ enum Sorting { ByName, ByGameVersion };
+ const QMap<QString, Sorting> getAvailableSortings();
+ QString translateCurrentSorting();
+ void setSorting(Sorting sorting);
+ Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
+
+ protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
+ bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
+
+ private:
+ QMap<QString, Sorting> sortings;
+ Sorting currentSorting;
+ QString searchTerm;
+};
+
class ListModel : public QAbstractListModel {
Q_OBJECT
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 356d919d..49666cf6 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -41,6 +41,7 @@
#include <Version.h>
#include "StringUtils.h"
+#include "ui/widgets/ProjectItem.h"
#include <QLabel>
#include <QtMath>
@@ -79,7 +80,20 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
{
- return true;
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ if (searchTerm.startsWith("#"))
+ return pack.packCode == searchTerm.mid(1);
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
}
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
@@ -139,39 +153,57 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- 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
- QString edit = pack.description.left(97);
- edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
- return edit;
+ switch (role) {
+ case 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;
+ }
+ case 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);
+ return icon;
}
- return pack.description;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logo)) {
- return (m_logoMap.value(pack.logo));
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logo);
- return icon;
- } 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
- return QColor(244, 229, 66);
+ case 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
+ return QColor(244, 229, 66);
+ }
}
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
void ListModel::fill(ModpackList modpacks_)
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
index 51a58d99..c802a4b5 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
@@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel {
QString translateCurrentSorting();
void setSorting(Sorting sorting);
Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
@@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel {
private:
QMap<QString, Sorting> sortings;
Sorting currentSorting;
+ QString searchTerm;
};
class ListModel : public QAbstractListModel {
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
index 0103bbaa..4104f139 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
@@ -35,6 +35,7 @@
*/
#include "Page.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_Page.h"
#include <QInputDialog>
@@ -110,6 +111,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &Page::triggerSearch);
+
connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
@@ -125,6 +128,9 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
ui->thirdPartyPackList->selectionModel()->reset();
ui->privatePackList->selectionModel()->reset();
+ ui->publicPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->thirdPartyPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->privatePackList->setItemDelegate(new ProjectItemDelegate(this));
onTabChanged(ui->tabWidget->currentIndex());
}
@@ -319,6 +325,8 @@ void Page::onTabChanged(int tab)
currentModpackInfo = ui->publicPackDescription;
}
+ triggerSearch();
+
currentList->selectionModel()->reset();
QModelIndex idx = currentList->currentIndex();
if (idx.isValid()) {
@@ -358,4 +366,9 @@ void Page::onRemovePackClicked()
onPackSelectionChanged();
}
+void Page::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace LegacyFTB
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
index a12b0745..4d317b7c 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
@@ -43,7 +43,6 @@
#include "QObjectPtr.h"
#include "modplatform/legacy_ftb/PackFetchTask.h"
#include "modplatform/legacy_ftb/PackHelpers.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
class NewInstanceDialog;
@@ -56,8 +55,6 @@ class Page;
class ListModel;
class FilterModel;
-class PrivatePackListModel;
-class PrivatePackFilterModel;
class PrivatePackManager;
class Page : public QWidget, public BasePage {
@@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage {
void onAddPackClicked();
void onRemovePackClicked();
+ void triggerSearch();
+
private:
FilterModel* currentModel = nullptr;
QTreeView* currentList = nullptr;
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
index ad08dc25..56cba748 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
@@ -10,8 +10,29 @@
<height>602</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
@@ -36,9 +57,9 @@
</item>
<item row="0" column="1">
<widget class="QTextBrowser" name="publicPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
@@ -50,10 +71,10 @@
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QTextBrowser" name="thirdPartyPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
<item row="0" column="0">
<widget class="QTreeView" name="thirdPartyPackList">
@@ -104,16 +125,16 @@
</item>
<item row="0" column="1" rowspan="3">
<widget class="QTextBrowser" name="privatePackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
</widget>
</item>
- <item>
+ <item row="5" column="0">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QLabel" name="label">
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index ebc5556c..f691a185 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -38,8 +38,8 @@
#include "BuildConfig.h"
#include "Json.h"
-#include "minecraft/MinecraftInstance.h"
-#include "minecraft/PackProfile.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "net/NetJob.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@@ -130,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,
void ModpackListModel::performPaginatedSearch()
{
- // TODO: Move to standalone API
+ if (hasActiveSearchJob())
+ return;
+
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const ModrinthAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ } // TODO: Move to standalone API
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?"
@@ -167,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()
void ModpackListModel::refresh()
{
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
@@ -307,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
endInsertRows();
}
+void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = doc.object();
+
+ Modrinth::Modpack pack;
+ try {
+ Modrinth::loadIndexedPack(pack, packObj);
+ pack.id = Json::ensureString(packObj, "id", pack.id);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void ModpackListModel::searchRequestFailed(QString reason)
{
- auto failed_action = jobPtr->getFailedActions().at(0);
+ auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) {
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 721c69f5..2a9d6226 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -73,6 +73,9 @@ class ModpackListModel : public QAbstractListModel {
void refresh();
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
inline auto canFetchMore(const QModelIndex& parent) const -> bool override
@@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {
public slots:
void searchRequestFinished(QJsonDocument& doc_all);
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
protected slots:
@@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
QByteArray m_specific_response;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 41fd5003..8e2b9a90 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -52,7 +52,8 @@
#include <QKeyEvent>
#include <QPushButton>
-ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
+ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
@@ -64,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &ModrinthPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
ui->sortByBox->addItem(tr("Sort by Relevance"));
ui->sortByBox->addItem(tr("Sort by Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows"));
@@ -102,6 +114,11 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
this->triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QObject::eventFilter(watched, event);
@@ -200,12 +217,13 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
qDebug() << *response;
qWarning() << "Error while reading modrinth modpack version: " << e.cause();
}
-
for (auto version : current.versions) {
+ auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
if (!version.name.contains(version.version))
- ui->versionSelectionBox->addItem(QString("%1 — %2").arg(version.name, version.version), QVariant(version.id));
+ ui->versionSelectionBox->addItem(QString("%1 — %2%3").arg(version.name, version.version, release_type),
+ QVariant(version.id));
else
- ui->versionSelectionBox->addItem(version.name, QVariant(version.id));
+ ui->versionSelectionBox->addItem(QString("%1%2").arg(version.name, release_type), QVariant(version.id));
}
QVariant current_updated;
@@ -309,6 +327,7 @@ void ModrinthPage::suggestCurrent()
void ModrinthPage::triggerSearch()
{
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(m_model->activeSearchJob().get());
}
void ModrinthPage::onVersionSelectionChanged(QString version)
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
index b7054c88..4240dcaf 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -41,7 +41,9 @@
#include "ui/pages/BasePage.h"
#include "modplatform/modrinth/ModrinthPackManifest.h"
+#include "ui/widgets/ProgressWidget.h"
+#include <QTimer>
#include <QWidget>
namespace Ui {
@@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {
Modrinth::Modpack current;
QString selectedVersion;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
index 6d8b2b67..78a25fea 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -10,8 +10,8 @@
<height>600</height>
</rect>
</property>
- <layout class="QVBoxLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
@@ -29,7 +29,7 @@
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
index ce7475b6..85601829 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -39,12 +39,12 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec
void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
- return ::Modrinth::loadDependencyVersions(m, arr);
+ return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance);
}
auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q
void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ
void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs
void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
index 616c1a81..a4197b22 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
@@ -65,21 +65,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
+ std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{
- auto loaderCompatible = !loaders.has_value();
-
- if (!loaderCompatible) {
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value());
- for (auto remoteLoader : ver.loaders) {
- if (loaderStrings.contains(remoteLoader)) {
- loaderCompatible = true;
- break;
- }
- }
- }
-
- return ver.mcVersion.contains(mineVer) && loaderCompatible;
+ return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
}
ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
index 86ba1ccb..311bcfe3 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
@@ -93,7 +93,7 @@ class ModrinthModPage : public ModPage {
[[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const
-> bool override;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index e8c5ac92..3cd1d9a2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -39,6 +39,7 @@
#include "Json.h"
#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
#include <QIcon>
@@ -54,21 +55,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logoName)) {
- return (m_logoMap.value(pack.logoName));
+ switch (role) {
+ case 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;
+ }
+ case 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);
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+
+ return {};
}
int Technic::ListModel::columnCount(const QModelIndex& parent) const
@@ -87,21 +114,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)
return;
}
currentSearchTerm = term;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
performSearch();
}
void Technic::ListModel::performSearch()
{
+ if (hasActiveSearchJob())
+ return;
+
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
@@ -113,6 +144,9 @@ void Technic::ListModel::performSearch()
} else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
+ } else if (currentSearchTerm.startsWith("#")) {
+ searchUrl = QString("https://api.technicpack.net/modpack/%1?build=%2").arg(currentSearchTerm.mid(1), 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);
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h
index d7a635d4..aeb4f308 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h
@@ -58,6 +58,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void searchRequestFinished();
void searchRequestFailed();
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
index 54b86feb..190b7c68 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
@@ -34,6 +34,7 @@
*/
#include "TechnicPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h"
#include <QKeyEvent>
@@ -51,7 +52,8 @@
#include "net/ApiDownload.h"
-TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
+TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
@@ -59,8 +61,21 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p
model = new Technic::ListModel(this);
ui->packView->setModel(model);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &TechnicPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
@@ -71,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -100,6 +120,7 @@ void TechnicPage::openedImpl()
void TechnicPage::triggerSearch()
{
model->searchWithTerm(ui->searchEdit->text());
+ m_fetch_progress.watch(model->activeSearchJob().get());
}
void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h
index 91b61eaf..01439337 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h
@@ -35,13 +35,14 @@
#pragma once
+#include <QTimer>
#include <QWidget>
#include <Application.h>
#include "TechnicData.h"
#include "net/NetJob.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class TechnicPage;
@@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {
NetJob::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
index 15bf645f..b988eda2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="3" column="0" colspan="2">
+ <item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
@@ -44,7 +44,7 @@
</item>
</layout>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QListView" name="packView">
diff --git a/launcher/ui/themes/CatPack.cpp b/launcher/ui/themes/CatPack.cpp
index f0d8ddd5..bbcb58bc 100644
--- a/launcher/ui/themes/CatPack.cpp
+++ b/launcher/ui/themes/CatPack.cpp
@@ -99,18 +99,22 @@ QDate ensureDay(int year, int month, int day)
QString JsonCatPack::path()
{
- const QDate now = QDate::currentDate();
+ return path(QDate::currentDate());
+}
+
+QString JsonCatPack::path(QDate now)
+{
for (auto var : m_variants) {
QDate startDate = ensureDay(now.year(), var.startTime.month, var.startTime.day);
QDate endDate = ensureDay(now.year(), var.endTime.month, var.endTime.day);
if (startDate > endDate) { // it's spans over multiple years
- if (endDate <= now) // end date is in the past so jump one year into the future for endDate
+ if (endDate < now) // end date is in the past so jump one year into the future for endDate
endDate = endDate.addYears(1);
else // end date is in the future so jump one year into the past for startDate
startDate = startDate.addYears(-1);
}
- if (startDate >= now && now >= endDate)
+ if (startDate <= now && now <= endDate)
return var.path;
}
return m_defaultPath;
diff --git a/launcher/ui/themes/CatPack.h b/launcher/ui/themes/CatPack.h
index fdd117a7..1d310e79 100644
--- a/launcher/ui/themes/CatPack.h
+++ b/launcher/ui/themes/CatPack.h
@@ -52,9 +52,9 @@ class BasicCatPack : public CatPack {
public:
BasicCatPack(QString id, QString name) : m_id(id), m_name(name) {}
BasicCatPack(QString id) : BasicCatPack(id, id) {}
- virtual QString id() { return m_id; }
- virtual QString name() { return m_name; }
- virtual QString path();
+ virtual QString id() override { return m_id; }
+ virtual QString name() override { return m_name; }
+ virtual QString path() override;
protected:
QString m_id;
@@ -83,7 +83,8 @@ class JsonCatPack : public BasicCatPack {
PartialDate endTime;
};
JsonCatPack(QFileInfo& manifestInfo);
- virtual QString path();
+ virtual QString path() override;
+ QString path(QDate now);
private:
QString m_defaultPath;
diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp
index 1f03f9ea..69f72fea 100644
--- a/launcher/ui/widgets/InfoFrame.cpp
+++ b/launcher/ui/widgets/InfoFrame.cpp
@@ -158,12 +158,12 @@ QString InfoFrame::renderColorCodes(QString input)
//
// TODO: Wrap links inside <a> tags
- // https://minecraft.fandom.com/wiki/Formatting_codes#Color_codes
+ // https://minecraft.wiki/w/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" } };
- // https://minecraft.fandom.com/wiki/Formatting_codes#Formatting_codes
+ // https://minecraft.wiki/w/Formatting_codes#Formatting_codes
const QMap<QChar, QString> formatting_codes_map = { { 'l', "b" }, { 'm', "s" }, { 'n', "u" }, { 'o', "i" } };
QString html("<html>");
diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp
index 42279a66..d2d92191 100644
--- a/launcher/ui/widgets/JavaSettingsWidget.cpp
+++ b/launcher/ui/widgets/JavaSettingsWidget.cpp
@@ -186,12 +186,20 @@ QString JavaSettingsWidget::javaPath() const
int JavaSettingsWidget::maxHeapSize() const
{
- return m_maxMemSpinBox->value();
+ auto min = m_minMemSpinBox->value();
+ auto max = m_maxMemSpinBox->value();
+ if (max < min)
+ max = min;
+ return max;
}
int JavaSettingsWidget::minHeapSize() const
{
- return m_minMemSpinBox->value();
+ auto min = m_minMemSpinBox->value();
+ auto max = m_maxMemSpinBox->value();
+ if (min > max)
+ min = max;
+ return min;
}
bool JavaSettingsWidget::permGenEnabled() const
@@ -214,17 +222,9 @@ void JavaSettingsWidget::memoryValueChanged(int)
if (obj == m_minMemSpinBox && min != observedMinMemory) {
observedMinMemory = min;
actuallyChanged = true;
- if (min > max) {
- observedMaxMemory = min;
- m_maxMemSpinBox->setValue(min);
- }
} else if (obj == m_maxMemSpinBox && max != observedMaxMemory) {
observedMaxMemory = max;
actuallyChanged = true;
- if (min > max) {
- observedMinMemory = max;
- m_minMemSpinBox->setValue(max);
- }
} else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) {
observedPermGenMemory = permgen;
actuallyChanged = true;
@@ -361,8 +361,8 @@ void JavaSettingsWidget::checkJavaPath(const QString& path)
setJavaStatus(JavaStatus::Pending);
m_checker.reset(new JavaChecker());
m_checker->m_path = path;
- m_checker->m_minMem = m_minMemSpinBox->value();
- m_checker->m_maxMem = m_maxMemSpinBox->value();
+ m_checker->m_minMem = minHeapSize();
+ m_checker->m_maxMem = maxHeapSize();
if (m_permGenSpinBox->isVisible()) {
m_checker->m_permGen = m_permGenSpinBox->value();
}
@@ -415,6 +415,9 @@ void JavaSettingsWidget::updateThresholds()
} else if (observedMaxMemory > (m_availableMemory * 0.9)) {
iconName = "status-yellow";
m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (observedMaxMemory < observedMinMemory) {
+ iconName = "status-yellow";
+ m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
m_labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp
index 1481c1b6..60b92b28 100644
--- a/launcher/ui/widgets/ProjectItem.cpp
+++ b/launcher/ui/widgets/ProjectItem.cpp
@@ -34,8 +34,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
icon_width = icon_size.width();
icon_height = icon_size.height();
- icon_x_margin = (rect.height() - icon_width) / 2;
icon_y_margin = (rect.height() - icon_height) / 2;
+ icon_x_margin = icon_y_margin; // use same margins for consistency
}
// Centralize icon with a margin to separate from the other elements