Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feat/acknowledge_release_type
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
@ -127,6 +127,9 @@ void CustomPage::loaderFilterChanged()
|
||||
ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));
|
||||
ui->loaderVersionList->setEmptyMode(VersionListView::String);
|
||||
return;
|
||||
} else if (ui->neoForgeFilter->isChecked()) {
|
||||
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
|
||||
m_selectedLoader = "net.neoforged";
|
||||
} else if (ui->forgeFilter->isChecked()) {
|
||||
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
|
||||
m_selectedLoader = "net.minecraftforge";
|
||||
|
@ -194,6 +194,16 @@
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="neoForgeFilter">
|
||||
<property name="text">
|
||||
<string>NeoForge</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">loaderBtnGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="forgeFilter">
|
||||
<property name="text">
|
||||
|
@ -35,20 +35,28 @@
|
||||
*/
|
||||
|
||||
#include "ImportPage.h"
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui_ImportPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QValidator>
|
||||
#include <utility>
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
#include "InstanceImportTask.h"
|
||||
|
||||
class UrlValidator : public QValidator {
|
||||
public:
|
||||
using QValidator::QValidator;
|
||||
|
||||
State validate(QString& in, int& pos) const
|
||||
State validate(QString& in, [[maybe_unused]] int& pos) const
|
||||
{
|
||||
const QUrl url(in);
|
||||
if (url.isValid() && !url.isRelative() && !url.isEmpty()) {
|
||||
@ -106,10 +114,61 @@ void ImportPage::updateState()
|
||||
bool isMRPack = fi.suffix() == "mrpack";
|
||||
|
||||
if (fi.exists() && (isZip || isMRPack)) {
|
||||
QFileInfo fi(url.fileName());
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this));
|
||||
auto extra_info = QMap(m_extra_info);
|
||||
qDebug() << "Pack Extra Info" << extra_info << m_extra_info;
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
|
||||
dialog->setSuggestedIcon("default");
|
||||
}
|
||||
} else if (url.scheme() == "curseforge") {
|
||||
// need to find the download link for the modpack
|
||||
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
|
||||
QUrlQuery query(url);
|
||||
auto addonId = query.allQueryItemValues("addonId")[0];
|
||||
auto fileId = query.allQueryItemValues("fileId")[0];
|
||||
auto array = std::make_shared<QByteArray>();
|
||||
|
||||
auto api = FlameAPI();
|
||||
auto job = api.getFile(addonId, fileId, array);
|
||||
|
||||
connect(job.get(), &NetJob::failed, this,
|
||||
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
connect(job.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] {
|
||||
qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
|
||||
auto doc = Json::requireDocument(*array);
|
||||
auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
|
||||
// No way to find out if it's a mod or a modpack before here
|
||||
// And also we need to check if it ends with .zip, instead of any better way
|
||||
auto fileName = Json::ensureString(data, "fileName");
|
||||
if (fileName.endsWith(".zip")) {
|
||||
// Have to use ensureString then use QUrl to get proper url encoding
|
||||
auto dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
|
||||
if (!dl_url.isValid()) {
|
||||
CustomMessageBox::selectable(
|
||||
this, tr("Error"),
|
||||
tr("The modpack %1 is blocked for third-parties! Please download it manually.").arg(fileName),
|
||||
QMessageBox::Critical)
|
||||
->show();
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo dl_file(dl_url.fileName());
|
||||
QString pack_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
|
||||
|
||||
QMap<QString, QString> extra_info;
|
||||
extra_info.insert("pack_id", addonId);
|
||||
extra_info.insert("pack_version_id", fileId);
|
||||
|
||||
dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info)));
|
||||
dialog->setSuggestedIcon("default");
|
||||
|
||||
} else {
|
||||
CustomMessageBox::selectable(this, tr("Error"), tr("This url isn't a valid modpack !"), QMessageBox::Critical)->show();
|
||||
}
|
||||
});
|
||||
ProgressDialog dlUrlDialod(this);
|
||||
dlUrlDialod.setSkipButton(true, tr("Abort"));
|
||||
dlUrlDialod.execWithTask(job.get());
|
||||
return;
|
||||
} else {
|
||||
if (input.endsWith("?client=y")) {
|
||||
input.chop(9);
|
||||
@ -118,7 +177,8 @@ void ImportPage::updateState()
|
||||
}
|
||||
// hook, line and sinker.
|
||||
QFileInfo fi(url.fileName());
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this));
|
||||
auto extra_info = QMap(m_extra_info);
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
|
||||
dialog->setSuggestedIcon("default");
|
||||
}
|
||||
} else {
|
||||
@ -132,6 +192,12 @@ void ImportPage::setUrl(const QString& url)
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info)
|
||||
{
|
||||
m_extra_info = extra_info;
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ImportPage::on_modpackBtn_clicked()
|
||||
{
|
||||
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
|
||||
|
@ -62,7 +62,7 @@ class ImportPage : public QWidget, public BasePage {
|
||||
|
||||
void setUrl(const QString& url);
|
||||
void openedImpl() override;
|
||||
|
||||
void setExtraInfo(const QMap<QString, QString>& extra_info);
|
||||
private slots:
|
||||
void on_modpackBtn_clicked();
|
||||
void updateState();
|
||||
@ -73,4 +73,5 @@ class ImportPage : public QWidget, public BasePage {
|
||||
private:
|
||||
Ui::ImportPage* ui = nullptr;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
QMap<QString, QString> m_extra_info = {};
|
||||
};
|
||||
|
@ -40,7 +40,7 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>- CurseForge modpacks (ZIP)</string>
|
||||
<string>- CurseForge modpacks (ZIP / curseforge:// URL)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
|
@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
|
||||
|
||||
auto sort = getCurrentSortingMethodByIndex();
|
||||
|
||||
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
|
||||
return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };
|
||||
}
|
||||
|
||||
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
|
||||
@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
|
||||
if (!m_filter->versions.empty())
|
||||
versions = m_filter->versions;
|
||||
|
||||
return { pack, versions, profile->getModLoaders() };
|
||||
return { pack, versions, profile->getSupportedModLoaders() };
|
||||
}
|
||||
|
||||
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
|
||||
|
@ -124,8 +124,7 @@ void ModPage::updateVersionList()
|
||||
auto version = current_pack->versions[i];
|
||||
bool valid = false;
|
||||
for (auto& mcVer : m_filter->versions) {
|
||||
// NOTE: Flame doesn't care about loader, so passing it changes nothing.
|
||||
if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
|
||||
if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
|
||||
|
||||
virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0;
|
||||
|
||||
[[nodiscard]] bool supportsFiltering() const override { return true; };
|
||||
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
|
||||
|
@ -17,7 +17,7 @@
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "net/Download.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
@ -102,7 +102,7 @@ QHash<int, QByteArray> ResourceModel::roleNames() const
|
||||
return roles;
|
||||
}
|
||||
|
||||
bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
|
||||
{
|
||||
int pos = index.row();
|
||||
if (pos >= m_packs.size() || pos < 0 || !index.isValid())
|
||||
@ -132,6 +132,32 @@ void ResourceModel::search()
|
||||
if (hasActiveSearchJob())
|
||||
return;
|
||||
|
||||
if (m_search_term.startsWith("#")) {
|
||||
auto projectId = m_search_term.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::ProjectInfoCallbacks callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
searchRequestFailed(reason, -1);
|
||||
};
|
||||
callbacks.on_abort = [this] {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
searchRequestAborted();
|
||||
};
|
||||
|
||||
callbacks.on_succeed = [this](auto& doc, auto& pack) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
searchRequestForOneSucceeded(doc);
|
||||
};
|
||||
if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job)
|
||||
runSearchJob(job);
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto args{ createSearchArguments() };
|
||||
|
||||
auto callbacks{ createSearchCallbacks() };
|
||||
@ -189,11 +215,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)
|
||||
|
||||
// Use default if no callbacks are set
|
||||
if (!callbacks.on_succeed)
|
||||
callbacks.on_succeed = [this, entry](auto& doc, auto pack) {
|
||||
callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
auto pack = newpack;
|
||||
infoRequestSucceeded(doc, pack, entry);
|
||||
};
|
||||
if (!callbacks.on_fail)
|
||||
callbacks.on_fail = [this](QString reason) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
|
||||
};
|
||||
|
||||
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
|
||||
runInfoJob(job);
|
||||
@ -281,7 +314,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
|
||||
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 icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
|
||||
|
||||
auto full_file_path = cache_entry->getFullPath();
|
||||
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
|
||||
@ -310,7 +343,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
|
||||
#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
|
||||
QJsonArray ResourceModel::documentToArray([[maybe_unused]] QJsonDocument& doc) const
|
||||
{
|
||||
NEED_FOR_CALLBACK_ASSERT("documentToArray");
|
||||
return {};
|
||||
@ -372,7 +405,28 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ResourceModel::searchRequestFailed(QString reason, int network_error_code)
|
||||
void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc)
|
||||
{
|
||||
ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||
|
||||
try {
|
||||
auto obj = Json::requireObject(doc);
|
||||
if (obj.contains("data"))
|
||||
obj = Json::requireObject(obj, "data");
|
||||
loadIndexedPack(*pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
|
||||
}
|
||||
|
||||
m_search_state = SearchState::Finished;
|
||||
|
||||
beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1);
|
||||
m_packs.append(pack);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
|
||||
{
|
||||
switch (network_error_code) {
|
||||
default:
|
||||
|
@ -42,7 +42,10 @@ class ResourceModel : public QAbstractListModel {
|
||||
[[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 rowCount(const QModelIndex& parent) const override
|
||||
{
|
||||
return parent.isValid() ? 0 : static_cast<int>(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); }
|
||||
|
||||
@ -146,6 +149,7 @@ class ResourceModel : public QAbstractListModel {
|
||||
private:
|
||||
/* Default search request callbacks */
|
||||
void searchRequestSucceeded(QJsonDocument&);
|
||||
void searchRequestForOneSucceeded(QJsonDocument&);
|
||||
void searchRequestFailed(QString reason, int network_error_code);
|
||||
void searchRequestAborted();
|
||||
|
||||
|
@ -9,7 +9,8 @@
|
||||
namespace ResourceDownload {
|
||||
|
||||
ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
|
||||
: ResourceModel(api), m_base_instance(base_inst){};
|
||||
: ResourceModel(api), m_base_instance(base_inst)
|
||||
{}
|
||||
|
||||
/******** Make data requests ********/
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -44,9 +44,6 @@
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "Markdown.h"
|
||||
#include "ResourceDownloadTask.h"
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
|
||||
#include "ui/dialogs/ResourceDownloadDialog.h"
|
||||
#include "ui/pages/modplatform/ResourceModel.h"
|
||||
@ -283,7 +280,7 @@ void ResourcePage::updateVersionList()
|
||||
updateSelectionButton();
|
||||
}
|
||||
|
||||
void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
|
||||
void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
||||
{
|
||||
if (!curr.isValid()) {
|
||||
return;
|
||||
@ -310,9 +307,9 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
|
||||
updateUi();
|
||||
}
|
||||
|
||||
void ResourcePage::onVersionSelectionChanged(QString data)
|
||||
void ResourcePage::onVersionSelectionChanged(QString versionData)
|
||||
{
|
||||
if (data.isNull() || data.isEmpty()) {
|
||||
if (versionData.isNull() || versionData.isEmpty()) {
|
||||
m_selected_version_index = -1;
|
||||
return;
|
||||
}
|
||||
@ -398,7 +395,7 @@ void ResourcePage::openUrl(const QUrl& url)
|
||||
if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) {
|
||||
m_parent_dialog->selectPage(page);
|
||||
|
||||
auto newPage = m_parent_dialog->getSelectedPage();
|
||||
auto newPage = m_parent_dialog->selectedPage();
|
||||
|
||||
QLineEdit* searchEdit = newPage->m_ui->searchEdit;
|
||||
auto model = newPage->m_model;
|
||||
|
@ -97,7 +97,11 @@ class ResourcePage : public QWidget, public BasePage {
|
||||
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; };
|
||||
virtual bool optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
Q_UNUSED(ver);
|
||||
return false;
|
||||
};
|
||||
|
||||
public:
|
||||
BaseInstance& m_base_instance;
|
||||
|
@ -9,7 +9,8 @@
|
||||
namespace ResourceDownload {
|
||||
|
||||
ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api)
|
||||
: ResourceModel(api), m_base_instance(base_inst){};
|
||||
: ResourceModel(api), m_base_instance(base_inst)
|
||||
{}
|
||||
|
||||
/******** Make data requests ********/
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "ShaderPackPage.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "ui_ResourcePage.h"
|
||||
|
||||
#include "ShaderPackModel.h"
|
||||
@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
|
||||
const std::shared_ptr<ResourceFolderModel> base_model)
|
||||
{
|
||||
QString custom_target_folder;
|
||||
if (version.loaders.contains(QStringLiteral("canvas")))
|
||||
if (version.loaders & ModPlatform::Cauldron)
|
||||
custom_target_folder = QStringLiteral("resourcepacks");
|
||||
m_model->addPack(pack, version, base_model, false, custom_target_folder);
|
||||
}
|
||||
|
@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen
|
||||
if (searchTerm.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
|
||||
if (searchTerm.startsWith("#"))
|
||||
return QString::number(pack.id) == searchTerm.mid(1);
|
||||
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,9 @@
|
||||
#include <BuildConfig.h>
|
||||
#include <Json.h>
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
namespace Atl {
|
||||
|
||||
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
@ -44,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
|
||||
ATLauncher::IndexedPack pack = modpacks.at(pos);
|
||||
if (role == Qt::DisplayRole) {
|
||||
return pack.name;
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
return pack.name;
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
if (m_logoMap.contains(pack.safeName)) {
|
||||
return (m_logoMap.value(pack.safeName));
|
||||
switch (role) {
|
||||
case Qt::ToolTipRole: {
|
||||
if (pack.description.length() > 100) {
|
||||
// some magic to prevent to long tooltips and replace html linebreaks
|
||||
QString edit = pack.description.left(97);
|
||||
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
|
||||
return edit;
|
||||
}
|
||||
return pack.description;
|
||||
}
|
||||
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
|
||||
case Qt::DecorationRole: {
|
||||
if (m_logoMap.contains(pack.safeName)) {
|
||||
return (m_logoMap.value(pack.safeName));
|
||||
}
|
||||
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
|
||||
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
((ListModel*)this)->requestLogo(pack.safeName, url);
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
((ListModel*)this)->requestLogo(pack.safeName, url);
|
||||
|
||||
return icon;
|
||||
} else if (role == Qt::UserRole) {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
return icon;
|
||||
}
|
||||
case Qt::UserRole: {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return pack.name;
|
||||
case Qt::SizeHintRole:
|
||||
return QSize(0, 58);
|
||||
// Custom data
|
||||
case UserDataTypes::TITLE:
|
||||
return pack.name;
|
||||
case UserDataTypes::DESCRIPTION:
|
||||
return pack.description;
|
||||
case UserDataTypes::SELECTED:
|
||||
return false;
|
||||
case UserDataTypes::INSTALLED:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
return {};
|
||||
}
|
||||
|
||||
void ListModel::request()
|
||||
@ -75,7 +101,7 @@ void ListModel::request()
|
||||
|
||||
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
|
||||
@ -137,8 +163,7 @@ void ListModel::requestFailed(QString reason)
|
||||
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
|
||||
{
|
||||
if (m_logoMap.contains(logo)) {
|
||||
callback(
|
||||
APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
@ -168,9 +193,9 @@ void ListModel::requestLogo(QString file, QString url)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file));
|
||||
auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {
|
||||
|
@ -43,6 +43,8 @@
|
||||
#include "Json.h"
|
||||
#include "modplatform/atlauncher/ATLShareCode.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
|
||||
: QAbstractListModel(parent), m_version(version), m_mods(mods)
|
||||
{
|
||||
@ -113,7 +115,7 @@ QVariant AtlOptionalModListModel::data(const QModelIndex& index, int role) const
|
||||
return {};
|
||||
}
|
||||
|
||||
bool AtlOptionalModListModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
bool AtlOptionalModListModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
|
||||
{
|
||||
if (role == Qt::CheckStateRole) {
|
||||
auto row = index.row();
|
||||
@ -155,7 +157,7 @@ void AtlOptionalModListModel::useShareCode(const QString& code)
|
||||
{
|
||||
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
|
||||
auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
|
||||
m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response));
|
||||
m_jobPtr->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), m_response));
|
||||
|
||||
connect(m_jobPtr.get(), &NetJob::succeeded, this, &AtlOptionalModListModel::shareCodeSuccess);
|
||||
connect(m_jobPtr.get(), &NetJob::failed, this, &AtlOptionalModListModel::shareCodeFailure);
|
||||
@ -206,7 +208,7 @@ void AtlOptionalModListModel::shareCodeSuccess()
|
||||
emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
|
||||
}
|
||||
|
||||
void AtlOptionalModListModel::shareCodeFailure(const QString& reason)
|
||||
void AtlOptionalModListModel::shareCodeFailure([[maybe_unused]] const QString& reason)
|
||||
{
|
||||
m_jobPtr.reset();
|
||||
|
||||
@ -281,15 +283,15 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
|
||||
// if the dependency is 'effectively hidden', then track which mods
|
||||
// depend on it - so we can efficiently disable it when no more dependents
|
||||
// depend on it.
|
||||
auto dependants = m_dependants[dependencyName];
|
||||
auto dependents = m_dependents[dependencyName];
|
||||
|
||||
if (enable) {
|
||||
dependants.append(mod.name);
|
||||
dependents.append(mod.name);
|
||||
} else {
|
||||
dependants.removeAll(mod.name);
|
||||
dependents.removeAll(mod.name);
|
||||
|
||||
// if there are no longer any dependents, let's disable the mod
|
||||
if (dependencyMod.effectively_hidden && dependants.isEmpty()) {
|
||||
if (dependencyMod.effectively_hidden && dependents.isEmpty()) {
|
||||
setMod(dependencyMod, dependencyIndex, false, shouldEmit);
|
||||
}
|
||||
}
|
||||
@ -297,8 +299,8 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
|
||||
|
||||
// disable mods that depend on this one, if disabling
|
||||
if (!enable) {
|
||||
auto dependants = m_dependants[mod.name];
|
||||
for (const auto& dependencyName : dependants) {
|
||||
auto dependents = m_dependents[mod.name];
|
||||
for (const auto& dependencyName : dependents) {
|
||||
auto dependencyIndex = m_index[dependencyName];
|
||||
auto dependencyMod = m_mods.at(dependencyIndex);
|
||||
|
||||
|
@ -90,7 +90,7 @@ class AtlOptionalModListModel : public QAbstractListModel {
|
||||
|
||||
QMap<QString, bool> m_selection;
|
||||
QMap<QString, int> m_index;
|
||||
QMap<QString, QVector<QString>> m_dependants;
|
||||
QMap<QString, QVector<QString>> m_dependents;
|
||||
};
|
||||
|
||||
class AtlOptionalModDialog : public QDialog {
|
||||
|
@ -35,11 +35,11 @@
|
||||
*/
|
||||
|
||||
#include "AtlPage.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
#include "ui_AtlPage.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "AtlOptionalModDialog.h"
|
||||
#include "AtlUserInteractionSupportImpl.h"
|
||||
#include "modplatform/atlauncher/ATLPackInstallTask.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),
|
||||
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
|
||||
|
||||
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
||||
}
|
||||
|
||||
AtlPage::~AtlPage()
|
||||
@ -123,13 +125,13 @@ void AtlPage::triggerSearch()
|
||||
filterModel->setSearchTerm(ui->searchEdit->text());
|
||||
}
|
||||
|
||||
void AtlPage::onSortingSelectionChanged(QString data)
|
||||
void AtlPage::onSortingSelectionChanged(QString sort)
|
||||
{
|
||||
auto toSet = filterModel->getAvailableSortings().value(data);
|
||||
auto toSet = filterModel->getAvailableSortings().value(sort);
|
||||
filterModel->setSorting(toSet);
|
||||
}
|
||||
|
||||
void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
void AtlPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
|
||||
@ -151,13 +153,13 @@ void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void AtlPage::onVersionSelectionChanged(QString data)
|
||||
void AtlPage::onVersionSelectionChanged(QString version)
|
||||
{
|
||||
if (data.isNull() || data.isEmpty()) {
|
||||
if (version.isNull() || version.isEmpty()) {
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
|
||||
selectedVersion = data;
|
||||
selectedVersion = version;
|
||||
suggestCurrent();
|
||||
}
|
||||
|
@ -11,44 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
@ -68,7 +31,34 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>96</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
@ -78,6 +68,31 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
<italic>true</italic>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
|
@ -1,8 +1,12 @@
|
||||
#include "FlameModel.h"
|
||||
#include <Json.h>
|
||||
#include "Application.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <Version.h>
|
||||
|
||||
#include <QtMath>
|
||||
@ -70,7 +74,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool ListModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
bool ListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
|
||||
{
|
||||
int pos = index.row();
|
||||
if (pos >= modpacks.size() || pos < 0 || !index.isValid())
|
||||
@ -104,9 +108,9 @@ void ListModel::requestLogo(QString logo, QString url)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo));
|
||||
auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
|
||||
@ -130,7 +134,7 @@ void ListModel::requestLogo(QString logo, QString url)
|
||||
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
|
||||
{
|
||||
if (m_logoMap.contains(logo)) {
|
||||
callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
@ -141,7 +145,7 @@ Qt::ItemFlags ListModel::flags(const QModelIndex& index) const
|
||||
return QAbstractListModel::flags(index);
|
||||
}
|
||||
|
||||
bool ListModel::canFetchMore(const QModelIndex& parent) const
|
||||
bool ListModel::canFetchMore([[maybe_unused]] const QModelIndex& parent) const
|
||||
{
|
||||
return searchState == CanPossiblyFetchMore;
|
||||
}
|
||||
@ -159,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
|
||||
|
||||
void ListModel::performPaginatedSearch()
|
||||
{
|
||||
if (currentSearchTerm.startsWith("#")) {
|
||||
auto projectId = currentSearchTerm.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::ProjectInfoCallbacks callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
|
||||
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
|
||||
static const FlameAPI api;
|
||||
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
|
||||
jobPtr = job;
|
||||
jobPtr->start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
|
||||
auto searchUrl = QString(
|
||||
"https://api.curseforge.com/v1/mods/search?"
|
||||
@ -173,7 +192,7 @@ void ListModel::performPaginatedSearch()
|
||||
.arg(currentSearchTerm)
|
||||
.arg(currentSort + 1);
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||
@ -187,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
|
||||
}
|
||||
currentSearchTerm = term;
|
||||
currentSort = sort;
|
||||
if (jobPtr) {
|
||||
if (hasActiveSearchJob()) {
|
||||
jobPtr->abort();
|
||||
searchState = ResetRequested;
|
||||
return;
|
||||
} else {
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
}
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
|
||||
nextSearchOffset = 0;
|
||||
performPaginatedSearch();
|
||||
}
|
||||
|
||||
void Flame::ListModel::searchRequestFinished()
|
||||
{
|
||||
jobPtr.reset();
|
||||
if (hasActiveSearchJob())
|
||||
return;
|
||||
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
@ -244,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
auto packObj = Json::ensureObject(doc.object(), "data");
|
||||
|
||||
Flame::IndexedPack pack;
|
||||
try {
|
||||
Flame::loadIndexedPack(pack, packObj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qWarning() << "Error while loading pack from CurseForge: " << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
|
||||
modpacks.append({ pack });
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void Flame::ListModel::searchRequestFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
|
||||
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
||||
void searchWithTerm(const QString& term, const int sort);
|
||||
|
||||
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
||||
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
||||
|
||||
private slots:
|
||||
void performPaginatedSearch();
|
||||
|
||||
@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
|
||||
|
||||
void searchRequestFinished();
|
||||
void searchRequestFailed(QString reason);
|
||||
void searchRequestForOneSucceeded(QJsonDocument&);
|
||||
|
||||
private:
|
||||
void requestLogo(QString file, QString url);
|
||||
@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
|
||||
int currentSort = 0;
|
||||
int nextSearchOffset = 0;
|
||||
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
|
||||
NetJob::Ptr jobPtr;
|
||||
Task::Ptr jobPtr;
|
||||
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
|
||||
};
|
||||
|
||||
|
@ -46,9 +46,12 @@
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
static FlameAPI api;
|
||||
|
||||
FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
|
||||
FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
|
||||
@ -59,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren
|
||||
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
|
||||
|
||||
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
|
||||
m_search_timer.setSingleShot(true);
|
||||
|
||||
connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch);
|
||||
|
||||
m_fetch_progress.hideIfInactive(true);
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
|
||||
|
||||
// index is used to set the sorting with the curseforge api
|
||||
ui->sortByBox->addItem(tr("Sort by Featured"));
|
||||
ui->sortByBox->addItem(tr("Sort by Popularity"));
|
||||
@ -88,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
|
||||
triggerSearch();
|
||||
keyEvent->accept();
|
||||
return true;
|
||||
} else {
|
||||
if (m_search_timer.isActive())
|
||||
m_search_timer.stop();
|
||||
|
||||
m_search_timer.start(350);
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
@ -112,9 +131,10 @@ void FlamePage::openedImpl()
|
||||
void FlamePage::triggerSearch()
|
||||
{
|
||||
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
|
||||
m_fetch_progress.watch(listModel->activeSearchJob().get());
|
||||
}
|
||||
|
||||
void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
|
||||
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
|
||||
@ -132,7 +152,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
|
||||
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
int addonId = current.addonId;
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
|
||||
netJob->addNetAction(
|
||||
Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {
|
||||
if (addonId != current.addonId) {
|
||||
@ -208,17 +229,17 @@ void FlamePage::suggestCurrent()
|
||||
|
||||
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
|
||||
QString editedLogoName;
|
||||
editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
|
||||
editedLogoName = "curseforge_" + current.logoName;
|
||||
listModel->getLogo(current.logoName, current.logoUrl,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
|
||||
void FlamePage::onVersionSelectionChanged(QString data)
|
||||
void FlamePage::onVersionSelectionChanged(QString version)
|
||||
{
|
||||
bool is_blocked = false;
|
||||
ui->versionSelectionBox->currentData().toInt(&is_blocked);
|
||||
|
||||
if (data.isNull() || data.isEmpty() || is_blocked) {
|
||||
if (version.isNull() || version.isEmpty() || is_blocked) {
|
||||
m_selected_version_index = -1;
|
||||
return;
|
||||
}
|
||||
|
@ -39,8 +39,9 @@
|
||||
|
||||
#include <Application.h>
|
||||
#include <modplatform/flame/FlamePackIndex.h>
|
||||
#include "tasks/Task.h"
|
||||
#include <QTimer>
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "ui/widgets/ProgressWidget.h"
|
||||
|
||||
namespace Ui {
|
||||
class FlamePage;
|
||||
@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
|
||||
Flame::IndexedPack current;
|
||||
|
||||
int m_selected_version_index = -1;
|
||||
|
||||
ProgressWidget m_fetch_progress;
|
||||
|
||||
// Used to do instant searching with a delay to cache quick changes
|
||||
QTimer m_search_timer;
|
||||
};
|
||||
|
@ -47,7 +47,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="packView">
|
||||
@ -77,7 +77,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
|
@ -31,8 +31,8 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
|
||||
|
||||
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
|
||||
{
|
||||
return FlameMod::loadDependencyVersions(m, arr);
|
||||
};
|
||||
return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
{
|
||||
@ -121,4 +121,27 @@ auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonAr
|
||||
return Json::ensureArray(obj.object(), "data");
|
||||
}
|
||||
|
||||
FlameShaderPackModel::FlameShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new FlameAPI) {}
|
||||
|
||||
void FlameShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
|
||||
{
|
||||
FlameMod::loadIndexedPack(m, obj);
|
||||
}
|
||||
|
||||
// We already deal with the URLs when initializing the pack, due to the API response's structure
|
||||
void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
|
||||
{
|
||||
FlameMod::loadBody(m, obj);
|
||||
}
|
||||
|
||||
void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
}
|
||||
|
||||
auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
{
|
||||
return Json::ensureArray(obj.object(), "data");
|
||||
}
|
||||
|
||||
} // namespace ResourceDownload
|
||||
|
@ -68,4 +68,21 @@ class FlameTexturePackModel : public TexturePackResourceModel {
|
||||
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
||||
};
|
||||
|
||||
class FlameShaderPackModel : public ShaderPackResourceModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FlameShaderPackModel(const BaseInstance&);
|
||||
~FlameShaderPackModel() override = default;
|
||||
|
||||
private:
|
||||
[[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
|
||||
[[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
|
||||
|
||||
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
|
||||
void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
|
||||
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
|
||||
};
|
||||
|
||||
} // namespace ResourceDownload
|
||||
|
@ -68,10 +68,10 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
|
||||
|
||||
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
|
||||
{
|
||||
Q_UNUSED(loaders);
|
||||
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
|
||||
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() &&
|
||||
(!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
|
||||
}
|
||||
|
||||
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
@ -173,6 +173,45 @@ void FlameTexturePackPage::openUrl(const QUrl& url)
|
||||
TexturePackResourcePage::openUrl(url);
|
||||
}
|
||||
|
||||
FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
|
||||
: ShaderPackResourcePage(dialog, instance)
|
||||
{
|
||||
m_model = new FlameShaderPackModel(instance);
|
||||
m_ui->packView->setModel(m_model);
|
||||
|
||||
addSortings();
|
||||
|
||||
// sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
|
||||
// so it's best not to connect them in the parent's constructor...
|
||||
connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
|
||||
connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameShaderPackPage::onSelectionChanged);
|
||||
connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameShaderPackPage::onVersionSelectionChanged);
|
||||
connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected);
|
||||
|
||||
m_ui->packDescription->setMetaEntry(metaEntryBase());
|
||||
}
|
||||
|
||||
bool FlameShaderPackPage::optedOut(ModPlatform::IndexedVersion& ver) const
|
||||
{
|
||||
return isOptedOut(ver);
|
||||
}
|
||||
|
||||
void FlameShaderPackPage::openUrl(const QUrl& url)
|
||||
{
|
||||
if (url.scheme().isEmpty()) {
|
||||
QString query = url.query(QUrl::FullyDecoded);
|
||||
|
||||
if (query.startsWith("remoteUrl=")) {
|
||||
// attempt to resolve url from warning page
|
||||
query.remove(0, 10);
|
||||
ShaderPackResourcePage::openUrl({ QUrl::fromPercentEncoding(query.toUtf8()) }); // double decoding is necessary
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderPackResourcePage::openUrl(url);
|
||||
}
|
||||
|
||||
// I don't know why, but doing this on the parent class makes it so that
|
||||
// other mod providers start loading before being selected, at least with
|
||||
// my Qt, so we need to implement this in every derived class...
|
||||
@ -188,5 +227,9 @@ auto FlameTexturePackPage::shouldDisplay() const -> bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto FlameShaderPackPage::shouldDisplay() const -> bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ResourceDownload
|
||||
|
@ -44,6 +44,7 @@
|
||||
|
||||
#include "ui/pages/modplatform/ModPage.h"
|
||||
#include "ui/pages/modplatform/ResourcePackPage.h"
|
||||
#include "ui/pages/modplatform/ShaderPackPage.h"
|
||||
#include "ui/pages/modplatform/TexturePackPage.h"
|
||||
|
||||
namespace ResourceDownload {
|
||||
@ -95,7 +96,7 @@ class FlameModPage : public ModPage {
|
||||
|
||||
bool validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override;
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;
|
||||
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
void openUrl(const QUrl& url) override;
|
||||
@ -155,4 +156,31 @@ class FlameTexturePackPage : public TexturePackResourcePage {
|
||||
void openUrl(const QUrl& url) override;
|
||||
};
|
||||
|
||||
class FlameShaderPackPage : public ShaderPackResourcePage {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static FlameShaderPackPage* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
|
||||
{
|
||||
return ShaderPackResourcePage::create<FlameShaderPackPage>(dialog, instance);
|
||||
}
|
||||
|
||||
FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);
|
||||
~FlameShaderPackPage() override = default;
|
||||
|
||||
[[nodiscard]] bool shouldDisplay() const override;
|
||||
|
||||
[[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); }
|
||||
[[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); }
|
||||
[[nodiscard]] inline auto id() const -> QString override { return Flame::id(); }
|
||||
[[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); }
|
||||
[[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); }
|
||||
|
||||
[[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
|
||||
|
||||
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
|
||||
|
||||
void openUrl(const QUrl& url) override;
|
||||
};
|
||||
|
||||
} // namespace ResourceDownload
|
||||
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "ImportFTBPage.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
#include "ui_ImportFTBPage.h"
|
||||
|
||||
#include <QWidget>
|
||||
@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
|
||||
ui->setupUi(this);
|
||||
|
||||
{
|
||||
currentModel = new FilterModel(this);
|
||||
listModel = new ListModel(this);
|
||||
currentModel->setSourceModel(listModel);
|
||||
|
||||
ui->modpackList->setModel(listModel);
|
||||
ui->modpackList->setModel(currentModel);
|
||||
ui->modpackList->setSortingEnabled(true);
|
||||
ui->modpackList->header()->hide();
|
||||
ui->modpackList->setIndentation(0);
|
||||
ui->modpackList->setIconSize(QSize(42, 42));
|
||||
|
||||
for (int i = 0; i < currentModel->getAvailableSortings().size(); i++) {
|
||||
ui->sortByBox->addItem(currentModel->getAvailableSortings().keys().at(i));
|
||||
}
|
||||
|
||||
ui->sortByBox->setCurrentText(currentModel->translateCurrentSorting());
|
||||
}
|
||||
|
||||
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
|
||||
|
||||
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged);
|
||||
|
||||
connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
|
||||
|
||||
ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
|
||||
ui->modpackList->selectionModel()->reset();
|
||||
}
|
||||
|
||||
@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr
|
||||
onPackSelectionChanged();
|
||||
return;
|
||||
}
|
||||
Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
|
||||
Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();
|
||||
onPackSelectionChanged(&selectedPack);
|
||||
}
|
||||
|
||||
@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
|
||||
void ImportFTBPage::onSortingSelectionChanged(QString sort)
|
||||
{
|
||||
FilterModel::Sorting toSet = currentModel->getAvailableSortings().value(sort);
|
||||
currentModel->setSorting(toSet);
|
||||
}
|
||||
|
||||
void ImportFTBPage::triggerSearch()
|
||||
{
|
||||
currentModel->setSearchTerm(ui->searchEdit->text());
|
||||
}
|
||||
|
||||
} // namespace FTBImportAPP
|
||||
|
@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage {
|
||||
void suggestCurrent();
|
||||
void onPackSelectionChanged(Modpack* pack = nullptr);
|
||||
private slots:
|
||||
void onSortingSelectionChanged(QString data);
|
||||
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
void triggerSearch();
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
Modpack selected;
|
||||
ListModel* listModel = nullptr;
|
||||
FilterModel* currentModel = nullptr;
|
||||
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
Ui::ImportFTBPage* ui = nullptr;
|
||||
|
@ -10,8 +10,8 @@
|
||||
<height>1011</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QTreeView" name="modpackList">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
@ -21,6 +21,54 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>265</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -23,7 +23,9 @@
|
||||
#include <QIcon>
|
||||
#include <QProcessEnvironment>
|
||||
#include "FileSystem.h"
|
||||
#include "StringUtils.h"
|
||||
#include "modplatform/import_ftb/PackHelpers.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
namespace FTBImportAPP {
|
||||
|
||||
@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
|
||||
auto pack = modpacks.at(pos);
|
||||
if (role == Qt::DisplayRole) {
|
||||
return pack.name;
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
return pack.icon;
|
||||
} else if (role == Qt::UserRole) {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
return tr("Minecraft %1").arg(pack.mcVersion);
|
||||
if (role == Qt::ToolTipRole) {
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
switch (role) {
|
||||
case Qt::ToolTipRole:
|
||||
return tr("Minecraft %1").arg(pack.mcVersion);
|
||||
case Qt::DecorationRole:
|
||||
return pack.icon;
|
||||
case Qt::UserRole: {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return pack.name;
|
||||
case Qt::SizeHintRole:
|
||||
return QSize(0, 58);
|
||||
// Custom data
|
||||
case UserDataTypes::TITLE:
|
||||
return pack.name;
|
||||
case UserDataTypes::DESCRIPTION:
|
||||
return tr("Minecraft %1").arg(pack.mcVersion);
|
||||
case UserDataTypes::SELECTED:
|
||||
return false;
|
||||
case UserDataTypes::INSTALLED:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
currentSorting = Sorting::ByGameVersion;
|
||||
sortings.insert(tr("Sort by Name"), Sorting::ByName);
|
||||
sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
|
||||
}
|
||||
|
||||
bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
|
||||
{
|
||||
Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
|
||||
Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
|
||||
|
||||
if (currentSorting == Sorting::ByGameVersion) {
|
||||
Version lv(leftPack.mcVersion);
|
||||
Version rv(rightPack.mcVersion);
|
||||
return lv < rv;
|
||||
|
||||
} else if (currentSorting == Sorting::ByName) {
|
||||
return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
|
||||
}
|
||||
|
||||
// UHM, some inavlid value set?!
|
||||
qWarning() << "Invalid sorting set!";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
|
||||
{
|
||||
if (searchTerm.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
|
||||
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
void FilterModel::setSearchTerm(const QString term)
|
||||
{
|
||||
searchTerm = term.trimmed();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
|
||||
{
|
||||
return sortings;
|
||||
}
|
||||
|
||||
QString FilterModel::translateCurrentSorting()
|
||||
{
|
||||
return sortings.key(currentSorting);
|
||||
}
|
||||
|
||||
void FilterModel::setSorting(Sorting s)
|
||||
{
|
||||
currentSorting = s;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
FilterModel::Sorting FilterModel::getCurrentSorting()
|
||||
{
|
||||
return currentSorting;
|
||||
}
|
||||
} // namespace FTBImportAPP
|
@ -20,11 +20,33 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QIcon>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVariant>
|
||||
#include "modplatform/import_ftb/PackHelpers.h"
|
||||
|
||||
namespace FTBImportAPP {
|
||||
|
||||
class FilterModel : public QSortFilterProxyModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
FilterModel(QObject* parent = Q_NULLPTR);
|
||||
enum Sorting { ByName, ByGameVersion };
|
||||
const QMap<QString, Sorting> getAvailableSortings();
|
||||
QString translateCurrentSorting();
|
||||
void setSorting(Sorting sorting);
|
||||
Sorting getCurrentSorting();
|
||||
void setSearchTerm(QString term);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
|
||||
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
|
||||
|
||||
private:
|
||||
QMap<QString, Sorting> sortings;
|
||||
Sorting currentSorting;
|
||||
QString searchTerm;
|
||||
};
|
||||
|
||||
class ListModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -35,11 +35,13 @@
|
||||
|
||||
#include "ListModel.h"
|
||||
#include "Application.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include <Version.h>
|
||||
#include "StringUtils.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QtMath>
|
||||
@ -76,9 +78,22 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
|
||||
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
|
||||
{
|
||||
return true;
|
||||
if (searchTerm.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
|
||||
if (searchTerm.startsWith("#"))
|
||||
return pack.packCode == searchTerm.mid(1);
|
||||
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
void FilterModel::setSearchTerm(const QString term)
|
||||
{
|
||||
searchTerm = term.trimmed();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
|
||||
@ -138,45 +153,63 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
|
||||
Modpack pack = modpacks.at(pos);
|
||||
if (role == Qt::DisplayRole) {
|
||||
return pack.name + "\n" + translatePackType(pack.type);
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
if (pack.description.length() > 100) {
|
||||
// some magic to prevent to long tooltips and replace html linebreaks
|
||||
QString edit = pack.description.left(97);
|
||||
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
|
||||
return edit;
|
||||
switch (role) {
|
||||
case Qt::ToolTipRole: {
|
||||
if (pack.description.length() > 100) {
|
||||
// some magic to prevent to long tooltips and replace html linebreaks
|
||||
QString edit = pack.description.left(97);
|
||||
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
|
||||
return edit;
|
||||
}
|
||||
return pack.description;
|
||||
}
|
||||
return pack.description;
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
if (m_logoMap.contains(pack.logo)) {
|
||||
return (m_logoMap.value(pack.logo));
|
||||
case Qt::DecorationRole: {
|
||||
if (m_logoMap.contains(pack.logo)) {
|
||||
return (m_logoMap.value(pack.logo));
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel*)this)->requestLogo(pack.logo);
|
||||
return icon;
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel*)this)->requestLogo(pack.logo);
|
||||
return icon;
|
||||
} else if (role == Qt::ForegroundRole) {
|
||||
if (pack.broken) {
|
||||
// FIXME: Hardcoded color
|
||||
return QColor(255, 0, 50);
|
||||
} else if (pack.bugged) {
|
||||
// FIXME: Hardcoded color
|
||||
// bugged pack, currently only indicates bugged xml
|
||||
return QColor(244, 229, 66);
|
||||
case Qt::UserRole: {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
} else if (role == Qt::UserRole) {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
case Qt::ForegroundRole: {
|
||||
if (pack.broken) {
|
||||
// FIXME: Hardcoded color
|
||||
return QColor(255, 0, 50);
|
||||
} else if (pack.bugged) {
|
||||
// FIXME: Hardcoded color
|
||||
// bugged pack, currently only indicates bugged xml
|
||||
return QColor(244, 229, 66);
|
||||
}
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return pack.name;
|
||||
case Qt::SizeHintRole:
|
||||
return QSize(0, 58);
|
||||
// Custom data
|
||||
case UserDataTypes::TITLE:
|
||||
return pack.name;
|
||||
case UserDataTypes::DESCRIPTION:
|
||||
return pack.description;
|
||||
case UserDataTypes::SELECTED:
|
||||
return false;
|
||||
case UserDataTypes::INSTALLED:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
return {};
|
||||
}
|
||||
|
||||
void ListModel::fill(ModpackList modpacks)
|
||||
void ListModel::fill(ModpackList modpacks_)
|
||||
{
|
||||
beginResetModel();
|
||||
this->modpacks = modpacks;
|
||||
this->modpacks = modpacks_;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
@ -229,9 +262,9 @@ void ListModel::requestLogo(QString file)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file));
|
||||
NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {
|
||||
@ -255,7 +288,7 @@ void ListModel::requestLogo(QString file)
|
||||
void ListModel::getLogo(const QString& logo, LogoCallback callback)
|
||||
{
|
||||
if (m_logoMap.contains(logo)) {
|
||||
callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel {
|
||||
QString translateCurrentSorting();
|
||||
void setSorting(Sorting sorting);
|
||||
Sorting getCurrentSorting();
|
||||
void setSearchTerm(QString term);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
|
||||
@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel {
|
||||
private:
|
||||
QMap<QString, Sorting> sortings;
|
||||
Sorting currentSorting;
|
||||
QString searchTerm;
|
||||
};
|
||||
|
||||
class ListModel : public QAbstractListModel {
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "Page.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
#include "ui_Page.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
@ -110,6 +111,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
|
||||
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
|
||||
|
||||
connect(ui->searchEdit, &QLineEdit::textChanged, this, &Page::triggerSearch);
|
||||
|
||||
connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
|
||||
connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
|
||||
connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
|
||||
@ -125,6 +128,9 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
|
||||
ui->thirdPartyPackList->selectionModel()->reset();
|
||||
ui->privatePackList->selectionModel()->reset();
|
||||
|
||||
ui->publicPackList->setItemDelegate(new ProjectItemDelegate(this));
|
||||
ui->thirdPartyPackList->setItemDelegate(new ProjectItemDelegate(this));
|
||||
ui->privatePackList->setItemDelegate(new ProjectItemDelegate(this));
|
||||
onTabChanged(ui->tabWidget->currentIndex());
|
||||
}
|
||||
|
||||
@ -215,7 +221,7 @@ void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack)
|
||||
privateListModel->addPack(pack);
|
||||
}
|
||||
|
||||
void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
|
||||
void Page::ftbPrivatePackDataDownloadFailed([[maybe_unused]] QString reason, QString packCode)
|
||||
{
|
||||
auto reply = QMessageBox::question(this, tr("FTB private packs"),
|
||||
tr("Failed to download pack information for code %1.\nShould it be removed now?").arg(packCode));
|
||||
@ -224,7 +230,7 @@ void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
|
||||
}
|
||||
}
|
||||
|
||||
void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
void Page::onPublicPackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)
|
||||
{
|
||||
if (!now.isValid()) {
|
||||
onPackSelectionChanged();
|
||||
@ -234,7 +240,7 @@ void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
onPackSelectionChanged(&selectedPack);
|
||||
}
|
||||
|
||||
void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
void Page::onThirdPartyPackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)
|
||||
{
|
||||
if (!now.isValid()) {
|
||||
onPackSelectionChanged();
|
||||
@ -244,7 +250,7 @@ void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
onPackSelectionChanged(&selectedPack);
|
||||
}
|
||||
|
||||
void Page::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
void Page::onPrivatePackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)
|
||||
{
|
||||
if (!now.isValid()) {
|
||||
onPackSelectionChanged();
|
||||
@ -284,20 +290,20 @@ void Page::onPackSelectionChanged(Modpack* pack)
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void Page::onVersionSelectionItemChanged(QString data)
|
||||
void Page::onVersionSelectionItemChanged(QString version)
|
||||
{
|
||||
if (data.isNull() || data.isEmpty()) {
|
||||
if (version.isNull() || version.isEmpty()) {
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
|
||||
selectedVersion = data;
|
||||
selectedVersion = version;
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void Page::onSortingSelectionChanged(QString data)
|
||||
void Page::onSortingSelectionChanged(QString sort)
|
||||
{
|
||||
FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data);
|
||||
FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(sort);
|
||||
publicFilterModel->setSorting(toSet);
|
||||
thirdPartyFilterModel->setSorting(toSet);
|
||||
privateFilterModel->setSorting(toSet);
|
||||
@ -319,6 +325,8 @@ void Page::onTabChanged(int tab)
|
||||
currentModpackInfo = ui->publicPackDescription;
|
||||
}
|
||||
|
||||
triggerSearch();
|
||||
|
||||
currentList->selectionModel()->reset();
|
||||
QModelIndex idx = currentList->currentIndex();
|
||||
if (idx.isValid()) {
|
||||
@ -358,4 +366,9 @@ void Page::onRemovePackClicked()
|
||||
onPackSelectionChanged();
|
||||
}
|
||||
|
||||
void Page::triggerSearch()
|
||||
{
|
||||
currentModel->setSearchTerm(ui->searchEdit->text());
|
||||
}
|
||||
|
||||
} // namespace LegacyFTB
|
||||
|
@ -43,7 +43,6 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "modplatform/legacy_ftb/PackFetchTask.h"
|
||||
#include "modplatform/legacy_ftb/PackHelpers.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
|
||||
class NewInstanceDialog;
|
||||
@ -56,8 +55,6 @@ class Page;
|
||||
|
||||
class ListModel;
|
||||
class FilterModel;
|
||||
class PrivatePackListModel;
|
||||
class PrivatePackFilterModel;
|
||||
class PrivatePackManager;
|
||||
|
||||
class Page : public QWidget, public BasePage {
|
||||
@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage {
|
||||
void onAddPackClicked();
|
||||
void onRemovePackClicked();
|
||||
|
||||
void triggerSearch();
|
||||
|
||||
private:
|
||||
FilterModel* currentModel = nullptr;
|
||||
QTreeView* currentList = nullptr;
|
||||
|
@ -10,8 +10,29 @@
|
||||
<height>602</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter...</string>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
@ -36,9 +57,9 @@
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextBrowser" name="publicPackDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -50,10 +71,10 @@
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextBrowser" name="thirdPartyPackDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeView" name="thirdPartyPackList">
|
||||
@ -104,16 +125,16 @@
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="3">
|
||||
<widget class="QTextBrowser" name="privatePackDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="5" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
|
@ -38,10 +38,12 @@
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace Modrinth {
|
||||
@ -115,7 +117,7 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
|
||||
{
|
||||
int pos = index.row();
|
||||
if (pos >= modpacks.size() || pos < 0 || !index.isValid())
|
||||
@ -128,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,
|
||||
|
||||
void ModpackListModel::performPaginatedSearch()
|
||||
{
|
||||
// TODO: Move to standalone API
|
||||
if (hasActiveSearchJob())
|
||||
return;
|
||||
|
||||
if (currentSearchTerm.startsWith("#")) {
|
||||
auto projectId = currentSearchTerm.mid(1);
|
||||
if (!projectId.isEmpty()) {
|
||||
ResourceAPI::ProjectInfoCallbacks callbacks;
|
||||
|
||||
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
|
||||
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
|
||||
static const ModrinthAPI api;
|
||||
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
|
||||
jobPtr = job;
|
||||
jobPtr->start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
} // TODO: Move to standalone API
|
||||
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
|
||||
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
|
||||
"/search?"
|
||||
@ -142,7 +161,7 @@ void ModpackListModel::performPaginatedSearch()
|
||||
.arg(currentSearchTerm)
|
||||
.arg(currentSort);
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
|
||||
QJsonParseError parse_error_all{};
|
||||
@ -165,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()
|
||||
|
||||
void ModpackListModel::refresh()
|
||||
{
|
||||
if (jobPtr) {
|
||||
if (hasActiveSearchJob()) {
|
||||
jobPtr->abort();
|
||||
searchState = ResetRequested;
|
||||
return;
|
||||
} else {
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
|
||||
nextSearchOffset = 0;
|
||||
performPaginatedSearch();
|
||||
}
|
||||
@ -194,8 +214,6 @@ static auto sortFromIndex(int index) -> QString
|
||||
case 4:
|
||||
return "updated";
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ModpackListModel::searchWithTerm(const QString& term, const int sort)
|
||||
@ -218,9 +236,7 @@ void ModpackListModel::searchWithTerm(const QString& term, const int sort)
|
||||
void ModpackListModel::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());
|
||||
callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
@ -232,10 +248,9 @@ void ModpackListModel::requestLogo(QString logo, QString url)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry =
|
||||
APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo));
|
||||
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));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
|
||||
@ -310,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
auto packObj = doc.object();
|
||||
|
||||
Modrinth::Modpack pack;
|
||||
try {
|
||||
Modrinth::loadIndexedPack(pack, packObj);
|
||||
pack.id = Json::ensureString(packObj, "id", pack.id);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
|
||||
modpacks.append({ pack });
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ModpackListModel::searchRequestFailed(QString reason)
|
||||
{
|
||||
auto failed_action = jobPtr->getFailedActions().at(0);
|
||||
auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
|
||||
if (!failed_action->m_reply) {
|
||||
// Network error
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
|
||||
|
@ -73,6 +73,9 @@ class ModpackListModel : public QAbstractListModel {
|
||||
void refresh();
|
||||
void searchWithTerm(const QString& term, const int sort);
|
||||
|
||||
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
||||
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
||||
|
||||
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
||||
|
||||
inline auto canFetchMore(const QModelIndex& parent) const -> bool override
|
||||
@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {
|
||||
public slots:
|
||||
void searchRequestFinished(QJsonDocument& doc_all);
|
||||
void searchRequestFailed(QString reason);
|
||||
void searchRequestForOneSucceeded(QJsonDocument&);
|
||||
|
||||
protected slots:
|
||||
|
||||
@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {
|
||||
int nextSearchOffset = 0;
|
||||
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
|
||||
|
||||
NetJob::Ptr jobPtr;
|
||||
Task::Ptr jobPtr;
|
||||
|
||||
std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
|
||||
QByteArray m_specific_response;
|
||||
|
@ -46,11 +46,14 @@
|
||||
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QKeyEvent>
|
||||
#include <QPushButton>
|
||||
|
||||
ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
|
||||
ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
@ -62,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
|
||||
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
|
||||
|
||||
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
|
||||
m_search_timer.setSingleShot(true);
|
||||
|
||||
connect(&m_search_timer, &QTimer::timeout, this, &ModrinthPage::triggerSearch);
|
||||
|
||||
m_fetch_progress.hideIfInactive(true);
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
|
||||
|
||||
ui->sortByBox->addItem(tr("Sort by Relevance"));
|
||||
ui->sortByBox->addItem(tr("Sort by Total Downloads"));
|
||||
ui->sortByBox->addItem(tr("Sort by Follows"));
|
||||
@ -100,12 +114,17 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
|
||||
this->triggerSearch();
|
||||
keyEvent->accept();
|
||||
return true;
|
||||
} else {
|
||||
if (m_search_timer.isActive())
|
||||
m_search_timer.stop();
|
||||
|
||||
m_search_timer.start(350);
|
||||
}
|
||||
}
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
|
||||
void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
|
||||
@ -127,7 +146,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
|
||||
|
||||
QString id = current.id;
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
|
||||
if (id != current.id) {
|
||||
@ -176,7 +195,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
|
||||
QString id = current.id;
|
||||
|
||||
netJob->addNetAction(
|
||||
Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
|
||||
if (id != current.id) {
|
||||
@ -308,11 +327,12 @@ void ModrinthPage::suggestCurrent()
|
||||
void ModrinthPage::triggerSearch()
|
||||
{
|
||||
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
|
||||
m_fetch_progress.watch(m_model->activeSearchJob().get());
|
||||
}
|
||||
|
||||
void ModrinthPage::onVersionSelectionChanged(QString data)
|
||||
void ModrinthPage::onVersionSelectionChanged(QString version)
|
||||
{
|
||||
if (data.isNull() || data.isEmpty()) {
|
||||
if (version.isNull() || version.isEmpty()) {
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
|
@ -41,7 +41,9 @@
|
||||
#include "ui/pages/BasePage.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||
#include "ui/widgets/ProgressWidget.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {
|
||||
|
||||
Modrinth::Modpack current;
|
||||
QString selectedVersion;
|
||||
|
||||
ProgressWidget m_fetch_progress;
|
||||
|
||||
// Used to do instant searching with a delay to cache quick changes
|
||||
QTimer m_search_timer;
|
||||
};
|
||||
|
@ -10,8 +10,8 @@
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="font">
|
||||
<font>
|
||||
@ -29,7 +29,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
@ -47,7 +47,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="packView">
|
||||
@ -77,7 +77,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<item row="4" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
|
@ -39,13 +39,13 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec
|
||||
|
||||
void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
|
||||
{
|
||||
return ::Modrinth::loadDependencyVersions(m, arr);
|
||||
};
|
||||
return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
{
|
||||
@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q
|
||||
|
||||
void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ
|
||||
|
||||
void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs
|
||||
|
||||
void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
|
||||
}
|
||||
|
||||
auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
|
||||
|
@ -65,21 +65,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
|
||||
|
||||
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver,
|
||||
QString mineVer,
|
||||
std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
|
||||
{
|
||||
auto loaderCompatible = !loaders.has_value();
|
||||
|
||||
if (!loaderCompatible) {
|
||||
auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value());
|
||||
for (auto remoteLoader : ver.loaders) {
|
||||
if (loaderStrings.contains(remoteLoader)) {
|
||||
loaderCompatible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ver.mcVersion.contains(mineVer) && loaderCompatible;
|
||||
return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
|
||||
}
|
||||
|
||||
ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
|
||||
|
@ -93,7 +93,7 @@ class ModrinthModPage : public ModPage {
|
||||
|
||||
[[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
|
||||
|
||||
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const
|
||||
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const
|
||||
-> bool override;
|
||||
};
|
||||
|
||||
|
@ -38,6 +38,9 @@
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
@ -52,21 +55,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
|
||||
Modpack pack = modpacks.at(pos);
|
||||
if (role == Qt::DisplayRole) {
|
||||
return pack.name;
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
if (m_logoMap.contains(pack.logoName)) {
|
||||
return (m_logoMap.value(pack.logoName));
|
||||
switch (role) {
|
||||
case Qt::ToolTipRole: {
|
||||
if (pack.description.length() > 100) {
|
||||
// some magic to prevent to long tooltips and replace html linebreaks
|
||||
QString edit = pack.description.left(97);
|
||||
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
|
||||
return edit;
|
||||
}
|
||||
return pack.description;
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
|
||||
return icon;
|
||||
} else if (role == Qt::UserRole) {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
case Qt::DecorationRole: {
|
||||
if (m_logoMap.contains(pack.logoName)) {
|
||||
return (m_logoMap.value(pack.logoName));
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
|
||||
return icon;
|
||||
}
|
||||
case Qt::UserRole: {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return pack.name;
|
||||
case Qt::SizeHintRole:
|
||||
return QSize(0, 58);
|
||||
// Custom data
|
||||
case UserDataTypes::TITLE:
|
||||
return pack.name;
|
||||
case UserDataTypes::DESCRIPTION:
|
||||
return pack.description;
|
||||
case UserDataTypes::SELECTED:
|
||||
return false;
|
||||
case UserDataTypes::INSTALLED:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int Technic::ListModel::columnCount(const QModelIndex& parent) const
|
||||
@ -85,21 +114,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)
|
||||
return;
|
||||
}
|
||||
currentSearchTerm = term;
|
||||
if (jobPtr) {
|
||||
if (hasActiveSearchJob()) {
|
||||
jobPtr->abort();
|
||||
searchState = ResetRequested;
|
||||
return;
|
||||
} else {
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
|
||||
performSearch();
|
||||
}
|
||||
|
||||
void Technic::ListModel::performSearch()
|
||||
{
|
||||
if (hasActiveSearchJob())
|
||||
return;
|
||||
|
||||
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
|
||||
QString searchUrl = "";
|
||||
if (currentSearchTerm.isEmpty()) {
|
||||
@ -111,12 +144,15 @@ void Technic::ListModel::performSearch()
|
||||
} else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
|
||||
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
|
||||
searchMode = Single;
|
||||
} else if (currentSearchTerm.startsWith("#")) {
|
||||
searchUrl = QString("https://api.technicpack.net/modpack/%1?build=%2").arg(currentSearchTerm.mid(1), BuildConfig.TECHNIC_API_BUILD);
|
||||
searchMode = Single;
|
||||
} else {
|
||||
searchUrl =
|
||||
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
|
||||
searchMode = List;
|
||||
}
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||
@ -157,7 +193,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
pack.logoName = "null";
|
||||
} else {
|
||||
pack.logoUrl = rawURL;
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1);
|
||||
}
|
||||
pack.broken = false;
|
||||
newList.append(pack);
|
||||
@ -179,7 +215,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
auto iconUrl = Json::requireString(iconObj, "url");
|
||||
|
||||
pack.logoUrl = iconUrl;
|
||||
pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
|
||||
pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
|
||||
} else {
|
||||
pack.logoUrl = "null";
|
||||
pack.logoName = "null";
|
||||
@ -254,7 +290,7 @@ void Technic::ListModel::requestLogo(QString logo, QString url)
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
|
||||
auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
|
||||
|
@ -58,6 +58,9 @@ class ListModel : public QAbstractListModel {
|
||||
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
||||
void searchWithTerm(const QString& term);
|
||||
|
||||
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
||||
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
||||
|
||||
private slots:
|
||||
void searchRequestFinished();
|
||||
void searchRequestFailed();
|
||||
|
@ -34,6 +34,7 @@
|
||||
*/
|
||||
|
||||
#include "TechnicPage.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
#include "ui_TechnicPage.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
@ -49,7 +50,10 @@
|
||||
#include "Application.h"
|
||||
#include "modplatform/technic/SolderPackManifest.h"
|
||||
|
||||
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
|
||||
@ -57,8 +61,21 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p
|
||||
model = new Technic::ListModel(this);
|
||||
ui->packView->setModel(model);
|
||||
|
||||
m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
|
||||
m_search_timer.setSingleShot(true);
|
||||
|
||||
connect(&m_search_timer, &QTimer::timeout, this, &TechnicPage::triggerSearch);
|
||||
|
||||
m_fetch_progress.hideIfInactive(true);
|
||||
m_fetch_progress.setFixedHeight(24);
|
||||
m_fetch_progress.progressFormat("");
|
||||
|
||||
ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
|
||||
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
|
||||
|
||||
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
||||
}
|
||||
|
||||
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
|
||||
@ -69,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
|
||||
triggerSearch();
|
||||
keyEvent->accept();
|
||||
return true;
|
||||
} else {
|
||||
if (m_search_timer.isActive())
|
||||
m_search_timer.stop();
|
||||
|
||||
m_search_timer.start(350);
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
@ -98,9 +120,10 @@ void TechnicPage::openedImpl()
|
||||
void TechnicPage::triggerSearch()
|
||||
{
|
||||
model->searchWithTerm(ui->searchEdit->text());
|
||||
m_fetch_progress.watch(model->activeSearchJob().get());
|
||||
}
|
||||
|
||||
void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
|
||||
@ -125,7 +148,7 @@ void TechnicPage::suggestCurrent()
|
||||
return;
|
||||
}
|
||||
|
||||
QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0);
|
||||
QString editedLogoName = "technic_" + current.logoName;
|
||||
model->getLogo(current.logoName, current.logoUrl,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
|
||||
@ -136,7 +159,7 @@ void TechnicPage::suggestCurrent()
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
|
||||
QString slug = current.slug;
|
||||
netJob->addNetAction(Net::Download::makeByteArray(
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(
|
||||
QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug] {
|
||||
jobPtr.reset();
|
||||
@ -232,7 +255,7 @@ void TechnicPage::metadataLoaded()
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
|
||||
auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
|
||||
|
||||
@ -304,13 +327,13 @@ void TechnicPage::onSolderLoaded()
|
||||
metadataLoaded();
|
||||
}
|
||||
|
||||
void TechnicPage::onVersionSelectionChanged(QString data)
|
||||
void TechnicPage::onVersionSelectionChanged(QString version)
|
||||
{
|
||||
if (data.isNull() || data.isEmpty()) {
|
||||
if (version.isNull() || version.isEmpty()) {
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
|
||||
selectedVersion = data;
|
||||
selectedVersion = version;
|
||||
selectVersion();
|
||||
}
|
||||
|
@ -35,13 +35,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include <Application.h>
|
||||
#include "TechnicData.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "ui/widgets/ProgressWidget.h"
|
||||
|
||||
namespace Ui {
|
||||
class TechnicPage;
|
||||
@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {
|
||||
|
||||
NetJob::Ptr jobPtr;
|
||||
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
|
||||
|
||||
ProgressWidget m_fetch_progress;
|
||||
|
||||
// Used to do instant searching with a delay to cache quick changes
|
||||
QTimer m_search_timer;
|
||||
};
|
||||
|
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
@ -44,7 +44,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListView" name="packView">
|
||||
|
Reference in New Issue
Block a user