aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/update-flake.yml2
-rw-r--r--flake.lock18
-rw-r--r--flake.nix16
-rw-r--r--launcher/minecraft/mod/Mod.cpp18
-rw-r--r--launcher/minecraft/mod/Mod.h2
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp19
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h1
-rw-r--r--launcher/modplatform/ResourceAPI.h4
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.cpp3
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui11
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp30
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp56
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h1
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp56
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.ui89
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp51
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.h6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp20
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.h8
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.ui4
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp29
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h3
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui52
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.cpp105
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.h22
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp90
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.h2
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.cpp13
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.h5
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.ui49
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp58
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h6
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp20
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h7
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp72
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.h3
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.cpp23
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.h8
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.ui4
-rw-r--r--launcher/ui/widgets/ProjectItem.cpp2
-rw-r--r--nix/default.nix31
-rw-r--r--nix/dev.nix38
-rw-r--r--nix/distribution.nix46
-rw-r--r--nix/pkg/default.nix (renamed from nix/package.nix)0
-rw-r--r--nix/pkg/wrapper.nix91
-rw-r--r--program_info/org.prismlauncher.PrismLauncher.desktop.in2
51 files changed, 956 insertions, 261 deletions
diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml
index ad4016ff..16dbd724 100644
--- a/.github/workflows/update-flake.yml
+++ b/.github/workflows/update-flake.yml
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- - uses: cachix/install-nix-action@v22
+ - uses: cachix/install-nix-action@6a9a9e84a173d90b3ffb42c5ddaf9ea033fad011 # v23
- uses: DeterminateSystems/update-flake-lock@v20
with:
diff --git a/flake.lock b/flake.lock
index f27ca520..b1486ea6 100644
--- a/flake.lock
+++ b/flake.lock
@@ -21,11 +21,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
- "lastModified": 1690933134,
- "narHash": "sha256-ab989mN63fQZBFrkk4Q8bYxQCktuHmBIBqUG1jl6/FQ=",
+ "lastModified": 1693611461,
+ "narHash": "sha256-aPODl8vAgGQ0ZYFIRisxYG5MOGSkIczvu2Cd8Gb9+1Y=",
"owner": "hercules-ci",
"repo": "flake-parts",
- "rev": "59cf3f1447cfc75087e7273b04b31e689a8599fb",
+ "rev": "7f53fdb7bdc5bb237da7fefef12d099e4fd611ca",
"type": "github"
},
"original": {
@@ -91,11 +91,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1693145325,
- "narHash": "sha256-Gat9xskErH1zOcLjYMhSDBo0JTBZKfGS0xJlIRnj6Rc=",
+ "lastModified": 1693626178,
+ "narHash": "sha256-Rpiy6lIOu4zny8tfGuIeN1ji9eSz9nPmm9yBhh/4IOM=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "cddebdb60de376c1bdb7a4e6ee3d98355453fe56",
+ "rev": "bfb7dfec93f3b5d7274db109f2990bc889861caf",
"type": "github"
},
"original": {
@@ -108,11 +108,11 @@
"nixpkgs-lib": {
"locked": {
"dir": "lib",
- "lastModified": 1690881714,
- "narHash": "sha256-h/nXluEqdiQHs1oSgkOOWF+j8gcJMWhwnZ9PFabN6q0=",
+ "lastModified": 1693471703,
+ "narHash": "sha256-0l03ZBL8P1P6z8MaSDS/MvuU8E75rVxe5eE1N6gxeTo=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "9e1960bc196baf6881340d53dccb203a951745a2",
+ "rev": "3e52e76b70d5508f3cec70b882a29199f4d1ee85",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index c3148fe0..d45282aa 100644
--- a/flake.nix
+++ b/flake.nix
@@ -23,5 +23,19 @@
outputs = inputs:
inputs.flake-parts.lib.mkFlake
{inherit inputs;}
- {imports = [./nix];};
+ {
+ imports = [
+ inputs.pre-commit-hooks.flakeModule
+
+ ./nix/dev.nix
+ ./nix/distribution.nix
+ ];
+
+ systems = [
+ "x86_64-linux"
+ "aarch64-linux"
+ "x86_64-darwin"
+ "aarch64-darwin"
+ ];
+ };
}
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index ae3dea8d..c9952998 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -132,17 +132,23 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
- if (metadata()) {
- Metadata::remove(index_dir, metadata()->slug);
- } else {
- auto n = name();
- Metadata::remove(index_dir, n);
- }
+ destroyMetadata(index_dir);
}
return Resource::destroy(attempt_trash);
}
+void Mod::destroyMetadata(QDir& index_dir)
+{
+ if (metadata()) {
+ Metadata::remove(index_dir, metadata()->slug);
+ } else {
+ auto n = name();
+ Metadata::remove(index_dir, n);
+ }
+ m_local_details.metadata = nullptr;
+}
+
auto Mod::details() const -> const ModDetails&
{
return m_local_details;
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 6dafecfc..e97ee9d3 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -93,6 +93,8 @@ class Mod : public Resource {
// Delete all the files of this mod
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
+ // Delete the metadata only
+ void destroyMetadata(QDir& index_dir);
void finishResolvingWithDetails(ModDetails&& details);
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index eed35615..a5f1489d 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -233,6 +233,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
return true;
}
+bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
+{
+ if (indexes.isEmpty())
+ return true;
+
+ for (auto i : indexes) {
+ if (i.column() != 0) {
+ continue;
+ }
+ auto m = at(i.row());
+ auto index_dir = indexDir();
+ m->destroyMetadata(index_dir);
+ }
+
+ update();
+
+ return true;
+}
+
bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index f1890e87..61d840f9 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -81,6 +81,7 @@ class ModFolderModel : public ResourceFolderModel {
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList& indexes);
+ bool deleteModsMetadata(const QModelIndexList& indexes);
bool isValid();
diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h
index 7965d0f5..3b195938 100644
--- a/launcher/modplatform/ResourceAPI.h
+++ b/launcher/modplatform/ResourceAPI.h
@@ -105,7 +105,9 @@ class ResourceAPI {
void operator=(ProjectInfoArgs other) { pack = other.pack; }
};
struct ProjectInfoCallbacks {
- std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
+ std::function<void(QJsonDocument&, const ModPlatform::IndexedPack&)> on_succeed;
+ std::function<void(QString const& reason)> on_fail;
+ std::function<void()> on_abort;
};
struct DependencySearchArgs {
diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
index 78b39fff..dccccdc2 100644
--- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp
+++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
@@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo
callbacks.on_succeed(doc, args.pack);
});
-
+ QObject::connect(job.get(), &NetJob::failed, [callbacks](QString reason) { callbacks.on_fail(reason); });
+ QObject::connect(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
return job;
}
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index 3c836691..ba703f77 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -168,6 +168,17 @@
<string>Go to mods home page</string>
</property>
</action>
+ <action name="actionRemoveItemMetadata">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove metadata</string>
+ </property>
+ <property name="toolTip">
+ <string>Remove mod's metadata</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 0f5e29cb..b42bbf46 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -92,6 +92,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
+ ui->actionsToolbar->insertActionAfter(ui->actionRemoveItem, ui->actionRemoveItemMetadata);
+ connect(ui->actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
+
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
@@ -104,11 +108,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
if (selected <= 1) {
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
} else {
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mods' metadata"));
}
ui->actionVisitItemPage->setEnabled(selected != 0);
+ ui->actionRemoveItemMetadata->setEnabled(selected != 0);
});
connect(mods.get(), &ModFolderModel::rowsInserted, this,
@@ -297,3 +306,24 @@ void ModFolderPage::visitModPages()
DesktopServices::openUrl(url);
}
}
+
+void ModFolderPage::deleteModMetadata()
+{
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
+ auto selectionCount = m_model->selectedMods(selection).length();
+ if (selectionCount == 0)
+ return;
+ if (selectionCount > 1) {
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
+ tr("You are about to remove the metadata for %1 mods.\n"
+ "Are you sure?")
+ .arg(selectionCount),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+ }
+
+ m_model->deleteModsMetadata(selection);
+}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index a23dcae1..0c654d0d 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -61,6 +61,7 @@ class ModFolderPage : public ExternalResourcesPage {
private slots:
void removeItems(const QItemSelection& selection) override;
+ void deleteModMetadata();
void installMods();
void updateMods();
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
index 0a7edb7b..cb8f1920 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -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);
@@ -372,6 +405,27 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
endInsertRows();
}
+void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
+
+ try {
+ auto obj = Json::requireObject(doc);
+ if (obj.contains("data"))
+ obj = Json::requireObject(obj, "data");
+ loadIndexedPack(*pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
+ }
+
+ m_search_state = SearchState::Finished;
+
+ beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1);
+ m_packs.append(pack);
+ endInsertRows();
+}
+
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
{
switch (network_error_code) {
diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h
index cc813d6e..ecf4f8f7 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.h
+++ b/launcher/ui/pages/modplatform/ResourceModel.h
@@ -149,6 +149,7 @@ class ResourceModel : public QAbstractListModel {
private:
/* Default search request callbacks */
void searchRequestSucceeded(QJsonDocument&);
+ void searchRequestForOneSucceeded(QJsonDocument&);
void searchRequestFailed(QString reason, int network_error_code);
void searchRequestAborted();
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index c087e2be..fc7d64a4 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -44,9 +44,6 @@
#include <QKeyEvent>
#include "Markdown.h"
-#include "ResourceDownloadTask.h"
-
-#include "minecraft/MinecraftInstance.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h"
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
index 9cd5eed5..dee3784e 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
@@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen
if (searchTerm.isEmpty()) {
return true;
}
-
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ if (searchTerm.startsWith("#"))
+ return QString::number(pack.id) == searchTerm.mid(1);
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index 39f4f346..b6fb7153 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -21,6 +21,7 @@
#include <Json.h>
#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
namespace Atl {
@@ -46,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
ATLauncher::IndexedPack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::ToolTipRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.safeName)) {
- return (m_logoMap.value(pack.safeName));
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
}
- auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.safeName)) {
+ return (m_logoMap.value(pack.safeName));
+ }
+ auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
- auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
- ((ListModel*)this)->requestLogo(pack.safeName, url);
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
+ ((ListModel*)this)->requestLogo(pack.safeName, url);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
void ListModel::request()
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
index 5e3b9ecf..c7e80027 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -35,11 +35,11 @@
*/
#include "AtlPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_AtlPage.h"
#include "BuildConfig.h"
-#include "AtlOptionalModDialog.h"
#include "AtlUserInteractionSupportImpl.h"
#include "modplatform/atlauncher/ATLPackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
@@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
AtlPage::~AtlPage()
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
index 746aa6d1..8b674733 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
@@ -11,21 +11,28 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QTreeView" name="packView">
- <property name="alternatingRowColors">
- <bool>true</bool>
+ <item row="3" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
</property>
- <property name="iconSize">
- <size>
- <width>96</width>
- <height>48</height>
- </size>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
@@ -36,39 +43,22 @@
</property>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
- </property>
- <property name="wordWrap">
+ <item row="1" column="0">
+ <widget class="QTreeView" name="packView">
+ <property name="alternatingRowColors">
<bool>true</bool>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ <property name="iconSize">
+ <size>
+ <width>96</width>
+ <height>48</height>
+ </size>
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
</layout>
</item>
- <item row="0" column="0">
+ <item row="1" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
@@ -78,6 +68,31 @@
</property>
</widget>
</item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<tabstops>
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index ff21d010..8875a945 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -1,6 +1,8 @@
#include "FlameModel.h"
#include <Json.h>
#include "Application.h"
+#include "modplatform/ResourceAPI.h"
+#include "modplatform/flame/FlameAPI.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@@ -161,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch()
{
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const FlameAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ }
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
auto searchUrl = QString(
"https://api.curseforge.com/v1/mods/search?"
@@ -189,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
}
currentSearchTerm = term;
currentSort = sort;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
void Flame::ListModel::searchRequestFinished()
{
- jobPtr.reset();
+ if (hasActiveSearchJob())
+ return;
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
@@ -246,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
endInsertRows();
}
+void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = Json::ensureObject(doc.object(), "data");
+
+ Flame::IndexedPack pack;
+ try {
+ Flame::loadIndexedPack(pack, packObj);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading pack from CurseForge: " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void Flame::ListModel::searchRequestFailed(QString reason)
{
jobPtr.reset();
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h
index b3bc96b8..fd8496df 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.h
@@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void performPaginatedSearch();
@@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished();
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
private:
void requestLogo(QString file, QString url);
@@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
int currentSort = 0;
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 183e16f9..50656f42 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -50,7 +50,8 @@
static FlameAPI api;
-FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
+FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
@@ -61,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
// index is used to set the sorting with the curseforge api
ui->sortByBox->addItem(tr("Sort by Featured"));
ui->sortByBox->addItem(tr("Sort by Popularity"));
@@ -90,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -114,6 +131,7 @@ void FlamePage::openedImpl()
void FlamePage::triggerSearch()
{
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(listModel->activeSearchJob().get());
}
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h
index ff5c7975..d35858fb 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.h
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.h
@@ -39,8 +39,9 @@
#include <Application.h>
#include <modplatform/flame/FlamePackIndex.h>
-#include "tasks/Task.h"
+#include <QTimer>
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class FlamePage;
@@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
Flame::IndexedPack current;
int m_selected_version_index = -1;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui
index 71d19513..f9e1fe67 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item row="2" column="0">
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item row="3" column="0">
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
index 5c9ff63b..d3ead083 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
@@ -17,6 +17,7 @@
*/
#include "ImportFTBPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QWidget>
@@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
ui->setupUi(this);
{
+ currentModel = new FilterModel(this);
listModel = new ListModel(this);
+ currentModel->setSourceModel(listModel);
- ui->modpackList->setModel(listModel);
+ ui->modpackList->setModel(currentModel);
ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0);
ui->modpackList->setIconSize(QSize(42, 42));
+
+ for (int i = 0; i < currentModel->getAvailableSortings().size(); i++) {
+ ui->sortByBox->addItem(currentModel->getAvailableSortings().keys().at(i));
+ }
+
+ ui->sortByBox->setCurrentText(currentModel->translateCurrentSorting());
}
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged);
+
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
+
+ ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}
@@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr
onPackSelectionChanged();
return;
}
- Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
+ Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
@@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
dialog->setSuggestedPack();
}
+void ImportFTBPage::onSortingSelectionChanged(QString sort)
+{
+ FilterModel::Sorting toSet = currentModel->getAvailableSortings().value(sort);
+ currentModel->setSorting(toSet);
+}
+
+void ImportFTBPage::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace FTBImportAPP
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
index 54c49f7b..8e966127 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
@@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage {
void suggestCurrent();
void onPackSelectionChanged(Modpack* pack = nullptr);
private slots:
+ void onSortingSelectionChanged(QString data);
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
+ void triggerSearch();
private:
bool initialized = false;
Modpack selected;
ListModel* listModel = nullptr;
+ FilterModel* currentModel = nullptr;
NewInstanceDialog* dialog = nullptr;
Ui::ImportFTBPage* ui = nullptr;
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
index 32d548b0..5e09fb6d 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
@@ -10,8 +10,8 @@
<height>1011</height>
</rect>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@@ -21,6 +21,54 @@
</property>
</widget>
</item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="sortByBox">
+ <property name="minimumSize">
+ <size>
+ <width>265</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
index dc78f451..134bdc0c 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
@@ -23,7 +23,9 @@
#include <QIcon>
#include <QProcessEnvironment>
#include "FileSystem.h"
+#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
+#include "ui/widgets/ProjectItem.h"
namespace FTBImportAPP {
@@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
auto pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- return pack.icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
- } else if (role == Qt::ToolTipRole) {
- return tr("Minecraft %1").arg(pack.mcVersion);
+ if (role == Qt::ToolTipRole) {
}
- return QVariant();
+ switch (role) {
+ case Qt::ToolTipRole:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case Qt::DecorationRole:
+ return pack.icon;
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
+{
+ currentSorting = Sorting::ByGameVersion;
+ sortings.insert(tr("Sort by Name"), Sorting::ByName);
+ sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
+}
+
+bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
+{
+ Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
+ Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
+
+ if (currentSorting == Sorting::ByGameVersion) {
+ Version lv(leftPack.mcVersion);
+ Version rv(rightPack.mcVersion);
+ return lv < rv;
+
+ } else if (currentSorting == Sorting::ByName) {
+ return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ }
+
+ // UHM, some inavlid value set?!
+ qWarning() << "Invalid sorting set!";
+ return true;
+}
+
+bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
+{
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
+}
+
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
+{
+ return sortings;
+}
+
+QString FilterModel::translateCurrentSorting()
+{
+ return sortings.key(currentSorting);
+}
+
+void FilterModel::setSorting(Sorting s)
+{
+ currentSorting = s;
+ invalidate();
+}
+
+FilterModel::Sorting FilterModel::getCurrentSorting()
+{
+ return currentSorting;
}
} // namespace FTBImportAPP \ No newline at end of file
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
index c67aa896..11192827 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
@@ -20,11 +20,33 @@
#include <QAbstractListModel>
#include <QIcon>
+#include <QSortFilterProxyModel>
#include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
+class FilterModel : public QSortFilterProxyModel {
+ Q_OBJECT
+ public:
+ FilterModel(QObject* parent = Q_NULLPTR);
+ enum Sorting { ByName, ByGameVersion };
+ const QMap<QString, Sorting> getAvailableSortings();
+ QString translateCurrentSorting();
+ void setSorting(Sorting sorting);
+ Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
+
+ protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
+ bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
+
+ private:
+ QMap<QString, Sorting> sortings;
+ Sorting currentSorting;
+ QString searchTerm;
+};
+
class ListModel : public QAbstractListModel {
Q_OBJECT
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 356d919d..49666cf6 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -41,6 +41,7 @@
#include <Version.h>
#include "StringUtils.h"
+#include "ui/widgets/ProjectItem.h"
#include <QLabel>
#include <QtMath>
@@ -79,7 +80,20 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
{
- return true;
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ if (searchTerm.startsWith("#"))
+ return pack.packCode == searchTerm.mid(1);
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
}
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
@@ -139,39 +153,57 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name + "\n" + translatePackType(pack.type);
- } else if (role == Qt::ToolTipRole) {
- if (pack.description.length() > 100) {
- // some magic to prevent to long tooltips and replace html linebreaks
- QString edit = pack.description.left(97);
- edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
- return edit;
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ }
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.logo)) {
+ return (m_logoMap.value(pack.logo));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel*)this)->requestLogo(pack.logo);
+ return icon;
}
- return pack.description;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logo)) {
- return (m_logoMap.value(pack.logo));
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logo);
- return icon;
- } else if (role == Qt::ForegroundRole) {
- if (pack.broken) {
- // FIXME: Hardcoded color
- return QColor(255, 0, 50);
- } else if (pack.bugged) {
- // FIXME: Hardcoded color
- // bugged pack, currently only indicates bugged xml
- return QColor(244, 229, 66);
+ case Qt::ForegroundRole: {
+ if (pack.broken) {
+ // FIXME: Hardcoded color
+ return QColor(255, 0, 50);
+ } else if (pack.bugged) {
+ // FIXME: Hardcoded color
+ // bugged pack, currently only indicates bugged xml
+ return QColor(244, 229, 66);
+ }
}
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
void ListModel::fill(ModpackList modpacks_)
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
index 51a58d99..c802a4b5 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
@@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel {
QString translateCurrentSorting();
void setSorting(Sorting sorting);
Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
@@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel {
private:
QMap<QString, Sorting> sortings;
Sorting currentSorting;
+ QString searchTerm;
};
class ListModel : public QAbstractListModel {
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
index 0103bbaa..4104f139 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
@@ -35,6 +35,7 @@
*/
#include "Page.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_Page.h"
#include <QInputDialog>
@@ -110,6 +111,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &Page::triggerSearch);
+
connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
@@ -125,6 +128,9 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
ui->thirdPartyPackList->selectionModel()->reset();
ui->privatePackList->selectionModel()->reset();
+ ui->publicPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->thirdPartyPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->privatePackList->setItemDelegate(new ProjectItemDelegate(this));
onTabChanged(ui->tabWidget->currentIndex());
}
@@ -319,6 +325,8 @@ void Page::onTabChanged(int tab)
currentModpackInfo = ui->publicPackDescription;
}
+ triggerSearch();
+
currentList->selectionModel()->reset();
QModelIndex idx = currentList->currentIndex();
if (idx.isValid()) {
@@ -358,4 +366,9 @@ void Page::onRemovePackClicked()
onPackSelectionChanged();
}
+void Page::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace LegacyFTB
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
index a12b0745..4d317b7c 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
@@ -43,7 +43,6 @@
#include "QObjectPtr.h"
#include "modplatform/legacy_ftb/PackFetchTask.h"
#include "modplatform/legacy_ftb/PackHelpers.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
class NewInstanceDialog;
@@ -56,8 +55,6 @@ class Page;
class ListModel;
class FilterModel;
-class PrivatePackListModel;
-class PrivatePackFilterModel;
class PrivatePackManager;
class Page : public QWidget, public BasePage {
@@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage {
void onAddPackClicked();
void onRemovePackClicked();
+ void triggerSearch();
+
private:
FilterModel* currentModel = nullptr;
QTreeView* currentList = nullptr;
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
index ad08dc25..56cba748 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
@@ -10,8 +10,29 @@
<height>602</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
@@ -36,9 +57,9 @@
</item>
<item row="0" column="1">
<widget class="QTextBrowser" name="publicPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
@@ -50,10 +71,10 @@
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QTextBrowser" name="thirdPartyPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
<item row="0" column="0">
<widget class="QTreeView" name="thirdPartyPackList">
@@ -104,16 +125,16 @@
</item>
<item row="0" column="1" rowspan="3">
<widget class="QTextBrowser" name="privatePackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
</widget>
</item>
- <item>
+ <item row="5" column="0">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QLabel" name="label">
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index ebc5556c..f691a185 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -38,8 +38,8 @@
#include "BuildConfig.h"
#include "Json.h"
-#include "minecraft/MinecraftInstance.h"
-#include "minecraft/PackProfile.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "net/NetJob.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@@ -130,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,
void ModpackListModel::performPaginatedSearch()
{
- // TODO: Move to standalone API
+ if (hasActiveSearchJob())
+ return;
+
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const ModrinthAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ } // TODO: Move to standalone API
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?"
@@ -167,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()
void ModpackListModel::refresh()
{
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
@@ -307,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
endInsertRows();
}
+void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = doc.object();
+
+ Modrinth::Modpack pack;
+ try {
+ Modrinth::loadIndexedPack(pack, packObj);
+ pack.id = Json::ensureString(packObj, "id", pack.id);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void ModpackListModel::searchRequestFailed(QString reason)
{
- auto failed_action = jobPtr->getFailedActions().at(0);
+ auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) {
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 721c69f5..2a9d6226 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -73,6 +73,9 @@ class ModpackListModel : public QAbstractListModel {
void refresh();
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
inline auto canFetchMore(const QModelIndex& parent) const -> bool override
@@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {
public slots:
void searchRequestFinished(QJsonDocument& doc_all);
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
protected slots:
@@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
QByteArray m_specific_response;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 41fd5003..f7fa8fd7 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -52,7 +52,8 @@
#include <QKeyEvent>
#include <QPushButton>
-ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
+ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
@@ -64,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &ModrinthPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
ui->sortByBox->addItem(tr("Sort by Relevance"));
ui->sortByBox->addItem(tr("Sort by Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows"));
@@ -102,6 +114,11 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
this->triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QObject::eventFilter(watched, event);
@@ -309,6 +326,7 @@ void ModrinthPage::suggestCurrent()
void ModrinthPage::triggerSearch()
{
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(m_model->activeSearchJob().get());
}
void ModrinthPage::onVersionSelectionChanged(QString version)
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
index b7054c88..4240dcaf 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -41,7 +41,9 @@
#include "ui/pages/BasePage.h"
#include "modplatform/modrinth/ModrinthPackManifest.h"
+#include "ui/widgets/ProgressWidget.h"
+#include <QTimer>
#include <QWidget>
namespace Ui {
@@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {
Modrinth::Modpack current;
QString selectedVersion;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
index 6d8b2b67..78a25fea 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -10,8 +10,8 @@
<height>600</height>
</rect>
</property>
- <layout class="QVBoxLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
@@ -29,7 +29,7 @@
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index e8c5ac92..3cd1d9a2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -39,6 +39,7 @@
#include "Json.h"
#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
#include <QIcon>
@@ -54,21 +55,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logoName)) {
- return (m_logoMap.value(pack.logoName));
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ }
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.logoName)) {
+ return (m_logoMap.value(pack.logoName));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+
+ return {};
}
int Technic::ListModel::columnCount(const QModelIndex& parent) const
@@ -87,21 +114,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)
return;
}
currentSearchTerm = term;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
performSearch();
}
void Technic::ListModel::performSearch()
{
+ if (hasActiveSearchJob())
+ return;
+
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
@@ -113,6 +144,9 @@ void Technic::ListModel::performSearch()
} else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
+ } else if (currentSearchTerm.startsWith("#")) {
+ searchUrl = QString("https://api.technicpack.net/modpack/%1?build=%2").arg(currentSearchTerm.mid(1), BuildConfig.TECHNIC_API_BUILD);
+ searchMode = Single;
} else {
searchUrl =
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h
index d7a635d4..aeb4f308 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h
@@ -58,6 +58,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void searchRequestFinished();
void searchRequestFailed();
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
index 54b86feb..190b7c68 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
@@ -34,6 +34,7 @@
*/
#include "TechnicPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h"
#include <QKeyEvent>
@@ -51,7 +52,8 @@
#include "net/ApiDownload.h"
-TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
+TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
@@ -59,8 +61,21 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p
model = new Technic::ListModel(this);
ui->packView->setModel(model);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &TechnicPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
@@ -71,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -100,6 +120,7 @@ void TechnicPage::openedImpl()
void TechnicPage::triggerSearch()
{
model->searchWithTerm(ui->searchEdit->text());
+ m_fetch_progress.watch(model->activeSearchJob().get());
}
void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h
index 91b61eaf..01439337 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h
@@ -35,13 +35,14 @@
#pragma once
+#include <QTimer>
#include <QWidget>
#include <Application.h>
#include "TechnicData.h"
#include "net/NetJob.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class TechnicPage;
@@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {
NetJob::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
index 15bf645f..b988eda2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="3" column="0" colspan="2">
+ <item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
@@ -44,7 +44,7 @@
</item>
</layout>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QListView" name="packView">
diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp
index 1481c1b6..60b92b28 100644
--- a/launcher/ui/widgets/ProjectItem.cpp
+++ b/launcher/ui/widgets/ProjectItem.cpp
@@ -34,8 +34,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
icon_width = icon_size.width();
icon_height = icon_size.height();
- icon_x_margin = (rect.height() - icon_width) / 2;
icon_y_margin = (rect.height() - icon_height) / 2;
+ icon_x_margin = icon_y_margin; // use same margins for consistency
}
// Centralize icon with a margin to separate from the other elements
diff --git a/nix/default.nix b/nix/default.nix
deleted file mode 100644
index 71c95c2c..00000000
--- a/nix/default.nix
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- inputs,
- self,
- ...
-}: {
- imports = [
- ./dev.nix
- ./distribution.nix
- ];
-
- _module.args = {
- # User-friendly version number.
- version = builtins.substring 0 8 self.lastModifiedDate;
- };
-
- perSystem = {system, ...}: {
- # Nixpkgs instantiated for supported systems with our overlay.
- _module.args.pkgs = import inputs.nixpkgs {
- inherit system;
- overlays = [self.overlays.default];
- };
- };
-
- # Supported systems.
- systems = [
- "x86_64-linux"
- "aarch64-linux"
- "x86_64-darwin"
- "aarch64-darwin"
- ];
-}
diff --git a/nix/dev.nix b/nix/dev.nix
index a9c1dc65..c476ed10 100644
--- a/nix/dev.nix
+++ b/nix/dev.nix
@@ -1,37 +1,33 @@
{
- inputs,
- self,
- ...
-}: {
perSystem = {
- system,
+ config,
+ lib,
pkgs,
...
}: {
- checks = {
- pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run {
- src = self;
- hooks = {
- markdownlint.enable = true;
+ pre-commit.settings = {
+ hooks = {
+ markdownlint.enable = true;
- alejandra.enable = true;
- deadnix.enable = true;
- nil.enable = true;
+ alejandra.enable = true;
+ deadnix.enable = true;
+ nil.enable = true;
- clang-format = {
- enable = true;
- types_or = ["c" "c++" "java" "json" "objective-c"];
- };
+ clang-format = {
+ enable = true;
+ types_or = ["c" "c++" "java" "json" "objective-c"];
};
-
- tools.clang-tools = pkgs.clang-tools_16;
};
+
+ tools.clang-tools = lib.mkForce pkgs.clang-tools_16;
};
devShells.default = pkgs.mkShell {
- inherit (self.checks.${system}.pre-commit-check) shellHook;
+ shellHook = ''
+ ${config.pre-commit.installationScript}
+ '';
- inputsFrom = [self.packages.${system}.prismlauncher-unwrapped];
+ inputsFrom = [config.packages.prismlauncher-unwrapped];
buildInputs = with pkgs; [ccache ninja];
};
diff --git a/nix/distribution.nix b/nix/distribution.nix
index d0904d41..6b93d355 100644
--- a/nix/distribution.nix
+++ b/nix/distribution.nix
@@ -1,30 +1,44 @@
{
inputs,
self,
- version,
...
}: {
- perSystem = {pkgs, ...}: {
- packages = {
- inherit (pkgs) prismlauncher-qt5-unwrapped prismlauncher-qt5 prismlauncher-unwrapped prismlauncher;
- default = pkgs.prismlauncher;
+ perSystem = {
+ lib,
+ pkgs,
+ ...
+ }: {
+ packages = let
+ ourPackages = lib.fix (final: self.overlays.default ({inherit (pkgs) darwin;} // final) pkgs);
+ in {
+ inherit
+ (ourPackages)
+ prismlauncher-qt5-unwrapped
+ prismlauncher-qt5
+ prismlauncher-unwrapped
+ prismlauncher
+ ;
+ default = ourPackages.prismlauncher;
};
};
flake = {
overlays.default = final: prev: let
- # Helper function to build prism against different versions of Qt.
- mkPrism = qt:
- qt.callPackage ./package.nix {
- inherit (inputs) libnbtplusplus;
- inherit (prev.darwin.apple_sdk.frameworks) Cocoa;
- inherit self version;
- };
+ version = builtins.substring 0 8 self.lastModifiedDate or "dirty";
+
+ # common args for prismlauncher evaluations
+ unwrappedArgs = {
+ inherit (inputs) libnbtplusplus;
+ inherit (final.darwin.apple_sdk.frameworks) Cocoa;
+ inherit self version;
+ };
in {
- prismlauncher-qt5-unwrapped = mkPrism final.libsForQt5;
- prismlauncher-qt5 = prev.prismlauncher-qt5.override {prismlauncher-unwrapped = final.prismlauncher-qt5-unwrapped;};
- prismlauncher-unwrapped = mkPrism final.qt6Packages;
- prismlauncher = prev.prismlauncher.override {inherit (final) prismlauncher-unwrapped;};
+ prismlauncher-qt5-unwrapped = prev.libsForQt5.callPackage ./pkg unwrappedArgs;
+ prismlauncher-qt5 = prev.libsForQt5.callPackage ./pkg/wrapper.nix {
+ prismlauncher-unwrapped = final.prismlauncher-qt5-unwrapped;
+ };
+ prismlauncher-unwrapped = prev.qt6Packages.callPackage ./pkg unwrappedArgs;
+ prismlauncher = prev.qt6Packages.callPackage ./pkg/wrapper.nix {inherit (final) prismlauncher-unwrapped;};
};
};
}
diff --git a/nix/package.nix b/nix/pkg/default.nix
index 074214c4..074214c4 100644
--- a/nix/package.nix
+++ b/nix/pkg/default.nix
diff --git a/nix/pkg/wrapper.nix b/nix/pkg/wrapper.nix
new file mode 100644
index 00000000..8d160143
--- /dev/null
+++ b/nix/pkg/wrapper.nix
@@ -0,0 +1,91 @@
+{
+ lib,
+ stdenv,
+ symlinkJoin,
+ prismlauncher-unwrapped,
+ wrapQtAppsHook,
+ qtbase, # needed for wrapQtAppsHook
+ qtsvg,
+ qtwayland,
+ xorg,
+ libpulseaudio,
+ libGL,
+ glfw,
+ openal,
+ jdk8,
+ jdk17,
+ gamemode,
+ flite,
+ mesa-demos,
+ udev,
+ msaClientID ? null,
+ gamemodeSupport ? stdenv.isLinux,
+ textToSpeechSupport ? stdenv.isLinux,
+ jdks ? [jdk17 jdk8],
+ additionalLibs ? [],
+ additionalPrograms ? [],
+}: let
+ prismlauncherFinal = prismlauncher-unwrapped.override {
+ inherit msaClientID gamemodeSupport;
+ };
+in
+ symlinkJoin {
+ name = "prismlauncher-${prismlauncherFinal.version}";
+
+ paths = [prismlauncherFinal];
+
+ nativeBuildInputs = [
+ wrapQtAppsHook
+ ];
+
+ buildInputs =
+ [
+ qtbase
+ qtsvg
+ ]
+ ++ lib.optional (lib.versionAtLeast qtbase.version "6" && stdenv.isLinux) qtwayland;
+
+ postBuild = ''
+ wrapQtAppsHook
+ '';
+
+ qtWrapperArgs = let
+ runtimeLibs =
+ (with xorg; [
+ libX11
+ libXext
+ libXcursor
+ libXrandr
+ libXxf86vm
+ ])
+ ++ [
+ # lwjgl
+ libpulseaudio
+ libGL
+ glfw
+ openal
+ stdenv.cc.cc.lib
+
+ # oshi
+ udev
+ ]
+ ++ lib.optional gamemodeSupport gamemode.lib
+ ++ lib.optional textToSpeechSupport flite
+ ++ additionalLibs;
+
+ runtimePrograms =
+ [
+ xorg.xrandr
+ mesa-demos # need glxinfo
+ ]
+ ++ additionalPrograms;
+ in
+ ["--prefix PRISMLAUNCHER_JAVA_PATHS : ${lib.makeSearchPath "bin/java" jdks}"]
+ ++ lib.optionals stdenv.isLinux [
+ "--set LD_LIBRARY_PATH /run/opengl-driver/lib:${lib.makeLibraryPath runtimeLibs}"
+ # xorg.xrandr needed for LWJGL [2.9.2, 3) https://github.com/LWJGL/lwjgl/issues/128
+ "--prefix PATH : ${lib.makeBinPath runtimePrograms}"
+ ];
+
+ inherit (prismlauncherFinal) meta;
+ }
diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in
index 816c0059..76f4b19c 100644
--- a/program_info/org.prismlauncher.PrismLauncher.desktop.in
+++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in
@@ -4,7 +4,7 @@ Name=Prism Launcher
Comment=A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.
Type=Application
Terminal=false
-Exec=@Launcher_APP_BINARY_NAME@
+Exec=@Launcher_APP_BINARY_NAME@ %U
StartupNotify=true
Icon=org.prismlauncher.PrismLauncher
Categories=Game;ActionGame;AdventureGame;Simulation;