aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui/pages
diff options
context:
space:
mode:
authorTrial97 <alexandru.tripon97@gmail.com>2023-05-31 20:12:12 +0300
committerTrial97 <alexandru.tripon97@gmail.com>2023-05-31 20:12:12 +0300
commit29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089 (patch)
treece92f8a86d08531879105a16194a14391c0ae2ea /launcher/ui/pages
parente8ee4497f77a571b305a48b70f84c8729b800859 (diff)
parent954d4d701a136e79c25b58f9680d26a555a6e6fe (diff)
downloadPrismLauncher-29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089.tar.gz
PrismLauncher-29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089.tar.bz2
PrismLauncher-29c3dc40ef7f5b1fce5ab5970a39613d0f7f5089.zip
Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into logdir
Diffstat (limited to 'launcher/ui/pages')
-rw-r--r--launcher/ui/pages/global/APIPage.cpp7
-rw-r--r--launcher/ui/pages/global/APIPage.ui51
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp239
-rw-r--r--launcher/ui/pages/global/LauncherPage.h13
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui257
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp1
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui133
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp49
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.h3
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp76
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.h14
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui42
-rw-r--r--launcher/ui/pages/instance/LogPage.cpp31
-rw-r--r--launcher/ui/pages/instance/ManagedPackPage.cpp47
-rw-r--r--launcher/ui/pages/instance/ManagedPackPage.h4
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp27
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h18
-rw-r--r--launcher/ui/pages/instance/OtherLogsPage.cpp31
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.cpp104
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.h25
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.cpp48
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.h1
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp16
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.cpp98
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.h25
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.cpp106
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.h25
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp69
-rw-r--r--launcher/ui/pages/instance/VersionPage.h9
-rw-r--r--launcher/ui/pages/instance/VersionPage.ui13
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp21
-rw-r--r--launcher/ui/pages/instance/WorldListPage.ui2
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp348
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h94
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp362
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h97
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp445
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h147
-rw-r--r--launcher/ui/pages/modplatform/ResourcePackModel.cpp46
-rw-r--r--launcher/ui/pages/modplatform/ResourcePackModel.h43
-rw-r--r--launcher/ui/pages/modplatform/ResourcePackPage.cpp46
-rw-r--r--launcher/ui/pages/modplatform/ResourcePackPage.h50
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp413
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.h111
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.ui (renamed from launcher/ui/pages/modplatform/ModPage.ui)18
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackModel.cpp46
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackModel.h43
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.cpp54
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.h52
-rw-r--r--launcher/ui/pages/modplatform/TexturePackModel.cpp84
-rw-r--r--launcher/ui/pages/modplatform/TexturePackModel.h27
-rw-r--r--launcher/ui/pages/modplatform/TexturePackPage.h50
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModModel.cpp31
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModModel.h26
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.cpp97
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.h70
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp119
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.h70
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp182
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.h141
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp93
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbFilterModel.h51
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbListModel.cpp304
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbListModel.h83
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbPage.cpp200
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbPage.h105
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbPage.ui86
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp2
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp48
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h44
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp84
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h66
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp7
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h1
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp116
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h101
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp147
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h153
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp6
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.cpp8
83 files changed, 3824 insertions, 2816 deletions
diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp
index e3d30475..f662ee1c 100644
--- a/launcher/ui/pages/global/APIPage.cpp
+++ b/launcher/ui/pages/global/APIPage.cpp
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (c) 2022 Lenny McLennington <lenny@sneed.church>
+ * 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
@@ -147,6 +148,8 @@ void APIPage::loadSettings()
ui->metaURL->setText(metaURL);
QString flameKey = s->get("FlameKeyOverride").toString();
ui->flameKey->setText(flameKey);
+ QString modrinthToken = s->get("ModrinthToken").toString();
+ ui->modrinthToken->setText(modrinthToken);
QString customUserAgent = s->get("UserAgentOverride").toString();
ui->userAgentLineEdit->setText(customUserAgent);
}
@@ -177,6 +180,8 @@ void APIPage::applySettings()
s->set("MetaURLOverride", metaURL);
QString flameKey = ui->flameKey->text();
s->set("FlameKeyOverride", flameKey);
+ QString modrinthToken = ui->modrinthToken->text();
+ s->set("ModrinthToken", modrinthToken);
s->set("UserAgentOverride", ui->userAgentLineEdit->text());
}
diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui
index d56a9ef6..40b89d91 100644
--- a/launcher/ui/pages/global/APIPage.ui
+++ b/launcher/ui/pages/global/APIPage.ui
@@ -196,24 +196,69 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_flame">
+ <widget class="QGroupBox" name="groupBox_modrinth">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
- <string>&amp;CurseForge Core API</string>
+ <string>&amp;Modrinth API</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
- <string>Note: you probably don't need to set this if CurseForge already works.</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: you only need to set this to access private data. Read the &lt;a href=&quot;https://docs.modrinth.com/api-spec/#section/Authentication&quot;&gt;documentation&lt;/a&gt; for more information.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
+ <string>Enter a custom API token for Modrinth here.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="modrinthToken">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="placeholderText">
+ <string>(None)</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_flame">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>&amp;CurseForge Core API</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Note: you probably don't need to set this if CurseForge already works.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
<string>Enter a custom API Key for CurseForge here.</string>
</property>
<property name="textFormat">
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index cae0635f..816dde72 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -44,14 +44,13 @@
#include <QTextCharFormat>
#include <QMenuBar>
-#include "updater/UpdateChecker.h"
-
#include "settings/SettingsObject.h"
#include <FileSystem.h>
#include "Application.h"
#include "BuildConfig.h"
#include "DesktopServices.h"
#include "ui/themes/ITheme.h"
+#include "updater/ExternalUpdater.h"
#include <QApplication>
#include <QProcess>
@@ -80,32 +79,12 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
m_languageModel = APPLICATION->translations();
loadSettings();
- if(BuildConfig.UPDATER_ENABLED)
- {
- QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
-
- if (APPLICATION->updateChecker()->hasChannels())
- {
- refreshUpdateChannelList();
- }
- else
- {
- APPLICATION->updateChecker()->updateChanList(false);
- }
+ ui->updateSettingsBox->setHidden(!APPLICATION->updater());
- if (APPLICATION->updateChecker()->getExternalUpdater())
- {
- ui->updateChannelComboBox->setVisible(false);
- ui->updateChannelDescLabel->setVisible(false);
- ui->updateChannelLabel->setVisible(false);
- }
- }
- else
- {
- ui->updateSettingsBox->setHidden(true);
- }
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
+
+ connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentCatChanged, APPLICATION, &Application::currentCatChanged);
}
LauncherPage::~LauncherPage()
@@ -161,8 +140,8 @@ void LauncherPage::on_instDirBrowseBtn_clicked()
if (result == QMessageBox::Ok)
{
ui->instDirTextBox->setText(cooked_dir);
- }
- }
+ }
+ }
else
{
ui->instDirTextBox->setText(cooked_dir);
@@ -181,6 +160,7 @@ void LauncherPage::on_iconsDirBrowseBtn_clicked()
ui->iconsDirTextBox->setText(cooked_dir);
}
}
+
void LauncherPage::on_modsDirBrowseBtn_clicked()
{
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text());
@@ -193,81 +173,20 @@ void LauncherPage::on_modsDirBrowseBtn_clicked()
}
}
-void LauncherPage::on_metadataDisableBtn_clicked()
+void LauncherPage::on_downloadsDirBrowseBtn_clicked()
{
- ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
-}
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text());
-void LauncherPage::refreshUpdateChannelList()
-{
- // Stop listening for selection changes. It's going to change a lot while we update it and
- // we don't need to update the
- // description label constantly.
- QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this,
- SLOT(updateChannelSelectionChanged(int)));
-
- QList<UpdateChecker::ChannelListEntry> channelList = APPLICATION->updateChecker()->getChannelList();
- ui->updateChannelComboBox->clear();
- int selection = -1;
- for (int i = 0; i < channelList.count(); i++)
+ if (!raw_dir.isEmpty() && QDir(raw_dir).exists())
{
- UpdateChecker::ChannelListEntry entry = channelList.at(i);
-
- // When it comes to selection, we'll rely on the indexes of a channel entry being the
- // same in the
- // combo box as it is in the update checker's channel list.
- // This probably isn't very safe, but the channel list doesn't change often enough (or
- // at all) for
- // this to be a big deal. Hope it doesn't break...
- ui->updateChannelComboBox->addItem(entry.name);
-
- // If the update channel we just added was the selected one, set the current index in
- // the combo box to it.
- if (entry.id == m_currentUpdateChannel)
- {
- qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel;
- selection = i;
- }
+ QString cooked_dir = FS::NormalizePath(raw_dir);
+ ui->downloadsDirTextBox->setText(cooked_dir);
}
-
- ui->updateChannelComboBox->setCurrentIndex(selection);
-
- // Start listening for selection changes again and update the description label.
- QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this,
- SLOT(updateChannelSelectionChanged(int)));
- refreshUpdateChannelDesc();
-
- // Now that we've updated the channel list, we can enable the combo box.
- // It starts off disabled so that if the channel list hasn't been loaded, it will be
- // disabled.
- ui->updateChannelComboBox->setEnabled(true);
-}
-
-void LauncherPage::updateChannelSelectionChanged(int index)
-{
- refreshUpdateChannelDesc();
}
-void LauncherPage::refreshUpdateChannelDesc()
+void LauncherPage::on_metadataDisableBtn_clicked()
{
- // Get the channel list.
- QList<UpdateChecker::ChannelListEntry> channelList = APPLICATION->updateChecker()->getChannelList();
- int selectedIndex = ui->updateChannelComboBox->currentIndex();
- if (selectedIndex < 0)
- {
- return;
- }
- if (selectedIndex < channelList.count())
- {
- // Find the channel list entry with the given index.
- UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex);
-
- // Set the description text.
- ui->updateChannelDescLabel->setText(selected.description);
-
- // Set the currently selected channel ID.
- m_currentUpdateChannel = selected.id;
- }
+ ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
}
void LauncherPage::applySettings()
@@ -275,82 +194,9 @@ void LauncherPage::applySettings()
auto s = APPLICATION->settings();
// Updates
- if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
+ if (APPLICATION->updater())
{
- APPLICATION->updateChecker()->getExternalUpdater()->setAutomaticallyChecksForUpdates(
- ui->autoUpdateCheckBox->isChecked());
- }
- else
- {
- s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
- }
-
- s->set("UpdateChannel", m_currentUpdateChannel);
- auto original = s->get("IconTheme").toString();
- //FIXME: make generic
- switch (ui->themeComboBox->currentIndex())
- {
- case 0:
- s->set("IconTheme", "pe_colored");
- break;
- case 1:
- s->set("IconTheme", "pe_light");
- break;
- case 2:
- s->set("IconTheme", "pe_dark");
- break;
- case 3:
- s->set("IconTheme", "pe_blue");
- break;
- case 4:
- s->set("IconTheme", "breeze_light");
- break;
- case 5:
- s->set("IconTheme", "breeze_dark");
- break;
- case 6:
- s->set("IconTheme", "OSX");
- break;
- case 7:
- s->set("IconTheme", "iOS");
- break;
- case 8:
- s->set("IconTheme", "flat");
- break;
- case 9:
- s->set("IconTheme", "flat_white");
- break;
- case 10:
- s->set("IconTheme", "multimc");
- break;
- case 11:
- s->set("IconTheme", "custom");
- break;
- }
-
- if(original != s->get("IconTheme"))
- {
- APPLICATION->setIconTheme(s->get("IconTheme").toString());
- }
-
- auto originalAppTheme = s->get("ApplicationTheme").toString();
- auto newAppTheme = ui->themeComboBoxColors->currentData().toString();
- if(originalAppTheme != newAppTheme)
- {
- s->set("ApplicationTheme", newAppTheme);
- APPLICATION->setApplicationTheme(newAppTheme, false);
- }
-
- switch (ui->themeBackgroundCat->currentIndex()) {
- case 0: // original cat
- s->set("BackgroundCat", "kitteh");
- break;
- case 1: // rory the cat
- s->set("BackgroundCat", "rory");
- break;
- case 2: // rory the cat flat edition
- s->set("BackgroundCat", "rory-flat");
- break;
+ APPLICATION->updater()->setAutomaticallyChecksForUpdates(ui->autoUpdateCheckBox->isChecked());
}
s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked());
@@ -370,6 +216,8 @@ void LauncherPage::applySettings()
s->set("InstanceDir", ui->instDirTextBox->text());
s->set("CentralModsDir", ui->modsDirTextBox->text());
s->set("IconsDir", ui->iconsDirTextBox->text());
+ s->set("DownloadsDir", ui->downloadsDirTextBox->text());
+ s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked());
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
switch (sortMode)
@@ -390,56 +238,11 @@ void LauncherPage::loadSettings()
{
auto s = APPLICATION->settings();
// Updates
- if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
- {
- ui->autoUpdateCheckBox->setChecked(
- APPLICATION->updateChecker()->getExternalUpdater()->getAutomaticallyChecksForUpdates());
- }
- else
+ if (APPLICATION->updater())
{
- ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
+ ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates());
}
- m_currentUpdateChannel = s->get("UpdateChannel").toString();
- //FIXME: make generic
- auto theme = s->get("IconTheme").toString();
- QStringList iconThemeOptions{"pe_colored",
- "pe_light",
- "pe_dark",
- "pe_blue",
- "breeze_light",
- "breeze_dark",
- "OSX",
- "iOS",
- "flat",
- "flat_white",
- "multimc",
- "custom"};
- ui->themeComboBox->setCurrentIndex(iconThemeOptions.indexOf(theme));
-
- auto cat = s->get("BackgroundCat").toString();
- if (cat == "kitteh") {
- ui->themeBackgroundCat->setCurrentIndex(0);
- } else if (cat == "rory") {
- ui->themeBackgroundCat->setCurrentIndex(1);
- } else if (cat == "rory-flat") {
- ui->themeBackgroundCat->setCurrentIndex(2);
- }
-
- {
- auto currentTheme = s->get("ApplicationTheme").toString();
- auto themes = APPLICATION->getValidApplicationThemes();
- int idx = 0;
- for(auto &theme: themes)
- {
- ui->themeComboBoxColors->addItem(theme->name(), theme->id());
- if(currentTheme == theme->id())
- {
- ui->themeComboBoxColors->setCurrentIndex(idx);
- }
- idx++;
- }
- }
// Toolbar/menu bar settings (not applicable if native menu bar is present)
ui->toolsBox->setEnabled(!QMenuBar().isNativeMenuBar());
@@ -471,6 +274,8 @@ void LauncherPage::loadSettings()
ui->instDirTextBox->setText(s->get("InstanceDir").toString());
ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
+ ui->downloadsDirTextBox->setText(s->get("DownloadsDir").toString());
+ ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool());
QString sortMode = s->get("InstSortMode").toString();
diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h
index f38c922e..33f66f1b 100644
--- a/launcher/ui/pages/global/LauncherPage.h
+++ b/launcher/ui/pages/global/LauncherPage.h
@@ -88,25 +88,14 @@ slots:
void on_instDirBrowseBtn_clicked();
void on_modsDirBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked();
+ void on_downloadsDirBrowseBtn_clicked();
void on_metadataDisableBtn_clicked();
/*!
- * Updates the list of update channels in the combo box.
- */
- void refreshUpdateChannelList();
-
- /*!
- * Updates the channel description label.
- */
- void refreshUpdateChannelDesc();
-
- /*!
* Updates the font preview
*/
void refreshFontPreview();
- void updateChannelSelectionChanged(int index);
-
private:
Ui::LauncherPage *ui;
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index c44718a1..55bd3eea 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>514</width>
+ <width>511</width>
<height>629</height>
</rect>
</property>
@@ -58,42 +58,54 @@
</property>
</widget>
</item>
- <item>
- <widget class="QLabel" name="updateChannelLabel">
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="foldersBox">
+ <property name="title">
+ <string>Folders</string>
+ </property>
+ <layout class="QGridLayout" name="foldersBoxLayout">
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelDownloadsDir">
<property name="text">
- <string>Up&amp;date Channel:</string>
+ <string>&amp;Downloads:</string>
</property>
<property name="buddy">
- <cstring>updateChannelComboBox</cstring>
+ <cstring>downloadsDirTextBox</cstring>
</property>
</widget>
</item>
- <item>
- <widget class="QComboBox" name="updateChannelComboBox">
- <property name="enabled">
- <bool>false</bool>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelInstDir">
+ <property name="text">
+ <string>I&amp;nstances:</string>
+ </property>
+ <property name="buddy">
+ <cstring>instDirTextBox</cstring>
</property>
</widget>
</item>
- <item>
- <widget class="QLabel" name="updateChannelDescLabel">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="instDirTextBox"/>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="downloadsDirTextBox"/>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="iconsDirTextBox"/>
+ </item>
+ <item row="3" column="2">
+ <widget class="QToolButton" name="downloadsDirBrowseBtn">
<property name="text">
- <string>No channel selected.</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
+ <string notr="true">...</string>
</property>
</widget>
</item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="foldersBox">
- <property name="title">
- <string>Folders</string>
- </property>
- <layout class="QGridLayout" name="foldersBoxLayout">
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="modsDirTextBox"/>
+ </item>
<item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text">
@@ -101,6 +113,16 @@
</property>
</widget>
</item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelModsDir">
+ <property name="text">
+ <string>&amp;Mods:</string>
+ </property>
+ <property name="buddy">
+ <cstring>modsDirTextBox</cstring>
+ </property>
+ </widget>
+ </item>
<item row="0" column="2">
<widget class="QToolButton" name="instDirBrowseBtn">
<property name="text">
@@ -115,9 +137,6 @@
</property>
</widget>
</item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="instDirTextBox"/>
- </item>
<item row="2" column="0">
<widget class="QLabel" name="labelIconsDir">
<property name="text">
@@ -128,29 +147,13 @@
</property>
</widget>
</item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="modsDirTextBox"/>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="labelInstDir">
- <property name="text">
- <string>I&amp;nstances:</string>
- </property>
- <property name="buddy">
- <cstring>instDirTextBox</cstring>
+ <item row="4" column="1" colspan="2">
+ <widget class="QCheckBox" name="downloadsDirWatchRecursiveCheckBox">
+ <property name="toolTip">
+ <string>When enabled, in addition to the downloads folder, its sub folders will also be searched when looking for resources (e.g. when looking for blocked mods on CurseForge).</string>
</property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="iconsDirTextBox"/>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="labelModsDir">
<property name="text">
- <string>&amp;Mods:</string>
- </property>
- <property name="buddy">
- <cstring>modsDirTextBox</cstring>
+ <string>Check downloads folder recursively</string>
</property>
</widget>
</item>
@@ -243,150 +246,9 @@
<property name="title">
<string>Theme</string>
</property>
- <layout class="QFormLayout" name="formLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>&amp;Icons</string>
- </property>
- <property name="buddy">
- <cstring>themeComboBox</cstring>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="themeComboBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <item>
- <property name="text">
- <string>Simple (Colored Icons)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Simple (Light Icons)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Simple (Dark Icons)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Simple (Blue Icons)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Breeze Light</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Breeze Dark</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">OSX</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string notr="true">iOS</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Flat</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Flat (White)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Legacy</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Custom</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>&amp;Colors</string>
- </property>
- <property name="buddy">
- <cstring>themeComboBoxColors</cstring>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QComboBox" name="themeComboBoxColors">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>C&amp;at</string>
- </property>
- <property name="buddy">
- <cstring>themeBackgroundCat</cstring>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QComboBox" name="themeBackgroundCat">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::StrongFocus</enum>
- </property>
- <item>
- <property name="text">
- <string>Background Cat (from MultiMC)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Rory ID 11 (drawn by Ashtaka)</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Rory ID 11 (flat edition, drawn by Ashtaka)</string>
- </property>
- </item>
- </widget>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="ThemeCustomizationWidget" name="themeCustomizationWidget" native="true"/>
</item>
</layout>
</widget>
@@ -570,10 +432,17 @@
</item>
</layout>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>ThemeCustomizationWidget</class>
+ <extends>QWidget</extends>
+ <header>ui/widgets/ThemeCustomizationWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>autoUpdateCheckBox</tabstop>
- <tabstop>updateChannelComboBox</tabstop>
<tabstop>instDirTextBox</tabstop>
<tabstop>instDirBrowseBtn</tabstop>
<tabstop>modsDirTextBox</tabstop>
@@ -582,8 +451,6 @@
<tabstop>iconsDirBrowseBtn</tabstop>
<tabstop>sortLastLaunchedBtn</tabstop>
<tabstop>sortByNameBtn</tabstop>
- <tabstop>themeComboBox</tabstop>
- <tabstop>themeComboBoxColors</tabstop>
<tabstop>showConsoleCheck</tabstop>
<tabstop>autoCloseConsoleCheck</tabstop>
<tabstop>showConsoleErrorCheck</tabstop>
diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index cc597fe0..eca3e865 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -46,7 +46,6 @@
MinecraftPage::MinecraftPage(QWidget *parent) : QWidget(parent), ui(new Ui::MinecraftPage)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
loadSettings();
updateCheckboxStuff();
}
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 640f436d..103881b5 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>936</width>
- <height>1134</height>
+ <height>541</height>
</rect>
</property>
<property name="sizePolicy">
@@ -39,7 +39,7 @@
</property>
<widget class="QWidget" name="minecraftTab">
<attribute name="title">
- <string notr="true">Minecraft</string>
+ <string notr="true">General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
@@ -112,22 +112,29 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
+ <widget class="QGroupBox" name="gameTimeGroupBox">
<property name="title">
- <string>Native library workarounds</string>
+ <string>Game time</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <widget class="QCheckBox" name="useNativeGLFWCheck">
+ <widget class="QCheckBox" name="showGameTime">
<property name="text">
- <string>Use system installation of &amp;GLFW</string>
+ <string>Show time spent &amp;playing instances</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <widget class="QCheckBox" name="showGlobalGameTime">
<property name="text">
- <string>Use system installation of &amp;OpenAL</string>
+ <string>Show time spent playing across &amp;all instances</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="recordGameTime">
+ <property name="text">
+ <string>&amp;Record time spent playing instances</string>
</property>
</widget>
</item>
@@ -135,38 +142,28 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="perfomanceGroupBox">
+ <widget class="QGroupBox" name="groupBox">
<property name="title">
- <string>Performance</string>
+ <string>Miscellaneous</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QCheckBox" name="enableFeralGamemodeCheck">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>Enable Feral GameMode</string>
- </property>
- </widget>
- </item>
+ <layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QCheckBox" name="enableMangoHud">
+ <widget class="QCheckBox" name="closeAfterLaunchCheck">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically reopen when the game crashes or exits.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Enable MangoHud</string>
+ <string>&amp;Close the launcher after game window opens</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <widget class="QCheckBox" name="quitAfterGameStopCheck">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>Use discrete GPU</string>
+ <string>&amp;Quit the launcher after game window closes</string>
</property>
</widget>
</item>
@@ -174,29 +171,42 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="gameTimeGroupBox">
+ <spacer name="verticalSpacerMinecraft">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Tweaks</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title">
- <string>Game time</string>
+ <string>Native library workarounds</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_6">
+ <layout class="QVBoxLayout" name="verticalLayout_11">
<item>
- <widget class="QCheckBox" name="showGameTime">
- <property name="text">
- <string>Show time spent &amp;playing instances</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QCheckBox" name="showGlobalGameTime">
+ <widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
- <string>Show time spent playing across &amp;all instances</string>
+ <string>Use system installation of &amp;GLFW</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="recordGameTime">
+ <widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
- <string>&amp;Record time spent playing instances</string>
+ <string>Use system installation of &amp;OpenAL</string>
</property>
</widget>
</item>
@@ -204,28 +214,38 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox">
+ <widget class="QGroupBox" name="perfomanceGroupBox">
<property name="title">
- <string>Miscellaneous</string>
+ <string>Performance</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
- <widget class="QCheckBox" name="closeAfterLaunchCheck">
+ <widget class="QCheckBox" name="enableFeralGamemodeCheck">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically reopen when the game crashes or exits.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>&amp;Close the launcher after game window opens</string>
+ <string>Enable Feral GameMode</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="quitAfterGameStopCheck">
+ <widget class="QCheckBox" name="enableMangoHud">
<property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
- <string>&amp;Quit the launcher after game window closes</string>
+ <string>Enable MangoHud</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Use discrete GPU</string>
</property>
</widget>
</item>
@@ -233,14 +253,14 @@
</widget>
</item>
<item>
- <spacer name="verticalSpacerMinecraft">
+ <spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>0</width>
- <height>0</height>
+ <width>20</width>
+ <height>40</height>
</size>
</property>
</spacer>
@@ -255,11 +275,6 @@
<tabstop>maximizedCheckBox</tabstop>
<tabstop>windowWidthSpinBox</tabstop>
<tabstop>windowHeightSpinBox</tabstop>
- <tabstop>useNativeGLFWCheck</tabstop>
- <tabstop>useNativeOpenALCheck</tabstop>
- <tabstop>enableFeralGamemodeCheck</tabstop>
- <tabstop>enableMangoHud</tabstop>
- <tabstop>useDiscreteGpuCheck</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index c66d1368..1115ddc3 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -1,4 +1,5 @@
#include "ExternalResourcesPage.h"
+#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ExternalResourcesPage.h"
#include "DesktopServices.h"
@@ -128,7 +129,7 @@ bool ExternalResourcesPage::eventFilter(QObject* obj, QEvent* ev)
{
if (ev->type() != QEvent::KeyPress)
return QWidget::eventFilter(obj, ev);
-
+
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(ev);
if (obj == ui->treeView)
return listFilter(keyEvent);
@@ -140,7 +141,6 @@ void ExternalResourcesPage::addItem()
{
if (!m_controlsEnabled)
return;
-
auto list = GuiUtil::BrowseForFiles(
helpPage(), tr("Select %1", "Select whatever type of files the page contains. Example: 'Loader Mods'").arg(displayName()),
@@ -157,8 +157,50 @@ void ExternalResourcesPage::removeItem()
{
if (!m_controlsEnabled)
return;
-
+
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+
+ int count = 0;
+ bool folder = false;
+ for (auto& i : selection.indexes()) {
+ if (i.column() == 0) {
+ count++;
+
+ // if a folder is selected, show the confirmation dialog
+ if (m_model->at(i.row()).fileinfo().isDir())
+ folder = true;
+ }
+ }
+
+ QString text;
+ bool multiple = count > 1;
+
+ if (multiple) {
+ text = tr("You are about to remove %1 items.\n"
+ "This may be permanent and they will be gone from the folder.\n\n"
+ "Are you sure?")
+ .arg(count);
+ } else if (folder) {
+ text = tr("You are about to remove the folder \"%1\".\n"
+ "This may be permanent and it will be gone from the parent folder.\n\n"
+ "Are you sure?")
+ .arg(m_model->at(selection.indexes().at(0).row()).fileinfo().fileName());
+ }
+
+ if (!text.isEmpty()) {
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), text, QMessageBox::Warning,
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+ }
+
+ removeItems(selection);
+}
+
+void ExternalResourcesPage::removeItems(const QItemSelection& selection)
+{
m_model->deleteResources(selection.indexes());
}
@@ -209,4 +251,3 @@ bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, const
return true;
}
-
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h
index 2d1a5b51..d17fbb7f 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.h
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.h
@@ -50,7 +50,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
void filterTextChanged(const QString& newContents);
virtual void addItem();
- virtual void removeItem();
+ void removeItem();
+ virtual void removeItems(const QItemSelection &selection);
virtual void enableItem();
virtual void disableItem();
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index af2ba7c8..4b4c73dc 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -48,18 +48,23 @@
#include "JavaCommon.h"
#include "Application.h"
+#include "minecraft/auth/AccountList.h"
#include "java/JavaInstallList.h"
#include "java/JavaUtils.h"
#include "FileSystem.h"
-
InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent)
: QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst)
{
m_settings = inst->settings();
ui->setupUi(this);
+ accountMenu = new QMenu(this);
+ // Use undocumented property... https://stackoverflow.com/questions/7121718/create-a-scrollbar-in-a-submenu-qt
+ accountMenu->setStyleSheet("QMenu { menu-scrollable: 1; }");
+ ui->instanceAccountSelector->setMenu(accountMenu);
+
connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked);
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings);
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
@@ -275,6 +280,13 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("JoinServerOnLaunchAddress");
}
+ // Use an account for this instance
+ bool useAccountForInstance = ui->instanceAccountGroupBox->isChecked();
+ m_settings->set("UseAccountForInstance", useAccountForInstance);
+ if (!useAccountForInstance) {
+ m_settings->reset("InstanceAccountId");
+ }
+
// FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext();
}
@@ -372,6 +384,9 @@ void InstanceSettingsPage::loadSettings()
ui->serverJoinGroupBox->setChecked(m_settings->get("JoinServerOnLaunch").toBool());
ui->serverJoinAddress->setText(m_settings->get("JoinServerOnLaunchAddress").toString());
+
+ ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
+ updateAccountsMenu();
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()
@@ -437,6 +452,65 @@ void InstanceSettingsPage::on_javaTestBtn_clicked()
checker->run();
}
+void InstanceSettingsPage::updateAccountsMenu()
+{
+ accountMenu->clear();
+
+ auto accounts = APPLICATION->accounts();
+ int accountIndex = accounts->findAccountByProfileId(m_settings->get("InstanceAccountId").toString());
+ MinecraftAccountPtr defaultAccount = accounts->defaultAccount();
+
+ if (accountIndex != -1 && accounts->at(accountIndex)) {
+ defaultAccount = accounts->at(accountIndex);
+ }
+
+ if (defaultAccount) {
+ ui->instanceAccountSelector->setText(defaultAccount->profileName());
+ ui->instanceAccountSelector->setIcon(getFaceForAccount(defaultAccount));
+ } else {
+ ui->instanceAccountSelector->setText(tr("No default account"));
+ ui->instanceAccountSelector->setIcon(APPLICATION->getThemedIcon("noaccount"));
+ }
+
+ for (int i = 0; i < accounts->count(); i++) {
+ MinecraftAccountPtr account = accounts->at(i);
+ QAction* action = new QAction(account->profileName(), this);
+ action->setData(i);
+ action->setCheckable(true);
+ if (accountIndex == i) {
+ action->setChecked(true);
+ }
+ action->setIcon(getFaceForAccount(account));
+ accountMenu->addAction(action);
+ connect(action, SIGNAL(triggered(bool)), this, SLOT(changeInstanceAccount()));
+ }
+}
+
+QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account)
+{
+ if (auto face = account->getFace(); !face.isNull()) {
+ return face;
+ }
+
+ return APPLICATION->getThemedIcon("noaccount");
+}
+
+void InstanceSettingsPage::changeInstanceAccount()
+{
+ QAction* sAction = (QAction*)sender();
+
+ Q_ASSERT(sAction->data().type() == QVariant::Type::Int);
+
+ QVariant data = sAction->data();
+ int index = data.toInt();
+ auto accounts = APPLICATION->accounts();
+ auto account = accounts->at(index);
+ m_settings->set("InstanceAccountId", account->profileId());
+
+ ui->instanceAccountSelector->setText(account->profileName());
+ ui->instanceAccountSelector->setIcon(getFaceForAccount(account));
+}
+
void InstanceSettingsPage::on_maxMemSpinBox_valueChanged(int i)
{
updateThresholds();
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h
index 7450188d..cb6fbae0 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.h
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.h
@@ -37,12 +37,13 @@
#include <QWidget>
-#include "java/JavaChecker.h"
-#include "BaseInstance.h"
#include <QObjectPtr.h>
-#include "ui/pages/BasePage.h"
-#include "JavaCommon.h"
+#include <QMenu>
#include "Application.h"
+#include "BaseInstance.h"
+#include "JavaCommon.h"
+#include "java/JavaChecker.h"
+#include "ui/pages/BasePage.h"
class JavaChecker;
namespace Ui
@@ -92,9 +93,14 @@ private slots:
void globalSettingsButtonClicked(bool checked);
+ void updateAccountsMenu();
+ QIcon getFaceForAccount(MinecraftAccountPtr account);
+ void changeInstanceAccount();
+
private:
Ui::InstanceSettingsPage *ui;
BaseInstance *m_instance;
SettingsObjectPtr m_settings;
unique_qobject_ptr<JavaCommon::TestCheck> checker;
+ QMenu *accountMenu = nullptr;
};
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index b064367d..1b986184 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -609,6 +609,48 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="instanceAccountGroupBox">
+ <property name="title">
+ <string>Override default account</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_15">
+ <item>
+ <layout class="QGridLayout" name="instanceAccountLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="instanceAccountNameLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Account:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="instanceAccountSelector">
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacerMiscellaneous">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp
index 31c3e925..639cd711 100644
--- a/launcher/ui/pages/instance/LogPage.cpp
+++ b/launcher/ui/pages/instance/LogPage.cpp
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * 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>
*
* 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
@@ -39,7 +40,7 @@
#include "Application.h"
-#include <QIcon>
+#include <QIdentityProxyModel>
#include <QScrollBar>
#include <QShortcut>
@@ -277,28 +278,22 @@ void LogPage::on_btnPaste_clicked()
//FIXME: turn this into a proper task and move the upload logic out of GuiUtil!
m_model->append(
MessageLevel::Launcher,
- QString("%2: Log upload triggered at: %1").arg(
- QDateTime::currentDateTime().toString(Qt::RFC2822Date),
- BuildConfig.LAUNCHER_DISPLAYNAME
+ QString("Log upload triggered at: %1").arg(
+ QDateTime::currentDateTime().toString(Qt::RFC2822Date)
)
);
- auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this);
- if(!url.isEmpty())
+ auto url = GuiUtil::uploadPaste(tr("Minecraft Log"), m_model->toPlainText(), this);
+ if(!url.has_value())
{
- m_model->append(
- MessageLevel::Launcher,
- QString("%2: Log uploaded to: %1").arg(
- url,
- BuildConfig.LAUNCHER_DISPLAYNAME
- )
- );
+ m_model->append(MessageLevel::Error, QString("Log upload canceled"));
+ }
+ else if (url->isNull())
+ {
+ m_model->append(MessageLevel::Error, QString("Log upload failed!"));
}
else
{
- m_model->append(
- MessageLevel::Error,
- QString("%1: Log upload failed!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)
- );
+ m_model->append(MessageLevel::Launcher, QString("Log uploaded to: %1").arg(url.value()));
}
}
diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp
index 4de80468..d0701a7a 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.cpp
+++ b/launcher/ui/pages/instance/ManagedPackPage.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2022 flow <flowlnlnln@gmail.com>
+// SPDX-FileCopyrightText: 2022 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
@@ -9,14 +9,13 @@
#include <QProxyStyle>
#include <QStyleFactory>
-#include <HoeDown.h>
-
#include "Application.h"
#include "BuildConfig.h"
#include "InstanceImportTask.h"
#include "InstanceList.h"
#include "InstanceTask.h"
#include "Json.h"
+#include "Markdown.h"
#include "modplatform/modrinth/ModrinthPackManifest.h"
@@ -31,8 +30,6 @@ class NoBigComboBoxStyle : public QProxyStyle {
Q_OBJECT
public:
- NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
-
// clang-format off
int styleHint(QStyle::StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override
{
@@ -42,6 +39,37 @@ class NoBigComboBoxStyle : public QProxyStyle {
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
// clang-format on
+
+ /**
+ * Something about QProxyStyle and QStyle objects means they can't be free'd just
+ * because all the widgets using them are gone.
+ * They seems to be tied to the QApplicaiton lifecycle.
+ * So make singletons tied to the lifetime of the application to clean them up and ensure they aren't
+ * being remade over and over again, thus leaking memory.
+ */
+ public:
+ static NoBigComboBoxStyle* getInstance(QStyle* style)
+ {
+ static QHash<QStyle*, NoBigComboBoxStyle*> s_singleton_instances_ = {};
+ static std::mutex s_singleton_instances_mutex_;
+
+ std::lock_guard<std::mutex> lock(s_singleton_instances_mutex_);
+ auto inst_iter = s_singleton_instances_.constFind(style);
+ NoBigComboBoxStyle* inst = nullptr;
+ if (inst_iter == s_singleton_instances_.constEnd() || *inst_iter == nullptr) {
+ inst = new NoBigComboBoxStyle(style);
+ inst->setParent(APPLICATION);
+ s_singleton_instances_.insert(style, inst);
+ qDebug() << "QProxyStyle NoBigComboBox created for" << style->objectName() << style;
+ } else {
+ inst = *inst_iter;
+ }
+ return inst;
+ }
+
+ private:
+ NoBigComboBoxStyle(QStyle* style) : QProxyStyle(style) {}
+
};
ManagedPackPage* ManagedPackPage::createPage(BaseInstance* inst, QString type, QWidget* parent)
@@ -63,8 +91,10 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi
// NOTE: GTK2 themes crash with the proxy style.
// This seems like an upstream bug, so there's not much else that can be done.
- if (!QStyleFactory::keys().contains("gtk2"))
- ui->versionsComboBox->setStyle(new NoBigComboBoxStyle(ui->versionsComboBox->style()));
+ if (!QStyleFactory::keys().contains("gtk2")){
+ auto comboStyle = NoBigComboBoxStyle::getInstance(ui->versionsComboBox->style());
+ ui->versionsComboBox->setStyle(comboStyle);
+ }
ui->reloadButton->setVisible(false);
connect(ui->reloadButton, &QPushButton::clicked, this, [this](bool){
@@ -263,8 +293,7 @@ void ModrinthManagedPackPage::suggestVersion()
auto index = ui->versionsComboBox->currentIndex();
auto version = m_pack.versions.at(index);
- HoeDown md_parser;
- ui->changelogTextBrowser->setHtml(md_parser.process(version.changelog.toUtf8()));
+ ui->changelogTextBrowser->setHtml(markdownToHTML(version.changelog.toUtf8()));
ManagedPackPage::suggestVersion();
}
diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h
index d29a5e88..1ac6fc03 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.h
+++ b/launcher/ui/pages/instance/ManagedPackPage.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2022 flow <flowlnlnln@gmail.com>
+// SPDX-FileCopyrightText: 2022 flowln <flowlnlnln@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-only
@@ -12,6 +12,8 @@
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlamePackIndex.h"
+#include "net/NetJob.h"
+
#include "ui/pages/BasePage.h"
#include <QWidget>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 0a2e6155..4548af59 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * 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>
*
* 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
@@ -48,8 +49,8 @@
#include "ui/GuiUtil.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ModDownloadDialog.h"
#include "ui/dialogs/ModUpdateDialog.h"
+#include "ui/dialogs/ResourceDownloadDialog.h"
#include "DesktopServices.h"
@@ -58,7 +59,7 @@
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/ModFolderModel.h"
-#include "modplatform/ModAPI.h"
+#include "modplatform/ResourceAPI.h"
#include "Version.h"
#include "tasks/ConcurrentTask.h"
@@ -139,13 +140,8 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelI
return true;
}
-void ModFolderPage::removeItem()
+void ModFolderPage::removeItems(const QItemSelection &selection)
{
-
- if (!m_controlsEnabled)
- return;
-
- auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->deleteMods(selection.indexes());
}
@@ -157,12 +153,12 @@ void ModFolderPage::installMods()
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
- if (profile->getModLoaders() == ModAPI::Unspecified) {
+ if (!profile->getModLoaders().has_value()) {
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
return;
}
- ModDownloadDialog mdownload(m_model, this, m_instance);
+ ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
ConcurrentTask* tasks = new ConcurrentTask(this);
connect(tasks, &Task::failed, [this, tasks](QString reason) {
@@ -277,3 +273,12 @@ bool CoreModFolderPage::shouldDisplay() const
}
return false;
}
+
+NilModFolderPage::NilModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
+ : ModFolderPage(inst, mods, parent)
+{}
+
+bool NilModFolderPage::shouldDisplay() const
+{
+ return m_model->dir().exists();
+}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index f20adf34..2fc7b574 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * 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>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -59,7 +60,7 @@ class ModFolderPage : public ExternalResourcesPage {
private slots:
void runningStateChanged(bool running);
- void removeItem() override;
+ void removeItems(const QItemSelection &selection) override;
void installMods();
void updateMods();
@@ -80,3 +81,16 @@ class CoreModFolderPage : public ModFolderPage {
virtual bool shouldDisplay() const override;
};
+
+class NilModFolderPage : public ModFolderPage {
+ public:
+ explicit NilModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
+ virtual ~NilModFolderPage() = default;
+
+ virtual QString displayName() const override { return tr("Nilmods"); }
+ virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); }
+ virtual QString id() const override { return "nilmods"; }
+ virtual QString helpPage() const override { return "Nilmods"; }
+
+ virtual bool shouldDisplay() const override;
+};
diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp
index 0c1939c6..bbdd7324 100644
--- a/launcher/ui/pages/instance/OtherLogsPage.cpp
+++ b/launcher/ui/pages/instance/OtherLogsPage.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022 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
@@ -204,7 +205,7 @@ void OtherLogsPage::on_btnReload_clicked()
void OtherLogsPage::on_btnPaste_clicked()
{
- GuiUtil::uploadPaste(ui->text->toPlainText(), this);
+ GuiUtil::uploadPaste(m_currentFile, ui->text->toPlainText(), this);
}
void OtherLogsPage::on_btnCopy_clicked()
@@ -219,13 +220,21 @@ void OtherLogsPage::on_btnDelete_clicked()
setControlsEnabled(false);
return;
}
- if (QMessageBox::question(this, tr("Delete"),
- tr("Do you really want to delete %1?").arg(m_currentFile),
- QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
- {
+ if (QMessageBox::question(this, tr("Confirm Deletion"),
+ tr("You are about to delete \"%1\".\n"
+ "This may be permanent and it will be gone from the logs folder.\n\n"
+ "Are you sure?")
+ .arg(m_currentFile),
+ QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) {
return;
}
QFile file(FS::PathCombine(m_path, m_currentFile));
+
+ if (FS::trash(file.fileName()))
+ {
+ return;
+ }
+
if (!file.remove())
{
QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2")
@@ -243,15 +252,15 @@ void OtherLogsPage::on_btnClean_clicked()
return;
}
QMessageBox *messageBox = new QMessageBox(this);
- messageBox->setWindowTitle(tr("Clean up"));
+ messageBox->setWindowTitle(tr("Confirm Cleanup"));
if(toDelete.size() > 5)
{
- messageBox->setText(tr("Do you really want to delete all log files?"));
+ messageBox->setText(tr("Are you sure you want to delete all log files?"));
messageBox->setDetailedText(toDelete.join('\n'));
}
else
{
- messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n')));
+ messageBox->setText(tr("Are you sure you want to delete all these files?\n%1").arg(toDelete.join('\n')));
}
messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
messageBox->setDefaultButton(QMessageBox::Ok);
@@ -267,6 +276,10 @@ void OtherLogsPage::on_btnClean_clicked()
for(auto item: toDelete)
{
QFile file(FS::PathCombine(m_path, item));
+ if (FS::trash(file.fileName()))
+ {
+ continue;
+ }
if (!file.remove())
{
failed.push_back(item);
diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp
new file mode 100644
index 00000000..24bfb38d
--- /dev/null
+++ b/launcher/ui/pages/instance/ResourcePackPage.cpp
@@ -0,0 +1,104 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourcePackPage.h"
+
+#include "ResourceDownloadTask.h"
+
+#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ProgressDialog.h"
+#include "ui/dialogs/ResourceDownloadDialog.h"
+
+ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr<ResourcePackFolderModel> model, QWidget* parent)
+ : ExternalResourcesPage(instance, model, parent)
+{
+ ui->actionDownloadItem->setText(tr("Download packs"));
+ ui->actionDownloadItem->setToolTip(tr("Download resource packs from online platforms"));
+ ui->actionDownloadItem->setEnabled(true);
+ connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs);
+ ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
+
+ ui->actionViewConfigs->setVisible(false);
+}
+
+bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+ auto sourceCurrent = m_filterModel->mapToSource(current);
+ int row = sourceCurrent.row();
+ auto& rp = static_cast<ResourcePack&>(m_model->at(row));
+ ui->frame->updateWithResourcePack(rp);
+
+ return true;
+}
+
+void ResourcePackPage::downloadRPs()
+{
+ if (!m_controlsEnabled)
+ return;
+ if (m_instance->typeName() != "Minecraft")
+ return; // this is a null instance or a legacy instance
+
+ ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast<ResourcePackFolderModel>(m_model), m_instance);
+ if (mdownload.exec()) {
+ auto tasks = new ConcurrentTask(this);
+ connect(tasks, &Task::failed, [this, tasks](QString reason) {
+ CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
+ tasks->deleteLater();
+ });
+ connect(tasks, &Task::aborted, [this, tasks]() {
+ CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
+ tasks->deleteLater();
+ });
+ connect(tasks, &Task::succeeded, [this, tasks]() {
+ QStringList warnings = tasks->warnings();
+ if (warnings.count())
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
+
+ tasks->deleteLater();
+ });
+
+ for (auto& task : mdownload.getTasks()) {
+ tasks->addTask(task);
+ }
+
+ ProgressDialog loadDialog(this);
+ loadDialog.setSkipButton(true, tr("Abort"));
+ loadDialog.execWithTask(tasks);
+
+ m_model->update();
+ }
+}
diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h
index 9633e3b4..b04aa2e9 100644
--- a/launcher/ui/pages/instance/ResourcePackPage.h
+++ b/launcher/ui/pages/instance/ResourcePackPage.h
@@ -1,6 +1,8 @@
-// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
@@ -44,12 +46,7 @@ class ResourcePackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
- explicit ResourcePackPage(MinecraftInstance *instance, std::shared_ptr<ResourcePackFolderModel> model, QWidget *parent = 0)
- : ExternalResourcesPage(instance, model, parent)
- {
- ui->actionViewConfigs->setVisible(false);
- }
- virtual ~ResourcePackPage() {}
+ explicit ResourcePackPage(MinecraftInstance *instance, std::shared_ptr<ResourcePackFolderModel> model, QWidget *parent = 0);
QString displayName() const override { return tr("Resource packs"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); }
@@ -63,13 +60,7 @@ public:
}
public slots:
- bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override
- {
- auto sourceCurrent = m_filterModel->mapToSource(current);
- int row = sourceCurrent.row();
- auto& rp = static_cast<ResourcePack&>(m_model->at(row));
- ui->frame->updateWithResourcePack(rp);
-
- return true;
- }
+ bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
+ void downloadRPs();
};
+
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp
index 0092aef3..ca368d3b 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.cpp
+++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * 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>
*
* 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
@@ -379,6 +380,24 @@ void ScreenshotsPage::on_actionUpload_triggered()
if (selection.isEmpty())
return;
+
+ QString text;
+ if (selection.size() > 1)
+ text = tr("You are about to upload %1 screenshots.\n\n"
+ "Are you sure?")
+ .arg(selection.size());
+ else
+ text =
+ tr("You are about to upload the selected screenshot.\n\n"
+ "Are you sure?");
+
+ auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+
QList<ScreenShot::Ptr> uploaded;
auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network()));
if(selection.size() < 2)
@@ -491,17 +510,32 @@ void ScreenshotsPage::on_actionCopy_File_s_triggered()
void ScreenshotsPage::on_actionDelete_triggered()
{
- auto mbox = CustomMessageBox::selectable(
- this, tr("Are you sure?"), tr("This will delete all selected screenshots."),
- QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No);
- std::unique_ptr<QMessageBox> box(mbox);
+ auto selected = ui->listView->selectionModel()->selectedIndexes();
+
+ int count = ui->listView->selectionModel()->selectedRows().size();
+ QString text;
+ if (count > 1)
+ text = tr("You are about to delete %1 screenshots.\n"
+ "This may be permanent and they will be gone from the folder.\n\n"
+ "Are you sure?")
+ .arg(count);
+ else
+ text = tr("You are about to delete the selected screenshot.\n"
+ "This may be permanent and it will be gone from the folder.\n\n"
+ "Are you sure?")
+ .arg(count);
- if (box->exec() != QMessageBox::Yes)
+ auto response =
+ CustomMessageBox::selectable(this, tr("Confirm Deletion"), text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)->exec();
+
+ if (response != QMessageBox::Yes)
return;
- auto selected = ui->listView->selectionModel()->selectedIndexes();
for (auto item : selected)
{
+ if (FS::trash(m_model->filePath(item)))
+ continue;
+
m_model->remove(item);
}
}
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h
index 2eb0de04..89611b6d 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.h
+++ b/launcher/ui/pages/instance/ScreenshotsPage.h
@@ -44,6 +44,7 @@
class QFileSystemModel;
class QIdentityProxyModel;
+class QItemSelection;
namespace Ui
{
class ScreenshotsPage;
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index a625e20b..4b1fa08a 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * 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>
*
* 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
@@ -35,6 +36,7 @@
*/
#include "ServersPage.h"
+#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ServersPage.h"
#include <FileSystem.h>
@@ -48,6 +50,7 @@
#include <QFileSystemWatcher>
#include <QMenu>
+#include <QTimer>
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
@@ -799,6 +802,17 @@ void ServersPage::on_actionAdd_triggered()
void ServersPage::on_actionRemove_triggered()
{
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
+ tr("You are about to remove \"%1\".\n"
+ "This is permanent and the server will be gone from your list forever (A LONG TIME).\n\n"
+ "Are you sure?")
+ .arg(m_model->at(currentServer)->m_name),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+
m_model->removeRow(currentServer);
}
diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp
new file mode 100644
index 00000000..2d0c10aa
--- /dev/null
+++ b/launcher/ui/pages/instance/ShaderPackPage.cpp
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ShaderPackPage.h"
+#include "ui_ExternalResourcesPage.h"
+
+#include "ResourceDownloadTask.h"
+
+#include "minecraft/mod/ShaderPackFolderModel.h"
+
+#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ProgressDialog.h"
+#include "ui/dialogs/ResourceDownloadDialog.h"
+
+
+ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget* parent)
+ : ExternalResourcesPage(instance, model, parent)
+{
+ ui->actionDownloadItem->setText(tr("Download shaders"));
+ ui->actionDownloadItem->setToolTip(tr("Download shaders from online platforms"));
+ ui->actionDownloadItem->setEnabled(true);
+ connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaders);
+ ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
+
+ ui->actionViewConfigs->setVisible(false);
+}
+
+void ShaderPackPage::downloadShaders()
+{
+ if (!m_controlsEnabled)
+ return;
+ if (m_instance->typeName() != "Minecraft")
+ return; // this is a null instance or a legacy instance
+
+ ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast<ShaderPackFolderModel>(m_model), m_instance);
+ if (mdownload.exec()) {
+ auto tasks = new ConcurrentTask(this);
+ connect(tasks, &Task::failed, [this, tasks](QString reason) {
+ CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
+ tasks->deleteLater();
+ });
+ connect(tasks, &Task::aborted, [this, tasks]() {
+ CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
+ tasks->deleteLater();
+ });
+ connect(tasks, &Task::succeeded, [this, tasks]() {
+ QStringList warnings = tasks->warnings();
+ if (warnings.count())
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
+
+ tasks->deleteLater();
+ });
+
+ for (auto& task : mdownload.getTasks()) {
+ tasks->addTask(task);
+ }
+
+ ProgressDialog loadDialog(this);
+ loadDialog.setSkipButton(true, tr("Abort"));
+ loadDialog.execWithTask(tasks);
+
+ m_model->update();
+ }
+}
diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h
index 7f7ff8c1..a779fd8c 100644
--- a/launcher/ui/pages/instance/ShaderPackPage.h
+++ b/launcher/ui/pages/instance/ShaderPackPage.h
@@ -1,6 +1,8 @@
-// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
@@ -36,28 +38,21 @@
#pragma once
#include "ExternalResourcesPage.h"
-#include "ui_ExternalResourcesPage.h"
-
-#include "minecraft/mod/ShaderPackFolderModel.h"
class ShaderPackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
- explicit ShaderPackPage(MinecraftInstance *instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget *parent = 0)
- : ExternalResourcesPage(instance, model, parent)
- {
- ui->actionViewConfigs->setVisible(false);
- }
- virtual ~ShaderPackPage() {}
+ explicit ShaderPackPage(MinecraftInstance *instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget *parent = nullptr);
+ ~ShaderPackPage() override = default;
QString displayName() const override { return tr("Shader packs"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("shaderpacks"); }
QString id() const override { return "shaderpacks"; }
QString helpPage() const override { return "Resource-packs"; }
- virtual bool shouldDisplay() const override
- {
- return true;
- }
+ bool shouldDisplay() const override { return true; }
+
+ public slots:
+ void downloadShaders();
};
diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp
new file mode 100644
index 00000000..427aba11
--- /dev/null
+++ b/launcher/ui/pages/instance/TexturePackPage.cpp
@@ -0,0 +1,106 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TexturePackPage.h"
+
+#include "ResourceDownloadTask.h"
+
+#include "minecraft/mod/TexturePack.h"
+
+#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ProgressDialog.h"
+#include "ui/dialogs/ResourceDownloadDialog.h"
+
+TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr<TexturePackFolderModel> model, QWidget* parent)
+ : ExternalResourcesPage(instance, model, parent)
+{
+ ui->actionDownloadItem->setText(tr("Download packs"));
+ ui->actionDownloadItem->setToolTip(tr("Download texture packs from online platforms"));
+ ui->actionDownloadItem->setEnabled(true);
+ connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs);
+ ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
+
+ ui->actionViewConfigs->setVisible(false);
+}
+
+bool TexturePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+ auto sourceCurrent = m_filterModel->mapToSource(current);
+ int row = sourceCurrent.row();
+ auto& rp = static_cast<TexturePack&>(m_model->at(row));
+ ui->frame->updateWithTexturePack(rp);
+
+ return true;
+}
+
+void TexturePackPage::downloadTPs()
+{
+ if (!m_controlsEnabled)
+ return;
+ if (m_instance->typeName() != "Minecraft")
+ return; // this is a null instance or a legacy instance
+
+ ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast<TexturePackFolderModel>(m_model), m_instance);
+ if (mdownload.exec()) {
+ auto tasks = new ConcurrentTask(this);
+ connect(tasks, &Task::failed, [this, tasks](QString reason) {
+ CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
+ tasks->deleteLater();
+ });
+ connect(tasks, &Task::aborted, [this, tasks]() {
+ CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
+ tasks->deleteLater();
+ });
+ connect(tasks, &Task::succeeded, [this, tasks]() {
+ QStringList warnings = tasks->warnings();
+ if (warnings.count())
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
+
+ tasks->deleteLater();
+ });
+
+ for (auto& task : mdownload.getTasks()) {
+ tasks->addTask(task);
+ }
+
+ ProgressDialog loadDialog(this);
+ loadDialog.setSkipButton(true, tr("Abort"));
+ loadDialog.execWithTask(tasks);
+
+ m_model->update();
+ }
+}
diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h
index 69b836ca..47a8fa60 100644
--- a/launcher/ui/pages/instance/TexturePackPage.h
+++ b/launcher/ui/pages/instance/TexturePackPage.h
@@ -1,6 +1,8 @@
-// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
@@ -39,18 +41,12 @@
#include "ui_ExternalResourcesPage.h"
#include "minecraft/mod/TexturePackFolderModel.h"
-#include "minecraft/mod/TexturePack.h"
class TexturePackPage : public ExternalResourcesPage
{
Q_OBJECT
public:
- explicit TexturePackPage(MinecraftInstance *instance, std::shared_ptr<TexturePackFolderModel> model, QWidget *parent = 0)
- : ExternalResourcesPage(instance, model, parent)
- {
- ui->actionViewConfigs->setVisible(false);
- }
- virtual ~TexturePackPage() {}
+ explicit TexturePackPage(MinecraftInstance *instance, std::shared_ptr<TexturePackFolderModel> model, QWidget* parent = nullptr);
QString displayName() const override { return tr("Texture packs"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("resourcepacks"); }
@@ -63,13 +59,6 @@ public:
}
public slots:
- bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override
- {
- auto sourceCurrent = m_filterModel->mapToSource(current);
- int row = sourceCurrent.row();
- auto& rp = static_cast<TexturePack&>(m_model->at(row));
- ui->frame->updateWithTexturePack(rp);
-
- return true;
- }
+ bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
+ void downloadTPs();
};
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index c8a65f10..74b7ec7c 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -1,8 +1,11 @@
-// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2022-2023 Sefa Eyeoglu <contact@scrumplex.net>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022-2023 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
@@ -162,7 +165,7 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
auto proxy = new IconProxy(ui->packageView);
proxy->setSourceModel(m_profile.get());
- m_filterModel = new QSortFilterProxyModel();
+ m_filterModel = new QSortFilterProxyModel(this);
m_filterModel->setDynamicSortFilter(true);
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
@@ -282,6 +285,7 @@ void VersionPage::updateButtons(int row)
ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible());
ui->actionDownload_All->setEnabled(controlsEnabled);
ui->actionAdd_Empty->setEnabled(controlsEnabled);
+ ui->actionImport_Components->setEnabled(controlsEnabled);
ui->actionReload->setEnabled(controlsEnabled);
ui->actionInstall_mods->setEnabled(controlsEnabled);
ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled);
@@ -318,13 +322,29 @@ void VersionPage::on_actionReload_triggered()
void VersionPage::on_actionRemove_triggered()
{
- if (ui->packageView->currentIndex().isValid())
+ if (!ui->packageView->currentIndex().isValid())
{
- // FIXME: use actual model, not reloading.
- if (!m_profile->remove(ui->packageView->currentIndex().row()))
- {
- QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
- }
+ return;
+ }
+ int index = ui->packageView->currentIndex().row();
+ auto component = m_profile->getComponent(index);
+ if (component->isCustom())
+ {
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
+ tr("You are about to remove \"%1\".\n"
+ "This is permanent and will completely remove the custom component.\n\n"
+ "Are you sure?")
+ .arg(component->getName()),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+ }
+ // FIXME: use actual model, not reloading.
+ if (!m_profile->remove(index))
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
}
updateButtons();
reloadPackProfile();
@@ -359,6 +379,20 @@ void VersionPage::on_actionReplace_Minecraft_jar_triggered()
updateButtons();
}
+void VersionPage::on_actionImport_Components_triggered()
+{
+ QStringList list = GuiUtil::BrowseForFiles("component", tr("Select components"), tr("Components (*.json)"),
+ APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
+
+ if (!list.isEmpty()) {
+ if (!m_profile->installComponents(list)) {
+ QMessageBox::warning(this, tr("Failed to import components"),
+ tr("Some components could not be imported. Check logs for details"));
+ }
+ }
+
+ updateButtons();
+}
void VersionPage::on_actionAdd_Agents_triggered()
{
@@ -467,7 +501,7 @@ void VersionPage::on_actionDownload_All_triggered()
return;
}
ProgressDialog tDialog(this);
- connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
+ connect(updateTask.get(), &Task::failed, this, &VersionPage::onGameUpdateError);
// FIXME: unused return value
tDialog.execWithTask(updateTask.get());
updateButtons();
@@ -644,7 +678,7 @@ void VersionPage::onGameUpdateError(QString error)
CustomMessageBox::selectable(this, tr("Error updating instance"), error, QMessageBox::Warning)->show();
}
-Component * VersionPage::current()
+ComponentPtr VersionPage::current()
{
auto row = currentRow();
if(row < 0)
@@ -707,6 +741,19 @@ void VersionPage::on_actionRevert_triggered()
{
return;
}
+ auto component = m_profile->getComponent(version);
+
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Reversion"),
+ tr("You are about to revert \"%1\".\n"
+ "This is permanent and will completely revert your customizations.\n\n"
+ "Are you sure?")
+ .arg(component->getName()),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+
if(!m_profile->revertToBase(version))
{
// TODO: some error box here
diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h
index 166f36bb..d0087714 100644
--- a/launcher/ui/pages/instance/VersionPage.h
+++ b/launcher/ui/pages/instance/VersionPage.h
@@ -1,7 +1,11 @@
-// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2022-2023 Sefa Eyeoglu <contact@scrumplex.net>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2022-2023 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
@@ -86,6 +90,7 @@ private slots:
void on_actionMove_down_triggered();
void on_actionAdd_to_Minecraft_jar_triggered();
void on_actionReplace_Minecraft_jar_triggered();
+ void on_actionImport_Components_triggered();
void on_actionAdd_Agents_triggered();
void on_actionRevert_triggered();
void on_actionEdit_triggered();
@@ -99,7 +104,7 @@ private slots:
void updateVersionControls();
private:
- Component * current();
+ ComponentPtr current();
int currentRow();
void updateButtons(int row = -1);
void preselect(int row = 0);
diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui
index 4cd50885..4777eafe 100644
--- a/launcher/ui/pages/instance/VersionPage.ui
+++ b/launcher/ui/pages/instance/VersionPage.ui
@@ -108,6 +108,7 @@
<addaction name="actionReplace_Minecraft_jar"/>
<addaction name="actionAdd_Agents"/>
<addaction name="actionAdd_Empty"/>
+ <addaction name="actionImport_Components"/>
<addaction name="separator"/>
<addaction name="actionMinecraftFolder"/>
<addaction name="actionLibrariesFolder"/>
@@ -226,10 +227,10 @@
</action>
<action name="actionAdd_Agents">
<property name="text">
- <string>Add Agents</string>
+ <string>Add Agents</string>
</property>
<property name="toolTip">
- <string>Add Java agents.</string>
+ <string>Add Java agents.</string>
</property>
</action>
<action name="actionAdd_Empty">
@@ -272,6 +273,14 @@
<string>Open the instance's local libraries folder.</string>
</property>
</action>
+ <action name="actionImport_Components">
+ <property name="text">
+ <string>Import Components</string>
+ </property>
+ <property name="toolTip">
+ <string>Import existing component JSON files.</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index 93458ce4..b6ad159e 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * 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>
*
* 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
@@ -35,6 +36,7 @@
*/
#include "WorldListPage.h"
+#include "ui/dialogs/CustomMessageBox.h"
#include "ui_WorldListPage.h"
#include "minecraft/WorldList.h"
@@ -43,9 +45,9 @@
#include <QKeyEvent>
#include <QClipboard>
#include <QMessageBox>
+#include <QSortFilterProxyModel>
#include <QTreeView>
#include <QInputDialog>
-#include <QProcess>
#include <Qt>
#include "tools/MCEditTool.h"
@@ -105,6 +107,7 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worl
auto head = ui->worldTreeView->header();
head->setSectionResizeMode(0, QHeaderView::Stretch);
head->setSectionResizeMode(1, QHeaderView::ResizeToContents);
+ head->setSectionResizeMode(4, QHeaderView::ResizeToContents);
connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged);
worldChanged(QModelIndex(), QModelIndex());
@@ -192,12 +195,14 @@ void WorldListPage::on_actionRemove_triggered()
if(!proxiedIndex.isValid())
return;
- auto result = QMessageBox::question(this,
- tr("Are you sure?"),
- tr("This will remove the selected world permenantly.\n"
- "The world will be gone forever (A LONG TIME).\n"
- "\n"
- "Do you want to continue?"));
+ auto result = CustomMessageBox::selectable(this, tr("Confirm Deletion"),
+ tr("You are about to delete \"%1\".\n"
+ "The world may be gone forever (A LONG TIME).\n\n"
+ "Are you sure?")
+ .arg(m_worlds->allWorlds().at(proxiedIndex.row()).name()),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
if(result != QMessageBox::Yes)
{
return;
diff --git a/launcher/ui/pages/instance/WorldListPage.ui b/launcher/ui/pages/instance/WorldListPage.ui
index 7c68bfae..d74dd079 100644
--- a/launcher/ui/pages/instance/WorldListPage.ui
+++ b/launcher/ui/pages/instance/WorldListPage.ui
@@ -109,7 +109,7 @@
</action>
<action name="actionRemove">
<property name="text">
- <string>Remove</string>
+ <string>Delete</string>
</property>
</action>
<action name="actionMCEdit">
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index ed58eb32..afd8b292 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -1,350 +1,70 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
#include "ModModel.h"
-#include "BuildConfig.h"
-#include "Json.h"
-#include "ModPage.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
-#include "ui/dialogs/ModDownloadDialog.h"
-
-#include "ui/widgets/ProjectItem.h"
#include <QMessageBox>
-namespace ModPlatform {
-
-// HACK: We need this to prevent callbacks from calling the ListModel after it has already been deleted.
-// This leaks a tiny bit of memory per time the user has opened the mod dialog. How to make this better?
-static QHash<ListModel*, bool> s_running;
-
-ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) { s_running.insert(this, true); }
-
-ListModel::~ListModel()
-{
- s_running.find(this).value() = false;
-}
+namespace ResourceDownload {
-auto ListModel::debugName() const -> QString
-{
- return m_parent->debugName();
-}
+ModModel::ModModel(BaseInstance const& base_inst, ResourceAPI* api) : ResourceModel(api), m_base_instance(base_inst) {}
/******** Make data requests ********/
-void ListModel::fetchMore(const QModelIndex& parent)
-{
- if (parent.isValid())
- return;
- if (nextSearchOffset == 0) {
- qWarning() << "fetchMore with 0 offset is wrong...";
- return;
- }
- performPaginatedSearch();
-}
-
-auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
-{
- int pos = index.row();
- if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
- return QString("INVALID INDEX %1").arg(pos);
- }
-
- ModPlatform::IndexedPack pack = modpacks.at(pos);
- 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");
- // un-const-ify this
- ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
- return icon;
- }
- case Qt::SizeHintRole:
- return QSize(0, 58);
- case Qt::UserRole: {
- QVariant v;
- v.setValue(pack);
- return v;
- }
- // Custom data
- case UserDataTypes::TITLE:
- return pack.name;
- case UserDataTypes::DESCRIPTION:
- return pack.description;
- case UserDataTypes::SELECTED:
- return m_parent->getDialog()->isModSelected(pack.name);
- default:
- break;
- }
-
- return {};
-}
-
-bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- int pos = index.row();
- if (pos >= modpacks.size() || pos < 0 || !index.isValid())
- return false;
-
- modpacks[pos] = value.value<ModPlatform::IndexedPack>();
-
- return true;
-}
-
-void ListModel::requestModVersions(ModPlatform::IndexedPack const& current, QModelIndex index)
-{
- auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
-
- m_parent->apiProvider()->getVersions({ current.addonId.toString(), getMineVersions(), profile->getModLoaders() },
- [this, current, index](QJsonDocument& doc, QString addonId) {
- if (!s_running.constFind(this).value())
- return;
- versionRequestSucceeded(doc, addonId, index);
- });
-}
-
-void ListModel::performPaginatedSearch()
+ResourceAPI::SearchArgs ModModel::createSearchArguments()
{
- auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
-
- m_parent->apiProvider()->searchMods(
- this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });
-}
+ auto profile = static_cast<MinecraftInstance const&>(m_base_instance).getPackProfile();
-void ListModel::requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index)
-{
- m_parent->apiProvider()->getModInfo(current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) {
- if (!s_running.constFind(this).value())
- return;
- infoRequestFinished(doc, pack, index);
- });
-}
+ Q_ASSERT(profile);
+ Q_ASSERT(m_filter);
-void ListModel::refresh()
-{
- if (jobPtr) {
- jobPtr->abort();
- searchState = ResetRequested;
- return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
- }
- nextSearchOffset = 0;
- performPaginatedSearch();
-}
+ std::optional<std::list<Version>> versions{};
-void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed)
-{
- if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filter_changed) {
- return;
+ { // Version filter
+ if (!m_filter->versions.empty())
+ versions = m_filter->versions;
}
- currentSearchTerm = term;
- currentSort = sort;
-
- refresh();
-}
+ auto sort = getCurrentSortingMethodByIndex();
-void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
-{
- if (m_logoMap.contains(logo)) {
- callback(APPLICATION->metacache()
- ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))
- ->getFullPath());
- } else {
- requestLogo(logo, logoUrl);
- }
+ return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
}
-void ListModel::requestLogo(QString logo, QString url)
+ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
{
- if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) {
- return;
- }
+ auto& pack = *m_packs[entry.row()];
+ auto profile = static_cast<MinecraftInstance const&>(m_base_instance).getPackProfile();
- MetaEntryPtr entry =
- APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
- auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+ Q_ASSERT(profile);
+ Q_ASSERT(m_filter);
- auto fullPath = entry->getFullPath();
- QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
- job->deleteLater();
- emit logoLoaded(logo, QIcon(fullPath));
- if (waitingCallbacks.contains(logo)) {
- waitingCallbacks.value(logo)(fullPath);
- }
- });
+ std::optional<std::list<Version>> versions{};
+ if (!m_filter->versions.empty())
+ versions = m_filter->versions;
- QObject::connect(job, &NetJob::failed, this, [this, logo, job] {
- job->deleteLater();
- emit logoFailed(logo);
- });
-
- job->start();
- m_loadingLogos.append(logo);
-}
-
-/******** Request callbacks ********/
-
-void ListModel::logoLoaded(QString logo, QIcon out)
-{
- m_loadingLogos.removeAll(logo);
- m_logoMap.insert(logo, out);
- for (int i = 0; i < modpacks.size(); i++) {
- if (modpacks[i].logoName == logo) {
- emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
- }
- }
+ return { pack, versions, profile->getModLoaders() };
}
-void ListModel::logoFailed(QString logo)
+ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
{
- m_failedLogos.append(logo);
- m_loadingLogos.removeAll(logo);
+ auto& pack = *m_packs[entry.row()];
+ return { pack };
}
-void ListModel::searchRequestFinished(QJsonDocument& doc)
+void ModModel::searchWithTerm(const QString& term, unsigned int sort, bool filter_changed)
{
- jobPtr.reset();
-
- QList<ModPlatform::IndexedPack> newList;
- auto packs = documentToArray(doc);
-
- for (auto packRaw : packs) {
- auto packObj = packRaw.toObject();
-
- ModPlatform::IndexedPack pack;
- try {
- loadIndexedPack(pack, packObj);
- newList.append(pack);
- } catch (const JSONValidationError& e) {
- qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
- continue;
- }
- }
-
- if (packs.size() < 25) {
- searchState = Finished;
- } else {
- nextSearchOffset += 25;
- searchState = CanPossiblyFetchMore;
- }
-
- // When you have a Qt build with assertions turned on, proceeding here will abort the application
- if (newList.size() == 0)
+ if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort && !filter_changed) {
return;
-
- beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
- modpacks.append(newList);
- endInsertRows();
-}
-
-void ListModel::searchRequestFailed(QString reason)
-{
- auto failed_action = jobPtr->getFailedActions().at(0);
- if (!failed_action->m_reply) {
- // Network error
- QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods."));
- } else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) {
- // 409 Gone, notify user to update
- QMessageBox::critical(nullptr, tr("Error"),
- //: %1 refers to the launcher itself
- QString("%1 %2")
- .arg(m_parent->displayName())
- .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)));
}
- jobPtr.reset();
- searchState = Finished;
-}
-
-void ListModel::searchRequestAborted()
-{
- if (searchState != ResetRequested)
- qCritical() << "Search task in ModModel aborted by an unknown reason!";
-
- // Retry fetching
- jobPtr.reset();
-
- beginResetModel();
- modpacks.clear();
- endResetModel();
-
- nextSearchOffset = 0;
- performPaginatedSearch();
-}
+ setSearchTerm(term);
+ m_current_sort_index = sort;
-void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
-{
- qDebug() << "Loading mod info";
-
- try {
- auto obj = Json::requireObject(doc);
- loadExtraPackInfo(pack, obj);
- } catch (const JSONValidationError& e) {
- qDebug() << doc;
- qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause();
- }
-
- // Check if the index is still valid for this mod or not
- if (pack.addonId == data(index, Qt::UserRole).value<ModPlatform::IndexedPack>().addonId) {
- // Cache info :^)
- QVariant new_pack;
- new_pack.setValue(pack);
- if (!setData(index, new_pack, Qt::UserRole)) {
- qWarning() << "Failed to cache mod info!";
- }
- }
-
- m_parent->updateUi();
-}
-
-void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index)
-{
- auto& current = m_parent->getCurrent();
- if (addonId != current.addonId) {
- return;
- }
-
- auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
-
- try {
- loadIndexedPackVersions(current, arr);
- } catch (const JSONValidationError& e) {
- qDebug() << doc;
- qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause();
- }
-
- // Cache info :^)
- QVariant new_pack;
- new_pack.setValue(current);
- if (!setData(index, new_pack, Qt::UserRole)) {
- qWarning() << "Failed to cache mod versions!";
- }
-
-
- m_parent->updateModVersions();
+ refresh();
}
-} // namespace ModPlatform
-
-/******** Helpers ********/
-
-auto ModPlatform::ListModel::getMineVersions() const -> std::list<Version>
-{
- return m_parent->getFilter()->versions;
-}
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h
index 36840649..5d4a7785 100644
--- a/launcher/ui/pages/modplatform/ModModel.h
+++ b/launcher/ui/pages/modplatform/ModModel.h
@@ -1,92 +1,52 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
#pragma once
#include <QAbstractListModel>
+#include "BaseInstance.h"
+
#include "modplatform/ModIndex.h"
-#include "net/NetJob.h"
+#include "modplatform/ResourceAPI.h"
+
+#include "ui/pages/modplatform/ResourceModel.h"
+#include "ui/widgets/ModFilterWidget.h"
-class ModPage;
class Version;
-namespace ModPlatform {
+namespace ResourceDownload {
-using LogoMap = QMap<QString, QIcon>;
-using LogoCallback = std::function<void (QString)>;
+class ModPage;
-class ListModel : public QAbstractListModel {
+class ModModel : public ResourceModel {
Q_OBJECT
public:
- ListModel(ModPage* parent);
- ~ListModel() override;
-
- inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); };
- inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; };
- inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
-
- auto debugName() const -> QString;
-
- /* Retrieve information from the model at a given index with the given role */
- auto data(const QModelIndex& index, int role) const -> QVariant override;
- bool setData(const QModelIndex &index, const QVariant &value, int role) override;
-
- inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; }
- inline NetJob* activeJob() { return jobPtr.get(); }
+ ModModel(const BaseInstance&, ResourceAPI* api);
/* Ask the API for more information */
- void fetchMore(const QModelIndex& parent) override;
- void refresh();
- void searchWithTerm(const QString& term, const int sort, const bool filter_changed);
- void requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index);
- void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index);
+ void searchWithTerm(const QString& term, unsigned int sort, bool filter_changed);
- virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
- virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
- virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0;
+ void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
+ void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override = 0;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override = 0;
- void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
-
- inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; };
+ void setFilter(std::shared_ptr<ModFilterWidget::Filter> filter) { m_filter = filter; }
public slots:
- void searchRequestFinished(QJsonDocument& doc);
- void searchRequestFailed(QString reason);
- void searchRequestAborted();
-
- void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index);
-
- void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index);
-
- protected slots:
-
- void logoFailed(QString logo);
- void logoLoaded(QString logo, QIcon out);
-
- void performPaginatedSearch();
+ ResourceAPI::SearchArgs createSearchArguments() override;
+ ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override;
+ ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override;
protected:
- virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0;
- virtual auto getSorts() const -> const char** = 0;
-
- void requestLogo(QString file, QString url);
-
- inline auto getMineVersions() const -> std::list<Version>;
+ auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0;
protected:
- ModPage* m_parent;
-
- QList<ModPlatform::IndexedPack> modpacks;
-
- LogoMap m_logoMap;
- QMap<QString, LogoCallback> waitingCallbacks;
- QStringList m_failedLogos;
- QStringList m_loadingLogos;
+ const BaseInstance& m_base_instance;
- QString currentSearchTerm;
- int currentSort = 0;
- int nextSearchOffset = 0;
- enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
-
- NetJob::Ptr jobPtr;
+ std::shared_ptr<ModFilterWidget::Filter> m_filter = nullptr;
};
-} // namespace ModPlatform
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 677bc4d6..04be43ad 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -1,4 +1,6 @@
-// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
@@ -35,61 +37,30 @@
*/
#include "ModPage.h"
-#include "Application.h"
-#include "ui_ModPage.h"
+#include "ui_ResourcePage.h"
#include <QDesktopServices>
#include <QKeyEvent>
#include <QRegularExpression>
+
#include <memory>
-#include <HoeDown.h>
+#include "Application.h"
+#include "ResourceDownloadTask.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
-#include "ui/dialogs/ModDownloadDialog.h"
-#include "ui/widgets/ProjectItem.h"
-
-
-ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
- : QWidget(dialog)
- , m_instance(instance)
- , ui(new Ui::ModPage)
- , dialog(dialog)
- , m_fetch_progress(this, false)
- , api(api)
-{
- ui->setupUi(this);
-
- connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch);
- connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
- connect(ui->packView, &QListView::doubleClicked, this, &ModPage::onModSelected);
- m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
- m_search_timer.setSingleShot(true);
+#include "ui/dialogs/ResourceDownloadDialog.h"
- connect(&m_search_timer, &QTimer::timeout, this, &ModPage::triggerSearch);
-
- ui->searchEdit->installEventFilter(this);
-
- ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
-
- m_fetch_progress.hideIfInactive(true);
- m_fetch_progress.setFixedHeight(24);
- m_fetch_progress.progressFormat("");
-
- ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, ui->gridLayout_3->columnCount());
-
- ui->packView->setItemDelegate(new ProjectItemDelegate(this));
- ui->packView->installEventFilter(this);
-
- connect(ui->packDescription, &QTextBrowser::anchorClicked, this, &ModPage::openUrl);
-}
+namespace ResourceDownload {
-ModPage::~ModPage()
+ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance)
+ : ResourcePage(dialog, instance)
{
- delete ui;
+ connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch);
+ connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
+ connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected);
}
void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& widget)
@@ -99,59 +70,19 @@ void ModPage::setFilterWidget(unique_qobject_ptr<ModFilterWidget>& widget)
m_filter_widget.swap(widget);
- ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, ui->gridLayout_3->columnCount());
+ m_ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, m_ui->gridLayout_3->columnCount());
- m_filter_widget->setInstance(static_cast<MinecraftInstance*>(m_instance));
+ m_filter_widget->setInstance(&static_cast<MinecraftInstance&>(m_base_instance));
m_filter = m_filter_widget->getFilter();
connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{
- ui->searchButton->setStyleSheet("text-decoration: underline");
+ m_ui->searchButton->setStyleSheet("text-decoration: underline");
});
connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{
- ui->searchButton->setStyleSheet("text-decoration: none");
+ m_ui->searchButton->setStyleSheet("text-decoration: none");
});
}
-
-/******** Qt things ********/
-
-void ModPage::openedImpl()
-{
- updateSelectionButton();
- triggerSearch();
-}
-
-auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- } else {
- if (m_search_timer.isActive())
- m_search_timer.stop();
-
- m_search_timer.start(350);
- }
- } else if (watched == ui->packView && event->type() == QEvent::KeyPress) {
- auto* keyEvent = dynamic_cast<QKeyEvent*>(event);
- if (keyEvent->key() == Qt::Key_Return) {
- onModSelected();
-
- // To have the 'select mod' button outlined instead of the 'review and confirm' one
- ui->modSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason);
- ui->packView->setFocus(Qt::FocusReason::NoFocusReason);
-
- keyEvent->accept();
- return true;
- }
- }
- return QWidget::eventFilter(watched, event);
-}
-
-
/******** Callbacks to events in the UI (set up in the derived classes) ********/
void ModPage::filterMods()
@@ -165,176 +96,37 @@ void ModPage::triggerSearch()
m_filter = m_filter_widget->getFilter();
if (changed) {
- ui->packView->clearSelection();
- ui->packDescription->clear();
- ui->versionSelectionBox->clear();
- updateSelectionButton();
- }
-
- listModel->searchWithTerm(getSearchTerm(), ui->sortByBox->currentIndex(), changed);
- m_fetch_progress.watch(listModel->activeJob());
-}
-
-QString ModPage::getSearchTerm() const
-{
- return ui->searchEdit->text();
-}
-void ModPage::setSearchTerm(QString term)
-{
- ui->searchEdit->setText(term);
-}
-
-void ModPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
-{
- ui->versionSelectionBox->clear();
-
- if (!curr.isValid()) { return; }
-
- current = listModel->data(curr, Qt::UserRole).value<ModPlatform::IndexedPack>();
-
- if (!current.versionsLoaded) {
- qDebug() << QString("Loading %1 mod versions").arg(debugName());
-
- ui->modSelectionButton->setText(tr("Loading versions..."));
- ui->modSelectionButton->setEnabled(false);
-
- listModel->requestModVersions(current, curr);
- } else {
- for (int i = 0; i < current.versions.size(); i++) {
- ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
- }
- if (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); }
-
+ m_ui->packView->clearSelection();
+ m_ui->packDescription->clear();
+ m_ui->versionSelectionBox->clear();
updateSelectionButton();
}
- if(!current.extraDataLoaded){
- qDebug() << QString("Loading %1 mod info").arg(debugName());
-
- listModel->requestModInfo(current, curr);
- }
-
- updateUi();
-}
-
-void ModPage::onVersionSelectionChanged(QString data)
-{
- if (data.isNull() || data.isEmpty()) {
- selectedVersion = -1;
- return;
- }
- selectedVersion = ui->versionSelectionBox->currentData().toInt();
- updateSelectionButton();
-}
-
-void ModPage::onModSelected()
-{
- if (selectedVersion < 0)
- return;
-
- auto& version = current.versions[selectedVersion];
- if (dialog->isModSelected(current.name, version.fileName)) {
- dialog->removeSelectedMod(current.name);
- } else {
- bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
- dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed));
- }
-
- updateSelectionButton();
-
- /* Force redraw on the mods list when the selection changes */
- ui->packView->adjustSize();
+ static_cast<ModModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt(), changed);
+ m_fetch_progress.watch(m_model->activeSearchJob().get());
}
-static const QRegularExpression modrinth(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?"));
-static const QRegularExpression curseForge(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?"));
-static const QRegularExpression curseForgeOld(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"));
-
-void ModPage::openUrl(const QUrl& url)
+QMap<QString, QString> ModPage::urlHandlers() const
{
- // do not allow other url schemes for security reasons
- if (!(url.scheme() == "http" || url.scheme() == "https")) {
- qWarning() << "Unsupported scheme" << url.scheme();
- return;
- }
-
- // detect mod URLs and search instead
-
- const QString address = url.host() + url.path();
- QRegularExpressionMatch match;
- QString page;
-
- match = modrinth.match(address);
- if (match.hasMatch())
- page = "modrinth";
- else if (APPLICATION->capabilities() & Application::SupportsFlame) {
- match = curseForge.match(address);
- if (!match.hasMatch())
- match = curseForgeOld.match(address);
-
- if (match.hasMatch())
- page = "curseforge";
- }
-
- if (!page.isNull()) {
- const QString slug = match.captured(1);
-
- // ensure the user isn't opening the same mod
- if (slug != current.slug) {
- dialog->selectPage(page);
-
- ModPage* newPage = dialog->getSelectedPage();
-
- QLineEdit* searchEdit = newPage->ui->searchEdit;
- ModPlatform::ListModel* model = newPage->listModel;
- QListView* view = newPage->ui->packView;
-
- auto jump = [url, slug, model, view] {
- for (int row = 0; row < model->rowCount({}); row++) {
- const QModelIndex index = model->index(row);
- const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
-
- if (pack.slug == slug) {
- view->setCurrentIndex(index);
- return;
- }
- }
-
- // The final fallback.
- QDesktopServices::openUrl(url);
- };
-
- searchEdit->setText(slug);
- newPage->triggerSearch();
-
- if (model->activeJob())
- connect(model->activeJob(), &Task::finished, jump);
- else
- jump();
-
- return;
- }
- }
-
- // open in the user's web browser
- QDesktopServices::openUrl(url);
+ QMap<QString, QString> map;
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?"), "modrinth");
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?"), "curseforge");
+ map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
+ return map;
}
/******** Make changes to the UI ********/
-void ModPage::retranslate()
-{
- ui->retranslateUi(this);
-}
-
-void ModPage::updateModVersions(int prev_count)
+void ModPage::updateVersionList()
{
- auto packProfile = (dynamic_cast<MinecraftInstance*>(m_instance))->getPackProfile();
+ m_ui->versionSelectionBox->clear();
+ auto packProfile = (dynamic_cast<MinecraftInstance&>(m_base_instance)).getPackProfile();
QString mcVersion = packProfile->getComponentVersion("net.minecraft");
- for (int i = 0; i < current.versions.size(); i++) {
- auto version = current.versions[i];
+ auto current_pack = getCurrentPack();
+ for (int i = 0; i < current_pack.versions.size(); i++) {
+ auto version = current_pack.versions[i];
bool valid = false;
for(auto& mcVer : m_filter->versions){
//NOTE: Flame doesn't care about loader, so passing it changes nothing.
@@ -346,88 +138,20 @@ void ModPage::updateModVersions(int prev_count)
// 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))
- ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ m_ui->versionSelectionBox->addItem(version.version, QVariant(i));
}
- if (ui->versionSelectionBox->count() == 0 && prev_count != 0) {
- ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1));
- ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
+ if (m_ui->versionSelectionBox->count() == 0) {
+ m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1));
+ m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :("));
}
updateSelectionButton();
}
-
-void ModPage::updateSelectionButton()
+void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
{
- if (!isOpened || selectedVersion < 0) {
- ui->modSelectionButton->setEnabled(false);
- return;
- }
-
- ui->modSelectionButton->setEnabled(true);
- auto& version = current.versions[selectedVersion];
- if (!dialog->isModSelected(current.name, version.fileName)) {
- ui->modSelectionButton->setText(tr("Select mod for download"));
- } else {
- ui->modSelectionButton->setText(tr("Deselect mod for download"));
- }
+ bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
+ m_parent_dialog->addResource(pack, version, is_indexed);
}
-void ModPage::updateUi()
-{
- QString text = "";
- QString name = current.name;
-
- if (current.websiteUrl.isEmpty())
- text = name;
- else
- text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
-
- if (!current.authors.empty()) {
- auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
- if (author.url.isEmpty()) { return author.name; }
- return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
- };
- QStringList authorStrs;
- for (auto& author : current.authors) {
- authorStrs.push_back(authorToStr(author));
- }
- text += "<br>" + tr(" by ") + authorStrs.join(", ");
- }
-
- if (current.extraDataLoaded) {
- if (!current.extraData.donate.isEmpty()) {
- text += "<br><br>" + tr("Donate information: ");
- auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
- return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform);
- };
- QStringList donates;
- for (auto& donate : current.extraData.donate) {
- donates.append(donateToStr(donate));
- }
- text += donates.join(", ");
- }
-
- if (!current.extraData.issuesUrl.isEmpty()
- || !current.extraData.sourceUrl.isEmpty()
- || !current.extraData.wikiUrl.isEmpty()
- || !current.extraData.discordUrl.isEmpty()) {
- text += "<br><br>" + tr("External links:") + "<br>";
- }
-
- if (!current.extraData.issuesUrl.isEmpty())
- text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current.extraData.issuesUrl) + "<br>";
- if (!current.extraData.wikiUrl.isEmpty())
- text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current.extraData.wikiUrl) + "<br>";
- if (!current.extraData.sourceUrl.isEmpty())
- text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extraData.sourceUrl) + "<br>";
- if (!current.extraData.discordUrl.isEmpty())
- text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current.extraData.discordUrl) + "<br>";
- }
-
- text += "<hr>";
-
- HoeDown h;
- ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8())));
- ui->packDescription->flush();
-}
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index c9ccbaf2..4ea55efa 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -1,105 +1,74 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
#pragma once
#include <QWidget>
-#include "Application.h"
-#include "modplatform/ModAPI.h"
#include "modplatform/ModIndex.h"
-#include "ui/pages/BasePage.h"
+
+#include "ui/pages/modplatform/ResourcePage.h"
#include "ui/pages/modplatform/ModModel.h"
#include "ui/widgets/ModFilterWidget.h"
-#include "ui/widgets/ProgressWidget.h"
-
-class ModDownloadDialog;
namespace Ui {
-class ModPage;
+class ResourcePage;
}
+namespace ResourceDownload {
+
+class ModDownloadDialog;
+
/* This page handles most logic related to browsing and selecting mods to download. */
-class ModPage : public QWidget, public BasePage {
+class ModPage : public ResourcePage {
Q_OBJECT
public:
template<typename T>
- static T* create(ModDownloadDialog* dialog, BaseInstance* instance)
+ static T* create(ModDownloadDialog* dialog, BaseInstance& instance)
{
auto page = new T(dialog, instance);
+ auto model = static_cast<ModModel*>(page->getModel());
- auto filter_widget = ModFilterWidget::create(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), page);
+ auto filter_widget = ModFilterWidget::create(static_cast<MinecraftInstance&>(instance).getPackProfile()->getComponentVersion("net.minecraft"), page);
page->setFilterWidget(filter_widget);
+ model->setFilter(page->getFilter());
+
+ connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList);
+ connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
return page;
}
- ~ModPage() override;
-
- /* Affects what the user sees */
- auto displayName() const -> QString override = 0;
- auto icon() const -> QIcon override = 0;
- auto id() const -> QString override = 0;
- auto helpPage() const -> QString override = 0;
-
- /* Used internally */
- virtual auto metaEntryBase() const -> QString = 0;
- virtual auto debugName() const -> QString = 0;
-
+ //: The plural version of 'mod'
+ [[nodiscard]] inline QString resourcesString() const override { return tr("mods"); }
+ //: The singular version of 'mods'
+ [[nodiscard]] inline QString resourceString() const override { return tr("mod"); }
- void retranslate() override;
+ [[nodiscard]] QMap<QString, QString> urlHandlers() const override;
- void updateUi();
+ void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override;
- auto shouldDisplay() const -> bool override = 0;
- virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0;
- virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; };
+ virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
- auto apiProvider() -> ModAPI* { return api.get(); };
+ [[nodiscard]] bool supportsFiltering() const override { return true; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
- auto getDialog() const -> const ModDownloadDialog* { return dialog; }
-
- /** Get the current term in the search bar. */
- auto getSearchTerm() const -> QString;
- /** Programatically set the term in the search bar. */
- void setSearchTerm(QString);
-
void setFilterWidget(unique_qobject_ptr<ModFilterWidget>&);
- auto getCurrent() -> ModPlatform::IndexedPack& { return current; }
- void updateModVersions(int prev_count = -1);
-
- void openedImpl() override;
- auto eventFilter(QObject* watched, QEvent* event) -> bool override;
-
- BaseInstance* m_instance;
+ public slots:
+ void updateVersionList() override;
protected:
- ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api);
- void updateSelectionButton();
+ ModPage(ModDownloadDialog* dialog, BaseInstance& instance);
protected slots:
virtual void filterMods();
- void triggerSearch();
- void onSelectionChanged(QModelIndex first, QModelIndex second);
- void onVersionSelectionChanged(QString data);
- void onModSelected();
- virtual void openUrl(const QUrl& url);
+ void triggerSearch() override;
protected:
- Ui::ModPage* ui = nullptr;
- ModDownloadDialog* dialog = nullptr;
-
unique_qobject_ptr<ModFilterWidget> m_filter_widget;
std::shared_ptr<ModFilterWidget::Filter> m_filter;
-
- ProgressWidget m_fetch_progress;
-
- ModPlatform::ListModel* listModel = nullptr;
- ModPlatform::IndexedPack current;
-
- std::unique_ptr<ModAPI> api;
-
- int selectedVersion = -1;
-
- // Used to do instant searching with a delay to cache quick changes
- QTimer m_search_timer;
};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
new file mode 100644
index 00000000..472aa851
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -0,0 +1,445 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "ResourceModel.h"
+
+#include <QCryptographicHash>
+#include <QIcon>
+#include <QMessageBox>
+#include <QPixmapCache>
+#include <QUrl>
+#include <memory>
+
+#include "Application.h"
+#include "BuildConfig.h"
+#include "Json.h"
+
+#include "net/Download.h"
+#include "net/NetJob.h"
+
+#include "modplatform/ModIndex.h"
+
+#include "ui/widgets/ProjectItem.h"
+
+namespace ResourceDownload {
+
+QHash<ResourceModel*, bool> ResourceModel::s_running_models;
+
+ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api)
+{
+ s_running_models.insert(this, true);
+}
+
+ResourceModel::~ResourceModel()
+{
+ s_running_models.find(this).value() = false;
+}
+
+auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant
+{
+ int pos = index.row();
+ if (pos >= m_packs.size() || pos < 0 || !index.isValid()) {
+ return QString("INVALID INDEX %1").arg(pos);
+ }
+
+ auto pack = m_packs.at(pos);
+ 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 (auto icon_or_none = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(index), pack->logoUrl);
+ icon_or_none.has_value())
+ return icon_or_none.value();
+
+ return APPLICATION->getThemedIcon("screenshot-placeholder");
+ }
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(*pack);
+ return v;
+ }
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack->name;
+ case UserDataTypes::DESCRIPTION:
+ return pack->description;
+ case UserDataTypes::SELECTED:
+ return pack->isAnyVersionSelected();
+ default:
+ break;
+ }
+
+ return {};
+}
+
+QHash<int, QByteArray> ResourceModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+
+ roles[Qt::ToolTipRole] = "toolTip";
+ roles[Qt::DecorationRole] = "decoration";
+ roles[Qt::SizeHintRole] = "sizeHint";
+ roles[Qt::UserRole] = "pack";
+ roles[UserDataTypes::TITLE] = "title";
+ roles[UserDataTypes::DESCRIPTION] = "description";
+ roles[UserDataTypes::SELECTED] = "selected";
+
+ return roles;
+}
+
+bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ int pos = index.row();
+ if (pos >= m_packs.size() || pos < 0 || !index.isValid())
+ return false;
+
+ m_packs[pos] = std::make_shared<ModPlatform::IndexedPack>(value.value<ModPlatform::IndexedPack>());
+ emit dataChanged(index, index);
+
+ return true;
+}
+
+QString ResourceModel::debugName() const
+{
+ return "ResourceDownload (Model)";
+}
+
+void ResourceModel::fetchMore(const QModelIndex& parent)
+{
+ if (parent.isValid() || m_search_state == SearchState::Finished)
+ return;
+
+ search();
+}
+
+void ResourceModel::search()
+{
+ if (hasActiveSearchJob())
+ return;
+
+ auto args{ createSearchArguments() };
+
+ auto callbacks{ createSearchCallbacks() };
+
+ // Use defaults if no callbacks are set
+ if (!callbacks.on_succeed)
+ callbacks.on_succeed = [this](auto& doc) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestSucceeded(doc);
+ };
+ if (!callbacks.on_fail)
+ callbacks.on_fail = [this](QString reason, int network_error_code) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestFailed(reason, network_error_code);
+ };
+ if (!callbacks.on_abort)
+ callbacks.on_abort = [this] {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestAborted();
+ };
+
+ if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job)
+ runSearchJob(job);
+}
+
+void ResourceModel::loadEntry(QModelIndex& entry)
+{
+ auto const& pack = m_packs[entry.row()];
+
+ if (!hasActiveInfoJob())
+ m_current_info_job.clear();
+
+ if (!pack->versionsLoaded) {
+ auto args{ createVersionsArguments(entry) };
+ auto callbacks{ createVersionsCallbacks(entry) };
+
+ // Use default if no callbacks are set
+ if (!callbacks.on_succeed)
+ callbacks.on_succeed = [this, entry](auto& doc, auto pack) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ versionRequestSucceeded(doc, pack, entry);
+ };
+
+ if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job)
+ runInfoJob(job);
+ }
+
+ if (!pack->extraDataLoaded) {
+ auto args{ createInfoArguments(entry) };
+ auto callbacks{ createInfoCallbacks(entry) };
+
+ // Use default if no callbacks are set
+ if (!callbacks.on_succeed)
+ callbacks.on_succeed = [this, entry](auto& doc, auto pack) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ infoRequestSucceeded(doc, pack, entry);
+ };
+
+ if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
+ runInfoJob(job);
+ }
+}
+
+void ResourceModel::refresh()
+{
+ bool reset_requested = false;
+
+ if (hasActiveInfoJob()) {
+ m_current_info_job.abort();
+ reset_requested = true;
+ }
+
+ if (hasActiveSearchJob()) {
+ m_current_search_job->abort();
+ reset_requested = true;
+ }
+
+ if (reset_requested) {
+ m_search_state = SearchState::ResetRequested;
+ return;
+ }
+
+ clearData();
+ m_search_state = SearchState::None;
+
+ m_next_search_offset = 0;
+ search();
+}
+
+void ResourceModel::clearData()
+{
+ beginResetModel();
+ m_packs.clear();
+ endResetModel();
+}
+
+void ResourceModel::runSearchJob(Task::Ptr ptr)
+{
+ m_current_search_job.reset(ptr); // clean up first
+ m_current_search_job->start();
+}
+void ResourceModel::runInfoJob(Task::Ptr ptr)
+{
+ if (!m_current_info_job.isRunning())
+ m_current_info_job.clear();
+
+ m_current_info_job.addTask(ptr);
+
+ if (!m_current_info_job.isRunning())
+ m_current_info_job.run();
+}
+
+std::optional<ResourceAPI::SortingMethod> ResourceModel::getCurrentSortingMethodByIndex() const
+{
+ std::optional<ResourceAPI::SortingMethod> sort{};
+
+ { // Find sorting method by ID
+ auto sorting_methods = getSortingMethods();
+ auto method = std::find_if(sorting_methods.constBegin(), sorting_methods.constEnd(),
+ [this](auto const& e) { return m_current_sort_index == e.index; });
+ if (method != sorting_methods.constEnd())
+ sort = *method;
+ }
+
+ return sort;
+}
+
+std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
+{
+ QPixmap pixmap;
+ if (QPixmapCache::find(url.toString(), &pixmap))
+ return { pixmap };
+
+ if (!m_current_icon_job)
+ m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network()));
+
+ if (m_currently_running_icon_actions.contains(url))
+ return {};
+ if (m_failed_icon_actions.contains(url))
+ return {};
+
+ auto cache_entry = APPLICATION->metacache()->resolveEntry(
+ metaEntryBase(),
+ QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
+ auto icon_fetch_action = Net::Download::makeCached(url, cache_entry);
+
+ auto full_file_path = cache_entry->getFullPath();
+ connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
+ auto icon = QIcon(full_file_path);
+ QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 })));
+
+ m_currently_running_icon_actions.remove(url);
+
+ emit dataChanged(index, index, { Qt::DecorationRole });
+ });
+ connect(icon_fetch_action.get(), &NetAction::failed, this, [=] {
+ m_currently_running_icon_actions.remove(url);
+ m_failed_icon_actions.insert(url);
+ });
+
+ m_currently_running_icon_actions.insert(url);
+
+ m_current_icon_job->addNetAction(icon_fetch_action);
+ if (!m_current_icon_job->isRunning())
+ QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start);
+
+ return {};
+}
+
+// No 'forgor to implement' shall pass here :blobfox_knife:
+#define NEED_FOR_CALLBACK_ASSERT(name) \
+ Q_ASSERT_X(0 != 0, #name, "You NEED to re-implement this if you intend on using the default callbacks.")
+
+QJsonArray ResourceModel::documentToArray(QJsonDocument& doc) const
+{
+ NEED_FOR_CALLBACK_ASSERT("documentToArray");
+ return {};
+}
+void ResourceModel::loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&)
+{
+ NEED_FOR_CALLBACK_ASSERT("loadIndexedPack");
+}
+void ResourceModel::loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&)
+{
+ NEED_FOR_CALLBACK_ASSERT("loadExtraPackInfo");
+}
+void ResourceModel::loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&)
+{
+ NEED_FOR_CALLBACK_ASSERT("loadIndexedPackVersions");
+}
+
+/* Default callbacks */
+
+void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
+{
+ QList<ModPlatform::IndexedPack::Ptr> newList;
+ auto packs = documentToArray(doc);
+
+ for (auto packRaw : packs) {
+ auto packObj = packRaw.toObject();
+
+ ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
+ try {
+ loadIndexedPack(*pack, packObj);
+ newList.append(pack);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading resource from " << debugName() << ": " << e.cause();
+ continue;
+ }
+ }
+
+ if (packs.size() < 25) {
+ m_search_state = SearchState::Finished;
+ } else {
+ m_next_search_offset += 25;
+ m_search_state = SearchState::CanFetchMore;
+ }
+
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (newList.size() == 0)
+ return;
+
+ beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1);
+ m_packs.append(newList);
+ endInsertRows();
+}
+
+void ResourceModel::searchRequestFailed(QString reason, int network_error_code)
+{
+ switch (network_error_code) {
+ default:
+ // Network error
+ QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods."));
+ break;
+ case 409:
+ // 409 Gone, notify user to update
+ QMessageBox::critical(nullptr, tr("Error"),
+ QString("%1").arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)));
+ break;
+ }
+
+ m_search_state = SearchState::Finished;
+}
+
+void ResourceModel::searchRequestAborted()
+{
+ if (m_search_state != SearchState::ResetRequested)
+ qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!";
+
+ // Retry fetching
+ clearData();
+
+ m_next_search_offset = 0;
+ search();
+}
+
+void ResourceModel::versionRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
+{
+ auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
+
+ // Check if the index is still valid for this resource or not
+ if (pack.addonId != current_pack.addonId)
+ return;
+
+ try {
+ auto arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
+ loadIndexedPackVersions(current_pack, arr);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading " << debugName() << " resource version: " << e.cause();
+ }
+
+ // Cache info :^)
+ QVariant new_pack;
+ new_pack.setValue(current_pack);
+ if (!setData(index, new_pack, Qt::UserRole)) {
+ qWarning() << "Failed to cache resource versions!";
+ return;
+ }
+
+ emit versionListUpdated();
+}
+
+void ResourceModel::infoRequestSucceeded(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
+{
+ auto current_pack = data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
+
+ // Check if the index is still valid for this resource or not
+ if (pack.addonId != current_pack.addonId)
+ return;
+
+ try {
+ auto obj = Json::requireObject(doc);
+ loadExtraPackInfo(current_pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
+ }
+
+ // Cache info :^)
+ QVariant new_pack;
+ new_pack.setValue(current_pack);
+ if (!setData(index, new_pack, Qt::UserRole)) {
+ qWarning() << "Failed to cache resource info!";
+ return;
+ }
+
+ emit projectInfoUpdated();
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h
new file mode 100644
index 00000000..1ec42cda
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourceModel.h
@@ -0,0 +1,147 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include <optional>
+
+#include <QAbstractListModel>
+
+#include "QObjectPtr.h"
+
+#include "modplatform/ResourceAPI.h"
+
+#include "tasks/ConcurrentTask.h"
+
+class NetJob;
+class ResourceAPI;
+
+namespace ModPlatform {
+struct IndexedPack;
+}
+
+namespace ResourceDownload {
+
+class ResourceModel : public QAbstractListModel {
+ Q_OBJECT
+
+ Q_PROPERTY(QString search_term MEMBER m_search_term WRITE setSearchTerm)
+
+ public:
+ ResourceModel(ResourceAPI* api);
+ ~ResourceModel() override;
+
+ [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override;
+ [[nodiscard]] auto roleNames() const -> QHash<int, QByteArray> override;
+ bool setData(const QModelIndex& index, const QVariant& value, int role) override;
+
+ [[nodiscard]] virtual auto debugName() const -> QString;
+ [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0;
+
+ [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); }
+ [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }
+ [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }
+
+ [[nodiscard]] bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); }
+ [[nodiscard]] bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? m_current_search_job : nullptr; }
+
+ [[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); }
+
+ public slots:
+ void fetchMore(const QModelIndex& parent) override;
+ // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12
+ inline bool canFetchMore(const QModelIndex& parent) const override
+ {
+ return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore;
+ }
+
+ void setSearchTerm(QString term) { m_search_term = term; }
+
+ virtual ResourceAPI::SearchArgs createSearchArguments() = 0;
+ virtual ResourceAPI::SearchCallbacks createSearchCallbacks() { return {}; }
+
+ virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0;
+ virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) { return {}; }
+
+ virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0;
+ virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) { return {}; }
+
+ /** Requests the API for more entries. */
+ virtual void search();
+
+ /** Applies any processing / extra requests needed to fully load the specified entry's information. */
+ virtual void loadEntry(QModelIndex&);
+
+ /** Schedule a refresh, clearing the current state. */
+ void refresh();
+
+ /** Gets the icon at the URL for the given index. If it's not fetched yet, fetch it and update when fisinhed. */
+ std::optional<QIcon> getIcon(QModelIndex&, const QUrl&);
+
+ protected:
+ /** Resets the model's data. */
+ void clearData();
+
+ void runSearchJob(Task::Ptr);
+ void runInfoJob(Task::Ptr);
+
+ [[nodiscard]] auto getCurrentSortingMethodByIndex() const -> std::optional<ResourceAPI::SortingMethod>;
+
+ /** Converts a JSON document to a common array format.
+ *
+ * This is needed so that different providers, with different JSON structures, can be parsed
+ * uniformally. You NEED to re-implement this if you intend on using the default callbacks.
+ */
+ [[nodiscard]] virtual auto documentToArray(QJsonDocument&) const -> QJsonArray;
+
+ /** Functions to load data into a pack.
+ *
+ * Those are needed for the same reason as documentToArray, and NEED to be re-implemented in the same way.
+ */
+
+ virtual void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&);
+ virtual void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&);
+ virtual void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&);
+
+ protected:
+ /* Basic search parameters */
+ enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None;
+ int m_next_search_offset = 0;
+ QString m_search_term;
+ unsigned int m_current_sort_index = 0;
+
+ std::unique_ptr<ResourceAPI> m_api;
+
+ // Job for searching for new entries
+ shared_qobject_ptr<Task> m_current_search_job;
+ // Job for fetching versions and extra info on existing entries
+ ConcurrentTask m_current_info_job;
+
+ shared_qobject_ptr<NetJob> m_current_icon_job;
+ QSet<QUrl> m_currently_running_icon_actions;
+ QSet<QUrl> m_failed_icon_actions;
+
+ QList<ModPlatform::IndexedPack::Ptr> m_packs;
+
+ // HACK: We need this to prevent callbacks from calling the model after it has already been deleted.
+ // This leaks a tiny bit of memory per time the user has opened a resource dialog. How to make this better?
+ static QHash<ResourceModel*, bool> s_running_models;
+
+ private:
+ /* Default search request callbacks */
+ void searchRequestSucceeded(QJsonDocument&);
+ void searchRequestFailed(QString reason, int network_error_code);
+ void searchRequestAborted();
+
+ void versionRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&);
+
+ void infoRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&);
+
+ signals:
+ void versionListUpdated();
+ void projectInfoUpdated();
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp
new file mode 100644
index 00000000..18c14bf8
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "ResourcePackModel.h"
+
+#include <QMessageBox>
+
+namespace ResourceDownload {
+
+ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
+ : ResourceModel(api), m_base_instance(base_inst){};
+
+/******** Make data requests ********/
+
+ResourceAPI::SearchArgs ResourcePackResourceModel::createSearchArguments()
+{
+ auto sort = getCurrentSortingMethodByIndex();
+ return { ModPlatform::ResourceType::RESOURCE_PACK, m_next_search_offset, m_search_term, sort };
+}
+
+ResourceAPI::VersionSearchArgs ResourcePackResourceModel::createVersionsArguments(QModelIndex& entry)
+{
+ auto& pack = m_packs[entry.row()];
+ return { *pack };
+}
+
+ResourceAPI::ProjectInfoArgs ResourcePackResourceModel::createInfoArguments(QModelIndex& entry)
+{
+ auto& pack = m_packs[entry.row()];
+ return { *pack };
+}
+
+void ResourcePackResourceModel::searchWithTerm(const QString& term, unsigned int sort)
+{
+ if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) {
+ return;
+ }
+
+ setSearchTerm(term);
+ m_current_sort_index = sort;
+
+ refresh();
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.h b/launcher/ui/pages/modplatform/ResourcePackModel.h
new file mode 100644
index 00000000..e2b4a195
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourcePackModel.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include <QAbstractListModel>
+
+#include "BaseInstance.h"
+
+#include "modplatform/ModIndex.h"
+
+#include "ui/pages/modplatform/ResourceModel.h"
+
+class Version;
+
+namespace ResourceDownload {
+
+class ResourcePackResourceModel : public ResourceModel {
+ Q_OBJECT
+
+ public:
+ ResourcePackResourceModel(BaseInstance const&, ResourceAPI*);
+
+ /* Ask the API for more information */
+ void searchWithTerm(const QString& term, unsigned int sort);
+
+ void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
+ void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0;
+
+ public slots:
+ ResourceAPI::SearchArgs createSearchArguments() override;
+ ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override;
+ ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override;
+
+ protected:
+ const BaseInstance& m_base_instance;
+
+ auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0;
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.cpp b/launcher/ui/pages/modplatform/ResourcePackPage.cpp
new file mode 100644
index 00000000..52fb4802
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourcePackPage.cpp
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "ResourcePackPage.h"
+#include "ui_ResourcePage.h"
+
+#include "ResourcePackModel.h"
+
+#include "ui/dialogs/ResourceDownloadDialog.h"
+
+#include <QRegularExpression>
+
+namespace ResourceDownload {
+
+ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance)
+ : ResourcePage(dialog, instance)
+{
+ connect(m_ui->searchButton, &QPushButton::clicked, this, &ResourcePackResourcePage::triggerSearch);
+ connect(m_ui->packView, &QListView::doubleClicked, this, &ResourcePackResourcePage::onResourceSelected);
+}
+
+/******** Callbacks to events in the UI (set up in the derived classes) ********/
+
+void ResourcePackResourcePage::triggerSearch()
+{
+ m_ui->packView->clearSelection();
+ m_ui->packDescription->clear();
+ m_ui->versionSelectionBox->clear();
+
+ updateSelectionButton();
+
+ static_cast<ResourcePackResourceModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt());
+ m_fetch_progress.watch(m_model->activeSearchJob().get());
+}
+
+QMap<QString, QString> ResourcePackResourcePage::urlHandlers() const
+{
+ QMap<QString, QString> map;
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/resourcepack\\/([^\\/]+)\\/?"), "modrinth");
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/texture-packs\\/([^\\/]+)\\/?"), "curseforge");
+ map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
+ return map;
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourcePackPage.h b/launcher/ui/pages/modplatform/ResourcePackPage.h
new file mode 100644
index 00000000..8c5cf08b
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourcePackPage.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include "ui/pages/modplatform/ResourcePage.h"
+#include "ui/pages/modplatform/ResourcePackModel.h"
+
+namespace Ui {
+class ResourcePage;
+}
+
+namespace ResourceDownload {
+
+class ResourcePackDownloadDialog;
+
+class ResourcePackResourcePage : public ResourcePage {
+ Q_OBJECT
+
+ public:
+ template <typename T>
+ static T* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ auto page = new T(dialog, instance);
+ auto model = static_cast<ResourcePackResourceModel*>(page->getModel());
+
+ connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList);
+ connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
+
+ return page;
+ }
+
+ //: The plural version of 'resource pack'
+ [[nodiscard]] inline QString resourcesString() const override { return tr("resource packs"); }
+ //: The singular version of 'resource packs'
+ [[nodiscard]] inline QString resourceString() const override { return tr("resource pack"); }
+
+ [[nodiscard]] bool supportsFiltering() const override { return false; };
+
+ [[nodiscard]] QMap<QString, QString> urlHandlers() const override;
+
+ protected:
+ ResourcePackResourcePage(ResourceDownloadDialog* dialog, BaseInstance& instance);
+
+ protected slots:
+ void triggerSearch() override;
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
new file mode 100644
index 00000000..f75bb886
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -0,0 +1,413 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ResourcePage.h"
+#include "ui_ResourcePage.h"
+
+#include <QDesktopServices>
+#include <QKeyEvent>
+
+#include "Markdown.h"
+#include "ResourceDownloadTask.h"
+
+#include "minecraft/MinecraftInstance.h"
+
+#include "ui/dialogs/ResourceDownloadDialog.h"
+#include "ui/pages/modplatform/ResourceModel.h"
+#include "ui/widgets/ProjectItem.h"
+
+namespace ResourceDownload {
+
+ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance)
+ : QWidget(parent), m_base_instance(base_instance), m_ui(new Ui::ResourcePage), m_parent_dialog(parent), m_fetch_progress(this, false)
+{
+ m_ui->setupUi(this);
+
+ m_ui->searchEdit->installEventFilter(this);
+
+ m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ m_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, &ResourcePage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ m_ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, m_ui->gridLayout_3->columnCount());
+
+ m_ui->packView->setItemDelegate(new ProjectItemDelegate(this));
+ m_ui->packView->installEventFilter(this);
+
+ connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl);
+}
+
+ResourcePage::~ResourcePage()
+{
+ delete m_ui;
+ if (m_model)
+ delete m_model;
+}
+
+void ResourcePage::retranslate()
+{
+ m_ui->retranslateUi(this);
+}
+
+void ResourcePage::openedImpl()
+{
+ if (!supportsFiltering())
+ m_ui->resourceFilterButton->setVisible(false);
+
+ //: String in the search bar of the mod downloading dialog
+ m_ui->searchEdit->setPlaceholderText(tr("Search for %1...").arg(resourcesString()));
+ m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString()));
+
+ updateSelectionButton();
+ triggerSearch();
+}
+
+auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool
+{
+ if (event->type() == QEvent::KeyPress) {
+ auto* keyEvent = static_cast<QKeyEvent*>(event);
+ if (watched == m_ui->searchEdit) {
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
+ }
+ } else if (watched == m_ui->packView) {
+ if (keyEvent->key() == Qt::Key_Return) {
+ onResourceSelected();
+
+ // To have the 'select mod' button outlined instead of the 'review and confirm' one
+ m_ui->resourceSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason);
+ m_ui->packView->setFocus(Qt::FocusReason::NoFocusReason);
+
+ keyEvent->accept();
+ return true;
+ }
+ }
+ }
+
+ return QWidget::eventFilter(watched, event);
+}
+
+QString ResourcePage::getSearchTerm() const
+{
+ return m_ui->searchEdit->text();
+}
+
+void ResourcePage::setSearchTerm(QString term)
+{
+ m_ui->searchEdit->setText(term);
+}
+
+void ResourcePage::addSortings()
+{
+ Q_ASSERT(m_model);
+
+ auto sorts = m_model->getSortingMethods();
+ std::sort(sorts.begin(), sorts.end(), [](auto const& l, auto const& r) { return l.index < r.index; });
+
+ for (auto&& sorting : sorts)
+ m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index));
+}
+
+bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack pack)
+{
+ QVariant v;
+ v.setValue(pack);
+ return m_model->setData(m_ui->packView->currentIndex(), v, Qt::UserRole);
+}
+
+ModPlatform::IndexedPack ResourcePage::getCurrentPack() const
+{
+ return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value<ModPlatform::IndexedPack>();
+}
+
+void ResourcePage::updateUi()
+{
+ auto current_pack = getCurrentPack();
+
+ QString text = "";
+ QString name = current_pack.name;
+
+ if (current_pack.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current_pack.websiteUrl + "\">" + name + "</a>";
+
+ if (!current_pack.authors.empty()) {
+ auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString {
+ if (author.url.isEmpty()) {
+ return author.name;
+ }
+ return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
+ };
+ QStringList authorStrs;
+ for (auto& author : current_pack.authors) {
+ authorStrs.push_back(authorToStr(author));
+ }
+ text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ }
+
+ if (current_pack.extraDataLoaded) {
+ if (!current_pack.extraData.donate.isEmpty()) {
+ text += "<br><br>" + tr("Donate information: ");
+ auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
+ return QString("<a href=\"%1\">%2</a>").arg(donate.url, donate.platform);
+ };
+ QStringList donates;
+ for (auto& donate : current_pack.extraData.donate) {
+ donates.append(donateToStr(donate));
+ }
+ text += donates.join(", ");
+ }
+
+ if (!current_pack.extraData.issuesUrl.isEmpty() || !current_pack.extraData.sourceUrl.isEmpty() ||
+ !current_pack.extraData.wikiUrl.isEmpty() || !current_pack.extraData.discordUrl.isEmpty()) {
+ text += "<br><br>" + tr("External links:") + "<br>";
+ }
+
+ if (!current_pack.extraData.issuesUrl.isEmpty())
+ text += "- " + tr("Issues: <a href=%1>%1</a>").arg(current_pack.extraData.issuesUrl) + "<br>";
+ if (!current_pack.extraData.wikiUrl.isEmpty())
+ text += "- " + tr("Wiki: <a href=%1>%1</a>").arg(current_pack.extraData.wikiUrl) + "<br>";
+ if (!current_pack.extraData.sourceUrl.isEmpty())
+ text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current_pack.extraData.sourceUrl) + "<br>";
+ if (!current_pack.extraData.discordUrl.isEmpty())
+ text += "- " + tr("Discord: <a href=%1>%1</a>").arg(current_pack.extraData.discordUrl) + "<br>";
+ }
+
+ text += "<hr>";
+
+ m_ui->packDescription->setHtml(
+ text + (current_pack.extraData.body.isEmpty() ? current_pack.description : markdownToHTML(current_pack.extraData.body)));
+ m_ui->packDescription->flush();
+}
+
+void ResourcePage::updateSelectionButton()
+{
+ if (!isOpened || m_selected_version_index < 0) {
+ m_ui->resourceSelectionButton->setEnabled(false);
+ return;
+ }
+
+ m_ui->resourceSelectionButton->setEnabled(true);
+ if (!getCurrentPack().isVersionSelected(m_selected_version_index)) {
+ m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString()));
+ } else {
+ m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString()));
+ }
+}
+
+void ResourcePage::updateVersionList()
+{
+ auto current_pack = getCurrentPack();
+
+ m_ui->versionSelectionBox->blockSignals(true);
+ m_ui->versionSelectionBox->clear();
+ m_ui->versionSelectionBox->blockSignals(false);
+
+ for (int i = 0; i < current_pack.versions.size(); i++) {
+ auto& version = current_pack.versions[i];
+ if (optedOut(version))
+ continue;
+
+ m_ui->versionSelectionBox->addItem(current_pack.versions[i].version, QVariant(i));
+ }
+
+ if (m_ui->versionSelectionBox->count() == 0) {
+ m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1));
+ m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :("));
+ }
+
+ updateSelectionButton();
+}
+
+void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
+{
+ if (!curr.isValid()) {
+ return;
+ }
+
+ auto current_pack = getCurrentPack();
+
+ bool request_load = false;
+ if (!current_pack.versionsLoaded) {
+ m_ui->resourceSelectionButton->setText(tr("Loading versions..."));
+ m_ui->resourceSelectionButton->setEnabled(false);
+
+ request_load = true;
+ } else {
+ updateVersionList();
+ }
+
+ if (!current_pack.extraDataLoaded)
+ request_load = true;
+
+ if (request_load)
+ m_model->loadEntry(curr);
+
+ updateUi();
+}
+
+void ResourcePage::onVersionSelectionChanged(QString data)
+{
+ if (data.isNull() || data.isEmpty()) {
+ m_selected_version_index = -1;
+ return;
+ }
+
+ m_selected_version_index = m_ui->versionSelectionBox->currentData().toInt();
+ updateSelectionButton();
+}
+
+void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+{
+ m_parent_dialog->addResource(pack, version);
+}
+
+void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+{
+ m_parent_dialog->removeResource(pack, version);
+}
+
+void ResourcePage::onResourceSelected()
+{
+ if (m_selected_version_index < 0)
+ return;
+
+ auto current_pack = getCurrentPack();
+ if (!current_pack.versionsLoaded)
+ return;
+
+ auto& version = current_pack.versions[m_selected_version_index];
+ if (version.is_currently_selected)
+ removeResourceFromDialog(current_pack, version);
+ else
+ addResourceToDialog(current_pack, version);
+
+ // Save the modified pack (and prevent warning in release build)
+ [[maybe_unused]] bool set = setCurrentPack(current_pack);
+ Q_ASSERT(set);
+
+ updateSelectionButton();
+
+ /* Force redraw on the resource list when the selection changes */
+ m_ui->packView->adjustSize();
+}
+
+void ResourcePage::openUrl(const QUrl& url)
+{
+ // do not allow other url schemes for security reasons
+ if (!(url.scheme() == "http" || url.scheme() == "https")) {
+ qWarning() << "Unsupported scheme" << url.scheme();
+ return;
+ }
+
+ // detect URLs and search instead
+
+ const QString address = url.host() + url.path();
+ QRegularExpressionMatch match;
+ QString page;
+
+ auto handlers = urlHandlers();
+ for (auto it = handlers.constKeyValueBegin(); it != handlers.constKeyValueEnd(); it++) {
+ auto&& [regex, candidate] = *it;
+ if (match = QRegularExpression(regex).match(address); match.hasMatch()) {
+ page = candidate;
+ break;
+ }
+ }
+
+ if (!page.isNull()) {
+ const QString slug = match.captured(1);
+
+ // ensure the user isn't opening the same mod
+ if (slug != getCurrentPack().slug) {
+ m_parent_dialog->selectPage(page);
+
+ auto newPage = m_parent_dialog->getSelectedPage();
+
+ QLineEdit* searchEdit = newPage->m_ui->searchEdit;
+ auto model = newPage->m_model;
+ QListView* view = newPage->m_ui->packView;
+
+ auto jump = [url, slug, model, view] {
+ for (int row = 0; row < model->rowCount({}); row++) {
+ const QModelIndex index = model->index(row);
+ const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
+
+ if (pack.slug == slug) {
+ view->setCurrentIndex(index);
+ return;
+ }
+ }
+
+ // The final fallback.
+ QDesktopServices::openUrl(url);
+ };
+
+ searchEdit->setText(slug);
+ newPage->triggerSearch();
+
+ if (model->hasActiveSearchJob())
+ connect(model->activeSearchJob().get(), &Task::finished, jump);
+ else
+ jump();
+
+ return;
+ }
+ }
+
+ // open in the user's web browser
+ QDesktopServices::openUrl(url);
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h
new file mode 100644
index 00000000..1896d53e
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ResourcePage.h
@@ -0,0 +1,111 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include <QTimer>
+#include <QWidget>
+
+#include "modplatform/ModIndex.h"
+#include "modplatform/ResourceAPI.h"
+
+#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
+
+namespace Ui {
+class ResourcePage;
+}
+
+class BaseInstance;
+
+namespace ResourceDownload {
+
+class ResourceDownloadDialog;
+class ResourceModel;
+
+class ResourcePage : public QWidget, public BasePage {
+ Q_OBJECT
+ public:
+ ~ResourcePage() override;
+
+ /* Affects what the user sees */
+ [[nodiscard]] auto displayName() const -> QString override = 0;
+ [[nodiscard]] auto icon() const -> QIcon override = 0;
+ [[nodiscard]] auto id() const -> QString override = 0;
+ [[nodiscard]] auto helpPage() const -> QString override = 0;
+ [[nodiscard]] bool shouldDisplay() const override = 0;
+
+ /* Used internally */
+ [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0;
+ [[nodiscard]] virtual auto debugName() const -> QString = 0;
+
+ //: The plural version of 'resource'
+ [[nodiscard]] virtual inline QString resourcesString() const { return tr("resources"); }
+ //: The singular version of 'resources'
+ [[nodiscard]] virtual inline QString resourceString() const { return tr("resource"); }
+
+ /* Features this resource's page supports */
+ [[nodiscard]] virtual bool supportsFiltering() const = 0;
+
+ void retranslate() override;
+ void openedImpl() override;
+ auto eventFilter(QObject* watched, QEvent* event) -> bool override;
+
+ /** Get the current term in the search bar. */
+ [[nodiscard]] auto getSearchTerm() const -> QString;
+ /** Programatically set the term in the search bar. */
+ void setSearchTerm(QString);
+
+ [[nodiscard]] bool setCurrentPack(ModPlatform::IndexedPack);
+ [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack;
+ [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; }
+ [[nodiscard]] auto getModel() const -> ResourceModel* { return m_model; }
+
+ protected:
+ ResourcePage(ResourceDownloadDialog* parent, BaseInstance&);
+
+ void addSortings();
+
+ public slots:
+ virtual void updateUi();
+ virtual void updateSelectionButton();
+ virtual void updateVersionList();
+
+ virtual void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+ virtual void removeResourceFromDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&);
+
+ protected slots:
+ virtual void triggerSearch() {}
+
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+ void onResourceSelected();
+
+ // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12
+
+ /** Associates regex expressions to pages in the order they're given in the map. */
+ virtual QMap<QString, QString> urlHandlers() const = 0;
+ virtual void openUrl(const QUrl&);
+
+ /** Whether the version is opted out or not. Currently only makes sense in CF. */
+ virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; };
+
+ public:
+ BaseInstance& m_base_instance;
+
+ protected:
+ Ui::ResourcePage* m_ui;
+
+ ResourceDownloadDialog* m_parent_dialog = nullptr;
+ ResourceModel* m_model = nullptr;
+
+ 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;
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui
index 94365aa5..73a9d3b1 100644
--- a/launcher/ui/pages/modplatform/ModPage.ui
+++ b/launcher/ui/pages/modplatform/ResourcePage.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>ModPage</class>
- <widget class="QWidget" name="ModPage">
+ <class>ResourcePage</class>
+ <widget class="QWidget" name="ResourcePage">
<property name="geometry">
<rect>
<x>0</x>
@@ -49,11 +49,7 @@
</widget>
</item>
<item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit">
- <property name="placeholderText">
- <string>Search for mods...</string>
- </property>
- </widget>
+ <widget class="QLineEdit" name="searchEdit"/>
</item>
<item row="2" column="0" colspan="4">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0">
@@ -74,16 +70,12 @@
<widget class="QComboBox" name="sortByBox"/>
</item>
<item row="1" column="2">
- <widget class="QPushButton" name="modSelectionButton">
- <property name="text">
- <string>Select mod for download</string>
- </property>
- </widget>
+ <widget class="QPushButton" name="resourceSelectionButton"/>
</item>
</layout>
</item>
<item row="0" column="1">
- <widget class="QPushButton" name="modFilterButton">
+ <widget class="QPushButton" name="resourceFilterButton">
<property name="text">
<string>Filter options</string>
</property>
diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp
new file mode 100644
index 00000000..aabd3be6
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "ShaderPackModel.h"
+
+#include <QMessageBox>
+
+namespace ResourceDownload {
+
+ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
+ : ResourceModel(api), m_base_instance(base_inst){};
+
+/******** Make data requests ********/
+
+ResourceAPI::SearchArgs ShaderPackResourceModel::createSearchArguments()
+{
+ auto sort = getCurrentSortingMethodByIndex();
+ return { ModPlatform::ResourceType::SHADER_PACK, m_next_search_offset, m_search_term, sort };
+}
+
+ResourceAPI::VersionSearchArgs ShaderPackResourceModel::createVersionsArguments(QModelIndex& entry)
+{
+ auto& pack = m_packs[entry.row()];
+ return { *pack };
+}
+
+ResourceAPI::ProjectInfoArgs ShaderPackResourceModel::createInfoArguments(QModelIndex& entry)
+{
+ auto& pack = m_packs[entry.row()];
+ return { *pack };
+}
+
+void ShaderPackResourceModel::searchWithTerm(const QString& term, unsigned int sort)
+{
+ if (m_search_term == term && m_search_term.isNull() == term.isNull() && m_current_sort_index == sort) {
+ return;
+ }
+
+ setSearchTerm(term);
+ m_current_sort_index = sort;
+
+ refresh();
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.h b/launcher/ui/pages/modplatform/ShaderPackModel.h
new file mode 100644
index 00000000..f3c695e9
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ShaderPackModel.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include <QAbstractListModel>
+
+#include "BaseInstance.h"
+
+#include "modplatform/ModIndex.h"
+
+#include "ui/pages/modplatform/ResourceModel.h"
+
+class Version;
+
+namespace ResourceDownload {
+
+class ShaderPackResourceModel : public ResourceModel {
+ Q_OBJECT
+
+ public:
+ ShaderPackResourceModel(BaseInstance const&, ResourceAPI*);
+
+ /* Ask the API for more information */
+ void searchWithTerm(const QString& term, unsigned int sort);
+
+ void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
+ void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&) override = 0;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&) override = 0;
+
+ public slots:
+ ResourceAPI::SearchArgs createSearchArguments() override;
+ ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override;
+ ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override;
+
+ protected:
+ const BaseInstance& m_base_instance;
+
+ auto documentToArray(QJsonDocument& obj) const -> QJsonArray override = 0;
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
new file mode 100644
index 00000000..251c07e7
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "ShaderPackPage.h"
+#include "ui_ResourcePage.h"
+
+#include "ShaderPackModel.h"
+
+#include "ui/dialogs/ResourceDownloadDialog.h"
+
+#include <QRegularExpression>
+
+namespace ResourceDownload {
+
+ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ : ResourcePage(dialog, instance)
+{
+ connect(m_ui->searchButton, &QPushButton::clicked, this, &ShaderPackResourcePage::triggerSearch);
+ connect(m_ui->packView, &QListView::doubleClicked, this, &ShaderPackResourcePage::onResourceSelected);
+}
+
+/******** Callbacks to events in the UI (set up in the derived classes) ********/
+
+void ShaderPackResourcePage::triggerSearch()
+{
+ m_ui->packView->clearSelection();
+ m_ui->packDescription->clear();
+ m_ui->versionSelectionBox->clear();
+
+ updateSelectionButton();
+
+ static_cast<ShaderPackResourceModel*>(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentData().toUInt());
+ m_fetch_progress.watch(m_model->activeSearchJob().get());
+}
+
+QMap<QString, QString> ShaderPackResourcePage::urlHandlers() const
+{
+ QMap<QString, QString> map;
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth");
+ map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), "curseforge");
+ map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge");
+ return map;
+}
+
+void ShaderPackResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version)
+{
+ if (version.loaders.contains(QStringLiteral("canvas")))
+ version.custom_target_folder = QStringLiteral("resourcepacks");
+
+ m_parent_dialog->addResource(pack, version);
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h
new file mode 100644
index 00000000..9039c4d9
--- /dev/null
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include "ui/pages/modplatform/ResourcePage.h"
+#include "ui/pages/modplatform/ShaderPackModel.h"
+
+namespace Ui {
+class ResourcePage;
+}
+
+namespace ResourceDownload {
+
+class ShaderPackDownloadDialog;
+
+class ShaderPackResourcePage : public ResourcePage {
+ Q_OBJECT
+
+ public:
+ template <typename T>
+ static T* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ auto page = new T(dialog, instance);
+ auto model = static_cast<ShaderPackResourceModel*>(page->getModel());
+
+ connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList);
+ connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
+
+ return page;
+ }
+
+ //: The plural version of 'shader pack'
+ [[nodiscard]] inline QString resourcesString() const override { return tr("shader packs"); }
+ //: The singular version of 'shader packs'
+ [[nodiscard]] inline QString resourceString() const override { return tr("shader pack"); }
+
+ [[nodiscard]] bool supportsFiltering() const override { return false; };
+
+ void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override;
+
+ [[nodiscard]] QMap<QString, QString> urlHandlers() const override;
+
+ protected:
+ ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);
+
+ protected slots:
+ void triggerSearch() override;
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/TexturePackModel.cpp b/launcher/ui/pages/modplatform/TexturePackModel.cpp
new file mode 100644
index 00000000..fa636951
--- /dev/null
+++ b/launcher/ui/pages/modplatform/TexturePackModel.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "TexturePackModel.h"
+
+#include "Application.h"
+
+#include "meta/Index.h"
+#include "meta/Version.h"
+
+static std::list<Version> s_availableVersions = {};
+
+namespace ResourceDownload {
+TexturePackResourceModel::TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api)
+ : ResourcePackResourceModel(inst, api), m_version_list(APPLICATION->metadataIndex()->get("net.minecraft"))
+{
+ if (!m_version_list->isLoaded()) {
+ qDebug() << "Loading version list...";
+ auto task = m_version_list->getLoadTask();
+ if (!task->isRunning())
+ task->start();
+ }
+}
+
+void waitOnVersionListLoad(Meta::VersionList::Ptr version_list)
+{
+ QEventLoop load_version_list_loop;
+
+ QTimer time_limit_for_list_load;
+ time_limit_for_list_load.setTimerType(Qt::TimerType::CoarseTimer);
+ time_limit_for_list_load.setSingleShot(true);
+ time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit);
+ time_limit_for_list_load.start(4000);
+
+ auto task = version_list->getLoadTask();
+ QObject::connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit);
+
+ load_version_list_loop.exec();
+ if (time_limit_for_list_load.isActive())
+ time_limit_for_list_load.stop();
+}
+
+ResourceAPI::SearchArgs TexturePackResourceModel::createSearchArguments()
+{
+ if (s_availableVersions.empty())
+ waitOnVersionListLoad(m_version_list);
+
+ auto args = ResourcePackResourceModel::createSearchArguments();
+
+ if (!m_version_list->isLoaded()) {
+ qCritical() << "The version list could not be loaded. Falling back to showing all entries.";
+ return args;
+ }
+
+ if (s_availableVersions.empty()) {
+ for (auto&& version : m_version_list->versions()) {
+ // FIXME: This duplicates the logic in meta for the 'texturepacks' trait. However, we don't have access to that
+ // information from the index file alone. Also, downloading every version's file isn't a very good idea.
+ if (auto ver = version->toComparableVersion(); ver <= maximumTexturePackVersion())
+ s_availableVersions.push_back(ver);
+ }
+ }
+
+ Q_ASSERT(!s_availableVersions.empty());
+
+ args.versions = s_availableVersions;
+
+ return args;
+}
+
+ResourceAPI::VersionSearchArgs TexturePackResourceModel::createVersionsArguments(QModelIndex& entry)
+{
+ auto args = ResourcePackResourceModel::createVersionsArguments(entry);
+ if (!m_version_list->isLoaded()) {
+ qCritical() << "The version list could not be loaded. Falling back to showing all entries.";
+ return args;
+ }
+
+ args.mcVersions = s_availableVersions;
+ return args;
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/TexturePackModel.h b/launcher/ui/pages/modplatform/TexturePackModel.h
new file mode 100644
index 00000000..bb2db5cf
--- /dev/null
+++ b/launcher/ui/pages/modplatform/TexturePackModel.h
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include "meta/VersionList.h"
+#include "ui/pages/modplatform/ResourcePackModel.h"
+
+namespace ResourceDownload {
+
+class TexturePackResourceModel : public ResourcePackResourceModel {
+ Q_OBJECT
+
+ public:
+ TexturePackResourceModel(BaseInstance const& inst, ResourceAPI* api);
+
+ [[nodiscard]] inline ::Version maximumTexturePackVersion() const { return { "1.6" }; }
+
+ ResourceAPI::SearchArgs createSearchArguments() override;
+ ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override;
+
+ protected:
+ Meta::VersionList::Ptr m_version_list;
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/TexturePackPage.h b/launcher/ui/pages/modplatform/TexturePackPage.h
new file mode 100644
index 00000000..0bdce2f9
--- /dev/null
+++ b/launcher/ui/pages/modplatform/TexturePackPage.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include "ui_ResourcePage.h"
+#include "ui/dialogs/ResourceDownloadDialog.h"
+#include "ui/pages/modplatform/ResourcePackPage.h"
+#include "ui/pages/modplatform/TexturePackModel.h"
+
+namespace Ui {
+class ResourcePage;
+}
+
+namespace ResourceDownload {
+
+class TexturePackDownloadDialog;
+
+class TexturePackResourcePage : public ResourcePackResourcePage {
+ Q_OBJECT
+
+ public:
+ template <typename T>
+ static T* create(TexturePackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ auto page = new T(dialog, instance);
+ auto model = static_cast<TexturePackResourceModel*>(page->getModel());
+
+ connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::updateVersionList);
+ connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi);
+
+ return page;
+ }
+
+ //: The plural version of 'texture pack'
+ [[nodiscard]] inline QString resourcesString() const override { return tr("texture packs"); }
+ //: The singular version of 'texture packs'
+ [[nodiscard]] inline QString resourceString() const override { return tr("texture pack"); }
+
+ protected:
+ TexturePackResourcePage(TexturePackDownloadDialog* dialog, BaseInstance& instance)
+ : ResourcePackResourcePage(dialog, instance)
+ {
+ connect(m_ui->searchButton, &QPushButton::clicked, this, &TexturePackResourcePage::triggerSearch);
+ connect(m_ui->packView, &QListView::doubleClicked, this, &TexturePackResourcePage::onResourceSelected);
+ }
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index 2ce04068..9ad26f47 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -86,14 +86,14 @@ void ListModel::request()
modpacks.clear();
endResetModel();
- auto *netJob = new NetJob("Atl::Request", APPLICATION->network());
+ auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
jobPtr = netJob;
jobPtr->start();
- QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished);
- QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed);
+ QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::requestFinished);
+ QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::requestFailed);
}
void ListModel::requestFinished()
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
deleted file mode 100644
index bc2c686c..00000000
--- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#include "FlameModModel.h"
-#include "Json.h"
-#include "modplatform/flame/FlameModIndex.h"
-
-namespace FlameMod {
-
-// NOLINTNEXTLINE(modernize-avoid-c-arrays)
-const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" };
-
-void ListModel::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 ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
-{
- FlameMod::loadBody(m, obj);
-}
-
-void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
-{
- FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
-}
-
-auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
-{
- return Json::ensureArray(obj.object(), "data");
-}
-
-} // namespace FlameMod
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h
deleted file mode 100644
index 6a6aef2e..00000000
--- a/launcher/ui/pages/modplatform/flame/FlameModModel.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include "FlameModPage.h"
-
-namespace FlameMod {
-
-class ListModel : public ModPlatform::ListModel {
- Q_OBJECT
-
- public:
- ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {}
- ~ListModel() override = default;
-
- private:
- 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;
-
- // NOLINTNEXTLINE(modernize-avoid-c-arrays)
- static const char* sorts[6];
- inline auto getSorts() const -> const char** override { return sorts; };
-};
-
-} // namespace FlameMod
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
deleted file mode 100644
index bad78c97..00000000
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * Prism Launcher - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "FlameModPage.h"
-#include "ui_ModPage.h"
-
-#include "FlameModModel.h"
-#include "ui/dialogs/ModDownloadDialog.h"
-
-FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
- : ModPage(dialog, instance, new FlameAPI())
-{
- listModel = new FlameMod::ListModel(this);
- ui->packView->setModel(listModel);
-
- // index is used to set the sorting with the flame api
- ui->sortByBox->addItem(tr("Sort by Featured"));
- ui->sortByBox->addItem(tr("Sort by Popularity"));
- ui->sortByBox->addItem(tr("Sort by Last Updated"));
- ui->sortByBox->addItem(tr("Sort by Name"));
- ui->sortByBox->addItem(tr("Sort by Author"));
- ui->sortByBox->addItem(tr("Sort by Downloads"));
-
- // 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 contructor...
- connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged);
- connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected);
-
- ui->packDescription->setMetaEntry(metaEntryBase());
-}
-
-auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
-{
- Q_UNUSED(loaders);
- return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
-}
-
-bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
-{
- return ver.downloadUrl.isEmpty();
-}
-
-// 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...
-auto FlameModPage::shouldDisplay() const -> bool { return true; }
-
-void FlameModPage::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);
- ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary
- return;
- }
- }
-
- ModPage::openUrl(url);
-}
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h
deleted file mode 100644
index 58479ab9..00000000
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * Prism Launcher - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "modplatform/ModAPI.h"
-#include "ui/pages/modplatform/ModPage.h"
-
-#include "modplatform/flame/FlameAPI.h"
-
-class FlameModPage : public ModPage {
- Q_OBJECT
-
- public:
- static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance* instance)
- {
- return ModPage::create<FlameModPage>(dialog, instance);
- }
-
- FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance);
- ~FlameModPage() override = default;
-
- inline auto displayName() const -> QString override { return "CurseForge"; }
- inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); }
- inline auto id() const -> QString override { return "curseforge"; }
- inline auto helpPage() const -> QString override { return "Mod-platform"; }
-
- inline auto debugName() const -> QString override { return "Flame"; }
- inline auto metaEntryBase() const -> QString override { return "FlameMods"; };
-
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override;
- bool optedOut(ModPlatform::IndexedVersion& ver) const override;
-
- auto shouldDisplay() const -> bool override;
-
- void openUrl(const QUrl& url) override;
-};
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index 127c3de5..5961ea02 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -155,7 +155,7 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch()
{
- NetJob* netJob = new NetJob("Flame::Search", APPLICATION->network());
+ auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
auto searchUrl = QString(
"https://api.curseforge.com/v1/mods/search?"
"gameId=432&"
@@ -172,8 +172,8 @@ void ListModel::performPaginatedSearch()
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
- QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
- QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
+ QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
+ QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed);
}
void ListModel::searchWithTerm(const QString& term, int sort)
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
new file mode 100644
index 00000000..e3d0bc14
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "FlameResourceModels.h"
+
+#include "Json.h"
+
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
+
+namespace ResourceDownload {
+
+FlameModModel::FlameModModel(BaseInstance const& base) : ModModel(base, new FlameAPI) {}
+
+void FlameModModel::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 FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadBody(m, obj);
+}
+
+void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return Json::ensureArray(obj.object(), "data");
+}
+
+FlameResourcePackModel::FlameResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new FlameAPI) {}
+
+void FlameResourcePackModel::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 FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadBody(m, obj);
+}
+
+void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto FlameResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return Json::ensureArray(obj.object(), "data");
+}
+
+FlameTexturePackModel::FlameTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new FlameAPI) {}
+
+void FlameTexturePackModel::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 FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadBody(m, obj);
+}
+
+void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+
+ QVector<ModPlatform::IndexedVersion> filtered_versions(m.versions.size());
+
+ // FIXME: Client-side version filtering. This won't take into account any user-selected filtering.
+ for (auto const& version : m.versions) {
+ auto const& mc_versions = version.mcVersion;
+
+ if (std::any_of(mc_versions.constBegin(), mc_versions.constEnd(),
+ [this](auto const& mc_version){ return Version(mc_version) <= maximumTexturePackVersion(); }))
+ filtered_versions.push_back(version);
+ }
+
+ m.versions = filtered_versions;
+}
+
+ResourceAPI::SearchArgs FlameTexturePackModel::createSearchArguments()
+{
+ auto args = TexturePackResourceModel::createSearchArguments();
+
+ auto profile = static_cast<const MinecraftInstance&>(m_base_instance).getPackProfile();
+ QString instance_minecraft_version = profile->getComponentVersion("net.minecraft");
+
+ // Bypass the texture pack logic, because we can't do multiple versions in the API query
+ args.versions = { instance_minecraft_version };
+
+ return args;
+}
+
+ResourceAPI::VersionSearchArgs FlameTexturePackModel::createVersionsArguments(QModelIndex& entry)
+{
+ auto args = TexturePackResourceModel::createVersionsArguments(entry);
+
+ // Bypass the texture pack logic, because we can't do multiple versions in the API query
+ args.mcVersions = {};
+
+ return args;
+}
+
+auto FlameTexturePackModel::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
new file mode 100644
index 00000000..0252ac40
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include "ui/pages/modplatform/ModModel.h"
+#include "ui/pages/modplatform/ResourcePackModel.h"
+#include "ui/pages/modplatform/flame/FlameResourcePages.h"
+
+namespace ResourceDownload {
+
+class FlameModModel : public ModModel {
+ Q_OBJECT
+
+ public:
+ FlameModModel(const BaseInstance&);
+ ~FlameModModel() 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;
+};
+
+class FlameResourcePackModel : public ResourcePackResourceModel {
+ Q_OBJECT
+
+ public:
+ FlameResourcePackModel(const BaseInstance&);
+ ~FlameResourcePackModel() 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;
+};
+
+class FlameTexturePackModel : public TexturePackResourceModel {
+ Q_OBJECT
+
+ public:
+ FlameTexturePackModel(const BaseInstance&);
+ ~FlameTexturePackModel() 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;
+
+ ResourceAPI::SearchArgs createSearchArguments() override;
+ ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) 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
new file mode 100644
index 00000000..f93e27e6
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
@@ -0,0 +1,182 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FlameResourcePages.h"
+#include "ui_ResourcePage.h"
+
+#include "FlameResourceModels.h"
+#include "ui/dialogs/ResourceDownloadDialog.h"
+
+namespace ResourceDownload {
+
+static bool isOptedOut(ModPlatform::IndexedVersion const& ver)
+{
+ return ver.downloadUrl.isEmpty();
+}
+
+FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance)
+ : ModPage(dialog, instance)
+{
+ m_model = new FlameModModel(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 contructor...
+ connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameModPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
+{
+ Q_UNUSED(loaders);
+ return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
+}
+
+bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
+{
+ return isOptedOut(ver);
+}
+
+void FlameModPage::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);
+ ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary
+ return;
+ }
+ }
+
+ ModPage::openUrl(url);
+}
+
+FlameResourcePackPage::FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
+ : ResourcePackResourcePage(dialog, instance)
+{
+ m_model = new FlameResourcePackModel(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 contructor...
+ connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameResourcePackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameResourcePackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameResourcePackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+bool FlameResourcePackPage::optedOut(ModPlatform::IndexedVersion& ver) const
+{
+ return isOptedOut(ver);
+}
+
+void FlameResourcePackPage::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);
+ ResourcePackResourcePage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary
+ return;
+ }
+ }
+
+ ResourcePackResourcePage::openUrl(url);
+}
+
+FlameTexturePackPage::FlameTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance)
+ : TexturePackResourcePage(dialog, instance)
+{
+ m_model = new FlameTexturePackModel(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 contructor...
+ connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameTexturePackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameTexturePackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameTexturePackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+bool FlameTexturePackPage::optedOut(ModPlatform::IndexedVersion& ver) const
+{
+ return isOptedOut(ver);
+}
+
+void FlameTexturePackPage::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);
+ ResourcePackResourcePage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary
+ return;
+ }
+ }
+
+ TexturePackResourcePage::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...
+auto FlameModPage::shouldDisplay() const -> bool { return true; }
+auto FlameResourcePackPage::shouldDisplay() const -> bool { return true; }
+auto FlameTexturePackPage::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
new file mode 100644
index 00000000..103a6bb9
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
@@ -0,0 +1,141 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "Application.h"
+
+#include "modplatform/ResourceAPI.h"
+
+#include "ui/pages/modplatform/ModPage.h"
+#include "ui/pages/modplatform/ResourcePackPage.h"
+#include "ui/pages/modplatform/TexturePackPage.h"
+
+namespace ResourceDownload {
+
+namespace Flame {
+static inline QString displayName() { return "CurseForge"; }
+static inline QIcon icon() { return APPLICATION->getThemedIcon("flame"); }
+static inline QString id() { return "curseforge"; }
+static inline QString debugName() { return "Flame"; }
+static inline QString metaEntryBase() { return "FlameMods"; }
+}
+
+class FlameModPage : public ModPage {
+ Q_OBJECT
+
+ public:
+ static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ModPage::create<FlameModPage>(dialog, instance);
+ }
+
+ FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance);
+ ~FlameModPage() 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 "Mod-platform"; }
+
+ bool validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override;
+ bool optedOut(ModPlatform::IndexedVersion& ver) const override;
+
+ void openUrl(const QUrl& url) override;
+};
+
+class FlameResourcePackPage : public ResourcePackResourcePage {
+ Q_OBJECT
+
+ public:
+ static FlameResourcePackPage* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ResourcePackResourcePage::create<FlameResourcePackPage>(dialog, instance);
+ }
+
+ FlameResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance);
+ ~FlameResourcePackPage() 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;
+};
+
+class FlameTexturePackPage : public TexturePackResourcePage {
+ Q_OBJECT
+
+ public:
+ static FlameTexturePackPage* create(TexturePackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return TexturePackResourcePage::create<FlameTexturePackPage>(dialog, instance);
+ }
+
+ FlameTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance);
+ ~FlameTexturePackPage() 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/ftb/FtbFilterModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
deleted file mode 100644
index e2b548f2..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "FtbFilterModel.h"
-
-#include <QDebug>
-
-#include "modplatform/modpacksch/FTBPackManifest.h"
-
-#include "StringUtils.h"
-
-namespace Ftb {
-
-FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
-{
- currentSorting = Sorting::ByPlays;
- sortings.insert(tr("Sort by Plays"), Sorting::ByPlays);
- sortings.insert(tr("Sort by Installs"), Sorting::ByInstalls);
- sortings.insert(tr("Sort by Name"), Sorting::ByName);
-}
-
-const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
-{
- return sortings;
-}
-
-QString FilterModel::translateCurrentSorting()
-{
- return sortings.key(currentSorting);
-}
-
-void FilterModel::setSorting(Sorting sorting)
-{
- currentSorting = sorting;
- invalidate();
-}
-
-FilterModel::Sorting FilterModel::getCurrentSorting()
-{
- return currentSorting;
-}
-
-void FilterModel::setSearchTerm(const QString& term)
-{
- searchTerm = term.trimmed();
- invalidate();
-}
-
-bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
-{
- if (searchTerm.isEmpty()) {
- return true;
- }
-
- auto index = sourceModel()->index(sourceRow, 0, sourceParent);
- auto pack = sourceModel()->data(index, Qt::UserRole).value<ModpacksCH::Modpack>();
- return pack.name.contains(searchTerm, Qt::CaseInsensitive);
-}
-
-bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
-{
- ModpacksCH::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<ModpacksCH::Modpack>();
- ModpacksCH::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<ModpacksCH::Modpack>();
-
- if (currentSorting == ByPlays) {
- return leftPack.plays < rightPack.plays;
- }
- else if (currentSorting == ByInstalls) {
- return leftPack.installs < rightPack.installs;
- }
- else if (currentSorting == ByName) {
- return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
- }
-
- // Invalid sorting set, somehow...
- qWarning() << "Invalid sorting set!";
- return true;
-}
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
deleted file mode 100644
index 1be28e99..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QtCore/QSortFilterProxyModel>
-
-namespace Ftb {
-
-class FilterModel : public QSortFilterProxyModel
-{
- Q_OBJECT
-
-public:
- FilterModel(QObject* parent = Q_NULLPTR);
- enum Sorting {
- ByPlays,
- ByInstalls,
- ByName,
- };
- const QMap<QString, Sorting> getAvailableSortings();
- QString translateCurrentSorting();
- void setSorting(Sorting sorting);
- Sorting getCurrentSorting();
- void setSearchTerm(const QString& term);
-
-protected:
- bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
- bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
-
-private:
- QMap<QString, Sorting> sortings;
- Sorting currentSorting;
- QString searchTerm { "" };
-
-};
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
deleted file mode 100644
index ce2b2b18..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "FtbListModel.h"
-
-#include "BuildConfig.h"
-#include "Application.h"
-#include "Json.h"
-
-#include <QPainter>
-
-namespace Ftb {
-
-ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
-{
-}
-
-ListModel::~ListModel()
-{
-}
-
-int ListModel::rowCount(const QModelIndex &parent) const
-{
- return parent.isValid() ? 0 : modpacks.size();
-}
-
-int ListModel::columnCount(const QModelIndex &parent) const
-{
- return parent.isValid() ? 0 : 1;
-}
-
-QVariant ListModel::data(const QModelIndex &index, int role) const
-{
- int pos = index.row();
- if(pos >= modpacks.size() || pos < 0 || !index.isValid())
- {
- return QString("INVALID INDEX %1").arg(pos);
- }
-
- ModpacksCH::Modpack pack = modpacks.at(pos);
- if(role == Qt::DisplayRole)
- {
- return pack.name;
- }
- else if (role == Qt::ToolTipRole)
- {
- return pack.synopsis;
- }
- else if(role == Qt::DecorationRole)
- {
- QIcon placeholder = APPLICATION->getThemedIcon("screenshot-placeholder");
-
- auto iter = m_logoMap.find(pack.name);
- if (iter != m_logoMap.end()) {
- auto & logo = *iter;
- if(!logo.result.isNull()) {
- return logo.result;
- }
- return placeholder;
- }
-
- for(auto art : pack.art) {
- if(art.type == "square") {
- ((ListModel *)this)->requestLogo(pack.name, art.url);
- }
- }
- return placeholder;
- }
- else if(role == Qt::UserRole)
- {
- QVariant v;
- v.setValue(pack);
- return v;
- }
-
- return QVariant();
-}
-
-void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
-{
- if(m_logoMap.contains(logo))
- {
- callback(APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
- }
- else
- {
- requestLogo(logo, logoUrl);
- }
-}
-
-void ListModel::request()
-{
- m_aborted = false;
-
- beginResetModel();
- modpacks.clear();
- endResetModel();
-
- auto *netJob = new NetJob("Ftb::Request", APPLICATION->network());
- auto url = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all");
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
- jobPtr = netJob;
- jobPtr->start();
-
- QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished);
- QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed);
-}
-
-void ListModel::abortRequest()
-{
- m_aborted = jobPtr->abort();
- jobPtr.reset();
-}
-
-void ListModel::requestFinished()
-{
- jobPtr.reset();
- remainingPacks.clear();
-
- QJsonParseError parse_error {};
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
- return;
- }
-
- auto packs = doc.object().value("packs").toArray();
- for(auto pack : packs) {
- auto packId = pack.toInt();
- remainingPacks.append(packId);
- }
-
- if(!remainingPacks.isEmpty()) {
- currentPack = remainingPacks.at(0);
- requestPack();
- }
-}
-
-void ListModel::requestFailed(QString reason)
-{
- jobPtr.reset();
- remainingPacks.clear();
-}
-
-void ListModel::requestPack()
-{
- auto *netJob = new NetJob("Ftb::Search", APPLICATION->network());
- auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1").arg(currentPack);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
- jobPtr = netJob;
- jobPtr->start();
-
- QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::packRequestFinished);
- QObject::connect(netJob, &NetJob::failed, this, &ListModel::packRequestFailed);
-}
-
-void ListModel::packRequestFinished()
-{
- if (!jobPtr || m_aborted)
- return;
-
- jobPtr.reset();
- remainingPacks.removeOne(currentPack);
-
- QJsonParseError parse_error;
- QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
-
- if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString();
- qWarning() << response;
- return;
- }
-
- auto obj = doc.object();
-
- ModpacksCH::Modpack pack;
- try
- {
- ModpacksCH::loadModpack(pack, obj);
- }
- catch (const JSONValidationError &e)
- {
- qDebug() << QString::fromUtf8(response);
- qWarning() << "Error while reading pack manifest from ModpacksCH: " << e.cause();
- return;
- }
-
- // Since there is no guarantee that packs have a version, this will just
- // ignore those "dud" packs.
- if (pack.versions.empty())
- {
- qWarning() << "ModpacksCH Pack " << pack.id << " ignored. reason: lacking any versions";
- }
- else
- {
- beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size());
- modpacks.append(pack);
- endInsertRows();
- }
-
- if(!remainingPacks.isEmpty()) {
- currentPack = remainingPacks.at(0);
- requestPack();
- }
-}
-
-void ListModel::packRequestFailed(QString reason)
-{
- jobPtr.reset();
- remainingPacks.removeOne(currentPack);
-}
-
-void ListModel::logoLoaded(QString logo, bool stale)
-{
- auto & logoObj = m_logoMap[logo];
- logoObj.downloadJob.reset();
- QString smallPath = logoObj.fullpath + ".small";
-
- QFileInfo smallInfo(smallPath);
-
- if(stale || !smallInfo.exists()) {
- QImage image(logoObj.fullpath);
- if (image.isNull())
- {
- logoObj.failed = true;
- return;
- }
- QImage small;
- if (image.width() > image.height()) {
- small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
- }
- else {
- small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
- }
- QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
- QImage square(QSize(256, 256), QImage::Format_ARGB32);
- square.fill(Qt::transparent);
-
- QPainter painter(&square);
- painter.drawImage(offset, small);
- painter.end();
-
- square.save(logoObj.fullpath + ".small", "PNG");
- }
-
- logoObj.result = QIcon(logoObj.fullpath + ".small");
- for(int i = 0; i < modpacks.size(); i++) {
- if(modpacks[i].name == logo) {
- emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
- }
- }
-}
-
-void ListModel::logoFailed(QString logo)
-{
- m_logoMap[logo].failed = true;
- m_logoMap[logo].downloadJob.reset();
-}
-
-void ListModel::requestLogo(QString logo, QString url)
-{
- if(m_logoMap.contains(logo)) {
- return;
- }
-
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
-
- bool stale = entry->isStale();
-
- NetJob *job = new NetJob(QString("ModpacksCH Icon Download %1").arg(logo), APPLICATION->network());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
-
- auto fullPath = entry->getFullPath();
- QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath, stale]
- {
- logoLoaded(logo, stale);
- });
-
- QObject::connect(job, &NetJob::failed, this, [this, logo]
- {
- logoFailed(logo);
- });
-
- auto &newLogoEntry = m_logoMap[logo];
- newLogoEntry.downloadJob = job;
- newLogoEntry.fullpath = fullPath;
- job->start();
-}
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.h b/launcher/ui/pages/modplatform/ftb/FtbListModel.h
deleted file mode 100644
index d7a120f0..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QAbstractListModel>
-
-#include "modplatform/modpacksch/FTBPackManifest.h"
-#include "net/NetJob.h"
-#include <QIcon>
-
-namespace Ftb {
-
-struct Logo {
- QString fullpath;
- NetJob::Ptr downloadJob;
- QIcon result;
- bool failed = false;
-};
-
-typedef QMap<QString, Logo> LogoMap;
-typedef std::function<void(QString)> LogoCallback;
-
-class ListModel : public QAbstractListModel
-{
- Q_OBJECT
-
-public:
- ListModel(QObject *parent);
- virtual ~ListModel();
-
- int rowCount(const QModelIndex &parent) const override;
- int columnCount(const QModelIndex &parent) const override;
- QVariant data(const QModelIndex &index, int role) const override;
-
- void request();
- void abortRequest();
-
- void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
-
- [[nodiscard]] bool isMakingRequest() const { return jobPtr.get(); }
- [[nodiscard]] bool wasAborted() const { return m_aborted; }
-
-private slots:
- void requestFinished();
- void requestFailed(QString reason);
-
- void requestPack();
- void packRequestFinished();
- void packRequestFailed(QString reason);
-
- void logoFailed(QString logo);
- void logoLoaded(QString logo, bool stale);
-
-private:
- void requestLogo(QString file, QString url);
-
-private:
- bool m_aborted = false;
-
- QList<ModpacksCH::Modpack> modpacks;
- LogoMap m_logoMap;
-
- NetJob::Ptr jobPtr;
- int currentPack;
- QList<int> remainingPacks;
- QByteArray response;
-};
-
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
deleted file mode 100644
index b08f3bc4..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright 2021 Philip T <me@phit.link>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "FtbPage.h"
-#include "ui_FtbPage.h"
-
-#include <QKeyEvent>
-
-#include "ui/dialogs/NewInstanceDialog.h"
-#include "modplatform/modpacksch/FTBPackInstallTask.h"
-
-#include "HoeDown.h"
-
-FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::FtbPage), dialog(dialog)
-{
- ui->setupUi(this);
-
- filterModel = new Ftb::FilterModel(this);
- listModel = new Ftb::ListModel(this);
- filterModel->setSourceModel(listModel);
- ui->packView->setModel(filterModel);
- ui->packView->setSortingEnabled(true);
- ui->packView->header()->hide();
- ui->packView->setIndentation(0);
-
- ui->searchEdit->installEventFilter(this);
-
- ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
-
- for(int i = 0; i < filterModel->getAvailableSortings().size(); i++)
- {
- ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i));
- }
- ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting());
-
- connect(ui->searchEdit, &QLineEdit::textChanged, this, &FtbPage::triggerSearch);
- connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged);
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged);
-
- ui->packDescription->setMetaEntry("FTBPacks");
-}
-
-FtbPage::~FtbPage()
-{
- delete ui;
-}
-
-bool FtbPage::eventFilter(QObject* watched, QEvent* event)
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- }
- }
- return QWidget::eventFilter(watched, event);
-}
-
-bool FtbPage::shouldDisplay() const
-{
- return true;
-}
-
-void FtbPage::retranslate()
-{
- ui->retranslateUi(this);
-}
-
-void FtbPage::openedImpl()
-{
- if(!initialised || listModel->wasAborted())
- {
- listModel->request();
- initialised = true;
- }
-
- suggestCurrent();
-}
-
-void FtbPage::closedImpl()
-{
- if (listModel->isMakingRequest())
- listModel->abortRequest();
-}
-
-void FtbPage::suggestCurrent()
-{
- if(!isOpened)
- {
- return;
- }
-
- if (selectedVersion.isEmpty())
- {
- dialog->setSuggestedPack();
- return;
- }
-
- dialog->setSuggestedPack(selected.name, selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this));
- for(auto art : selected.art) {
- if(art.type == "square") {
- QString editedLogoName;
- editedLogoName = selected.name;
-
- listModel->getLogo(selected.name, art.url, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName);
- });
- }
- }
-}
-
-void FtbPage::triggerSearch()
-{
- filterModel->setSearchTerm(ui->searchEdit->text());
-}
-
-void FtbPage::onSortingSelectionChanged(QString data)
-{
- auto toSet = filterModel->getAvailableSortings().value(data);
- filterModel->setSorting(toSet);
-}
-
-void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second)
-{
- ui->versionSelectionBox->clear();
-
- if(!first.isValid())
- {
- if(isOpened)
- {
- dialog->setSuggestedPack();
- }
- return;
- }
-
- selected = filterModel->data(first, Qt::UserRole).value<ModpacksCH::Modpack>();
-
- HoeDown hoedown;
- QString output = hoedown.process(selected.description.toUtf8());
- ui->packDescription->setHtml(output);
-
- // reverse foreach, so that the newest versions are first
- for (auto i = selected.versions.size(); i--;) {
- ui->versionSelectionBox->addItem(selected.versions.at(i).name);
- }
-
- suggestCurrent();
-}
-
-void FtbPage::onVersionSelectionChanged(QString data)
-{
- if(data.isNull() || data.isEmpty())
- {
- selectedVersion = "";
- return;
- }
-
- selectedVersion = data;
- suggestCurrent();
-}
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.h b/launcher/ui/pages/modplatform/ftb/FtbPage.h
deleted file mode 100644
index 631ae7f5..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "FtbFilterModel.h"
-#include "FtbListModel.h"
-
-#include <QWidget>
-
-#include "Application.h"
-#include "ui/pages/BasePage.h"
-#include "tasks/Task.h"
-
-namespace Ui
-{
- class FtbPage;
-}
-
-class NewInstanceDialog;
-
-class FtbPage : public QWidget, public BasePage
-{
-Q_OBJECT
-
-public:
- explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0);
- virtual ~FtbPage();
- virtual QString displayName() const override
- {
- return "FTB";
- }
- virtual QIcon icon() const override
- {
- return APPLICATION->getThemedIcon("ftb_logo");
- }
- virtual QString id() const override
- {
- return "ftb";
- }
- virtual QString helpPage() const override
- {
- return "FTB-platform";
- }
- virtual bool shouldDisplay() const override;
- void retranslate() override;
-
- void openedImpl() override;
- void closedImpl() override;
-
- bool eventFilter(QObject * watched, QEvent * event) override;
-
-private:
- void suggestCurrent();
-
-private slots:
- void triggerSearch();
-
- void onSortingSelectionChanged(QString data);
- void onSelectionChanged(QModelIndex first, QModelIndex second);
- void onVersionSelectionChanged(QString data);
-
-private:
- Ui::FtbPage *ui = nullptr;
- NewInstanceDialog* dialog = nullptr;
- Ftb::ListModel* listModel = nullptr;
- Ftb::FilterModel* filterModel = nullptr;
-
- ModpacksCH::Modpack selected;
- QString selectedVersion;
-
- bool initialised { false };
-};
diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.ui b/launcher/ui/pages/modplatform/ftb/FtbPage.ui
deleted file mode 100644
index 8de0f4e6..00000000
--- a/launcher/ui/pages/modplatform/ftb/FtbPage.ui
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>FtbPage</class>
- <widget class="QWidget" name="FtbPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>875</width>
- <height>745</height>
- </rect>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
- </layout>
- </item>
- <item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit">
- <property name="placeholderText">
- <string>Search and filter...</string>
- </property>
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <widget class="QTreeView" name="packView">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="iconSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="ProjectDescriptionPage" name="packDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- <property name="openLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>ProjectDescriptionPage</class>
- <extends>QTextBrowser</extends>
- <header>ui/widgets/ProjectDescriptionPage.h</header>
- </customwidget>
- </customwidgets>
- <tabstops>
- <tabstop>searchEdit</tabstop>
- <tabstop>versionSelectionBox</tabstop>
- </tabstops>
- <resources/>
- <connections/>
-</ui>
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 6b1f6b89..2343b79f 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -35,6 +35,8 @@
#include "ListModel.h"
#include "Application.h"
+#include "net/HttpMetaCache.h"
+#include "net/NetJob.h"
#include "StringUtils.h"
#include <Version.h>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
deleted file mode 100644
index af92e63e..00000000
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "ModrinthModModel.h"
-
-#include "modplatform/modrinth/ModrinthPackIndex.h"
-
-namespace Modrinth {
-
-// NOLINTNEXTLINE(modernize-avoid-c-arrays)
-const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
-
-void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
-{
- Modrinth::loadIndexedPack(m, obj);
-}
-
-void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
-{
- Modrinth::loadExtraPackData(m, obj);
-}
-
-void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
-{
- Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
-}
-
-auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
-{
- return obj.object().value("hits").toArray();
-}
-
-} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
deleted file mode 100644
index 386897fd..00000000
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "ModrinthModPage.h"
-
-namespace Modrinth {
-
-class ListModel : public ModPlatform::ListModel {
- Q_OBJECT
-
- public:
- ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){};
- ~ListModel() override = default;
-
- private:
- 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;
-
- // NOLINTNEXTLINE(modernize-avoid-c-arrays)
- static const char* sorts[5];
- inline auto getSorts() const -> const char** override { return sorts; };
-};
-
-} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
deleted file mode 100644
index c531ea90..00000000
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 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 "ModrinthModPage.h"
-#include "modplatform/modrinth/ModrinthAPI.h"
-#include "ui_ModPage.h"
-
-#include "ModrinthModModel.h"
-#include "ui/dialogs/ModDownloadDialog.h"
-
-ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance)
- : ModPage(dialog, instance, new ModrinthAPI())
-{
- listModel = new Modrinth::ListModel(this);
- ui->packView->setModel(listModel);
-
- // index is used to set the sorting with the modrinth api
- ui->sortByBox->addItem(tr("Sort by Relevance"));
- ui->sortByBox->addItem(tr("Sort by Downloads"));
- ui->sortByBox->addItem(tr("Sort by Follows"));
- ui->sortByBox->addItem(tr("Sort by Last Updated"));
- ui->sortByBox->addItem(tr("Sort by Newest"));
-
- // 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(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged);
- connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
-
- ui->packDescription->setMetaEntry(metaEntryBase());
-}
-
-auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
-{
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders);
-
- auto loaderCompatible = false;
- for (auto remoteLoader : ver.loaders)
- {
- if (loaderStrings.contains(remoteLoader)) {
- loaderCompatible = true;
- break;
- }
- }
- return ver.mcVersion.contains(mineVer) && loaderCompatible;
-}
-
-// 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...
-auto ModrinthModPage::shouldDisplay() const -> bool { return true; }
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
deleted file mode 100644
index 40d82e6f..00000000
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-only
-/*
- * PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 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 "modplatform/ModAPI.h"
-#include "ui/pages/modplatform/ModPage.h"
-
-#include "modplatform/modrinth/ModrinthAPI.h"
-
-class ModrinthModPage : public ModPage {
- Q_OBJECT
-
- public:
- static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance* instance)
- {
- return ModPage::create<ModrinthModPage>(dialog, instance);
- }
-
- ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance);
- ~ModrinthModPage() override = default;
-
- inline auto displayName() const -> QString override { return "Modrinth"; }
- inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); }
- inline auto id() const -> QString override { return "modrinth"; }
- inline auto helpPage() const -> QString override { return "Mod-platform"; }
-
- inline auto debugName() const -> QString override { return "Modrinth"; }
- inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
-
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override;
-
- auto shouldDisplay() const -> bool override;
-};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index e6704eef..346a00b0 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -40,7 +40,6 @@
#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
-#include "ui/dialogs/ModDownloadDialog.h"
#include "ui/widgets/ProjectItem.h"
#include <QMessageBox>
@@ -128,7 +127,7 @@ bool ModpackListModel::setData(const QModelIndex &index, const QVariant &value,
void ModpackListModel::performPaginatedSearch()
{
// TODO: Move to standalone API
- NetJob* netJob = new NetJob("Modrinth::SearchModpack", APPLICATION->network());
+ auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?"
"offset=%1&"
@@ -143,7 +142,7 @@ void ModpackListModel::performPaginatedSearch()
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response));
- QObject::connect(netJob, &NetJob::succeeded, this, [this] {
+ QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
QJsonParseError parse_error_all{};
QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all);
@@ -156,7 +155,7 @@ void ModpackListModel::performPaginatedSearch()
searchRequestFinished(doc_all);
});
- QObject::connect(netJob, &NetJob::failed, this, &ModpackListModel::searchRequestFailed);
+ QObject::connect(netJob.get(), &NetJob::failed, this, &ModpackListModel::searchRequestFailed);
jobPtr = netJob;
jobPtr->start();
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 3be377a1..6e6be4b9 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -38,6 +38,7 @@
#include <QAbstractListModel>
#include "modplatform/modrinth/ModrinthPackManifest.h"
+#include "net/NetJob.h"
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
class ModPage;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 8ab2ad1d..0bb11d83 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -42,11 +42,10 @@
#include "BuildConfig.h"
#include "InstanceImportTask.h"
#include "Json.h"
+#include "Markdown.h"
#include "ui/widgets/ProjectItem.h"
-#include <HoeDown.h>
-
#include <QComboBox>
#include <QKeyEvent>
#include <QPushButton>
@@ -280,8 +279,7 @@ void ModrinthPage::updateUI()
text += "<hr>";
- HoeDown h;
- text += h.process(current.extra.body.toUtf8());
+ text += markdownToHTML(current.extra.body.toUtf8());
ui->packDescription->setHtml(text + current.description);
ui->packDescription->flush();
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
new file mode 100644
index 00000000..f5d1cc28
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -0,0 +1,116 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ModrinthResourceModels.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "modplatform/modrinth/ModrinthPackIndex.h"
+
+namespace ResourceDownload {
+
+ModrinthModModel::ModrinthModModel(BaseInstance const& base) : ModModel(base, new ModrinthAPI) {}
+
+void ModrinthModModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadIndexedPack(m, obj);
+}
+
+void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadExtraPackData(m, obj);
+}
+
+void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return obj.object().value("hits").toArray();
+}
+
+ModrinthResourcePackModel::ModrinthResourcePackModel(const BaseInstance& base) : ResourcePackResourceModel(base, new ModrinthAPI){}
+
+void ModrinthResourcePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadIndexedPack(m, obj);
+}
+
+void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadExtraPackData(m, obj);
+}
+
+void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return obj.object().value("hits").toArray();
+}
+
+ModrinthTexturePackModel::ModrinthTexturePackModel(const BaseInstance& base) : TexturePackResourceModel(base, new ModrinthAPI){}
+
+void ModrinthTexturePackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadIndexedPack(m, obj);
+}
+
+void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadExtraPackData(m, obj);
+}
+
+void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return obj.object().value("hits").toArray();
+}
+
+ModrinthShaderPackModel::ModrinthShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new ModrinthAPI){}
+
+void ModrinthShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadIndexedPack(m, obj);
+}
+
+void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ ::Modrinth::loadExtraPackData(m, obj);
+}
+
+void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return obj.object().value("hits").toArray();
+}
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
new file mode 100644
index 00000000..b351b19b
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ui/pages/modplatform/ModModel.h"
+#include "ui/pages/modplatform/ResourcePackModel.h"
+#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h"
+
+namespace ResourceDownload {
+
+class ModrinthModModel : public ModModel {
+ Q_OBJECT
+
+ public:
+ ModrinthModModel(const BaseInstance&);
+ ~ModrinthModModel() override = default;
+
+ private:
+ [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; }
+ [[nodiscard]] QString metaEntryBase() const override { return Modrinth::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;
+};
+
+class ModrinthResourcePackModel : public ResourcePackResourceModel {
+ Q_OBJECT
+
+ public:
+ ModrinthResourcePackModel(const BaseInstance&);
+ ~ModrinthResourcePackModel() override = default;
+
+ private:
+ [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; }
+ [[nodiscard]] QString metaEntryBase() const override { return Modrinth::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;
+};
+
+class ModrinthTexturePackModel : public TexturePackResourceModel {
+ Q_OBJECT
+
+ public:
+ ModrinthTexturePackModel(const BaseInstance&);
+ ~ModrinthTexturePackModel() override = default;
+
+ private:
+ [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; }
+ [[nodiscard]] QString metaEntryBase() const override { return Modrinth::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;
+};
+
+class ModrinthShaderPackModel : public ShaderPackResourceModel {
+ Q_OBJECT
+
+ public:
+ ModrinthShaderPackModel(const BaseInstance&);
+ ~ModrinthShaderPackModel() override = default;
+
+ private:
+ [[nodiscard]] QString debugName() const override { return Modrinth::debugName() + " (Model)"; }
+ [[nodiscard]] QString metaEntryBase() const override { return Modrinth::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/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
new file mode 100644
index 00000000..dd143700
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
@@ -0,0 +1,147 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 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 "ModrinthResourcePages.h"
+#include "ui_ResourcePage.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
+
+#include "ui/dialogs/ResourceDownloadDialog.h"
+
+#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h"
+
+namespace ResourceDownload {
+
+ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance)
+ : ModPage(dialog, instance)
+{
+ m_model = new ModrinthModModel(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, &ModrinthModPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::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;
+}
+
+ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
+ : ResourcePackResourcePage(dialog, instance)
+{
+ m_model = new ModrinthResourcePackModel(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, &ModrinthResourcePackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthResourcePackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthResourcePackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+ModrinthTexturePackPage::ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance)
+ : TexturePackResourcePage(dialog, instance)
+{
+ m_model = new ModrinthTexturePackModel(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, &ModrinthTexturePackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthTexturePackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthTexturePackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+ModrinthShaderPackPage::ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ : ShaderPackResourcePage(dialog, instance)
+{
+ m_model = new ModrinthShaderPackModel(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, &ModrinthShaderPackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthShaderPackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthShaderPackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+// 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...
+auto ModrinthModPage::shouldDisplay() const -> bool { return true; }
+auto ModrinthResourcePackPage::shouldDisplay() const -> bool { return true; }
+auto ModrinthTexturePackPage::shouldDisplay() const -> bool { return true; }
+auto ModrinthShaderPackPage::shouldDisplay() const -> bool { return true; }
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
new file mode 100644
index 00000000..f4eb5ce0
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
@@ -0,0 +1,153 @@
+// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 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 "Application.h"
+
+#include "modplatform/ResourceAPI.h"
+
+#include "ui/pages/modplatform/ModPage.h"
+#include "ui/pages/modplatform/ResourcePackPage.h"
+#include "ui/pages/modplatform/TexturePackPage.h"
+#include "ui/pages/modplatform/ShaderPackPage.h"
+
+namespace ResourceDownload {
+
+namespace Modrinth {
+static inline QString displayName() { return "Modrinth"; }
+static inline QIcon icon() { return APPLICATION->getThemedIcon("modrinth"); }
+static inline QString id() { return "modrinth"; }
+static inline QString debugName() { return "Modrinth"; }
+static inline QString metaEntryBase() { return "ModrinthPacks"; }
+}
+
+class ModrinthModPage : public ModPage {
+ Q_OBJECT
+
+ public:
+ static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ModPage::create<ModrinthModPage>(dialog, instance);
+ }
+
+ ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance);
+ ~ModrinthModPage() override = default;
+
+ [[nodiscard]] bool shouldDisplay() const override;
+
+ [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); }
+ [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); }
+ [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); }
+ [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); }
+ [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); }
+
+ [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
+
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool override;
+};
+
+class ModrinthResourcePackPage : public ResourcePackResourcePage {
+ Q_OBJECT
+
+ public:
+ static ModrinthResourcePackPage* create(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ResourcePackResourcePage::create<ModrinthResourcePackPage>(dialog, instance);
+ }
+
+ ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance);
+ ~ModrinthResourcePackPage() override = default;
+
+ [[nodiscard]] bool shouldDisplay() const override;
+
+ [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); }
+ [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); }
+ [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); }
+ [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); }
+ [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); }
+
+ [[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
+};
+
+class ModrinthTexturePackPage : public TexturePackResourcePage {
+ Q_OBJECT
+
+ public:
+ static ModrinthTexturePackPage* create(TexturePackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return TexturePackResourcePage::create<ModrinthTexturePackPage>(dialog, instance);
+ }
+
+ ModrinthTexturePackPage(TexturePackDownloadDialog* dialog, BaseInstance& instance);
+ ~ModrinthTexturePackPage() override = default;
+
+ [[nodiscard]] bool shouldDisplay() const override;
+
+ [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); }
+ [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); }
+ [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); }
+ [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); }
+ [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); }
+
+ [[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
+};
+
+class ModrinthShaderPackPage : public ShaderPackResourcePage {
+ Q_OBJECT
+
+ public:
+ static ModrinthShaderPackPage* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ShaderPackResourcePage::create<ModrinthShaderPackPage>(dialog, instance);
+ }
+
+ ModrinthShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);
+ ~ModrinthShaderPackPage() override = default;
+
+ [[nodiscard]] bool shouldDisplay() const override;
+
+ [[nodiscard]] inline auto displayName() const -> QString override { return Modrinth::displayName(); }
+ [[nodiscard]] inline auto icon() const -> QIcon override { return Modrinth::icon(); }
+ [[nodiscard]] inline auto id() const -> QString override { return Modrinth::id(); }
+ [[nodiscard]] inline auto debugName() const -> QString override { return Modrinth::debugName(); }
+ [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Modrinth::metaEntryBase(); }
+
+ [[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
+};
+
+} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index b2af1ac0..50f0c72d 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -112,7 +112,7 @@ void Technic::ListModel::searchWithTerm(const QString& term)
void Technic::ListModel::performSearch()
{
- NetJob *netJob = new NetJob("Technic::Search", APPLICATION->network());
+ auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
searchUrl = QString("%1trending?build=%2")
@@ -137,8 +137,8 @@ void Technic::ListModel::performSearch()
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
- QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
- QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
+ QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
+ QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed);
}
void Technic::ListModel::searchRequestFinished()
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
index b15af244..859da97e 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
@@ -141,10 +141,10 @@ void TechnicPage::suggestCurrent()
return;
}
- NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
+ auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
QString slug = current.slug;
netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response));
- QObject::connect(netJob, &NetJob::succeeded, this, [this, slug]
+ QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug]
{
jobPtr.reset();
@@ -247,11 +247,11 @@ void TechnicPage::metadataLoaded()
// version so we can display something quicker
ui->versionSelectionBox->addItem(current.currentVersion);
- auto* netJob = new NetJob(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
+ auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
- QObject::connect(netJob, &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
+ QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
jobPtr = netJob;
jobPtr->start();