Merge pull request #193 from flowln/develop

Allow for downloading multiple mods at once
This commit is contained in:
timoreo22 2022-02-26 07:33:11 +01:00 committed by GitHub
commit ae354688c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 599 additions and 494 deletions

View File

@ -10,6 +10,7 @@ class ModDownloadTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr<ModFolderModel> mods); explicit ModDownloadTask(const QUrl sourceUrl, const QString filename, const std::shared_ptr<ModFolderModel> mods);
const QString& getFilename() const { return filename; }
public slots: public slots:
bool abort() override; bool abort() override;

View File

@ -5,6 +5,7 @@
#include <InstanceList.h> #include <InstanceList.h>
#include "ProgressDialog.h" #include "ProgressDialog.h"
#include "CustomMessageBox.h"
#include <QLayout> #include <QLayout>
#include <QPushButton> #include <QPushButton>
@ -39,9 +40,10 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods
// Bonk Qt over its stupid head and make sure it understands which button is the default one... // Bonk Qt over its stupid head and make sure it understands which button is the default one...
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button
auto OkButton = m_buttons->button(QDialogButtonBox::Ok); auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
OkButton->setEnabled(false);
OkButton->setDefault(true); OkButton->setDefault(true);
OkButton->setAutoDefault(true); OkButton->setAutoDefault(true);
connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::accept); connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm);
auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel);
CancelButton->setDefault(false); CancelButton->setDefault(false);
@ -52,6 +54,7 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods
HelpButton->setDefault(false); HelpButton->setDefault(false);
HelpButton->setAutoDefault(false); HelpButton->setAutoDefault(false);
connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
QMetaObject::connectSlotsByName(this); QMetaObject::connectSlotsByName(this);
setWindowModality(Qt::WindowModal); setWindowModality(Qt::WindowModal);
setWindowTitle("Download mods"); setWindowTitle("Download mods");
@ -67,6 +70,36 @@ void ModDownloadDialog::reject()
QDialog::reject(); QDialog::reject();
} }
void ModDownloadDialog::confirm()
{
auto keys = modTask.keys();
keys.sort(Qt::CaseInsensitive);
auto info = QString(tr("You're about to download the following mods:"));
info.append("\n\n");
for(auto task : keys){
info.append(task);
info.append("\n --> ");
info.append(tr("File name: "));
info.append(modTask.find(task).value()->getFilename());
info.append('\n');
}
auto confirm_dialog = CustomMessageBox::selectable(
this,
tr("Confirm mods to download"),
info,
QMessageBox::NoIcon,
QMessageBox::Cancel | QMessageBox::Ok,
QMessageBox::Ok
);
auto AcceptButton = confirm_dialog->button(QMessageBox::Ok);
connect(AcceptButton, &QPushButton::clicked, this, &ModDownloadDialog::accept);
confirm_dialog->open();
}
void ModDownloadDialog::accept() void ModDownloadDialog::accept()
{ {
QDialog::accept(); QDialog::accept();
@ -83,16 +116,35 @@ QList<BasePage *> ModDownloadDialog::getPages()
}; };
} }
void ModDownloadDialog::setSuggestedMod(const QString& name, ModDownloadTask* task) void ModDownloadDialog::addSelectedMod(const QString& name, ModDownloadTask* task)
{ {
modTask.reset(task); removeSelectedMod(name);
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(task); modTask.insert(name, task);
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty());
}
void ModDownloadDialog::removeSelectedMod(const QString &name)
{
if(modTask.contains(name))
delete modTask.find(name).value();
modTask.remove(name);
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty());
}
bool ModDownloadDialog::isModSelected(const QString &name, const QString& filename) const
{
// FIXME: Is there a way to check for versions without checking the filename
// as a heuristic, other than adding such info to ModDownloadTask itself?
auto iter = modTask.find(name);
return iter != modTask.end() && (iter.value()->getFilename() == filename);
} }
ModDownloadDialog::~ModDownloadDialog() ModDownloadDialog::~ModDownloadDialog()
{ {
} }
ModDownloadTask *ModDownloadDialog::getTask() { const QList<ModDownloadTask*> ModDownloadDialog::getTasks() {
return modTask.release(); return modTask.values();
} }

View File

@ -29,12 +29,15 @@ public:
QString dialogTitle() override; QString dialogTitle() override;
QList<BasePage *> getPages() override; QList<BasePage *> getPages() override;
void setSuggestedMod(const QString & name = QString(), ModDownloadTask * task = nullptr); void addSelectedMod(const QString & name = QString(), ModDownloadTask * task = nullptr);
void removeSelectedMod(const QString & name = QString());
bool isModSelected(const QString & name, const QString & filename) const;
ModDownloadTask * getTask(); const QList<ModDownloadTask*> getTasks();
const std::shared_ptr<ModFolderModel> &mods; const std::shared_ptr<ModFolderModel> &mods;
public slots: public slots:
void confirm();
void accept() override; void accept() override;
void reject() override; void reject() override;
@ -49,6 +52,6 @@ private:
ModrinthPage *modrinthPage = nullptr; ModrinthPage *modrinthPage = nullptr;
FlameModPage *flameModPage = nullptr; FlameModPage *flameModPage = nullptr;
std::unique_ptr<ModDownloadTask> modTask; QHash<QString, ModDownloadTask*> modTask;
BaseInstance *m_instance; BaseInstance *m_instance;
}; };

View File

@ -365,8 +365,7 @@ void ModFolderPage::on_actionInstall_mods_triggered()
} }
ModDownloadDialog mdownload(m_mods, this, m_inst); ModDownloadDialog mdownload(m_mods, this, m_inst);
if(mdownload.exec()) { if(mdownload.exec()) {
ModDownloadTask *task = mdownload.getTask(); for(auto task : mdownload.getTasks()){
if (task) {
connect(task, &Task::failed, [this, task](QString reason) { connect(task, &Task::failed, [this, task](QString reason) {
task->deleteLater(); task->deleteLater();
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();

View File

@ -4,24 +4,26 @@
#include <QKeyEvent> #include <QKeyEvent>
#include "Application.h" #include "Application.h"
#include "Json.h"
#include "ui/dialogs/ModDownloadDialog.h"
#include "InstanceImportTask.h"
#include "FlameModModel.h" #include "FlameModModel.h"
#include "InstanceImportTask.h"
#include "Json.h"
#include "ModDownloadTask.h" #include "ModDownloadTask.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "ui/dialogs/ModDownloadDialog.h"
FlameModPage::FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance) FlameModPage::FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance)
: QWidget(dialog), m_instance(instance), ui(new Ui::FlameModPage), dialog(dialog) : QWidget(dialog), m_instance(instance), ui(new Ui::FlameModPage),
{ dialog(dialog) {
ui->setupUi(this); ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &FlameModPage::triggerSearch); connect(ui->searchButton, &QPushButton::clicked, this,
&FlameModPage::triggerSearch);
ui->searchEdit->installEventFilter(this); ui->searchEdit->installEventFilter(this);
listModel = new FlameMod::ListModel(this); listModel = new FlameMod::ListModel(this);
ui->packView->setModel(listModel); ui->packView->setModel(listModel);
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(
Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
// index is used to set the sorting with the flame api // index is used to set the sorting with the flame api
@ -32,20 +34,21 @@ FlameModPage::FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance)
ui->sortByBox->addItem(tr("Sort by Author")); ui->sortByBox->addItem(tr("Sort by Author"));
ui->sortByBox->addItem(tr("Sort by Downloads")); ui->sortByBox->addItem(tr("Sort by Downloads"));
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this,
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); SLOT(triggerSearch()));
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); 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);
} }
FlameModPage::~FlameModPage() FlameModPage::~FlameModPage() { delete ui; }
{
delete ui;
}
bool FlameModPage::eventFilter(QObject* watched, QEvent* event) bool FlameModPage::eventFilter(QObject *watched, QEvent *event) {
{
if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Return) { if (keyEvent->key() == Qt::Key_Return) {
triggerSearch(); triggerSearch();
keyEvent->accept(); keyEvent->accept();
@ -55,32 +58,22 @@ bool FlameModPage::eventFilter(QObject* watched, QEvent* event)
return QWidget::eventFilter(watched, event); return QWidget::eventFilter(watched, event);
} }
bool FlameModPage::shouldDisplay() const bool FlameModPage::shouldDisplay() const { return true; }
{
return true;
}
void FlameModPage::openedImpl() void FlameModPage::openedImpl() {
{ updateSelectionButton();
suggestCurrent();
triggerSearch(); triggerSearch();
} }
void FlameModPage::triggerSearch() void FlameModPage::triggerSearch() {
{ listModel->searchWithTerm(ui->searchEdit->text(),
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); ui->sortByBox->currentIndex());
} }
void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second) {
{
ui->versionSelectionBox->clear(); ui->versionSelectionBox->clear();
if(!first.isValid()) if (!first.isValid()) {
{
if(isOpened)
{
dialog->setSuggestedMod();
}
return; return;
} }
@ -93,14 +86,14 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
else else
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
if (!current.authors.empty()) { if (!current.authors.empty()) {
auto authorToStr = [](FlameMod::ModpackAuthor & author) { auto authorToStr = [](FlameMod::ModpackAuthor &author) {
if(author.url.isEmpty()) { if (author.url.isEmpty()) {
return author.name; return author.name;
} }
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name); return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
}; };
QStringList authorStrs; QStringList authorStrs;
for(auto & author: current.authors) { for (auto &author : current.authors) {
authorStrs.push_back(authorToStr(author)); authorStrs.push_back(authorToStr(author));
} }
text += "<br>" + tr(" by ") + authorStrs.join(", "); text += "<br>" + tr(" by ") + authorStrs.join(", ");
@ -109,91 +102,113 @@ void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
ui->packDescription->setHtml(text + current.description); ui->packDescription->setHtml(text + current.description);
if (!current.versionsLoaded) if (!current.versionsLoaded) {
{
qDebug() << "Loading flame mod versions"; qDebug() << "Loading flame mod versions";
auto netJob = new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), APPLICATION->network());
ui->modSelectionButton->setText(tr("Loading versions..."));
ui->modSelectionButton->setEnabled(false);
auto netJob =
new NetJob(QString("Flame::ModVersions(%1)").arg(current.name),
APPLICATION->network());
auto response = new QByteArray(); auto response = new QByteArray();
int addonId = current.addonId; int addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response)); netJob->addNetAction(Net::Download::makeByteArray(
QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files")
.arg(addonId),
response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response] QObject::connect(netJob, &NetJob::succeeded, this, [this, response] {
{
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from Flame at "
<< parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response; qWarning() << *response;
return; return;
} }
QJsonArray arr = doc.array(); QJsonArray arr = doc.array();
try try {
{ FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(),
FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance); m_instance);
} } catch (const JSONValidationError &e) {
catch(const JSONValidationError &e)
{
qDebug() << *response; qDebug() << *response;
qWarning() << "Error while reading Flame mod version: " << e.cause(); qWarning() << "Error while reading Flame mod version: " << e.cause();
} }
auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile(); auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
QString mcVersion = packProfile->getComponentVersion("net.minecraft"); QString mcVersion = packProfile->getComponentVersion("net.minecraft");
QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge"; QString loaderString =
for(int i = 0; i < current.versions.size(); i++) { (packProfile->getComponentVersion("net.minecraftforge").isEmpty())
? "fabric"
: "forge";
for (int i = 0; i < current.versions.size(); i++) {
auto version = current.versions[i]; auto version = current.versions[i];
if(!version.mcVersion.contains(mcVersion)){ if (!version.mcVersion.contains(mcVersion)) {
continue; continue;
} }
ui->versionSelectionBox->addItem(version.version, QVariant(i)); ui->versionSelectionBox->addItem(version.version, QVariant(i));
} }
if(ui->versionSelectionBox->count() == 0){ if (ui->versionSelectionBox->count() == 0) {
ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant(-1)); ui->versionSelectionBox->addItem(tr("No Valid Version found!"),
QVariant(-1));
} }
suggestCurrent();
ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
updateSelectionButton();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
{
netJob->deleteLater(); netJob->deleteLater();
delete response; delete response;
}); });
netJob->start(); netJob->start();
} else {
for (int i = 0; i < current.versions.size(); i++) {
ui->versionSelectionBox->addItem(current.versions[i].version,
QVariant(i));
} }
else if (ui->versionSelectionBox->count() == 0) {
{ ui->versionSelectionBox->addItem(tr("No Valid Version found!"),
for(int i = 0; i < current.versions.size(); i++) { QVariant(-1));
ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
} }
if(ui->versionSelectionBox->count() == 0){
ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant(-1)); updateSelectionButton();
}
suggestCurrent();
} }
} }
void FlameModPage::suggestCurrent() void FlameModPage::updateSelectionButton() {
{ if (!isOpened || selectedVersion < 0) {
if(!isOpened) ui->modSelectionButton->setEnabled(false);
{
return; return;
} }
if (selectedVersion == -1) ui->modSelectionButton->setEnabled(true);
{ auto &version = current.versions[selectedVersion];
dialog->setSuggestedMod(); if (!dialog->isModSelected(current.name, version.fileName)) {
return; ui->modSelectionButton->setText(tr("Select mod for download"));
} else {
ui->modSelectionButton->setText(tr("Deselect mod for download"));
} }
auto version = current.versions[selectedVersion];
dialog->setSuggestedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName , dialog->mods));
} }
void FlameModPage::onVersionSelectionChanged(QString data) void FlameModPage::onVersionSelectionChanged(QString data) {
{ if (data.isNull() || data.isEmpty()) {
if(data.isNull() || data.isEmpty())
{
selectedVersion = -1; selectedVersion = -1;
return; return;
} }
selectedVersion = ui->versionSelectionBox->currentData().toInt(); selectedVersion = ui->versionSelectionBox->currentData().toInt();
suggestCurrent(); updateSelectionButton();
}
void FlameModPage::onModSelected() {
auto &version = current.versions[selectedVersion];
if (dialog->isModSelected(current.name, version.fileName)) {
dialog->removeSelectedMod(current.name);
} else {
dialog->addSelectedMod(current.name,
new ModDownloadTask(version.downloadUrl,
version.fileName, dialog->mods));
}
updateSelectionButton();
} }

View File

@ -50,12 +50,13 @@ public:
BaseInstance *m_instance; BaseInstance *m_instance;
private: private:
void suggestCurrent(); void updateSelectionButton();
private slots: private slots:
void triggerSearch(); void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second); void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data); void onVersionSelectionChanged(QString data);
void onModSelected();
private: private:
Ui::FlameModPage *ui = nullptr; Ui::FlameModPage *ui = nullptr;

View File

@ -11,22 +11,56 @@
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0,0,0" columnminimumwidth="0,0,0">
<item row="1" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
</item>
<item row="1" column="0">
<widget class="QComboBox" name="sortByBox"/>
</item>
<item row="1" 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="2" column="2">
<widget class="QPushButton" name="modSelectionButton">
<property name="text">
<string>Select mod for download</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter ...</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0"> <item row="1" column="0">
<widget class="QListView" name="packView"> <widget class="QListView" name="packView">
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="horizontalScrollBarPolicy"> <property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
<property name="alternatingRowColors"> <property name="alternatingRowColors">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
@ -41,26 +75,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Version selected:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="sortByBox"/>
</item>
</layout>
</item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QPushButton" name="searchButton"> <widget class="QPushButton" name="searchButton">
<property name="text"> <property name="text">
@ -68,13 +82,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter ...</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>

View File

@ -4,24 +4,26 @@
#include <QKeyEvent> #include <QKeyEvent>
#include "Application.h" #include "Application.h"
#include "Json.h"
#include "ui/dialogs/ModDownloadDialog.h"
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "ModrinthModel.h" #include "Json.h"
#include "ModDownloadTask.h" #include "ModDownloadTask.h"
#include "ModrinthModel.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "ui/dialogs/ModDownloadDialog.h"
ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance) ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance)
: QWidget(dialog), m_instance(instance), ui(new Ui::ModrinthPage), dialog(dialog) : QWidget(dialog), m_instance(instance), ui(new Ui::ModrinthPage),
{ dialog(dialog) {
ui->setupUi(this); ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch); connect(ui->searchButton, &QPushButton::clicked, this,
&ModrinthPage::triggerSearch);
ui->searchEdit->installEventFilter(this); ui->searchEdit->installEventFilter(this);
listModel = new Modrinth::ListModel(this); listModel = new Modrinth::ListModel(this);
ui->packView->setModel(listModel); ui->packView->setModel(listModel);
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(
Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
// index is used to set the sorting with the modrinth api // index is used to set the sorting with the modrinth api
@ -31,20 +33,21 @@ ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance)
ui->sortByBox->addItem(tr("Sort by last updated")); ui->sortByBox->addItem(tr("Sort by last updated"));
ui->sortByBox->addItem(tr("Sort by newest")); ui->sortByBox->addItem(tr("Sort by newest"));
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this,
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); SLOT(triggerSearch()));
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &ModrinthPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this,
&ModrinthPage::onVersionSelectionChanged);
connect(ui->modSelectionButton, &QPushButton::clicked, this,
&ModrinthPage::onModSelected);
} }
ModrinthPage::~ModrinthPage() ModrinthPage::~ModrinthPage() { delete ui; }
{
delete ui;
}
bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) bool ModrinthPage::eventFilter(QObject *watched, QEvent *event) {
{
if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Return) { if (keyEvent->key() == Qt::Key_Return) {
triggerSearch(); triggerSearch();
keyEvent->accept(); keyEvent->accept();
@ -54,32 +57,22 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
return QWidget::eventFilter(watched, event); return QWidget::eventFilter(watched, event);
} }
bool ModrinthPage::shouldDisplay() const bool ModrinthPage::shouldDisplay() const { return true; }
{
return true;
}
void ModrinthPage::openedImpl() void ModrinthPage::openedImpl() {
{ updateSelectionButton();
suggestCurrent();
triggerSearch(); triggerSearch();
} }
void ModrinthPage::triggerSearch() void ModrinthPage::triggerSearch() {
{ listModel->searchWithTerm(ui->searchEdit->text(),
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); ui->sortByBox->currentIndex());
} }
void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) {
{
ui->versionSelectionBox->clear(); ui->versionSelectionBox->clear();
if(!first.isValid()) if (!first.isValid()) {
{
if(isOpened)
{
dialog->setSuggestedMod();
}
return; return;
} }
@ -91,93 +84,119 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
text = name; text = name;
else else
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
text += "<br>"+ tr(" by ") + "<a href=\""+current.author.url+"\">"+current.author.name+"</a><br><br>"; text += "<br>" + tr(" by ") + "<a href=\"" + current.author.url + "\">" +
current.author.name + "</a><br><br>";
ui->packDescription->setHtml(text + current.description); ui->packDescription->setHtml(text + current.description);
if (!current.versionsLoaded) if (!current.versionsLoaded) {
{
qDebug() << "Loading Modrinth mod versions"; qDebug() << "Loading Modrinth mod versions";
auto netJob = new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network());
ui->modSelectionButton->setText(tr("Loading versions..."));
ui->modSelectionButton->setEnabled(false);
auto netJob =
new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name),
APPLICATION->network());
auto response = new QByteArray(); auto response = new QByteArray();
QString addonId = current.addonId; QString addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId), response)); netJob->addNetAction(Net::Download::makeByteArray(
QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId),
response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response] QObject::connect(netJob, &NetJob::succeeded, this, [this, response] {
{
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << "Error while parsing JSON response from Modrinth at "
<< parse_error.offset
<< " reason: " << parse_error.errorString();
qWarning() << *response; qWarning() << *response;
return; return;
} }
QJsonArray arr = doc.array(); QJsonArray arr = doc.array();
try try {
{ Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(),
Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance); m_instance);
} } catch (const JSONValidationError &e) {
catch(const JSONValidationError &e)
{
qDebug() << *response; qDebug() << *response;
qWarning() << "Error while reading Modrinth mod version: " << e.cause(); qWarning() << "Error while reading Modrinth mod version: " << e.cause();
} }
auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile(); auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
QString mcVersion = packProfile->getComponentVersion("net.minecraft"); QString mcVersion = packProfile->getComponentVersion("net.minecraft");
QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge"; QString loaderString =
for(int i = 0; i < current.versions.size(); i++) { (packProfile->getComponentVersion("net.minecraftforge").isEmpty())
? "fabric"
: "forge";
for (int i = 0; i < current.versions.size(); i++) {
auto version = current.versions[i]; auto version = current.versions[i];
if(!version.mcVersion.contains(mcVersion) || !version.loaders.contains(loaderString)){ if (!version.mcVersion.contains(mcVersion) ||
!version.loaders.contains(loaderString)) {
continue; continue;
} }
ui->versionSelectionBox->addItem(version.version, QVariant(i)); ui->versionSelectionBox->addItem(version.version, QVariant(i));
} }
if(ui->versionSelectionBox->count() == 0){ if (ui->versionSelectionBox->count() == 0) {
ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant(-1)); ui->versionSelectionBox->addItem(tr("No Valid Version found !"),
QVariant(-1));
} }
suggestCurrent(); ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
updateSelectionButton();
}); });
QObject::connect(netJob, &NetJob::finished, this, [response, netJob]{
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
netJob->deleteLater(); netJob->deleteLater();
delete response; delete response;
}); });
netJob->start(); netJob->start();
} else {
for (int i = 0; i < current.versions.size(); i++) {
ui->versionSelectionBox->addItem(current.versions[i].version,
QVariant(i));
} }
else if (ui->versionSelectionBox->count() == 0) {
{ ui->versionSelectionBox->addItem(tr("No Valid Version found !"),
for(int i = 0; i < current.versions.size(); i++) { QVariant(-1));
ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
} }
if(ui->versionSelectionBox->count() == 0){
ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant(-1)); updateSelectionButton();
}
suggestCurrent();
} }
} }
void ModrinthPage::suggestCurrent() void ModrinthPage::updateSelectionButton() {
{ if (!isOpened || selectedVersion < 0) {
if(!isOpened) ui->modSelectionButton->setEnabled(false);
{
return; return;
} }
if (selectedVersion == -1) ui->modSelectionButton->setEnabled(true);
{ auto &version = current.versions[selectedVersion];
dialog->setSuggestedMod(); if (!dialog->isModSelected(current.name, version.fileName)) {
return; ui->modSelectionButton->setText(tr("Select mod for download"));
} else {
ui->modSelectionButton->setText(tr("Deselect mod for download"));
} }
auto version = current.versions[selectedVersion];
dialog->setSuggestedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName , dialog->mods));
} }
void ModrinthPage::onVersionSelectionChanged(QString data) void ModrinthPage::onVersionSelectionChanged(QString data) {
{ if (data.isNull() || data.isEmpty()) {
if(data.isNull() || data.isEmpty())
{
selectedVersion = -1; selectedVersion = -1;
return; return;
} }
selectedVersion = ui->versionSelectionBox->currentData().toInt(); selectedVersion = ui->versionSelectionBox->currentData().toInt();
suggestCurrent(); updateSelectionButton();
}
void ModrinthPage::onModSelected() {
auto &version = current.versions[selectedVersion];
if (dialog->isModSelected(current.name, version.fileName)) {
dialog->removeSelectedMod(current.name);
} else {
dialog->addSelectedMod(current.name,
new ModDownloadTask(version.downloadUrl,
version.fileName, dialog->mods));
}
updateSelectionButton();
} }

View File

@ -50,12 +50,13 @@ public:
BaseInstance *m_instance; BaseInstance *m_instance;
private: private:
void suggestCurrent(); void updateSelectionButton();
private slots: private slots:
void triggerSearch(); void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second); void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data); void onVersionSelectionChanged(QString data);
void onModSelected();
private: private:
Ui::ModrinthPage *ui = nullptr; Ui::ModrinthPage *ui = nullptr;

View File

@ -13,23 +13,7 @@
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0"> <item row="1" column="2">
<widget class="QListView" name="packView">
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QTextBrowser" name="packDescription"> <widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
@ -39,26 +23,22 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item row="1" column="0">
</item> <widget class="QListView" name="packView">
<item row="2" column="0" colspan="2"> <property name="horizontalScrollBarPolicy">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> <enum>Qt::ScrollBarAlwaysOff</enum>
<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>
<property name="alignment"> <property name="alternatingRowColors">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <bool>true</bool>
</property>
<property name="iconSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QComboBox" name="sortByBox"/>
</item>
</layout> </layout>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
@ -75,6 +55,33 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,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>
<item row="1" column="2">
<widget class="QPushButton" name="modSelectionButton">
<property name="text">
<string>Select mod for download</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
<tabstops> <tabstops>