Merge pull request #939 from flowln/mod_downloader_improve
Some more UI / UX improvements to the mod downloader!
This commit is contained in:
commit
1b0ca47682
@ -685,6 +685,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
|
||||
m_settings->registerSetting("UpdateDialogGeometry", "");
|
||||
|
||||
m_settings->registerSetting("ModDownloadGeometry", "");
|
||||
|
||||
// HACK: This code feels so stupid is there a less stupid way of doing this?
|
||||
{
|
||||
m_settings->registerSetting("PastebinURL", "");
|
||||
|
@ -896,6 +896,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/widgets/PageContainer.cpp
|
||||
ui/widgets/PageContainer.h
|
||||
ui/widgets/PageContainer_p.h
|
||||
ui/widgets/ProjectItem.h
|
||||
ui/widgets/ProjectItem.cpp
|
||||
ui/widgets/VersionListView.cpp
|
||||
ui/widgets/VersionListView.h
|
||||
ui/widgets/VersionSelectWidget.cpp
|
||||
|
@ -73,7 +73,7 @@ class ModAPI {
|
||||
};
|
||||
|
||||
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
|
||||
virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0;
|
||||
virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) = 0;
|
||||
|
||||
virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0;
|
||||
virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0;
|
||||
@ -85,7 +85,7 @@ class ModAPI {
|
||||
ModLoaderTypes loaders;
|
||||
};
|
||||
|
||||
virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0;
|
||||
virtual void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const = 0;
|
||||
|
||||
static auto getModLoaderString(ModLoaderType type) -> const QString {
|
||||
switch (type) {
|
||||
|
@ -75,6 +75,8 @@ struct ExtraPackData {
|
||||
QString sourceUrl;
|
||||
QString wikiUrl;
|
||||
QString discordUrl;
|
||||
|
||||
QString body;
|
||||
};
|
||||
|
||||
struct IndexedPack {
|
||||
|
@ -67,6 +67,43 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
|
||||
return changelog;
|
||||
}
|
||||
|
||||
auto FlameAPI::getModDescription(int modId) -> QString
|
||||
{
|
||||
QEventLoop lock;
|
||||
QString description;
|
||||
|
||||
auto* netJob = new NetJob(QString("Flame::ModDescription"), APPLICATION->network());
|
||||
auto* response = new QByteArray();
|
||||
netJob->addNetAction(Net::Download::makeByteArray(
|
||||
QString("https://api.curseforge.com/v1/mods/%1/description")
|
||||
.arg(QString::number(modId)), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &description] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from Flame::ModDescription at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
|
||||
netJob->failed(parse_error.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
description = Json::ensureString(doc.object(), "data");
|
||||
});
|
||||
|
||||
QObject::connect(netJob, &NetJob::finished, [response, &lock] {
|
||||
delete response;
|
||||
lock.quit();
|
||||
});
|
||||
|
||||
netJob->start();
|
||||
lock.exec();
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion
|
||||
{
|
||||
QEventLoop loop;
|
||||
|
@ -7,6 +7,7 @@ class FlameAPI : public NetworkModAPI {
|
||||
public:
|
||||
auto matchFingerprints(const QList<uint>& fingerprints, QByteArray* response) -> NetJob::Ptr;
|
||||
auto getModFileChangelog(int modId, int fileId) -> QString;
|
||||
auto getModDescription(int modId) -> QString;
|
||||
|
||||
auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion;
|
||||
|
||||
|
@ -4,10 +4,9 @@
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
static FlameAPI api;
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
|
||||
void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
{
|
||||
@ -31,10 +30,11 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
pack.authors.append(packAuthor);
|
||||
}
|
||||
|
||||
loadExtraPackData(pack, obj);
|
||||
pack.extraDataLoaded = false;
|
||||
loadURLs(pack, obj);
|
||||
}
|
||||
|
||||
void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
{
|
||||
auto links_obj = Json::ensureObject(obj, "links");
|
||||
|
||||
@ -50,6 +50,15 @@ void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
|
||||
if(pack.extraData.wikiUrl.endsWith('/'))
|
||||
pack.extraData.wikiUrl.chop(1);
|
||||
|
||||
if (!pack.extraData.body.isEmpty())
|
||||
pack.extraDataLoaded = true;
|
||||
}
|
||||
|
||||
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
|
||||
{
|
||||
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
|
||||
|
||||
if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty())
|
||||
pack.extraDataLoaded = true;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,8 @@
|
||||
namespace FlameMod {
|
||||
|
||||
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
QJsonArray& arr,
|
||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||
|
@ -31,48 +31,48 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const
|
||||
netJob->start();
|
||||
}
|
||||
|
||||
void NetworkModAPI::getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack)
|
||||
void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback)
|
||||
{
|
||||
auto response = new QByteArray();
|
||||
auto job = getProject(pack.addonId.toString(), response);
|
||||
|
||||
QObject::connect(job, &NetJob::succeeded, caller, [caller, &pack, response] {
|
||||
QObject::connect(job, &NetJob::succeeded, [callback, &pack, response] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset
|
||||
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
|
||||
caller->infoRequestFinished(doc, pack);
|
||||
callback(doc, pack);
|
||||
});
|
||||
|
||||
job->start();
|
||||
}
|
||||
|
||||
void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const
|
||||
void NetworkModAPI::getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const
|
||||
{
|
||||
auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network());
|
||||
auto netJob = new NetJob(QString("ModVersions(%2)").arg(args.addonId), APPLICATION->network());
|
||||
auto response = new QByteArray();
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, caller, [response, caller, args] {
|
||||
QObject::connect(netJob, &NetJob::succeeded, [response, callback, args] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset
|
||||
qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
|
||||
caller->versionRequestSucceeded(doc, args.addonId);
|
||||
callback(doc, args.addonId);
|
||||
});
|
||||
|
||||
QObject::connect(netJob, &NetJob::finished, caller, [response, netJob] {
|
||||
QObject::connect(netJob, &NetJob::finished, [response, netJob] {
|
||||
netJob->deleteLater();
|
||||
delete response;
|
||||
});
|
||||
|
@ -5,8 +5,8 @@
|
||||
class NetworkModAPI : public ModAPI {
|
||||
public:
|
||||
void searchMods(CallerType* caller, SearchArgs&& args) const override;
|
||||
void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override;
|
||||
void getVersions(CallerType* caller, VersionSearchArgs&& args) const override;
|
||||
void getModInfo(ModPlatform::IndexedPack& pack, std::function<void(QJsonDocument&, ModPlatform::IndexedPack&)> callback) override;
|
||||
void getVersions(VersionSearchArgs&& args, std::function<void(QJsonDocument&, QString)> callback) const override;
|
||||
|
||||
auto getProject(QString addonId, QByteArray* response) const -> NetJob* override;
|
||||
|
||||
|
@ -87,6 +87,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
|
||||
pack.extraData.donate.append(donate);
|
||||
}
|
||||
|
||||
pack.extraData.body = Json::ensureString(obj, "body");
|
||||
|
||||
pack.extraDataLoaded = true;
|
||||
}
|
||||
|
||||
|
@ -19,36 +19,33 @@
|
||||
#include "ModDownloadDialog.h"
|
||||
|
||||
#include <BaseVersion.h>
|
||||
#include <icons/IconList.h>
|
||||
#include <InstanceList.h>
|
||||
#include <icons/IconList.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "ProgressDialog.h"
|
||||
#include "ReviewMessageBox.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
#include <QValidator>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
#include "ui/pages/modplatform/modrinth/ModrinthModPage.h"
|
||||
#include "ModDownloadTask.h"
|
||||
#include "ui/pages/modplatform/flame/FlameModPage.h"
|
||||
#include "ui/pages/modplatform/modrinth/ModrinthModPage.h"
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
|
||||
|
||||
ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods, QWidget *parent,
|
||||
BaseInstance *instance)
|
||||
: QDialog(parent), mods(mods), m_instance(instance)
|
||||
ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel>& mods, QWidget* parent, BaseInstance* instance)
|
||||
: QDialog(parent), mods(mods), m_verticalLayout(new QVBoxLayout(this)), m_instance(instance)
|
||||
{
|
||||
setObjectName(QStringLiteral("ModDownloadDialog"));
|
||||
|
||||
resize(std::max(0.5*parent->width(), 400.0), std::max(0.75*parent->height(), 400.0));
|
||||
|
||||
m_verticalLayout = new QVBoxLayout(this);
|
||||
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
|
||||
|
||||
resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0));
|
||||
|
||||
setWindowIcon(APPLICATION->getThemedIcon("new"));
|
||||
// NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below.
|
||||
// NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not
|
||||
// move this below.
|
||||
m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
m_container = new PageContainer(this);
|
||||
@ -58,12 +55,17 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods
|
||||
|
||||
m_container->addButtons(m_buttons);
|
||||
|
||||
connect(m_container, &PageContainer::selectedPageChanged, this, &ModDownloadDialog::selectedPageChanged);
|
||||
|
||||
// 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
|
||||
auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
|
||||
OkButton->setEnabled(false);
|
||||
OkButton->setDefault(true);
|
||||
OkButton->setAutoDefault(true);
|
||||
OkButton->setText(tr("Review and confirm"));
|
||||
OkButton->setShortcut(tr("Ctrl+Return"));
|
||||
OkButton->setToolTip(tr("Opens a new popup to review your selected mods and confirm your selection. Shortcut: Ctrl+Return"));
|
||||
connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm);
|
||||
|
||||
auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel);
|
||||
@ -78,7 +80,9 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods
|
||||
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
setWindowTitle("Download mods");
|
||||
setWindowTitle(dialogTitle());
|
||||
|
||||
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("ModDownloadGeometry").toByteArray()));
|
||||
}
|
||||
|
||||
QString ModDownloadDialog::dialogTitle()
|
||||
@ -88,6 +92,7 @@ QString ModDownloadDialog::dialogTitle()
|
||||
|
||||
void ModDownloadDialog::reject()
|
||||
{
|
||||
APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64());
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
@ -114,12 +119,13 @@ void ModDownloadDialog::confirm()
|
||||
|
||||
void ModDownloadDialog::accept()
|
||||
{
|
||||
APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64());
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
QList<BasePage *> ModDownloadDialog::getPages()
|
||||
QList<BasePage*> ModDownloadDialog::getPages()
|
||||
{
|
||||
QList<BasePage *> pages;
|
||||
QList<BasePage*> pages;
|
||||
|
||||
pages.append(new ModrinthModPage(this, m_instance));
|
||||
if (APPLICATION->capabilities() & Application::SupportsFlame)
|
||||
@ -128,7 +134,7 @@ QList<BasePage *> ModDownloadDialog::getPages()
|
||||
return pages;
|
||||
}
|
||||
|
||||
void ModDownloadDialog::addSelectedMod(const QString& name, ModDownloadTask* task)
|
||||
void ModDownloadDialog::addSelectedMod(QString name, ModDownloadTask* task)
|
||||
{
|
||||
removeSelectedMod(name);
|
||||
modTask.insert(name, task);
|
||||
@ -136,16 +142,16 @@ void ModDownloadDialog::addSelectedMod(const QString& name, ModDownloadTask* tas
|
||||
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty());
|
||||
}
|
||||
|
||||
void ModDownloadDialog::removeSelectedMod(const QString &name)
|
||||
void ModDownloadDialog::removeSelectedMod(QString name)
|
||||
{
|
||||
if(modTask.contains(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
|
||||
bool ModDownloadDialog::isModSelected(QString name, 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?
|
||||
@ -153,16 +159,31 @@ bool ModDownloadDialog::isModSelected(const QString &name, const QString& filena
|
||||
return iter != modTask.end() && (iter.value()->getFilename() == filename);
|
||||
}
|
||||
|
||||
bool ModDownloadDialog::isModSelected(const QString &name) const
|
||||
bool ModDownloadDialog::isModSelected(QString name) const
|
||||
{
|
||||
auto iter = modTask.find(name);
|
||||
return iter != modTask.end();
|
||||
}
|
||||
|
||||
ModDownloadDialog::~ModDownloadDialog()
|
||||
const QList<ModDownloadTask*> ModDownloadDialog::getTasks()
|
||||
{
|
||||
}
|
||||
|
||||
const QList<ModDownloadTask*> ModDownloadDialog::getTasks() {
|
||||
return modTask.values();
|
||||
}
|
||||
|
||||
void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected)
|
||||
{
|
||||
auto* prev_page = dynamic_cast<ModPage*>(previous);
|
||||
if (!prev_page) {
|
||||
qCritical() << "Page '" << previous->displayName() << "' in ModDownloadDialog is not a ModPage!";
|
||||
return;
|
||||
}
|
||||
|
||||
auto* selected_page = dynamic_cast<ModPage*>(selected);
|
||||
if (!selected_page) {
|
||||
qCritical() << "Page '" << selected->displayName() << "' in ModDownloadDialog is not a ModPage!";
|
||||
return;
|
||||
}
|
||||
|
||||
// Same effect as having a global search bar
|
||||
selected_page->setSearchTerm(prev_page->getSearchTerm());
|
||||
}
|
||||
|
@ -21,11 +21,9 @@
|
||||
#include <QDialog>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "BaseVersion.h"
|
||||
#include "ui/pages/BasePageProvider.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "ModDownloadTask.h"
|
||||
#include "ui/pages/modplatform/flame/FlameModPage.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "ui/pages/BasePageProvider.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@ -36,21 +34,21 @@ class PageContainer;
|
||||
class QDialogButtonBox;
|
||||
class ModrinthModPage;
|
||||
|
||||
class ModDownloadDialog : public QDialog, public BasePageProvider
|
||||
class ModDownloadDialog final : public QDialog, public BasePageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods, QWidget *parent, BaseInstance *instance);
|
||||
~ModDownloadDialog();
|
||||
explicit ModDownloadDialog(const std::shared_ptr<ModFolderModel>& mods, QWidget* parent, BaseInstance* instance);
|
||||
~ModDownloadDialog() override = default;
|
||||
|
||||
QString dialogTitle() override;
|
||||
QList<BasePage *> getPages() override;
|
||||
QList<BasePage*> getPages() override;
|
||||
|
||||
void addSelectedMod(const QString & name = QString(), ModDownloadTask * task = nullptr);
|
||||
void removeSelectedMod(const QString & name = QString());
|
||||
bool isModSelected(const QString & name, const QString & filename) const;
|
||||
bool isModSelected(const QString & name) const;
|
||||
void addSelectedMod(QString name = QString(), ModDownloadTask* task = nullptr);
|
||||
void removeSelectedMod(QString name = QString());
|
||||
bool isModSelected(QString name, QString filename) const;
|
||||
bool isModSelected(QString name) const;
|
||||
|
||||
const QList<ModDownloadTask*> getTasks();
|
||||
const std::shared_ptr<ModFolderModel> &mods;
|
||||
@ -60,6 +58,9 @@ public slots:
|
||||
void accept() override;
|
||||
void reject() override;
|
||||
|
||||
private slots:
|
||||
void selectedPageChanged(BasePage* previous, BasePage* selected);
|
||||
|
||||
private:
|
||||
Ui::ModDownloadDialog *ui = nullptr;
|
||||
PageContainer * m_container = nullptr;
|
||||
|
@ -1,11 +1,16 @@
|
||||
#include "ReviewMessageBox.h"
|
||||
#include "ui_ReviewMessageBox.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QString const& icon)
|
||||
: QDialog(parent), ui(new Ui::ReviewMessageBox)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel);
|
||||
back_button->setText(tr("Back"));
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject);
|
||||
}
|
||||
|
@ -2,15 +2,27 @@
|
||||
|
||||
#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 {
|
||||
|
||||
ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) {}
|
||||
// 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;
|
||||
}
|
||||
|
||||
auto ListModel::debugName() const -> QString
|
||||
{
|
||||
@ -39,9 +51,6 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
|
||||
|
||||
ModPlatform::IndexedPack pack = modpacks.at(pos);
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
return pack.name;
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
if (pack.description.length() > 100) {
|
||||
// some magic to prevent to long tooltips and replace html linebreaks
|
||||
@ -64,20 +73,20 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
|
||||
((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;
|
||||
}
|
||||
case Qt::FontRole: {
|
||||
QFont font;
|
||||
if (m_parent->getDialog()->isModSelected(pack.name)) {
|
||||
font.setBold(true);
|
||||
font.setUnderline(true);
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
@ -85,11 +94,27 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant
|
||||
return {};
|
||||
}
|
||||
|
||||
void ListModel::requestModVersions(ModPlatform::IndexedPack const& current)
|
||||
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(this, { current.addonId.toString(), getMineVersions(), profile->getModLoaders() });
|
||||
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()
|
||||
@ -100,9 +125,13 @@ void ListModel::performPaginatedSearch()
|
||||
this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });
|
||||
}
|
||||
|
||||
void ListModel::requestModInfo(ModPlatform::IndexedPack& current)
|
||||
void ListModel::requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index)
|
||||
{
|
||||
m_parent->apiProvider()->getModInfo(this, current);
|
||||
m_parent->apiProvider()->getModInfo(current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) {
|
||||
if (!s_running.constFind(this).value())
|
||||
return;
|
||||
infoRequestFinished(doc, pack, index);
|
||||
});
|
||||
}
|
||||
|
||||
void ListModel::refresh()
|
||||
@ -256,7 +285,7 @@ void ListModel::searchRequestFailed(QString reason)
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack)
|
||||
void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index)
|
||||
{
|
||||
qDebug() << "Loading mod info";
|
||||
|
||||
@ -268,10 +297,20 @@ void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack
|
||||
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)
|
||||
void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index)
|
||||
{
|
||||
auto& current = m_parent->getCurrent();
|
||||
if (addonId != current.addonId) {
|
||||
@ -287,6 +326,14 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId)
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "modplatform/ModAPI.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
@ -19,7 +18,7 @@ class ListModel : public QAbstractListModel {
|
||||
|
||||
public:
|
||||
ListModel(ModPage* parent);
|
||||
~ListModel() override = default;
|
||||
~ListModel() override;
|
||||
|
||||
inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
|
||||
inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
|
||||
@ -29,15 +28,17 @@ class ListModel : public QAbstractListModel {
|
||||
|
||||
/* 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(); }
|
||||
|
||||
/* 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);
|
||||
void requestModVersions(const ModPlatform::IndexedPack& current);
|
||||
void requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index);
|
||||
void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index);
|
||||
|
||||
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
|
||||
virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {};
|
||||
@ -51,9 +52,9 @@ class ListModel : public QAbstractListModel {
|
||||
void searchRequestFinished(QJsonDocument& doc);
|
||||
void searchRequestFailed(QString reason);
|
||||
|
||||
void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack);
|
||||
void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index);
|
||||
|
||||
void versionRequestSucceeded(QJsonDocument doc, QString addonId);
|
||||
void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index);
|
||||
|
||||
protected slots:
|
||||
|
||||
|
@ -40,9 +40,12 @@
|
||||
#include <QKeyEvent>
|
||||
#include <memory>
|
||||
|
||||
#include <HoeDown.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)
|
||||
@ -50,17 +53,30 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
|
||||
, ui(new Ui::ModPage)
|
||||
, dialog(dialog)
|
||||
, filter_widget(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this)
|
||||
, 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);
|
||||
|
||||
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
|
||||
m_search_timer.setSingleShot(true);
|
||||
|
||||
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);
|
||||
|
||||
ui->gridLayout_3->addWidget(&filter_widget, 0, 0, 1, ui->gridLayout_3->columnCount());
|
||||
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->gridLayout_3->addWidget(&filter_widget, 1, 0, 1, ui->gridLayout_3->columnCount());
|
||||
|
||||
filter_widget.setInstance(static_cast<MinecraftInstance*>(m_instance));
|
||||
m_filter = filter_widget.getFilter();
|
||||
@ -71,6 +87,9 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
|
||||
connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{
|
||||
ui->searchButton->setStyleSheet("text-decoration: none");
|
||||
});
|
||||
|
||||
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
||||
ui->packView->installEventFilter(this);
|
||||
}
|
||||
|
||||
ModPage::~ModPage()
|
||||
@ -93,6 +112,23 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool
|
||||
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;
|
||||
}
|
||||
@ -120,16 +156,26 @@ void ModPage::triggerSearch()
|
||||
updateSelectionButton();
|
||||
}
|
||||
|
||||
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), changed);
|
||||
listModel->searchWithTerm(getSearchTerm(), ui->sortByBox->currentIndex(), changed);
|
||||
m_fetch_progress.watch(listModel->activeJob());
|
||||
}
|
||||
|
||||
void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
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 (!first.isValid()) { return; }
|
||||
if (!curr.isValid()) { return; }
|
||||
|
||||
current = listModel->data(first, Qt::UserRole).value<ModPlatform::IndexedPack>();
|
||||
current = listModel->data(curr, Qt::UserRole).value<ModPlatform::IndexedPack>();
|
||||
|
||||
if (!current.versionsLoaded) {
|
||||
qDebug() << QString("Loading %1 mod versions").arg(debugName());
|
||||
@ -137,7 +183,7 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
ui->modSelectionButton->setText(tr("Loading versions..."));
|
||||
ui->modSelectionButton->setEnabled(false);
|
||||
|
||||
listModel->requestModVersions(current);
|
||||
listModel->requestModVersions(current, curr);
|
||||
} else {
|
||||
for (int i = 0; i < current.versions.size(); i++) {
|
||||
ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
|
||||
@ -149,7 +195,8 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
|
||||
if(!current.extraDataLoaded){
|
||||
qDebug() << QString("Loading %1 mod info").arg(debugName());
|
||||
listModel->requestModInfo(current);
|
||||
|
||||
listModel->requestModInfo(current, curr);
|
||||
}
|
||||
|
||||
updateUi();
|
||||
@ -167,6 +214,9 @@ void ModPage::onVersionSelectionChanged(QString data)
|
||||
|
||||
void ModPage::onModSelected()
|
||||
{
|
||||
if (selectedVersion < 0)
|
||||
return;
|
||||
|
||||
auto& version = current.versions[selectedVersion];
|
||||
if (dialog->isModSelected(current.name, version.fileName)) {
|
||||
dialog->removeSelectedMod(current.name);
|
||||
@ -176,6 +226,9 @@ void ModPage::onModSelected()
|
||||
}
|
||||
|
||||
updateSelectionButton();
|
||||
|
||||
/* Force redraw on the mods list when the selection changes */
|
||||
ui->packView->adjustSize();
|
||||
}
|
||||
|
||||
|
||||
@ -285,5 +338,6 @@ void ModPage::updateUi()
|
||||
|
||||
text += "<hr>";
|
||||
|
||||
ui->packDescription->setHtml(text + current.description);
|
||||
HoeDown h;
|
||||
ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8())));
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "ui/pages/modplatform/ModModel.h"
|
||||
#include "ui/widgets/ModFilterWidget.h"
|
||||
#include "ui/widgets/ProgressWidget.h"
|
||||
|
||||
class ModDownloadDialog;
|
||||
|
||||
@ -45,6 +46,11 @@ class ModPage : public QWidget, public BasePage {
|
||||
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);
|
||||
|
||||
auto getCurrent() -> ModPlatform::IndexedPack& { return current; }
|
||||
void updateModVersions(int prev_count = -1);
|
||||
|
||||
@ -70,10 +76,15 @@ class ModPage : public QWidget, public BasePage {
|
||||
ModFilterWidget 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;
|
||||
};
|
||||
|
@ -12,6 +12,12 @@ 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);
|
||||
|
@ -13,6 +13,7 @@ class ListModel : public ModPlatform::ListModel {
|
||||
|
||||
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;
|
||||
|
@ -1,27 +1,33 @@
|
||||
#include "Common.h"
|
||||
|
||||
// Origin: Qt
|
||||
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
|
||||
qreal &widthUsed)
|
||||
// More specifically, this is a trimmed down version on the algorithm in:
|
||||
// https://code.woboq.org/qt5/qtbase/src/widgets/styles/qcommonstyle.cpp.html#846
|
||||
QList<std::pair<qreal, QString>> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height)
|
||||
{
|
||||
QStringList lines;
|
||||
QList<std::pair<qreal, QString>> lines;
|
||||
height = 0;
|
||||
widthUsed = 0;
|
||||
|
||||
textLayout.beginLayout();
|
||||
|
||||
QString str = textLayout.text();
|
||||
while (true)
|
||||
{
|
||||
while (true) {
|
||||
QTextLine line = textLayout.createLine();
|
||||
|
||||
if (!line.isValid())
|
||||
break;
|
||||
if (line.textLength() == 0)
|
||||
break;
|
||||
|
||||
line.setLineWidth(lineWidth);
|
||||
line.setPosition(QPointF(0, height));
|
||||
|
||||
height += line.height();
|
||||
lines.append(str.mid(line.textStart(), line.textLength()));
|
||||
widthUsed = qMax(widthUsed, line.naturalTextWidth());
|
||||
|
||||
lines.append(std::make_pair(line.naturalTextWidth(), str.mid(line.textStart(), line.textLength())));
|
||||
}
|
||||
|
||||
textLayout.endLayout();
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include <QStringList>
|
||||
|
||||
#include <QTextLayout>
|
||||
|
||||
QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
|
||||
qreal &widthUsed);
|
||||
/** Cuts out the text in textLayout into smaller pieces, according to the lineWidth.
|
||||
* Returns a list of pairs, each containing the width of that line and that line's string, respectively.
|
||||
* The total height of those lines is set in the last argument, 'height'.
|
||||
*/
|
||||
QList<std::pair<qreal, QString>> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height);
|
||||
|
@ -244,7 +244,14 @@ void PageContainer::help()
|
||||
|
||||
void PageContainer::currentChanged(const QModelIndex ¤t)
|
||||
{
|
||||
showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1);
|
||||
int selected_index = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1;
|
||||
|
||||
auto* selected = m_model->pages().at(selected_index);
|
||||
auto* previous = m_currentPage;
|
||||
|
||||
emit selectedPageChanged(previous, selected);
|
||||
|
||||
showPage(selected_index);
|
||||
}
|
||||
|
||||
bool PageContainer::prepareToClose()
|
||||
|
@ -95,6 +95,10 @@ private:
|
||||
public slots:
|
||||
void help();
|
||||
|
||||
signals:
|
||||
/** Emitted when the currently selected page is changed */
|
||||
void selectedPageChanged(BasePage* previous, BasePage* selected);
|
||||
|
||||
private slots:
|
||||
void currentChanged(const QModelIndex ¤t);
|
||||
void showPage(int row);
|
||||
|
@ -1,65 +1,103 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#include "ProgressWidget.h"
|
||||
#include <QProgressBar>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QEventLoop>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "tasks/Task.h"
|
||||
|
||||
ProgressWidget::ProgressWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
ProgressWidget::ProgressWidget(QWidget* parent, bool show_label) : QWidget(parent)
|
||||
{
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
|
||||
if (show_label) {
|
||||
m_label = new QLabel(this);
|
||||
m_label->setWordWrap(true);
|
||||
layout->addWidget(m_label);
|
||||
}
|
||||
|
||||
m_bar = new QProgressBar(this);
|
||||
m_bar->setMinimum(0);
|
||||
m_bar->setMaximum(100);
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->addWidget(m_label);
|
||||
layout->addWidget(m_bar);
|
||||
layout->addStretch();
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void ProgressWidget::start(std::shared_ptr<Task> task)
|
||||
void ProgressWidget::reset()
|
||||
{
|
||||
if (m_task)
|
||||
{
|
||||
disconnect(m_task.get(), 0, this, 0);
|
||||
}
|
||||
m_task = task;
|
||||
connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish);
|
||||
connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus);
|
||||
connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress);
|
||||
connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed);
|
||||
if (!m_task->isRunning())
|
||||
{
|
||||
QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection);
|
||||
}
|
||||
m_bar->reset();
|
||||
}
|
||||
|
||||
void ProgressWidget::progressFormat(QString format)
|
||||
{
|
||||
if (format.isEmpty())
|
||||
m_bar->setTextVisible(false);
|
||||
else
|
||||
m_bar->setFormat(format);
|
||||
}
|
||||
|
||||
void ProgressWidget::watch(Task* task)
|
||||
{
|
||||
if (!task)
|
||||
return;
|
||||
|
||||
if (m_task)
|
||||
disconnect(m_task, nullptr, this, nullptr);
|
||||
|
||||
m_task = task;
|
||||
|
||||
connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish);
|
||||
connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus);
|
||||
connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress);
|
||||
connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed);
|
||||
|
||||
show();
|
||||
}
|
||||
|
||||
void ProgressWidget::start(Task* task)
|
||||
{
|
||||
watch(task);
|
||||
if (!m_task->isRunning())
|
||||
QMetaObject::invokeMethod(m_task, "start", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
bool ProgressWidget::exec(std::shared_ptr<Task> task)
|
||||
{
|
||||
QEventLoop loop;
|
||||
|
||||
connect(task.get(), &Task::finished, &loop, &QEventLoop::quit);
|
||||
start(task);
|
||||
|
||||
start(task.get());
|
||||
|
||||
if (task->isRunning())
|
||||
{
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
return task->wasSuccessful();
|
||||
}
|
||||
|
||||
void ProgressWidget::show()
|
||||
{
|
||||
setHidden(false);
|
||||
}
|
||||
void ProgressWidget::hide()
|
||||
{
|
||||
setHidden(true);
|
||||
}
|
||||
|
||||
void ProgressWidget::handleTaskFinish()
|
||||
{
|
||||
if (!m_task->wasSuccessful())
|
||||
{
|
||||
if (!m_task->wasSuccessful() && m_label)
|
||||
m_label->setText(m_task->failReason());
|
||||
}
|
||||
|
||||
if (m_hide_if_inactive)
|
||||
hide();
|
||||
}
|
||||
void ProgressWidget::handleTaskStatus(const QString &status)
|
||||
void ProgressWidget::handleTaskStatus(const QString& status)
|
||||
{
|
||||
if (m_label)
|
||||
m_label->setText(status);
|
||||
}
|
||||
void ProgressWidget::handleTaskProgress(qint64 current, qint64 total)
|
||||
|
@ -9,24 +9,48 @@ class Task;
|
||||
class QProgressBar;
|
||||
class QLabel;
|
||||
|
||||
class ProgressWidget : public QWidget
|
||||
{
|
||||
class ProgressWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProgressWidget(QWidget *parent = nullptr);
|
||||
public:
|
||||
explicit ProgressWidget(QWidget* parent = nullptr, bool show_label = true);
|
||||
|
||||
public slots:
|
||||
void start(std::shared_ptr<Task> task);
|
||||
/** Whether to hide the widget automatically if it's watching no running task. */
|
||||
void hideIfInactive(bool hide) { m_hide_if_inactive = hide; }
|
||||
|
||||
/** Reset the displayed progress to 0 */
|
||||
void reset();
|
||||
|
||||
/** The text that shows up in the middle of the progress bar.
|
||||
* By default it's '%p%', with '%p' being the total progress in percentage.
|
||||
*/
|
||||
void progressFormat(QString);
|
||||
|
||||
public slots:
|
||||
/** Watch the progress of a task. */
|
||||
void watch(Task* task);
|
||||
|
||||
/** Watch the progress of a task, and start it if needed */
|
||||
void start(Task* task);
|
||||
|
||||
/** Blocking way of waiting for a task to finish. */
|
||||
bool exec(std::shared_ptr<Task> task);
|
||||
|
||||
private slots:
|
||||
/** Un-hide the widget if needed. */
|
||||
void show();
|
||||
|
||||
/** Make the widget invisible. */
|
||||
void hide();
|
||||
|
||||
private slots:
|
||||
void handleTaskFinish();
|
||||
void handleTaskStatus(const QString &status);
|
||||
void handleTaskStatus(const QString& status);
|
||||
void handleTaskProgress(qint64 current, qint64 total);
|
||||
void taskDestroyed();
|
||||
|
||||
private:
|
||||
QLabel *m_label;
|
||||
QProgressBar *m_bar;
|
||||
std::shared_ptr<Task> m_task;
|
||||
private:
|
||||
QLabel* m_label = nullptr;
|
||||
QProgressBar* m_bar = nullptr;
|
||||
Task* m_task = nullptr;
|
||||
|
||||
bool m_hide_if_inactive = false;
|
||||
};
|
||||
|
78
launcher/ui/widgets/ProjectItem.cpp
Normal file
78
launcher/ui/widgets/ProjectItem.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "ProjectItem.h"
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QPainter>
|
||||
|
||||
ProjectItemDelegate::ProjectItemDelegate(QWidget* parent) : QStyledItemDelegate(parent) {}
|
||||
|
||||
void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||
{
|
||||
painter->save();
|
||||
|
||||
QStyleOptionViewItem opt(option);
|
||||
initStyleOption(&opt, index);
|
||||
|
||||
auto& rect = opt.rect;
|
||||
auto icon_width = rect.height(), icon_height = rect.height();
|
||||
auto remaining_width = rect.width() - icon_width;
|
||||
|
||||
if (opt.state & QStyle::State_Selected) {
|
||||
painter->fillRect(rect, opt.palette.highlight());
|
||||
painter->setPen(opt.palette.highlightedText().color());
|
||||
} else if (opt.state & QStyle::State_MouseOver) {
|
||||
painter->fillRect(rect, opt.palette.window());
|
||||
}
|
||||
|
||||
{ // Icon painting
|
||||
// Square-sized, occupying the left portion
|
||||
opt.icon.paint(painter, rect.x(), rect.y(), icon_width, icon_height);
|
||||
}
|
||||
|
||||
{ // Title painting
|
||||
auto title = index.data(UserDataTypes::TITLE).toString();
|
||||
|
||||
painter->save();
|
||||
|
||||
auto font = opt.font;
|
||||
if (index.data(UserDataTypes::SELECTED).toBool()) {
|
||||
// Set nice font
|
||||
font.setBold(true);
|
||||
font.setUnderline(true);
|
||||
}
|
||||
|
||||
font.setPointSize(font.pointSize() + 2);
|
||||
painter->setFont(font);
|
||||
|
||||
// On the top, aligned to the left after the icon
|
||||
painter->drawText(rect.x() + icon_width, rect.y() + QFontMetrics(font).height(), title);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
{ // Description painting
|
||||
auto description = index.data(UserDataTypes::DESCRIPTION).toString();
|
||||
|
||||
QTextLayout text_layout(description, opt.font);
|
||||
|
||||
qreal height = 0;
|
||||
auto cut_text = viewItemTextLayout(text_layout, remaining_width, height);
|
||||
|
||||
// Get first line unconditionally
|
||||
description = cut_text.first().second;
|
||||
// Get second line, elided if needed
|
||||
if (cut_text.size() > 1) {
|
||||
if (cut_text.size() > 2)
|
||||
description += opt.fontMetrics.elidedText(cut_text.at(1).second, opt.textElideMode, cut_text.at(1).first);
|
||||
else
|
||||
description += cut_text.at(1).second;
|
||||
}
|
||||
|
||||
// On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare)
|
||||
painter->drawText(rect.x() + icon_width, rect.y() + rect.height() - 2.2 * opt.fontMetrics.height(), remaining_width,
|
||||
2 * opt.fontMetrics.height(), Qt::TextWordWrap, description);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
25
launcher/ui/widgets/ProjectItem.h
Normal file
25
launcher/ui/widgets/ProjectItem.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
/* Custom data types for our custom list models :) */
|
||||
enum UserDataTypes {
|
||||
TITLE = 257, // QString
|
||||
DESCRIPTION = 258, // QString
|
||||
SELECTED = 259 // bool
|
||||
};
|
||||
|
||||
/** This is an item delegate composed of:
|
||||
* - An Icon on the left
|
||||
* - A title
|
||||
* - A description
|
||||
* */
|
||||
class ProjectItemDelegate final : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ProjectItemDelegate(QWidget* parent);
|
||||
|
||||
void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override;
|
||||
|
||||
};
|
Loading…
Reference in New Issue
Block a user