feat:refactored modpack ux
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
		| @@ -109,6 +109,8 @@ class ResourceAPI { | |||||||
|     }; |     }; | ||||||
|     struct ProjectInfoCallbacks { |     struct ProjectInfoCallbacks { | ||||||
|         std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; |         std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed; | ||||||
|  |         std::function<void(QString const& reason)> on_fail; | ||||||
|  |         std::function<void()> on_abort; | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     struct DependencySearchArgs { |     struct DependencySearchArgs { | ||||||
|   | |||||||
| @@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo | |||||||
|  |  | ||||||
|         callbacks.on_succeed(doc, args.pack); |         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; |     return job; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -132,6 +132,36 @@ void ResourceModel::search() | |||||||
|     if (hasActiveSearchJob()) |     if (hasActiveSearchJob()) | ||||||
|         return; |         return; | ||||||
|  |  | ||||||
|  |     if (m_search_term.startsWith("#")) { | ||||||
|  |         auto projectId = m_search_term.removeFirst(); | ||||||
|  |         if (!projectId.isEmpty()) { | ||||||
|  |             ResourceAPI::ProjectInfoCallbacks callbacks; | ||||||
|  |  | ||||||
|  |             // Use defaults if no callbacks are set | ||||||
|  |             if (!callbacks.on_fail) | ||||||
|  |                 callbacks.on_fail = [this](QString reason) { | ||||||
|  |                     if (!s_running_models.constFind(this).value()) | ||||||
|  |                         return; | ||||||
|  |                     searchRequestFailed(reason, -1); | ||||||
|  |                 }; | ||||||
|  |             if (!callbacks.on_abort) | ||||||
|  |                 callbacks.on_abort = [this] { | ||||||
|  |                     if (!s_running_models.constFind(this).value()) | ||||||
|  |                         return; | ||||||
|  |                     searchRequestAborted(); | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |             if (!callbacks.on_succeed) | ||||||
|  |                 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 args{ createSearchArguments() }; | ||||||
|  |  | ||||||
|     auto callbacks{ createSearchCallbacks() }; |     auto callbacks{ createSearchCallbacks() }; | ||||||
| @@ -194,6 +224,12 @@ void ResourceModel::loadEntry(QModelIndex& entry) | |||||||
|                     return; |                     return; | ||||||
|                 infoRequestSucceeded(doc, pack, entry); |                 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) |         if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) | ||||||
|             runInfoJob(job); |             runInfoJob(job); | ||||||
| @@ -372,6 +408,27 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc) | |||||||
|     endInsertRows(); |     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) | void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code) | ||||||
| { | { | ||||||
|     switch (network_error_code) { |     switch (network_error_code) { | ||||||
|   | |||||||
| @@ -149,6 +149,7 @@ class ResourceModel : public QAbstractListModel { | |||||||
|    private: |    private: | ||||||
|     /* Default search request callbacks */ |     /* Default search request callbacks */ | ||||||
|     void searchRequestSucceeded(QJsonDocument&); |     void searchRequestSucceeded(QJsonDocument&); | ||||||
|  |     void searchRequestForOneSucceeded(QJsonDocument&); | ||||||
|     void searchRequestFailed(QString reason, int network_error_code); |     void searchRequestFailed(QString reason, int network_error_code); | ||||||
|     void searchRequestAborted(); |     void searchRequestAborted(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -44,9 +44,6 @@ | |||||||
| #include <QKeyEvent> | #include <QKeyEvent> | ||||||
|  |  | ||||||
| #include "Markdown.h" | #include "Markdown.h" | ||||||
| #include "ResourceDownloadTask.h" |  | ||||||
|  |  | ||||||
| #include "minecraft/MinecraftInstance.h" |  | ||||||
|  |  | ||||||
| #include "ui/dialogs/ResourceDownloadDialog.h" | #include "ui/dialogs/ResourceDownloadDialog.h" | ||||||
| #include "ui/pages/modplatform/ResourceModel.h" | #include "ui/pages/modplatform/ResourceModel.h" | ||||||
|   | |||||||
| @@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen | |||||||
|     if (searchTerm.isEmpty()) { |     if (searchTerm.isEmpty()) { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); |     QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); | ||||||
|     ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>(); |     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); |     return pack.name.contains(searchTerm, Qt::CaseInsensitive); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ | |||||||
| #include <Json.h> | #include <Json.h> | ||||||
|  |  | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
|  |  | ||||||
| namespace Atl { | namespace Atl { | ||||||
|  |  | ||||||
| @@ -46,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     ATLauncher::IndexedPack pack = modpacks.at(pos); |     ATLauncher::IndexedPack pack = modpacks.at(pos); | ||||||
|     if (role == Qt::DisplayRole) { |     switch (role) { | ||||||
|         return pack.name; |         case Qt::ToolTipRole: { | ||||||
|     } else if (role == Qt::ToolTipRole) { |             if (pack.description.length() > 100) { | ||||||
|         return pack.name; |                 // some magic to prevent to long tooltips and replace html linebreaks | ||||||
|     } else if (role == Qt::DecorationRole) { |                 QString edit = pack.description.left(97); | ||||||
|         if (m_logoMap.contains(pack.safeName)) { |                 edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); | ||||||
|             return (m_logoMap.value(pack.safeName)); |                 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()); |             auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower()); | ||||||
|         ((ListModel*)this)->requestLogo(pack.safeName, url); |             ((ListModel*)this)->requestLogo(pack.safeName, url); | ||||||
|  |  | ||||||
|         return icon; |             return icon; | ||||||
|     } else if (role == Qt::UserRole) { |         } | ||||||
|         QVariant v; |         case Qt::UserRole: { | ||||||
|         v.setValue(pack); |             QVariant v; | ||||||
|         return 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() | void ListModel::request() | ||||||
|   | |||||||
| @@ -35,11 +35,11 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #include "AtlPage.h" | #include "AtlPage.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
| #include "ui_AtlPage.h" | #include "ui_AtlPage.h" | ||||||
|  |  | ||||||
| #include "BuildConfig.h" | #include "BuildConfig.h" | ||||||
|  |  | ||||||
| #include "AtlOptionalModDialog.h" |  | ||||||
| #include "AtlUserInteractionSupportImpl.h" | #include "AtlUserInteractionSupportImpl.h" | ||||||
| #include "modplatform/atlauncher/ATLPackInstallTask.h" | #include "modplatform/atlauncher/ATLPackInstallTask.h" | ||||||
| #include "ui/dialogs/NewInstanceDialog.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->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged); | ||||||
|     connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged); |     connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged); | ||||||
|     connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged); |     connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged); | ||||||
|  |  | ||||||
|  |     ui->packView->setItemDelegate(new ProjectItemDelegate(this)); | ||||||
| } | } | ||||||
|  |  | ||||||
| AtlPage::~AtlPage() | AtlPage::~AtlPage() | ||||||
|   | |||||||
| @@ -11,44 +11,7 @@ | |||||||
|    </rect> |    </rect> | ||||||
|   </property> |   </property> | ||||||
|   <layout class="QGridLayout" name="gridLayout"> |   <layout class="QGridLayout" name="gridLayout"> | ||||||
|    <item row="1" column="0" colspan="2"> |    <item row="3" 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"> |  | ||||||
|     <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> |     <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> | ||||||
|      <item row="0" column="2"> |      <item row="0" column="2"> | ||||||
|       <widget class="QComboBox" name="versionSelectionBox"/> |       <widget class="QComboBox" name="versionSelectionBox"/> | ||||||
| @@ -68,7 +31,34 @@ | |||||||
|      </item> |      </item> | ||||||
|     </layout> |     </layout> | ||||||
|    </item> |    </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"> |     <widget class="QLineEdit" name="searchEdit"> | ||||||
|      <property name="placeholderText"> |      <property name="placeholderText"> | ||||||
|       <string>Search and filter...</string> |       <string>Search and filter...</string> | ||||||
| @@ -78,6 +68,31 @@ | |||||||
|      </property> |      </property> | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </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> |   </layout> | ||||||
|  </widget> |  </widget> | ||||||
|  <tabstops> |  <tabstops> | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #include "FlameModel.h" | #include "FlameModel.h" | ||||||
| #include <Json.h> | #include <Json.h> | ||||||
| #include "Application.h" | #include "Application.h" | ||||||
|  | #include "modplatform/ResourceAPI.h" | ||||||
|  | #include "modplatform/flame/FlameAPI.h" | ||||||
| #include "ui/widgets/ProjectItem.h" | #include "ui/widgets/ProjectItem.h" | ||||||
|  |  | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.h" | ||||||
| @@ -161,6 +163,25 @@ void ListModel::fetchMore(const QModelIndex& parent) | |||||||
|  |  | ||||||
| void ListModel::performPaginatedSearch() | void ListModel::performPaginatedSearch() | ||||||
| { | { | ||||||
|  |     if (currentSearchTerm.startsWith("#")) { | ||||||
|  |         auto projectId = currentSearchTerm.removeFirst(); | ||||||
|  |         if (!projectId.isEmpty()) { | ||||||
|  |             ResourceAPI::ProjectInfoCallbacks callbacks; | ||||||
|  |  | ||||||
|  |             // Use defaults if no callbacks are set | ||||||
|  |             if (!callbacks.on_fail) | ||||||
|  |                 callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; | ||||||
|  |  | ||||||
|  |             if (!callbacks.on_succeed) | ||||||
|  |                 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 netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network()); | ||||||
|     auto searchUrl = QString( |     auto searchUrl = QString( | ||||||
|                          "https://api.curseforge.com/v1/mods/search?" |                          "https://api.curseforge.com/v1/mods/search?" | ||||||
| @@ -189,23 +210,24 @@ void ListModel::searchWithTerm(const QString& term, int sort) | |||||||
|     } |     } | ||||||
|     currentSearchTerm = term; |     currentSearchTerm = term; | ||||||
|     currentSort = sort; |     currentSort = sort; | ||||||
|     if (jobPtr) { |     if (hasActiveSearchJob()) { | ||||||
|         jobPtr->abort(); |         jobPtr->abort(); | ||||||
|         searchState = ResetRequested; |         searchState = ResetRequested; | ||||||
|         return; |         return; | ||||||
|     } else { |  | ||||||
|         beginResetModel(); |  | ||||||
|         modpacks.clear(); |  | ||||||
|         endResetModel(); |  | ||||||
|         searchState = None; |  | ||||||
|     } |     } | ||||||
|  |     beginResetModel(); | ||||||
|  |     modpacks.clear(); | ||||||
|  |     endResetModel(); | ||||||
|  |     searchState = None; | ||||||
|  |  | ||||||
|     nextSearchOffset = 0; |     nextSearchOffset = 0; | ||||||
|     performPaginatedSearch(); |     performPaginatedSearch(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Flame::ListModel::searchRequestFinished() | void Flame::ListModel::searchRequestFinished() | ||||||
| { | { | ||||||
|     jobPtr.reset(); |     if (hasActiveSearchJob()) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|     QJsonParseError parse_error; |     QJsonParseError parse_error; | ||||||
|     QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); |     QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); | ||||||
| @@ -246,6 +268,25 @@ void Flame::ListModel::searchRequestFinished() | |||||||
|     endInsertRows(); |     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) | void Flame::ListModel::searchRequestFailed(QString reason) | ||||||
| { | { | ||||||
|     jobPtr.reset(); |     jobPtr.reset(); | ||||||
|   | |||||||
| @@ -40,6 +40,8 @@ class ListModel : public QAbstractListModel { | |||||||
|     void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); |     void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); | ||||||
|     void searchWithTerm(const QString& term, const int sort); |     void searchWithTerm(const QString& term, const int sort); | ||||||
|  |  | ||||||
|  |     [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } | ||||||
|  |  | ||||||
|    private slots: |    private slots: | ||||||
|     void performPaginatedSearch(); |     void performPaginatedSearch(); | ||||||
|  |  | ||||||
| @@ -48,6 +50,7 @@ class ListModel : public QAbstractListModel { | |||||||
|  |  | ||||||
|     void searchRequestFinished(); |     void searchRequestFinished(); | ||||||
|     void searchRequestFailed(QString reason); |     void searchRequestFailed(QString reason); | ||||||
|  |     void searchRequestForOneSucceeded(QJsonDocument&); | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     void requestLogo(QString file, QString url); |     void requestLogo(QString file, QString url); | ||||||
| @@ -63,7 +66,7 @@ class ListModel : public QAbstractListModel { | |||||||
|     int currentSort = 0; |     int currentSort = 0; | ||||||
|     int nextSearchOffset = 0; |     int nextSearchOffset = 0; | ||||||
|     enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; |     enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; | ||||||
|     NetJob::Ptr jobPtr; |     Task::Ptr jobPtr; | ||||||
|     std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); |     std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -61,6 +61,11 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren | |||||||
|     ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); |     ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); | ||||||
|     ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); |     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); | ||||||
|  |  | ||||||
|     // index is used to set the sorting with the curseforge api |     // index is used to set the sorting with the curseforge api | ||||||
|     ui->sortByBox->addItem(tr("Sort by Featured")); |     ui->sortByBox->addItem(tr("Sort by Featured")); | ||||||
|     ui->sortByBox->addItem(tr("Sort by Popularity")); |     ui->sortByBox->addItem(tr("Sort by Popularity")); | ||||||
| @@ -90,6 +95,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event) | |||||||
|             triggerSearch(); |             triggerSearch(); | ||||||
|             keyEvent->accept(); |             keyEvent->accept(); | ||||||
|             return true; |             return true; | ||||||
|  |         } else { | ||||||
|  |             if (m_search_timer.isActive()) | ||||||
|  |                 m_search_timer.stop(); | ||||||
|  |  | ||||||
|  |             m_search_timer.start(350); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return QWidget::eventFilter(watched, event); |     return QWidget::eventFilter(watched, event); | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ | |||||||
|  |  | ||||||
| #include <Application.h> | #include <Application.h> | ||||||
| #include <modplatform/flame/FlamePackIndex.h> | #include <modplatform/flame/FlamePackIndex.h> | ||||||
| #include "tasks/Task.h" | #include <QTimer> | ||||||
| #include "ui/pages/BasePage.h" | #include "ui/pages/BasePage.h" | ||||||
|  |  | ||||||
| namespace Ui { | namespace Ui { | ||||||
| @@ -86,4 +86,7 @@ class FlamePage : public QWidget, public BasePage { | |||||||
|     Flame::IndexedPack current; |     Flame::IndexedPack current; | ||||||
|  |  | ||||||
|     int m_selected_version_index = -1; |     int m_selected_version_index = -1; | ||||||
|  |  | ||||||
|  |     // Used to do instant searching with a delay to cache quick changes | ||||||
|  |     QTimer m_search_timer; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #include "ImportFTBPage.h" | #include "ImportFTBPage.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
| #include "ui_ImportFTBPage.h" | #include "ui_ImportFTBPage.h" | ||||||
|  |  | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
| @@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg | |||||||
|     ui->setupUi(this); |     ui->setupUi(this); | ||||||
|  |  | ||||||
|     { |     { | ||||||
|  |         currentModel = new FilterModel(this); | ||||||
|         listModel = new ListModel(this); |         listModel = new ListModel(this); | ||||||
|  |         currentModel->setSourceModel(listModel); | ||||||
|  |  | ||||||
|         ui->modpackList->setModel(listModel); |         ui->modpackList->setModel(currentModel); | ||||||
|         ui->modpackList->setSortingEnabled(true); |         ui->modpackList->setSortingEnabled(true); | ||||||
|         ui->modpackList->header()->hide(); |         ui->modpackList->header()->hide(); | ||||||
|         ui->modpackList->setIndentation(0); |         ui->modpackList->setIndentation(0); | ||||||
|         ui->modpackList->setIconSize(QSize(42, 42)); |         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->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(); |     ui->modpackList->selectionModel()->reset(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr | |||||||
|         onPackSelectionChanged(); |         onPackSelectionChanged(); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>(); |     Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>(); | ||||||
|     onPackSelectionChanged(&selectedPack); |     onPackSelectionChanged(&selectedPack); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack) | |||||||
|         dialog->setSuggestedPack(); |         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 | }  // namespace FTBImportAPP | ||||||
|   | |||||||
| @@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage { | |||||||
|     void suggestCurrent(); |     void suggestCurrent(); | ||||||
|     void onPackSelectionChanged(Modpack* pack = nullptr); |     void onPackSelectionChanged(Modpack* pack = nullptr); | ||||||
|    private slots: |    private slots: | ||||||
|  |     void onSortingSelectionChanged(QString data); | ||||||
|     void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); |     void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); | ||||||
|  |     void triggerSearch(); | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     bool initialized = false; |     bool initialized = false; | ||||||
|     Modpack selected; |     Modpack selected; | ||||||
|     ListModel* listModel = nullptr; |     ListModel* listModel = nullptr; | ||||||
|  |     FilterModel* currentModel = nullptr; | ||||||
|  |  | ||||||
|     NewInstanceDialog* dialog = nullptr; |     NewInstanceDialog* dialog = nullptr; | ||||||
|     Ui::ImportFTBPage* ui = nullptr; |     Ui::ImportFTBPage* ui = nullptr; | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ | |||||||
|     <height>1011</height> |     <height>1011</height> | ||||||
|    </rect> |    </rect> | ||||||
|   </property> |   </property> | ||||||
|   <layout class="QHBoxLayout" name="horizontalLayout"> |   <layout class="QGridLayout" name="gridLayout"> | ||||||
|    <item> |    <item row="1" column="1"> | ||||||
|     <widget class="QTreeView" name="modpackList"> |     <widget class="QTreeView" name="modpackList"> | ||||||
|      <property name="maximumSize"> |      <property name="maximumSize"> | ||||||
|       <size> |       <size> | ||||||
| @@ -21,6 +21,54 @@ | |||||||
|      </property> |      </property> | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </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> |   </layout> | ||||||
|  </widget> |  </widget> | ||||||
|  <resources/> |  <resources/> | ||||||
|   | |||||||
| @@ -23,7 +23,9 @@ | |||||||
| #include <QIcon> | #include <QIcon> | ||||||
| #include <QProcessEnvironment> | #include <QProcessEnvironment> | ||||||
| #include "FileSystem.h" | #include "FileSystem.h" | ||||||
|  | #include "StringUtils.h" | ||||||
| #include "modplatform/import_ftb/PackHelpers.h" | #include "modplatform/import_ftb/PackHelpers.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
|  |  | ||||||
| namespace FTBImportAPP { | namespace FTBImportAPP { | ||||||
|  |  | ||||||
| @@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     auto pack = modpacks.at(pos); |     auto pack = modpacks.at(pos); | ||||||
|     if (role == Qt::DisplayRole) { |     if (role == Qt::ToolTipRole) { | ||||||
|         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); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     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 | }  // namespace FTBImportAPP | ||||||
| @@ -20,11 +20,33 @@ | |||||||
|  |  | ||||||
| #include <QAbstractListModel> | #include <QAbstractListModel> | ||||||
| #include <QIcon> | #include <QIcon> | ||||||
|  | #include <QSortFilterProxyModel> | ||||||
| #include <QVariant> | #include <QVariant> | ||||||
| #include "modplatform/import_ftb/PackHelpers.h" | #include "modplatform/import_ftb/PackHelpers.h" | ||||||
|  |  | ||||||
| namespace FTBImportAPP { | 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 { | class ListModel : public QAbstractListModel { | ||||||
|     Q_OBJECT |     Q_OBJECT | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,6 +41,7 @@ | |||||||
|  |  | ||||||
| #include <Version.h> | #include <Version.h> | ||||||
| #include "StringUtils.h" | #include "StringUtils.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
|  |  | ||||||
| #include <QLabel> | #include <QLabel> | ||||||
| #include <QtMath> | #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 | 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() | 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); |     Modpack pack = modpacks.at(pos); | ||||||
|     if (role == Qt::DisplayRole) { |     switch (role) { | ||||||
|         return pack.name + "\n" + translatePackType(pack.type); |         case Qt::ToolTipRole: { | ||||||
|     } else if (role == Qt::ToolTipRole) { |             if (pack.description.length() > 100) { | ||||||
|         if (pack.description.length() > 100) { |                 // some magic to prevent to long tooltips and replace html linebreaks | ||||||
|             // some magic to prevent to long tooltips and replace html linebreaks |                 QString edit = pack.description.left(97); | ||||||
|             QString edit = pack.description.left(97); |                 edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); | ||||||
|             edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); |                 return edit; | ||||||
|             return edit; |             } | ||||||
|  |             return pack.description; | ||||||
|         } |         } | ||||||
|         return pack.description; |         case Qt::DecorationRole: { | ||||||
|     } else if (role == Qt::DecorationRole) { |             if (m_logoMap.contains(pack.logo)) { | ||||||
|         if (m_logoMap.contains(pack.logo)) { |                 return (m_logoMap.value(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"); |         case Qt::UserRole: { | ||||||
|         ((ListModel*)this)->requestLogo(pack.logo); |             QVariant v; | ||||||
|         return icon; |             v.setValue(pack); | ||||||
|     } else if (role == Qt::ForegroundRole) { |             return v; | ||||||
|         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) { |         case Qt::ForegroundRole: { | ||||||
|         QVariant v; |             if (pack.broken) { | ||||||
|         v.setValue(pack); |                 // FIXME: Hardcoded color | ||||||
|         return v; |                 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_) | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel { | |||||||
|     QString translateCurrentSorting(); |     QString translateCurrentSorting(); | ||||||
|     void setSorting(Sorting sorting); |     void setSorting(Sorting sorting); | ||||||
|     Sorting getCurrentSorting(); |     Sorting getCurrentSorting(); | ||||||
|  |     void setSearchTerm(QString term); | ||||||
|  |  | ||||||
|    protected: |    protected: | ||||||
|     bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; |     bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; | ||||||
| @@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel { | |||||||
|    private: |    private: | ||||||
|     QMap<QString, Sorting> sortings; |     QMap<QString, Sorting> sortings; | ||||||
|     Sorting currentSorting; |     Sorting currentSorting; | ||||||
|  |     QString searchTerm; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ListModel : public QAbstractListModel { | class ListModel : public QAbstractListModel { | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #include "Page.h" | #include "Page.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
| #include "ui_Page.h" | #include "ui_Page.h" | ||||||
|  |  | ||||||
| #include <QInputDialog> | #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->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged); | ||||||
|     connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged); |     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->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged); | ||||||
|     connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged); |     connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged); | ||||||
|     connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged); |     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->thirdPartyPackList->selectionModel()->reset(); | ||||||
|     ui->privatePackList->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()); |     onTabChanged(ui->tabWidget->currentIndex()); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -319,6 +325,8 @@ void Page::onTabChanged(int tab) | |||||||
|         currentModpackInfo = ui->publicPackDescription; |         currentModpackInfo = ui->publicPackDescription; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     triggerSearch(); | ||||||
|  |  | ||||||
|     currentList->selectionModel()->reset(); |     currentList->selectionModel()->reset(); | ||||||
|     QModelIndex idx = currentList->currentIndex(); |     QModelIndex idx = currentList->currentIndex(); | ||||||
|     if (idx.isValid()) { |     if (idx.isValid()) { | ||||||
| @@ -358,4 +366,9 @@ void Page::onRemovePackClicked() | |||||||
|     onPackSelectionChanged(); |     onPackSelectionChanged(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Page::triggerSearch() | ||||||
|  | { | ||||||
|  |     currentModel->setSearchTerm(ui->searchEdit->text()); | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace LegacyFTB | }  // namespace LegacyFTB | ||||||
|   | |||||||
| @@ -43,7 +43,6 @@ | |||||||
| #include "QObjectPtr.h" | #include "QObjectPtr.h" | ||||||
| #include "modplatform/legacy_ftb/PackFetchTask.h" | #include "modplatform/legacy_ftb/PackFetchTask.h" | ||||||
| #include "modplatform/legacy_ftb/PackHelpers.h" | #include "modplatform/legacy_ftb/PackHelpers.h" | ||||||
| #include "tasks/Task.h" |  | ||||||
| #include "ui/pages/BasePage.h" | #include "ui/pages/BasePage.h" | ||||||
|  |  | ||||||
| class NewInstanceDialog; | class NewInstanceDialog; | ||||||
| @@ -56,8 +55,6 @@ class Page; | |||||||
|  |  | ||||||
| class ListModel; | class ListModel; | ||||||
| class FilterModel; | class FilterModel; | ||||||
| class PrivatePackListModel; |  | ||||||
| class PrivatePackFilterModel; |  | ||||||
| class PrivatePackManager; | class PrivatePackManager; | ||||||
|  |  | ||||||
| class Page : public QWidget, public BasePage { | class Page : public QWidget, public BasePage { | ||||||
| @@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage { | |||||||
|     void onAddPackClicked(); |     void onAddPackClicked(); | ||||||
|     void onRemovePackClicked(); |     void onRemovePackClicked(); | ||||||
|  |  | ||||||
|  |     void triggerSearch(); | ||||||
|  |  | ||||||
|    private: |    private: | ||||||
|     FilterModel* currentModel = nullptr; |     FilterModel* currentModel = nullptr; | ||||||
|     QTreeView* currentList = nullptr; |     QTreeView* currentList = nullptr; | ||||||
|   | |||||||
| @@ -10,8 +10,29 @@ | |||||||
|     <height>602</height> |     <height>602</height> | ||||||
|    </rect> |    </rect> | ||||||
|   </property> |   </property> | ||||||
|   <layout class="QVBoxLayout" name="verticalLayout"> |   <layout class="QGridLayout" name="gridLayout_5"> | ||||||
|    <item> |    <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"> |     <widget class="QTabWidget" name="tabWidget"> | ||||||
|      <property name="currentIndex"> |      <property name="currentIndex"> | ||||||
|       <number>0</number> |       <number>0</number> | ||||||
| @@ -36,9 +57,9 @@ | |||||||
|        </item> |        </item> | ||||||
|        <item row="0" column="1"> |        <item row="0" column="1"> | ||||||
|         <widget class="QTextBrowser" name="publicPackDescription"> |         <widget class="QTextBrowser" name="publicPackDescription"> | ||||||
|            <property name="openExternalLinks"> |          <property name="openExternalLinks"> | ||||||
|             <bool>true</bool> |           <bool>true</bool> | ||||||
|            </property> |          </property> | ||||||
|         </widget> |         </widget> | ||||||
|        </item> |        </item> | ||||||
|       </layout> |       </layout> | ||||||
| @@ -50,10 +71,10 @@ | |||||||
|       <layout class="QGridLayout" name="gridLayout_3"> |       <layout class="QGridLayout" name="gridLayout_3"> | ||||||
|        <item row="0" column="1"> |        <item row="0" column="1"> | ||||||
|         <widget class="QTextBrowser" name="thirdPartyPackDescription"> |         <widget class="QTextBrowser" name="thirdPartyPackDescription"> | ||||||
|                <property name="openExternalLinks"> |          <property name="openExternalLinks"> | ||||||
|             <bool>true</bool> |           <bool>true</bool> | ||||||
|            </property> |          </property> | ||||||
|           </widget> |         </widget> | ||||||
|        </item> |        </item> | ||||||
|        <item row="0" column="0"> |        <item row="0" column="0"> | ||||||
|         <widget class="QTreeView" name="thirdPartyPackList"> |         <widget class="QTreeView" name="thirdPartyPackList"> | ||||||
| @@ -104,16 +125,16 @@ | |||||||
|        </item> |        </item> | ||||||
|        <item row="0" column="1" rowspan="3"> |        <item row="0" column="1" rowspan="3"> | ||||||
|         <widget class="QTextBrowser" name="privatePackDescription"> |         <widget class="QTextBrowser" name="privatePackDescription"> | ||||||
|            <property name="openExternalLinks"> |          <property name="openExternalLinks"> | ||||||
|             <bool>true</bool> |           <bool>true</bool> | ||||||
|            </property> |          </property> | ||||||
|           </widget> |         </widget> | ||||||
|        </item> |        </item> | ||||||
|       </layout> |       </layout> | ||||||
|      </widget> |      </widget> | ||||||
|     </widget> |     </widget> | ||||||
|    </item> |    </item> | ||||||
|    <item> |    <item row="5" column="0"> | ||||||
|     <layout class="QGridLayout" name="gridLayout_4"> |     <layout class="QGridLayout" name="gridLayout_4"> | ||||||
|      <item row="0" column="1"> |      <item row="0" column="1"> | ||||||
|       <widget class="QLabel" name="label"> |       <widget class="QLabel" name="label"> | ||||||
|   | |||||||
| @@ -38,8 +38,8 @@ | |||||||
|  |  | ||||||
| #include "BuildConfig.h" | #include "BuildConfig.h" | ||||||
| #include "Json.h" | #include "Json.h" | ||||||
| #include "minecraft/MinecraftInstance.h" | #include "modplatform/modrinth/ModrinthAPI.h" | ||||||
| #include "minecraft/PackProfile.h" | #include "net/NetJob.h" | ||||||
| #include "ui/widgets/ProjectItem.h" | #include "ui/widgets/ProjectItem.h" | ||||||
|  |  | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.h" | ||||||
| @@ -130,7 +130,28 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, | |||||||
|  |  | ||||||
| void ModpackListModel::performPaginatedSearch() | void ModpackListModel::performPaginatedSearch() | ||||||
| { | { | ||||||
|     // TODO: Move to standalone API |     if (hasActiveSearchJob()) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     if (currentSearchTerm.startsWith("#")) { | ||||||
|  |         auto projectId = currentSearchTerm.removeFirst(); | ||||||
|  |         if (!projectId.isEmpty()) { | ||||||
|  |             ResourceAPI::ProjectInfoCallbacks callbacks; | ||||||
|  |  | ||||||
|  |             // Use defaults if no callbacks are set | ||||||
|  |             if (!callbacks.on_fail) | ||||||
|  |                 callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; | ||||||
|  |  | ||||||
|  |             if (!callbacks.on_succeed) | ||||||
|  |                 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 netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network()); | ||||||
|     auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + |     auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + | ||||||
|                                 "/search?" |                                 "/search?" | ||||||
| @@ -167,16 +188,17 @@ void ModpackListModel::performPaginatedSearch() | |||||||
|  |  | ||||||
| void ModpackListModel::refresh() | void ModpackListModel::refresh() | ||||||
| { | { | ||||||
|     if (jobPtr) { |     if (hasActiveSearchJob()) { | ||||||
|         jobPtr->abort(); |         jobPtr->abort(); | ||||||
|         searchState = ResetRequested; |         searchState = ResetRequested; | ||||||
|         return; |         return; | ||||||
|     } else { |  | ||||||
|         beginResetModel(); |  | ||||||
|         modpacks.clear(); |  | ||||||
|         endResetModel(); |  | ||||||
|         searchState = None; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     beginResetModel(); | ||||||
|  |     modpacks.clear(); | ||||||
|  |     endResetModel(); | ||||||
|  |     searchState = None; | ||||||
|  |  | ||||||
|     nextSearchOffset = 0; |     nextSearchOffset = 0; | ||||||
|     performPaginatedSearch(); |     performPaginatedSearch(); | ||||||
| } | } | ||||||
| @@ -307,9 +329,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all) | |||||||
|     endInsertRows(); |     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) | 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) { |     if (!failed_action->m_reply) { | ||||||
|         // Network error |         // Network error | ||||||
|         QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); |         QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); | ||||||
|   | |||||||
| @@ -73,6 +73,8 @@ class ModpackListModel : public QAbstractListModel { | |||||||
|     void refresh(); |     void refresh(); | ||||||
|     void searchWithTerm(const QString& term, const int sort); |     void searchWithTerm(const QString& term, const int sort); | ||||||
|  |  | ||||||
|  |     [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } | ||||||
|  |  | ||||||
|     void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); |     void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); | ||||||
|  |  | ||||||
|     inline auto canFetchMore(const QModelIndex& parent) const -> bool override |     inline auto canFetchMore(const QModelIndex& parent) const -> bool override | ||||||
| @@ -83,6 +85,7 @@ class ModpackListModel : public QAbstractListModel { | |||||||
|    public slots: |    public slots: | ||||||
|     void searchRequestFinished(QJsonDocument& doc_all); |     void searchRequestFinished(QJsonDocument& doc_all); | ||||||
|     void searchRequestFailed(QString reason); |     void searchRequestFailed(QString reason); | ||||||
|  |     void searchRequestForOneSucceeded(QJsonDocument&); | ||||||
|  |  | ||||||
|    protected slots: |    protected slots: | ||||||
|  |  | ||||||
| @@ -111,7 +114,7 @@ class ModpackListModel : public QAbstractListModel { | |||||||
|     int nextSearchOffset = 0; |     int nextSearchOffset = 0; | ||||||
|     enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; |     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>(); |     std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>(); | ||||||
|     QByteArray m_specific_response; |     QByteArray m_specific_response; | ||||||
|   | |||||||
| @@ -64,6 +64,11 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget | |||||||
|     ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); |     ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); | ||||||
|     ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); |     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); | ||||||
|  |  | ||||||
|     ui->sortByBox->addItem(tr("Sort by Relevance")); |     ui->sortByBox->addItem(tr("Sort by Relevance")); | ||||||
|     ui->sortByBox->addItem(tr("Sort by Total Downloads")); |     ui->sortByBox->addItem(tr("Sort by Total Downloads")); | ||||||
|     ui->sortByBox->addItem(tr("Sort by Follows")); |     ui->sortByBox->addItem(tr("Sort by Follows")); | ||||||
| @@ -102,6 +107,11 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) | |||||||
|             this->triggerSearch(); |             this->triggerSearch(); | ||||||
|             keyEvent->accept(); |             keyEvent->accept(); | ||||||
|             return true; |             return true; | ||||||
|  |         } else { | ||||||
|  |             if (m_search_timer.isActive()) | ||||||
|  |                 m_search_timer.stop(); | ||||||
|  |  | ||||||
|  |             m_search_timer.start(350); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return QObject::eventFilter(watched, event); |     return QObject::eventFilter(watched, event); | ||||||
|   | |||||||
| @@ -42,6 +42,7 @@ | |||||||
|  |  | ||||||
| #include "modplatform/modrinth/ModrinthPackManifest.h" | #include "modplatform/modrinth/ModrinthPackManifest.h" | ||||||
|  |  | ||||||
|  | #include <QTimer> | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
|  |  | ||||||
| namespace Ui { | namespace Ui { | ||||||
| @@ -88,4 +89,7 @@ class ModrinthPage : public QWidget, public BasePage { | |||||||
|  |  | ||||||
|     Modrinth::Modpack current; |     Modrinth::Modpack current; | ||||||
|     QString selectedVersion; |     QString selectedVersion; | ||||||
|  |  | ||||||
|  |     // Used to do instant searching with a delay to cache quick changes | ||||||
|  |     QTimer m_search_timer; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ | |||||||
| #include "Json.h" | #include "Json.h" | ||||||
|  |  | ||||||
| #include "net/ApiDownload.h" | #include "net/ApiDownload.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
|  |  | ||||||
| #include <QIcon> | #include <QIcon> | ||||||
|  |  | ||||||
| @@ -54,21 +55,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     Modpack pack = modpacks.at(pos); |     Modpack pack = modpacks.at(pos); | ||||||
|     if (role == Qt::DisplayRole) { |     switch (role) { | ||||||
|         return pack.name; |         case Qt::ToolTipRole: { | ||||||
|     } else if (role == Qt::DecorationRole) { |             if (pack.description.length() > 100) { | ||||||
|         if (m_logoMap.contains(pack.logoName)) { |                 // some magic to prevent to long tooltips and replace html linebreaks | ||||||
|             return (m_logoMap.value(pack.logoName)); |                 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"); |         case Qt::DecorationRole: { | ||||||
|         ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); |             if (m_logoMap.contains(pack.logoName)) { | ||||||
|         return icon; |                 return (m_logoMap.value(pack.logoName)); | ||||||
|     } else if (role == Qt::UserRole) { |             } | ||||||
|         QVariant v; |             QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); | ||||||
|         v.setValue(pack); |             ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); | ||||||
|         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 {}; | ||||||
| } | } | ||||||
|  |  | ||||||
| int Technic::ListModel::columnCount(const QModelIndex& parent) const | int Technic::ListModel::columnCount(const QModelIndex& parent) const | ||||||
| @@ -87,21 +114,25 @@ void Technic::ListModel::searchWithTerm(const QString& term) | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     currentSearchTerm = term; |     currentSearchTerm = term; | ||||||
|     if (jobPtr) { |     if (hasActiveSearchJob()) { | ||||||
|         jobPtr->abort(); |         jobPtr->abort(); | ||||||
|         searchState = ResetRequested; |         searchState = ResetRequested; | ||||||
|         return; |         return; | ||||||
|     } else { |  | ||||||
|         beginResetModel(); |  | ||||||
|         modpacks.clear(); |  | ||||||
|         endResetModel(); |  | ||||||
|         searchState = None; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     beginResetModel(); | ||||||
|  |     modpacks.clear(); | ||||||
|  |     endResetModel(); | ||||||
|  |     searchState = None; | ||||||
|  |  | ||||||
|     performSearch(); |     performSearch(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Technic::ListModel::performSearch() | void Technic::ListModel::performSearch() | ||||||
| { | { | ||||||
|  |     if (hasActiveSearchJob()) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|     auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network()); |     auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network()); | ||||||
|     QString searchUrl = ""; |     QString searchUrl = ""; | ||||||
|     if (currentSearchTerm.isEmpty()) { |     if (currentSearchTerm.isEmpty()) { | ||||||
| @@ -113,6 +144,9 @@ void Technic::ListModel::performSearch() | |||||||
|     } else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) { |     } else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) { | ||||||
|         searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD); |         searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD); | ||||||
|         searchMode = Single; |         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 { |     } else { | ||||||
|         searchUrl = |         searchUrl = | ||||||
|             QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); |             QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); | ||||||
|   | |||||||
| @@ -58,6 +58,8 @@ class ListModel : public QAbstractListModel { | |||||||
|     void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); |     void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); | ||||||
|     void searchWithTerm(const QString& term); |     void searchWithTerm(const QString& term); | ||||||
|  |  | ||||||
|  |     [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } | ||||||
|  |  | ||||||
|    private slots: |    private slots: | ||||||
|     void searchRequestFinished(); |     void searchRequestFinished(); | ||||||
|     void searchRequestFailed(); |     void searchRequestFailed(); | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| #include "TechnicPage.h" | #include "TechnicPage.h" | ||||||
|  | #include "ui/widgets/ProjectItem.h" | ||||||
| #include "ui_TechnicPage.h" | #include "ui_TechnicPage.h" | ||||||
|  |  | ||||||
| #include <QKeyEvent> | #include <QKeyEvent> | ||||||
| @@ -59,8 +60,15 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p | |||||||
|     model = new Technic::ListModel(this); |     model = new Technic::ListModel(this); | ||||||
|     ui->packView->setModel(model); |     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); | ||||||
|  |  | ||||||
|     connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); |     connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); | ||||||
|     connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged); |     connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged); | ||||||
|  |  | ||||||
|  |     ui->packView->setItemDelegate(new ProjectItemDelegate(this)); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool TechnicPage::eventFilter(QObject* watched, QEvent* event) | bool TechnicPage::eventFilter(QObject* watched, QEvent* event) | ||||||
| @@ -71,6 +79,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event) | |||||||
|             triggerSearch(); |             triggerSearch(); | ||||||
|             keyEvent->accept(); |             keyEvent->accept(); | ||||||
|             return true; |             return true; | ||||||
|  |         } else { | ||||||
|  |             if (m_search_timer.isActive()) | ||||||
|  |                 m_search_timer.stop(); | ||||||
|  |  | ||||||
|  |             m_search_timer.start(350); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return QWidget::eventFilter(watched, event); |     return QWidget::eventFilter(watched, event); | ||||||
|   | |||||||
| @@ -35,12 +35,12 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include <QTimer> | ||||||
| #include <QWidget> | #include <QWidget> | ||||||
|  |  | ||||||
| #include <Application.h> | #include <Application.h> | ||||||
| #include "TechnicData.h" | #include "TechnicData.h" | ||||||
| #include "net/NetJob.h" | #include "net/NetJob.h" | ||||||
| #include "tasks/Task.h" |  | ||||||
| #include "ui/pages/BasePage.h" | #include "ui/pages/BasePage.h" | ||||||
|  |  | ||||||
| namespace Ui { | namespace Ui { | ||||||
| @@ -91,4 +91,7 @@ class TechnicPage : public QWidget, public BasePage { | |||||||
|  |  | ||||||
|     NetJob::Ptr jobPtr; |     NetJob::Ptr jobPtr; | ||||||
|     std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); |     std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); | ||||||
|  |  | ||||||
|  |     // Used to do instant searching with a delay to cache quick changes | ||||||
|  |     QTimer m_search_timer; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -34,8 +34,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o | |||||||
|             icon_width = icon_size.width(); |             icon_width = icon_size.width(); | ||||||
|             icon_height = icon_size.height(); |             icon_height = icon_size.height(); | ||||||
|  |  | ||||||
|             icon_x_margin = (rect.height() - icon_width) / 2; |  | ||||||
|             icon_y_margin = (rect.height() - icon_height) / 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 |         // Centralize icon with a margin to separate from the other elements | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Trial97
					Trial97