diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 1431ea92c..5d49c0dfa 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -356,4 +356,19 @@ QList ShaderPackDownloadDialog::getPages() return pages; } +void ModDownloadDialog::setModMetadata(std::shared_ptr meta) +{ + switch (meta->provider) { + case ModPlatform::ResourceProvider::MODRINTH: + selectPage(Modrinth::id()); + break; + case ModPlatform::ResourceProvider::FLAME: + selectPage(Flame::id()); + break; + } + m_container->hidePageList(); + m_buttons.hide(); + auto page = selectedPage(); + page->openProject(meta->project_id); +} } // namespace ResourceDownload diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index a6efca138..7a0d6e895 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -107,6 +107,8 @@ class ModDownloadDialog final : public ResourceDownloadDialog { QList getPages() override; GetModDependenciesTask::Ptr getModDependenciesTask() override; + void setModMetadata(std::shared_ptr); + private: BaseInstance* m_instance; }; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index ce1cc3a90..e33bcfc36 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -40,6 +40,7 @@ #include "ui_ExternalResourcesPage.h" #include +#include #include #include #include @@ -121,10 +122,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->addAction(ui->actionVisitItemPage); connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); + auto changeVersion = new QAction(tr("Reinstall")); + changeVersion->setToolTip(tr("Reinstall mod")); + changeVersion->setEnabled(false); + ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, changeVersion); + connect(changeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion); + auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); }; connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this, check_allow_update, actionRemoveItemMetadata] { + [this, check_allow_update, actionRemoveItemMetadata, changeVersion] { ui->actionUpdateItem->setEnabled(check_allow_update()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); @@ -134,11 +141,12 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr if (selected <= 1) { ui->actionVisitItemPage->setText(tr("Visit mod's page")); ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); - } else { ui->actionVisitItemPage->setText(tr("Visit mods' pages")); ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods")); } + + changeVersion->setEnabled(mods_list.length() == 1 && mods_list[0]->metadata() != nullptr); ui->actionVisitItemPage->setEnabled(selected != 0); actionRemoveItemMetadata->setEnabled(selected != 0); }); @@ -372,3 +380,54 @@ void ModFolderPage::deleteModMetadata() m_model->deleteModsMetadata(selection); } + +void ModFolderPage::changeModVersion() +{ + if (m_instance->typeName() != "Minecraft") + return; // this is a null instance or a legacy instance + + auto profile = static_cast(m_instance)->getPackProfile(); + if (!profile->getModLoaders().has_value()) { + QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); + return; + } + if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { + QMessageBox::critical(this, tr("Error"), tr("Mod updates are unavailable when metadata is disabled!")); + return; + } + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + auto mods_list = m_model->selectedMods(selection); + if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) + return; + + ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance); + mdownload.setModMetadata((*mods_list.begin())->metadata()); + if (mdownload.exec()) { + auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::aborted, [this, tasks]() { + CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) + CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + + tasks->deleteLater(); + }); + + for (auto& task : mdownload.getTasks()) { + tasks->addTask(task); + } + + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + + m_model->update(); + } +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 4672350c6..9aa2fdbb1 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -66,6 +66,7 @@ class ModFolderPage : public ExternalResourcesPage { void installMods(); void updateMods(bool includeDeps = false); void visitModPages(); + void changeModVersion(); protected: std::shared_ptr m_model; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index c628f74ac..e7012c095 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -79,4 +79,14 @@ bool ModModel::isPackInstalled(ModPlatform::IndexedPack::Ptr pack) const }); } +QVariant ModModel:: getInstalledPackVersion(ModPlatform::IndexedPack::Ptr pack) const +{ + auto allMods = static_cast(m_base_instance).loaderModList()->allMods(); + for (auto mod : allMods) { + if (auto meta = mod->metadata(); meta && meta->provider == pack->provider && meta->project_id == pack->addonId) { + return meta->version(); + } + } + return {}; +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index dd187aa8d..36540bb03 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -35,6 +35,7 @@ class ModModel : public ResourceModel { virtual ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) = 0; void setFilter(std::shared_ptr filter) { m_filter = filter; } + virtual QVariant getInstalledPackVersion(ModPlatform::IndexedPack::Ptr) const override; public slots: ResourceAPI::SearchArgs createSearchArguments() override; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index d6cc1fdcc..0ac08d4dd 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -120,6 +120,8 @@ void ModPage::updateVersionList() auto current_pack = getCurrentPack(); if (!current_pack) return; + auto installedVersion = m_model->getInstalledPackVersion(current_pack); + auto installedIndex = -1; for (int i = 0; i < current_pack->versions.size(); i++) { auto version = current_pack->versions[i]; bool valid = false; @@ -129,13 +131,19 @@ void ModPage::updateVersionList() break; } } - + QString flag; + if (installedIndex == -1 && installedVersion.isValid() && installedVersion == version.fileId) { + flag = tr("[installed]"); + installedIndex = i; + } // Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out if ((valid || m_filter->versions.empty()) && !optedOut(version)) { auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : ""; - m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i)); + m_ui->versionSelectionBox->addItem(QString("%1%2%3").arg(version.version, release_type, flag), QVariant(i)); } } + if (installedIndex != -1) + m_ui->versionSelectionBox->setCurrentIndex(installedIndex); if (m_ui->versionSelectionBox->count() == 0) { m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 12db49080..97f9e36b4 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -55,6 +55,8 @@ class ResourceModel : public QAbstractListModel { [[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); } + virtual QVariant getInstalledPackVersion(ModPlatform::IndexedPack::Ptr) const { return {}; } + public slots: void fetchMore(const QModelIndex& parent) override; // NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12 diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 44a91003d..00bd7548a 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -260,7 +260,9 @@ void ResourcePage::updateVersionList() m_ui->versionSelectionBox->clear(); m_ui->versionSelectionBox->blockSignals(false); - if (current_pack) + if (current_pack) { + auto installedVersion = m_model->getInstalledPackVersion(current_pack); + for (int i = 0; i < current_pack->versions.size(); i++) { auto& version = current_pack->versions[i]; if (optedOut(version)) @@ -269,9 +271,10 @@ void ResourcePage::updateVersionList() auto release_type = current_pack->versions[i].version_type.isValid() ? QString(" [%1]").arg(current_pack->versions[i].version_type.toString()) : ""; - m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i)); - } + m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i)); + } + } if (m_ui->versionSelectionBox->count() == 0) { m_ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); m_ui->resourceSelectionButton->setText(tr("Cannot select invalid version :(")); @@ -388,7 +391,7 @@ void ResourcePage::openUrl(const QUrl& url) } } - if (!page.isNull()) { + if (!page.isNull() && !m_do_not_jump_to_mod) { const QString slug = match.captured(1); // ensure the user isn't opening the same mod @@ -432,4 +435,53 @@ void ResourcePage::openUrl(const QUrl& url) QDesktopServices::openUrl(url); } +void ResourcePage::openProject(QVariant projectID) +{ + m_ui->sortByBox->hide(); + m_ui->searchButton->hide(); + m_ui->searchEdit->hide(); + m_ui->resourceFilterButton->hide(); + m_ui->packView->hide(); + m_ui->resourceSelectionButton->hide(); + m_do_not_jump_to_mod = true; + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + + auto okBtn = buttonBox->button(QDialogButtonBox::Ok); + okBtn->setDefault(true); + okBtn->setAutoDefault(true); + okBtn->setText(tr("Reinstall")); + okBtn->setShortcut(tr("Ctrl+Return")); + okBtn->setEnabled(false); + + auto cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); + cancelBtn->setDefault(false); + cancelBtn->setAutoDefault(false); + + connect(okBtn, &QPushButton::clicked, this, [this] { + onResourceSelected(); + m_parent_dialog->accept(); + }); + + connect(cancelBtn, &QPushButton::clicked, m_parent_dialog, &ResourceDownloadDialog::reject); + m_ui->gridLayout_4->addWidget(buttonBox, 1, 2); + + auto jump = [this, okBtn] { + for (int row = 0; row < m_model->rowCount({}); row++) { + const QModelIndex index = m_model->index(row); + m_ui->packView->setCurrentIndex(index); + okBtn->setEnabled(true); + return; + } + m_ui->packDescription->setText(tr("The resource was not found")); + }; + + m_ui->searchEdit->setText("#" + projectID.toString()); + triggerSearch(); + + if (m_model->hasActiveSearchJob()) + connect(m_model->activeSearchJob().get(), &Task::finished, jump); + else + jump(); +} } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 235b44412..212c83ea5 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -83,6 +83,8 @@ class ResourcePage : public QWidget, public BasePage { QList selectedPacks() { return m_model->selectedPacks(); } bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } + virtual void openProject(QVariant projectID); + protected slots: virtual void triggerSearch() {} @@ -118,6 +120,8 @@ class ResourcePage : public QWidget, public BasePage { // Used to do instant searching with a delay to cache quick changes QTimer m_search_timer; + + bool m_do_not_jump_to_mod = false; }; } // namespace ResourceDownload diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 05be1c3a5..ab4444c99 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -36,6 +36,7 @@ #pragma once +#include #include #include @@ -86,6 +87,8 @@ class PageContainer : public QWidget, public BasePageContainer { void changeEvent(QEvent*) override; + void hidePageList() { m_pageList->hide(); } + private: void createUI(); void retranslate();