From 756d933d1a5ed621a4681bbd3711d70768d84ad9 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 12 Dec 2022 23:51:22 -0300 Subject: fix: bind image fetch callback lambda to the parent object Fixes a possible crash with the callback being called after the image object was already deleted. Signed-off-by: flow --- launcher/ui/widgets/VariableSizedImageObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'launcher/ui/widgets') diff --git a/launcher/ui/widgets/VariableSizedImageObject.cpp b/launcher/ui/widgets/VariableSizedImageObject.cpp index e57f7e95..991b4a04 100644 --- a/launcher/ui/widgets/VariableSizedImageObject.cpp +++ b/launcher/ui/widgets/VariableSizedImageObject.cpp @@ -101,7 +101,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source, auto full_entry_path = entry->getFullPath(); auto source_url = source; - connect(job, &NetJob::succeeded, [this, doc, full_entry_path, source_url, posInDocument] { + connect(job, &NetJob::succeeded, this, [this, doc, full_entry_path, source_url, posInDocument] { qDebug() << "Loaded resource at" << full_entry_path; // If we flushed, don't proceed. -- cgit From c8d8dda79a8ac184c98922372ef7c3531f897486 Mon Sep 17 00:00:00 2001 From: leo78913 Date: Thu, 15 Dec 2022 16:34:52 -0300 Subject: fix: only show scrollbars when needed Signed-off-by: leo78913 --- launcher/ui/dialogs/IconPickerDialog.cpp | 1 - launcher/ui/dialogs/ImportResourcePackDialog.cpp | 1 - launcher/ui/pages/instance/NotesPage.ui | 3 --- launcher/ui/pages/instance/OtherLogsPage.ui | 3 --- launcher/ui/pages/instance/VersionPage.ui | 3 --- launcher/ui/widgets/ModListView.cpp | 1 - 6 files changed, 12 deletions(-) (limited to 'launcher/ui/widgets') diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index fcb645db..6fa26508 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -47,7 +47,6 @@ IconPickerDialog::IconPickerDialog(QWidget *parent) contentsWidget->setUniformItemSizes(true); contentsWidget->setTextElideMode(Qt::ElideRight); contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); contentsWidget->setItemDelegate(new ListViewDelegate()); diff --git a/launcher/ui/dialogs/ImportResourcePackDialog.cpp b/launcher/ui/dialogs/ImportResourcePackDialog.cpp index e807e926..e8902656 100644 --- a/launcher/ui/dialogs/ImportResourcePackDialog.cpp +++ b/launcher/ui/dialogs/ImportResourcePackDialog.cpp @@ -29,7 +29,6 @@ ImportResourcePackDialog::ImportResourcePackDialog(QWidget* parent) : QDialog(pa // NOTE: We can't have uniform sizes because the text may wrap if it's too long. If we set this, it will cut off the wrapped text. contentsWidget->setUniformItemSizes(false); contentsWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - contentsWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); contentsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); contentsWidget->setItemDelegate(new ListViewDelegate()); diff --git a/launcher/ui/pages/instance/NotesPage.ui b/launcher/ui/pages/instance/NotesPage.ui index 67cb261c..5f1984ad 100644 --- a/launcher/ui/pages/instance/NotesPage.ui +++ b/launcher/ui/pages/instance/NotesPage.ui @@ -25,9 +25,6 @@ - - Qt::ScrollBarAlwaysOn - true diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui index 77f3e647..3fdb023f 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.ui +++ b/launcher/ui/pages/instance/OtherLogsPage.ui @@ -48,9 +48,6 @@ false - - Qt::ScrollBarAlwaysOn - true diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index 74b9568a..4cd50885 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -28,9 +28,6 @@ - - Qt::ScrollBarAlwaysOn - Qt::ScrollBarAlwaysOff diff --git a/launcher/ui/widgets/ModListView.cpp b/launcher/ui/widgets/ModListView.cpp index c8ccd292..d1860f57 100644 --- a/launcher/ui/widgets/ModListView.cpp +++ b/launcher/ui/widgets/ModListView.cpp @@ -31,7 +31,6 @@ ModListView::ModListView ( QWidget* parent ) setSelectionMode ( QAbstractItemView::ExtendedSelection ); setHeaderHidden ( false ); setSelectionBehavior(QAbstractItemView::SelectRows); - setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOn ); setHorizontalScrollBarPolicy ( Qt::ScrollBarAsNeeded ); setDropIndicatorShown(true); setDragEnabled(true); -- cgit From 141e94369ed88e7099b46b45a0ed3683cada6329 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 29 Dec 2022 13:04:38 -0300 Subject: feat(Mods): hide 'Provider' column when no mods have providers This makes the mod list look a bit less polluted in the common case of mods having no provider whatsoever. Signed-off-by: flow --- launcher/ui/widgets/ModListView.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'launcher/ui/widgets') diff --git a/launcher/ui/widgets/ModListView.cpp b/launcher/ui/widgets/ModListView.cpp index c8ccd292..40d23f45 100644 --- a/launcher/ui/widgets/ModListView.cpp +++ b/launcher/ui/widgets/ModListView.cpp @@ -14,6 +14,9 @@ */ #include "ModListView.h" + +#include "minecraft/mod/ModFolderModel.h" + #include #include #include @@ -63,4 +66,17 @@ void ModListView::setModel ( QAbstractItemModel* model ) for(int i = 1; i < head->count(); i++) head->setSectionResizeMode(i, QHeaderView::ResizeToContents); } + + auto real_model = model; + if (auto proxy_model = dynamic_cast(model); proxy_model) + real_model = proxy_model->sourceModel(); + + if (auto mod_model = dynamic_cast(real_model); mod_model) { + connect(mod_model, &ModFolderModel::updateFinished, this, [this, mod_model]{ + auto mods = mod_model->allMods(); + // Hide the 'Provider' column if no mod has a defined provider! + setColumnHidden(ModFolderModel::Columns::ProviderColumn, + std::none_of(mods.constBegin(), mods.constEnd(), [](auto const mod){ return mod->provider().has_value(); })); + }); + } } -- cgit From 1b80ae0fca5e41d8caaa7d77d19faa9826752143 Mon Sep 17 00:00:00 2001 From: Tayou Date: Sat, 22 Oct 2022 19:43:04 +0200 Subject: add theme setup wizard Signed-off-by: Tayou --- launcher/Application.cpp | 21 +- launcher/Application.h | 4 +- launcher/CMakeLists.txt | 6 + launcher/ui/MainWindow.cpp | 2 +- launcher/ui/pages/global/LauncherPage.cpp | 110 -------- launcher/ui/pages/global/LauncherPage.ui | 166 +---------- launcher/ui/setupwizard/ThemeWizardPage.cpp | 70 +++++ launcher/ui/setupwizard/ThemeWizardPage.h | 44 +++ launcher/ui/setupwizard/ThemeWizardPage.ui | 336 +++++++++++++++++++++++ launcher/ui/themes/ITheme.cpp | 40 ++- launcher/ui/themes/ITheme.h | 36 ++- launcher/ui/themes/SystemTheme.cpp | 9 +- launcher/ui/themes/SystemTheme.h | 36 ++- launcher/ui/themes/ThemeManager.cpp | 6 +- launcher/ui/themes/ThemeManager.h | 3 +- launcher/ui/widgets/ThemeCustomizationWidget.cpp | 135 +++++++++ launcher/ui/widgets/ThemeCustomizationWidget.h | 64 +++++ launcher/ui/widgets/ThemeCustomizationWidget.ui | 182 ++++++++++++ 18 files changed, 982 insertions(+), 288 deletions(-) create mode 100644 launcher/ui/setupwizard/ThemeWizardPage.cpp create mode 100644 launcher/ui/setupwizard/ThemeWizardPage.h create mode 100644 launcher/ui/setupwizard/ThemeWizardPage.ui create mode 100644 launcher/ui/widgets/ThemeCustomizationWidget.cpp create mode 100644 launcher/ui/widgets/ThemeCustomizationWidget.h create mode 100644 launcher/ui/widgets/ThemeCustomizationWidget.ui (limited to 'launcher/ui/widgets') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 9d528d7a..3e64b74f 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -66,6 +66,7 @@ #include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h" #include "ui/setupwizard/PasteWizardPage.h" +#include "ui/setupwizard/ThemeWizardPage.h" #include "ui/dialogs/CustomMessageBox.h" @@ -846,10 +847,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) }); { - setIconTheme(settings()->get("IconTheme").toString()); - qDebug() << "<> Icon theme set."; - setApplicationTheme(settings()->get("ApplicationTheme").toString(), true); - qDebug() << "<> Application theme set."; + applyCurrentlySelectedTheme(); } updateCapabilities(); @@ -892,6 +890,7 @@ bool Application::createSetupWizard() return false; }(); bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; + bool themeInterventionRequired = settings()->get("ApplicationTheme") != ""; bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired; if(wizardRequired) @@ -911,6 +910,11 @@ bool Application::createSetupWizard() { m_setupWizard->addPage(new PasteWizardPage(m_setupWizard)); } + + if (themeInterventionRequired) + { + m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard)); + } connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); m_setupWizard->show(); return true; @@ -1118,9 +1122,14 @@ QList Application::getValidApplicationThemes() return m_themeManager->getValidApplicationThemes(); } -void Application::setApplicationTheme(const QString& name, bool initial) +void Application::applyCurrentlySelectedTheme() +{ + m_themeManager->applyCurrentlySelectedTheme(); +} + +void Application::setApplicationTheme(const QString& name) { - m_themeManager->setApplicationTheme(name, initial); + m_themeManager->setApplicationTheme(name); } void Application::setIconTheme(const QString& name) diff --git a/launcher/Application.h b/launcher/Application.h index 7884227a..a7938629 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -120,9 +120,11 @@ public: void setIconTheme(const QString& name); + void applyCurrentlySelectedTheme(); + QList getValidApplicationThemes(); - void setApplicationTheme(const QString& name, bool initial); + void setApplicationTheme(const QString& name); shared_qobject_ptr updateChecker() { return m_updateChecker; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 57480671..74b7b212 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -683,6 +683,8 @@ SET(LAUNCHER_SOURCES ui/setupwizard/LanguageWizardPage.h ui/setupwizard/PasteWizardPage.cpp ui/setupwizard/PasteWizardPage.h + ui/setupwizard/ThemeWizardPage.cpp + ui/setupwizard/ThemeWizardPage.h # GUI - themes ui/themes/FusionTheme.cpp @@ -922,6 +924,8 @@ SET(LAUNCHER_SOURCES ui/widgets/ProgressWidget.cpp ui/widgets/WideBar.h ui/widgets/WideBar.cpp + ui/widgets/ThemeCustomizationWidget.h + ui/widgets/ThemeCustomizationWidget.cpp # GUI - instance group view ui/instanceview/InstanceProxyModel.cpp @@ -939,6 +943,7 @@ SET(LAUNCHER_SOURCES qt_wrap_ui(LAUNCHER_UI ui/setupwizard/PasteWizardPage.ui + ui/setupwizard/ThemeWizardPage.ui ui/pages/global/AccountListPage.ui ui/pages/global/JavaPage.ui ui/pages/global/LauncherPage.ui @@ -971,6 +976,7 @@ qt_wrap_ui(LAUNCHER_UI ui/widgets/CustomCommands.ui ui/widgets/InfoFrame.ui ui/widgets/ModFilterWidget.ui + ui/widgets/ThemeCustomizationWidget.ui ui/dialogs/CopyInstanceDialog.ui ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProgressDialog.ui diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index e913849d..331ca0e1 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1346,7 +1346,7 @@ void MainWindow::updateThemeMenu() themeAction->setActionGroup(themesGroup); connect(themeAction, &QAction::triggered, [theme]() { - APPLICATION->setApplicationTheme(theme->id(),false); + APPLICATION->setApplicationTheme(theme->id()); APPLICATION->settings()->set("ApplicationTheme", theme->id()); }); } diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index bd7cec6a..69a8e3df 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -286,75 +286,6 @@ void LauncherPage::applySettings() } s->set("UpdateChannel", m_currentUpdateChannel); - auto original = s->get("IconTheme").toString(); - //FIXME: make generic - switch (ui->themeComboBox->currentIndex()) - { - case 0: - s->set("IconTheme", "pe_colored"); - break; - case 1: - s->set("IconTheme", "pe_light"); - break; - case 2: - s->set("IconTheme", "pe_dark"); - break; - case 3: - s->set("IconTheme", "pe_blue"); - break; - case 4: - s->set("IconTheme", "breeze_light"); - break; - case 5: - s->set("IconTheme", "breeze_dark"); - break; - case 6: - s->set("IconTheme", "OSX"); - break; - case 7: - s->set("IconTheme", "iOS"); - break; - case 8: - s->set("IconTheme", "flat"); - break; - case 9: - s->set("IconTheme", "flat_white"); - break; - case 10: - s->set("IconTheme", "multimc"); - break; - case 11: - s->set("IconTheme", "custom"); - break; - } - - if(original != s->get("IconTheme")) - { - APPLICATION->setIconTheme(s->get("IconTheme").toString()); - } - - auto originalAppTheme = s->get("ApplicationTheme").toString(); - auto newAppTheme = ui->themeComboBoxColors->currentData().toString(); - if(originalAppTheme != newAppTheme) - { - s->set("ApplicationTheme", newAppTheme); - APPLICATION->setApplicationTheme(newAppTheme, false); - } - - switch (ui->themeBackgroundCat->currentIndex()) { - case 0: // original cat - s->set("BackgroundCat", "kitteh"); - break; - case 1: // rory the cat - s->set("BackgroundCat", "rory"); - break; - case 2: // rory the cat flat edition - s->set("BackgroundCat", "rory-flat"); - break; - case 3: // teawie - s->set("BackgroundCat", "teawie"); - break; - } s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked()); @@ -404,47 +335,6 @@ void LauncherPage::loadSettings() } m_currentUpdateChannel = s->get("UpdateChannel").toString(); - //FIXME: make generic - auto theme = s->get("IconTheme").toString(); - QStringList iconThemeOptions{"pe_colored", - "pe_light", - "pe_dark", - "pe_blue", - "breeze_light", - "breeze_dark", - "OSX", - "iOS", - "flat", - "flat_white", - "multimc", - "custom"}; - ui->themeComboBox->setCurrentIndex(iconThemeOptions.indexOf(theme)); - - auto cat = s->get("BackgroundCat").toString(); - if (cat == "kitteh") { - ui->themeBackgroundCat->setCurrentIndex(0); - } else if (cat == "rory") { - ui->themeBackgroundCat->setCurrentIndex(1); - } else if (cat == "rory-flat") { - ui->themeBackgroundCat->setCurrentIndex(2); - } else if (cat == "teawie") { - ui->themeBackgroundCat->setCurrentIndex(3); - } - - { - auto currentTheme = s->get("ApplicationTheme").toString(); - auto themes = APPLICATION->getValidApplicationThemes(); - int idx = 0; - for(auto &theme: themes) - { - ui->themeComboBoxColors->addItem(theme->name(), theme->id()); - if(currentTheme == theme->id()) - { - ui->themeComboBoxColors->setCurrentIndex(idx); - } - idx++; - } - } // Toolbar/menu bar settings (not applicable if native menu bar is present) ui->toolsBox->setEnabled(!QMenuBar().isNativeMenuBar()); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index ded333aa..65f4a9d5 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -6,7 +6,7 @@ 0 0 - 514 + 511 629 @@ -38,7 +38,7 @@ QTabWidget::Rounded - 0 + 1 @@ -243,155 +243,9 @@ Theme - - - - - &Icons - - - themeComboBox - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - - Simple (Colored Icons) - - - - - Simple (Light Icons) - - - - - Simple (Dark Icons) - - - - - Simple (Blue Icons) - - - - - Breeze Light - - - - - Breeze Dark - - - - - OSX - - - - - iOS - - - - - Flat - - - - - Flat (White) - - - - - Legacy - - - - - Custom - - - - - - - - &Colors - - - themeComboBoxColors - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - - - - - C&at - - - themeBackgroundCat - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - - Background Cat (from MultiMC) - - - - - Rory ID 11 (drawn by Ashtaka) - - - - - Rory ID 11 (flat edition, drawn by Ashtaka) - - - - - Teawie (drawn by SympathyTea) - - - + + + @@ -575,6 +429,14 @@ + + + ThemeCustomizationWidget + QWidget +
ui/widgets/ThemeCustomizationWidget.h
+ 1 +
+
tabWidget autoUpdateCheckBox @@ -587,8 +449,6 @@ iconsDirBrowseBtn sortLastLaunchedBtn sortByNameBtn - themeComboBox - themeComboBoxColors showConsoleCheck autoCloseConsoleCheck showConsoleErrorCheck diff --git a/launcher/ui/setupwizard/ThemeWizardPage.cpp b/launcher/ui/setupwizard/ThemeWizardPage.cpp new file mode 100644 index 00000000..6f041134 --- /dev/null +++ b/launcher/ui/setupwizard/ThemeWizardPage.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "ThemeWizardPage.h" +#include "ui_ThemeWizardPage.h" + +#include "Application.h" +#include "ui/themes/ITheme.h" +#include "ui/widgets/ThemeCustomizationWidget.h" +#include "ui_ThemeCustomizationWidget.h" + +ThemeWizardPage::ThemeWizardPage(QWidget *parent) : +BaseWizardPage(parent), +ui(new Ui::ThemeWizardPage) { + ui->setupUi(this); + + ui->themeCustomizationWidget->showFeatures((ThemeFields)(ThemeFields::ICONS | ThemeFields::WIDGETS)); + connect(ui->themeCustomizationWidget, QOverload::of(&ThemeCustomizationWidget::currentIconThemeChanged), this, &ThemeWizardPage::updateIcons); + + updateIcons(); +} + +ThemeWizardPage::~ThemeWizardPage() { +delete ui; +} + +void ThemeWizardPage::initializePage() +{ +} + +bool ThemeWizardPage::validatePage() +{ + return true; +} + +void ThemeWizardPage::updateIcons() { + qDebug() << "Setting Icons"; + ui->previewIconButton0->setIcon(APPLICATION->getThemedIcon("new")); + ui->previewIconButton1->setIcon(APPLICATION->getThemedIcon("centralmods")); + ui->previewIconButton2->setIcon(APPLICATION->getThemedIcon("viewfolder")); + ui->previewIconButton3->setIcon(APPLICATION->getThemedIcon("launch")); + ui->previewIconButton4->setIcon(APPLICATION->getThemedIcon("copy")); + ui->previewIconButton5->setIcon(APPLICATION->getThemedIcon("export")); + ui->previewIconButton6->setIcon(APPLICATION->getThemedIcon("delete")); + ui->previewIconButton7->setIcon(APPLICATION->getThemedIcon("about")); + ui->previewIconButton8->setIcon(APPLICATION->getThemedIcon("settings")); + ui->previewIconButton9->setIcon(APPLICATION->getThemedIcon("cat")); + update(); + repaint(); + parentWidget()->update(); +} + +void ThemeWizardPage::retranslate() +{ + ui->retranslateUi(this); +} diff --git a/launcher/ui/setupwizard/ThemeWizardPage.h b/launcher/ui/setupwizard/ThemeWizardPage.h new file mode 100644 index 00000000..10913d1b --- /dev/null +++ b/launcher/ui/setupwizard/ThemeWizardPage.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "BaseWizardPage.h" + +namespace Ui { +class ThemeWizardPage; +} + +class ThemeWizardPage : public BaseWizardPage +{ + Q_OBJECT + +public: + explicit ThemeWizardPage(QWidget *parent = nullptr); + ~ThemeWizardPage(); + + void initializePage() override; + bool validatePage() override; + void retranslate() override; + +private slots: + void updateIcons(); + +private: + Ui::ThemeWizardPage *ui; +}; diff --git a/launcher/ui/setupwizard/ThemeWizardPage.ui b/launcher/ui/setupwizard/ThemeWizardPage.ui new file mode 100644 index 00000000..b743644f --- /dev/null +++ b/launcher/ui/setupwizard/ThemeWizardPage.ui @@ -0,0 +1,336 @@ + + + ThemeWizardPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + + + + Select the Theme you wish to use + + + + + + + + 0 + 100 + + + + + + + + Qt::Horizontal + + + + + + + Icon Preview: + + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 193 + + + + + + + + + ThemeCustomizationWidget + QWidget +
ui/widgets/ThemeCustomizationWidget.h
+
+
+ + +
diff --git a/launcher/ui/themes/ITheme.cpp b/launcher/ui/themes/ITheme.cpp index 8bfc466d..22043e44 100644 --- a/launcher/ui/themes/ITheme.cpp +++ b/launcher/ui/themes/ITheme.cpp @@ -1,19 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "ITheme.h" #include "rainbow.h" #include #include #include "Application.h" -void ITheme::apply(bool) +void ITheme::apply() { APPLICATION->setStyleSheet(QString()); QApplication::setStyle(QStyleFactory::create(qtTheme())); if (hasColorScheme()) { QApplication::setPalette(colorScheme()); } - if (hasStyleSheet()) - APPLICATION->setStyleSheet(appStyleSheet()); - + APPLICATION->setStyleSheet(appStyleSheet()); QDir::setSearchPaths("theme", searchPaths()); } diff --git a/launcher/ui/themes/ITheme.h b/launcher/ui/themes/ITheme.h index c2347cf6..bb5c8afe 100644 --- a/launcher/ui/themes/ITheme.h +++ b/launcher/ui/themes/ITheme.h @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #pragma once #include #include @@ -8,7 +42,7 @@ class ITheme { public: virtual ~ITheme() {} - virtual void apply(bool initial); + virtual void apply(); virtual QString id() = 0; virtual QString name() = 0; virtual bool hasStyleSheet() = 0; diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index a63d1741..d6ef442b 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -62,14 +62,9 @@ SystemTheme::SystemTheme() themeDebugLog() << "System theme not found, defaulted to Fusion"; } -void SystemTheme::apply(bool initial) +void SystemTheme::apply() { - // if we are applying the system theme as the first theme, just don't touch anything. it's for the better... - if(initial) - { - return; - } - ITheme::apply(initial); + ITheme::apply(); } QString SystemTheme::id() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index fe450600..5c9216eb 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -1,3 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #pragma once #include "ITheme.h" @@ -7,7 +41,7 @@ class SystemTheme: public ITheme public: SystemTheme(); virtual ~SystemTheme() {} - void apply(bool initial) override; + void apply() override; QString id() override; QString name() override; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 5a612472..a6cebc6f 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -120,18 +120,18 @@ void ThemeManager::applyCurrentlySelectedTheme() { setIconTheme(APPLICATION->settings()->get("IconTheme").toString()); themeDebugLog() << "<> Icon theme set."; - setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString(), true); + setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString()); themeDebugLog() << "<> Application theme set."; } -void ThemeManager::setApplicationTheme(const QString& name, bool initial) +void ThemeManager::setApplicationTheme(const QString& name) { auto systemPalette = qApp->palette(); auto themeIter = m_themes.find(name); if (themeIter != m_themes.end()) { auto& theme = themeIter->second; themeDebugLog() << "applying theme" << theme->name(); - theme->apply(initial); + theme->apply(); } else { themeWarningLog() << "Tried to set invalid theme:" << name; } diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index b85cb742..0a70ddfc 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -41,11 +41,12 @@ class ThemeManager { QList getValidApplicationThemes(); void setIconTheme(const QString& name); void applyCurrentlySelectedTheme(); - void setApplicationTheme(const QString& name, bool initial); + void setApplicationTheme(const QString& name); private: std::map> m_themes; MainWindow* m_mainWindow; + bool m_firstThemeInitialized; QString AddTheme(std::unique_ptr theme); ITheme* GetTheme(QString themeId); diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp new file mode 100644 index 00000000..0830a030 --- /dev/null +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "ThemeCustomizationWidget.h" +#include "ui_ThemeCustomizationWidget.h" + +#include "Application.h" +#include "ui/themes/ITheme.h" + +ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ThemeCustomizationWidget) +{ + ui->setupUi(this); + loadSettings(); + + connect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); + connect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyWidgetTheme); + connect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme); +} + +ThemeCustomizationWidget::~ThemeCustomizationWidget() +{ + delete ui; +} + +void ThemeCustomizationWidget::showFeatures(ThemeFields features) { + ui->iconsComboBox->setVisible(features & ThemeFields::ICONS); + ui->iconsLabel->setVisible(features & ThemeFields::ICONS); + ui->widgetStyleComboBox->setVisible(features & ThemeFields::WIDGETS); + ui->widgetThemeLabel->setVisible(features & ThemeFields::WIDGETS); + ui->backgroundCatComboBox->setVisible(features & ThemeFields::CAT); + ui->backgroundCatLabel->setVisible(features & ThemeFields::CAT); +} + +void ThemeCustomizationWidget::applyIconTheme(int index) { + emit currentIconThemeChanged(index); + + auto settings = APPLICATION->settings(); + auto original = settings->get("IconTheme").toString(); + // FIXME: make generic + settings->set("IconTheme", m_iconThemeOptions[index]); + + if (original != settings->get("IconTheme")) { + APPLICATION->applyCurrentlySelectedTheme(); + } +} + +void ThemeCustomizationWidget::applyWidgetTheme(int index) { + emit currentWidgetThemeChanged(index); + + auto settings = APPLICATION->settings(); + auto originalAppTheme = settings->get("ApplicationTheme").toString(); + auto newAppTheme = ui->widgetStyleComboBox->currentData().toString(); + if (originalAppTheme != newAppTheme) { + settings->set("ApplicationTheme", newAppTheme); + APPLICATION->applyCurrentlySelectedTheme(); + } +} + +void ThemeCustomizationWidget::applyCatTheme(int index) { + emit currentCatChanged(index); + + auto settings = APPLICATION->settings(); + switch (index) { + case 0: // original cat + settings->set("BackgroundCat", "kitteh"); + break; + case 1: // rory the cat + settings->set("BackgroundCat", "rory"); + break; + case 2: // rory the cat flat edition + settings->set("BackgroundCat", "rory-flat"); + break; + case 3: // teawie + settings->set("BackgroundCat", "teawie"); + break; + } +} + +void ThemeCustomizationWidget::applySettings() +{ + applyIconTheme(ui->iconsComboBox->currentIndex()); + applyWidgetTheme(ui->widgetStyleComboBox->currentIndex()); + applyCatTheme(ui->backgroundCatComboBox->currentIndex()); +} +void ThemeCustomizationWidget::loadSettings() +{ + auto settings = APPLICATION->settings(); + + // FIXME: make generic + auto theme = settings->get("IconTheme").toString(); + ui->iconsComboBox->setCurrentIndex(m_iconThemeOptions.indexOf(theme)); + + { + auto currentTheme = settings->get("ApplicationTheme").toString(); + auto themes = APPLICATION->getValidApplicationThemes(); + int idx = 0; + for (auto& theme : themes) { + ui->widgetStyleComboBox->addItem(theme->name(), theme->id()); + if (currentTheme == theme->id()) { + ui->widgetStyleComboBox->setCurrentIndex(idx); + } + idx++; + } + } + + auto cat = settings->get("BackgroundCat").toString(); + if (cat == "kitteh") { + ui->backgroundCatComboBox->setCurrentIndex(0); + } else if (cat == "rory") { + ui->backgroundCatComboBox->setCurrentIndex(1); + } else if (cat == "rory-flat") { + ui->backgroundCatComboBox->setCurrentIndex(2); + } else if (cat == "teawie") { + ui->backgroundCatComboBox->setCurrentIndex(3); + } +} + +void ThemeCustomizationWidget::retranslate() +{ + ui->retranslateUi(this); +} \ No newline at end of file diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h new file mode 100644 index 00000000..e17286e1 --- /dev/null +++ b/launcher/ui/widgets/ThemeCustomizationWidget.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include + +enum ThemeFields { + NONE = 0b0000, + ICONS = 0b0001, + WIDGETS = 0b0010, + CAT = 0b0100 +}; + +namespace Ui { +class ThemeCustomizationWidget; +} + +class ThemeCustomizationWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ThemeCustomizationWidget(QWidget *parent = nullptr); + ~ThemeCustomizationWidget(); + + void showFeatures(ThemeFields features); + + void applySettings(); + + void loadSettings(); + void retranslate(); + + Ui::ThemeCustomizationWidget *ui; + +private slots: + void applyIconTheme(int index); + void applyWidgetTheme(int index); + void applyCatTheme(int index); + +signals: + int currentIconThemeChanged(int index); + int currentWidgetThemeChanged(int index); + int currentCatChanged(int index); + +private: + + QStringList m_iconThemeOptions{ "pe_colored", "pe_light", "pe_dark", "pe_blue", "breeze_light", "breeze_dark", "OSX", "iOS", "flat", "flat_white", "multimc", "custom" }; +}; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui new file mode 100644 index 00000000..c184b8f3 --- /dev/null +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -0,0 +1,182 @@ + + + ThemeCustomizationWidget + + + + 0 + 0 + 400 + 311 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Icons + + + iconsComboBox + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + Simple (Colored Icons) + + + + + Simple (Light Icons) + + + + + Simple (Dark Icons) + + + + + Simple (Blue Icons) + + + + + Breeze Light + + + + + Breeze Dark + + + + + OSX + + + + + iOS + + + + + Flat + + + + + Flat (White) + + + + + Legacy + + + + + Custom + + + + + + + + &Colors + + + widgetStyleComboBox + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + C&at + + + backgroundCatComboBox + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + Background Cat (from MultiMC) + + + + + Rory ID 11 (drawn by Ashtaka) + + + + + Rory ID 11 (flat edition, drawn by Ashtaka) + + + + + Teawie (drawn by SympathyTea) + + + + + + + + + -- cgit From 49d317b19aa61fed056e0f14c12eb1997f68982d Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 9 Jan 2023 16:54:10 +0100 Subject: UX tweak + formatting + added cat to wizard Signed-off-by: Tayou --- launcher/ui/MainWindow.cpp | 16 +---- launcher/ui/setupwizard/ThemeWizardPage.cpp | 39 +++++++++--- launcher/ui/setupwizard/ThemeWizardPage.h | 1 + launcher/ui/setupwizard/ThemeWizardPage.ui | 26 +++++++- launcher/ui/themes/CustomTheme.cpp | 31 +++------- launcher/ui/themes/ITheme.h | 12 ++-- launcher/ui/themes/SystemTheme.cpp | 12 ++-- launcher/ui/themes/SystemTheme.h | 8 +-- launcher/ui/themes/ThemeManager.h | 4 +- launcher/ui/widgets/ThemeCustomizationWidget.cpp | 79 ++++++++++++------------ launcher/ui/widgets/ThemeCustomizationWidget.h | 1 + launcher/ui/widgets/ThemeCustomizationWidget.ui | 5 +- 12 files changed, 127 insertions(+), 107 deletions(-) (limited to 'launcher/ui/widgets') diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 331ca0e1..a921e378 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1652,16 +1652,6 @@ void MainWindow::onCatToggled(bool state) APPLICATION->settings()->set("TheCat", state); } -namespace { -template -T non_stupid_abs(T in) -{ - if (in < 0) - return -in; - return in; -} -} - void MainWindow::setCatBackground(bool enabled) { if (enabled) @@ -1671,11 +1661,11 @@ void MainWindow::setCatBackground(bool enabled) QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); QString cat = APPLICATION->settings()->get("BackgroundCat").toString(); - if (non_stupid_abs(now.daysTo(xmas)) <= 4) { + if (std::abs(now.daysTo(xmas)) <= 4) { cat += "-xmas"; - } else if (non_stupid_abs(now.daysTo(halloween)) <= 4) { + } else if (std::abs(now.daysTo(halloween)) <= 4) { cat += "-spooky"; - } else if (non_stupid_abs(now.daysTo(birthday)) <= 12) { + } else if (std::abs(now.daysTo(birthday)) <= 12) { cat += "-bday"; } view->setStyleSheet(QString(R"( diff --git a/launcher/ui/setupwizard/ThemeWizardPage.cpp b/launcher/ui/setupwizard/ThemeWizardPage.cpp index 6f041134..4e1eb488 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.cpp +++ b/launcher/ui/setupwizard/ThemeWizardPage.cpp @@ -23,31 +23,31 @@ #include "ui/widgets/ThemeCustomizationWidget.h" #include "ui_ThemeCustomizationWidget.h" -ThemeWizardPage::ThemeWizardPage(QWidget *parent) : -BaseWizardPage(parent), -ui(new Ui::ThemeWizardPage) { +ThemeWizardPage::ThemeWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(new Ui::ThemeWizardPage) +{ ui->setupUi(this); - ui->themeCustomizationWidget->showFeatures((ThemeFields)(ThemeFields::ICONS | ThemeFields::WIDGETS)); connect(ui->themeCustomizationWidget, QOverload::of(&ThemeCustomizationWidget::currentIconThemeChanged), this, &ThemeWizardPage::updateIcons); + connect(ui->themeCustomizationWidget, QOverload::of(&ThemeCustomizationWidget::currentCatChanged), this, &ThemeWizardPage::updateCat); updateIcons(); + updateCat(); } -ThemeWizardPage::~ThemeWizardPage() { -delete ui; -} - -void ThemeWizardPage::initializePage() +ThemeWizardPage::~ThemeWizardPage() { + delete ui; } +void ThemeWizardPage::initializePage() {} + bool ThemeWizardPage::validatePage() { return true; } -void ThemeWizardPage::updateIcons() { +void ThemeWizardPage::updateIcons() +{ qDebug() << "Setting Icons"; ui->previewIconButton0->setIcon(APPLICATION->getThemedIcon("new")); ui->previewIconButton1->setIcon(APPLICATION->getThemedIcon("centralmods")); @@ -64,6 +64,25 @@ void ThemeWizardPage::updateIcons() { parentWidget()->update(); } +void ThemeWizardPage::updateCat() +{ + qDebug() << "Setting Cat"; + + QDateTime now = QDateTime::currentDateTime(); + QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); + QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); + QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); + QString cat = APPLICATION->settings()->get("BackgroundCat").toString(); + if (std::abs(now.daysTo(xmas)) <= 4) { + cat += "-xmas"; + } else if (std::abs(now.daysTo(halloween)) <= 4) { + cat += "-spooky"; + } else if (std::abs(now.daysTo(birthday)) <= 12) { + cat += "-bday"; + } + ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(cat))); +} + void ThemeWizardPage::retranslate() { ui->retranslateUi(this); diff --git a/launcher/ui/setupwizard/ThemeWizardPage.h b/launcher/ui/setupwizard/ThemeWizardPage.h index 10913d1b..6562ad2e 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.h +++ b/launcher/ui/setupwizard/ThemeWizardPage.h @@ -38,6 +38,7 @@ public: private slots: void updateIcons(); + void updateCat(); private: Ui::ThemeWizardPage *ui; diff --git a/launcher/ui/setupwizard/ThemeWizardPage.ui b/launcher/ui/setupwizard/ThemeWizardPage.ui index b743644f..95b0f805 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.ui +++ b/launcher/ui/setupwizard/ThemeWizardPage.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 510 + 552 @@ -309,6 +309,28 @@
+ + + + + 0 + 256 + + + + + + + + 256 + 256 + + + + true + + + diff --git a/launcher/ui/themes/CustomTheme.cpp b/launcher/ui/themes/CustomTheme.cpp index 3ad61668..198e76ba 100644 --- a/launcher/ui/themes/CustomTheme.cpp +++ b/launcher/ui/themes/CustomTheme.cpp @@ -167,8 +167,6 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest if (!FS::ensureFolderPathExists(path) || !FS::ensureFolderPathExists(pathResources)) { themeWarningLog() << "couldn't create folder for theme!"; - m_palette = baseTheme->colorScheme(); - m_styleSheet = baseTheme->appStyleSheet(); return; } @@ -177,18 +175,15 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest bool jsonDataIncomplete = false; m_palette = baseTheme->colorScheme(); - if (!readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath, jsonDataIncomplete)) { - themeDebugLog() << "Did not read theme json file correctly, writing new one to: " << themeFilePath; - m_name = "Custom"; - m_palette = baseTheme->colorScheme(); - m_fadeColor = baseTheme->fadeColor(); - m_fadeAmount = baseTheme->fadeAmount(); - m_widgets = baseTheme->qtTheme(); - m_qssFilePath = "themeStyle.css"; - } else { + if (readThemeJson(themeFilePath, m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath, jsonDataIncomplete)) { + // If theme data was found, fade "Disabled" color of each role according to FadeAmount m_palette = fadeInactive(m_palette, m_fadeAmount, m_fadeColor); + } else { + themeDebugLog() << "Did not read theme json file correctly, not changing theme, keeping previous."; + return; } + // FIXME: This is kinda jank, it only actually checks if the qss file path is not present. It should actually check for any relevant missing data (e.g. name, colors) if (jsonDataIncomplete) { writeThemeJson(fileInfo.absoluteFilePath(), m_palette, m_fadeAmount, m_fadeColor, m_name, m_widgets, m_qssFilePath); } @@ -197,20 +192,14 @@ CustomTheme::CustomTheme(ITheme* baseTheme, QFileInfo& fileInfo, bool isManifest QFileInfo info(qssFilePath); if (info.isFile()) { try { - // TODO: validate css? + // TODO: validate qss? m_styleSheet = QString::fromUtf8(FS::read(qssFilePath)); } catch (const Exception& e) { - themeWarningLog() << "Couldn't load css:" << e.cause() << "from" << qssFilePath; - m_styleSheet = baseTheme->appStyleSheet(); + themeWarningLog() << "Couldn't load qss:" << e.cause() << "from" << qssFilePath; + return; } } else { - themeDebugLog() << "No theme css present."; - m_styleSheet = baseTheme->appStyleSheet(); - try { - FS::write(qssFilePath, m_styleSheet.toUtf8()); - } catch (const Exception& e) { - themeWarningLog() << "Couldn't write css:" << e.cause() << "to" << qssFilePath; - } + themeDebugLog() << "No theme qss present."; } } else { m_id = fileInfo.fileName(); diff --git a/launcher/ui/themes/ITheme.h b/launcher/ui/themes/ITheme.h index bb5c8afe..2e5b7f25 100644 --- a/launcher/ui/themes/ITheme.h +++ b/launcher/ui/themes/ITheme.h @@ -33,14 +33,13 @@ * limitations under the License. */ #pragma once -#include #include +#include class QStyle; -class ITheme -{ -public: +class ITheme { + public: virtual ~ITheme() {} virtual void apply(); virtual QString id() = 0; @@ -52,10 +51,7 @@ public: virtual QPalette colorScheme() = 0; virtual QColor fadeColor() = 0; virtual double fadeAmount() = 0; - virtual QStringList searchPaths() - { - return {}; - } + virtual QStringList searchPaths() { return {}; } static QPalette fadeInactive(QPalette in, qreal bias, QColor color); }; diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index d6ef442b..24875e33 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -34,24 +34,22 @@ */ #include "SystemTheme.h" #include +#include #include #include -#include #include "ThemeManager.h" SystemTheme::SystemTheme() { themeDebugLog() << "Determining System Theme..."; - const auto & style = QApplication::style(); + const auto& style = QApplication::style(); systemPalette = style->standardPalette(); QString lowerThemeName = style->objectName(); themeDebugLog() << "System theme seems to be:" << lowerThemeName; QStringList styles = QStyleFactory::keys(); - for(auto &st: styles) - { + for (auto& st : styles) { themeDebugLog() << "Considering theme from theme factory:" << st.toLower(); - if(st.toLower() == lowerThemeName) - { + if (st.toLower() == lowerThemeName) { systemTheme = st; themeDebugLog() << "System theme has been determined to be:" << systemTheme; return; @@ -99,7 +97,7 @@ double SystemTheme::fadeAmount() QColor SystemTheme::fadeColor() { - return QColor(128,128,128); + return QColor(128, 128, 128); } bool SystemTheme::hasStyleSheet() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 5c9216eb..b5c03def 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -36,9 +36,8 @@ #include "ITheme.h" -class SystemTheme: public ITheme -{ -public: +class SystemTheme : public ITheme { + public: SystemTheme(); virtual ~SystemTheme() {} void apply() override; @@ -52,7 +51,8 @@ public: QPalette colorScheme() override; double fadeAmount() override; QColor fadeColor() override; -private: + + private: QPalette systemPalette; QString systemTheme; }; diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index 0a70ddfc..bb10cd48 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -35,9 +35,6 @@ class ThemeManager { public: ThemeManager(MainWindow* mainWindow); - // maybe make private? Or put in ctor? - void InitializeThemes(); - QList getValidApplicationThemes(); void setIconTheme(const QString& name); void applyCurrentlySelectedTheme(); @@ -48,6 +45,7 @@ class ThemeManager { MainWindow* m_mainWindow; bool m_firstThemeInitialized; + void InitializeThemes(); QString AddTheme(std::unique_ptr theme); ITheme* GetTheme(QString themeId); }; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index 0830a030..eafcf482 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -36,18 +36,40 @@ ThemeCustomizationWidget::~ThemeCustomizationWidget() delete ui; } +/// +/// The layout was not quite right, so currently this just disables the UI elements, which should be hidden instead +/// TODO FIXME +/// +/// Original Method One: +/// ui->iconsComboBox->setVisible(features& ThemeFields::ICONS); +/// ui->iconsLabel->setVisible(features& ThemeFields::ICONS); +/// ui->widgetStyleComboBox->setVisible(features& ThemeFields::WIDGETS); +/// ui->widgetThemeLabel->setVisible(features& ThemeFields::WIDGETS); +/// ui->backgroundCatComboBox->setVisible(features& ThemeFields::CAT); +/// ui->backgroundCatLabel->setVisible(features& ThemeFields::CAT); +/// +/// original Method Two: +/// if (!(features & ThemeFields::ICONS)) { +/// ui->formLayout->setRowVisible(0, false); +/// } +/// if (!(features & ThemeFields::WIDGETS)) { +/// ui->formLayout->setRowVisible(1, false); +/// } +/// if (!(features & ThemeFields::CAT)) { +/// ui->formLayout->setRowVisible(2, false); +/// } +/// +/// void ThemeCustomizationWidget::showFeatures(ThemeFields features) { - ui->iconsComboBox->setVisible(features & ThemeFields::ICONS); - ui->iconsLabel->setVisible(features & ThemeFields::ICONS); - ui->widgetStyleComboBox->setVisible(features & ThemeFields::WIDGETS); - ui->widgetThemeLabel->setVisible(features & ThemeFields::WIDGETS); - ui->backgroundCatComboBox->setVisible(features & ThemeFields::CAT); - ui->backgroundCatLabel->setVisible(features & ThemeFields::CAT); + ui->iconsComboBox->setEnabled(features & ThemeFields::ICONS); + ui->iconsLabel->setEnabled(features & ThemeFields::ICONS); + ui->widgetStyleComboBox->setEnabled(features & ThemeFields::WIDGETS); + ui->widgetThemeLabel->setEnabled(features & ThemeFields::WIDGETS); + ui->backgroundCatComboBox->setEnabled(features & ThemeFields::CAT); + ui->backgroundCatLabel->setEnabled(features & ThemeFields::CAT); } void ThemeCustomizationWidget::applyIconTheme(int index) { - emit currentIconThemeChanged(index); - auto settings = APPLICATION->settings(); auto original = settings->get("IconTheme").toString(); // FIXME: make generic @@ -56,11 +78,11 @@ void ThemeCustomizationWidget::applyIconTheme(int index) { if (original != settings->get("IconTheme")) { APPLICATION->applyCurrentlySelectedTheme(); } + + emit currentIconThemeChanged(index); } void ThemeCustomizationWidget::applyWidgetTheme(int index) { - emit currentWidgetThemeChanged(index); - auto settings = APPLICATION->settings(); auto originalAppTheme = settings->get("ApplicationTheme").toString(); auto newAppTheme = ui->widgetStyleComboBox->currentData().toString(); @@ -68,26 +90,15 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index) { settings->set("ApplicationTheme", newAppTheme); APPLICATION->applyCurrentlySelectedTheme(); } + + emit currentWidgetThemeChanged(index); } void ThemeCustomizationWidget::applyCatTheme(int index) { - emit currentCatChanged(index); - auto settings = APPLICATION->settings(); - switch (index) { - case 0: // original cat - settings->set("BackgroundCat", "kitteh"); - break; - case 1: // rory the cat - settings->set("BackgroundCat", "rory"); - break; - case 2: // rory the cat flat edition - settings->set("BackgroundCat", "rory-flat"); - break; - case 3: // teawie - settings->set("BackgroundCat", "teawie"); - break; - } + settings->set("BackgroundCat", m_catOptions[index]); + + emit currentCatChanged(index); } void ThemeCustomizationWidget::applySettings() @@ -101,8 +112,8 @@ void ThemeCustomizationWidget::loadSettings() auto settings = APPLICATION->settings(); // FIXME: make generic - auto theme = settings->get("IconTheme").toString(); - ui->iconsComboBox->setCurrentIndex(m_iconThemeOptions.indexOf(theme)); + auto iconTheme = settings->get("IconTheme").toString(); + ui->iconsComboBox->setCurrentIndex(m_iconThemeOptions.indexOf(iconTheme)); { auto currentTheme = settings->get("ApplicationTheme").toString(); @@ -118,18 +129,10 @@ void ThemeCustomizationWidget::loadSettings() } auto cat = settings->get("BackgroundCat").toString(); - if (cat == "kitteh") { - ui->backgroundCatComboBox->setCurrentIndex(0); - } else if (cat == "rory") { - ui->backgroundCatComboBox->setCurrentIndex(1); - } else if (cat == "rory-flat") { - ui->backgroundCatComboBox->setCurrentIndex(2); - } else if (cat == "teawie") { - ui->backgroundCatComboBox->setCurrentIndex(3); - } + ui->backgroundCatComboBox->setCurrentIndex(m_catOptions.indexOf(cat)); } void ThemeCustomizationWidget::retranslate() { ui->retranslateUi(this); -} \ No newline at end of file +} diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h index e17286e1..653e89e7 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.h +++ b/launcher/ui/widgets/ThemeCustomizationWidget.h @@ -61,4 +61,5 @@ signals: private: QStringList m_iconThemeOptions{ "pe_colored", "pe_light", "pe_dark", "pe_blue", "breeze_light", "breeze_dark", "OSX", "iOS", "flat", "flat_white", "multimc", "custom" }; + QStringList m_catOptions{ "kitteh", "rory", "rory-flat" }; }; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index c184b8f3..9cc5cc76 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -11,9 +11,12 @@ - Form + Form + + QLayout::SetMinimumSize + 0 -- cgit From 6daa45783894fc7517917d6f6df0deaac1a41ba3 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 9 Jan 2023 16:58:27 +0100 Subject: Implement Suggestions from flow & Scrumplex Signed-off-by: Tayou --- launcher/Application.cpp | 7 ++- launcher/ui/MainWindow.cpp | 22 ++----- launcher/ui/setupwizard/ThemeWizardPage.cpp | 27 ++------ launcher/ui/setupwizard/ThemeWizardPage.h | 16 +++-- launcher/ui/themes/ThemeManager.cpp | 35 ++++++++--- launcher/ui/themes/ThemeManager.h | 9 +-- launcher/ui/widgets/ThemeCustomizationWidget.cpp | 22 +++++-- launcher/ui/widgets/ThemeCustomizationWidget.h | 50 +++++++++------ launcher/ui/widgets/ThemeCustomizationWidget.ui | 80 ------------------------ 9 files changed, 97 insertions(+), 171 deletions(-) (limited to 'launcher/ui/widgets') diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 3e64b74f..f2cc7bfb 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -498,7 +498,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Theming m_settings->registerSetting("IconTheme", QString("pe_colored")); - m_settings->registerSetting("ApplicationTheme", QString("system")); + m_settings->registerSetting("ApplicationTheme"); m_settings->registerSetting("BackgroundCat", QString("kitteh")); // Remembered state @@ -890,8 +890,8 @@ bool Application::createSetupWizard() return false; }(); bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; - bool themeInterventionRequired = settings()->get("ApplicationTheme") != ""; - bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired; + bool themeInterventionRequired = settings()->get("ApplicationTheme") == ""; + bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired; if(wizardRequired) { @@ -913,6 +913,7 @@ bool Application::createSetupWizard() if (themeInterventionRequired) { + settings()->set("ApplicationTheme", QString("system")); // set default theme after going into theme wizard m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard)); } connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index a921e378..ab80fb80 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -111,6 +111,7 @@ #include "ui/dialogs/ExportInstanceDialog.h" #include "ui/dialogs/ImportResourcePackDialog.h" #include "ui/themes/ITheme.h" +#include "ui/themes/ThemeManager.h" #include #include @@ -1654,20 +1655,7 @@ void MainWindow::onCatToggled(bool state) void MainWindow::setCatBackground(bool enabled) { - if (enabled) - { - QDateTime now = QDateTime::currentDateTime(); - QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); - QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); - QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); - QString cat = APPLICATION->settings()->get("BackgroundCat").toString(); - if (std::abs(now.daysTo(xmas)) <= 4) { - cat += "-xmas"; - } else if (std::abs(now.daysTo(halloween)) <= 4) { - cat += "-spooky"; - } else if (std::abs(now.daysTo(birthday)) <= 12) { - cat += "-bday"; - } + if (enabled) { view->setStyleSheet(QString(R"( InstanceView { @@ -1678,10 +1666,8 @@ InstanceView background-repeat: none; background-color:palette(base); })") - .arg(cat)); - } - else - { + .arg(ThemeManager::getCatImage())); + } else { view->setStyleSheet(QString()); } } diff --git a/launcher/ui/setupwizard/ThemeWizardPage.cpp b/launcher/ui/setupwizard/ThemeWizardPage.cpp index 4e1eb488..cc2d335b 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.cpp +++ b/launcher/ui/setupwizard/ThemeWizardPage.cpp @@ -20,6 +20,7 @@ #include "Application.h" #include "ui/themes/ITheme.h" +#include "ui/themes/ThemeManager.h" #include "ui/widgets/ThemeCustomizationWidget.h" #include "ui_ThemeCustomizationWidget.h" @@ -27,8 +28,8 @@ ThemeWizardPage::ThemeWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(n { ui->setupUi(this); - connect(ui->themeCustomizationWidget, QOverload::of(&ThemeCustomizationWidget::currentIconThemeChanged), this, &ThemeWizardPage::updateIcons); - connect(ui->themeCustomizationWidget, QOverload::of(&ThemeCustomizationWidget::currentCatChanged), this, &ThemeWizardPage::updateCat); + connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentIconThemeChanged, this, &ThemeWizardPage::updateIcons); + connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentCatChanged, this, &ThemeWizardPage::updateCat); updateIcons(); updateCat(); @@ -39,13 +40,6 @@ ThemeWizardPage::~ThemeWizardPage() delete ui; } -void ThemeWizardPage::initializePage() {} - -bool ThemeWizardPage::validatePage() -{ - return true; -} - void ThemeWizardPage::updateIcons() { qDebug() << "Setting Icons"; @@ -67,20 +61,7 @@ void ThemeWizardPage::updateIcons() void ThemeWizardPage::updateCat() { qDebug() << "Setting Cat"; - - QDateTime now = QDateTime::currentDateTime(); - QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); - QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); - QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); - QString cat = APPLICATION->settings()->get("BackgroundCat").toString(); - if (std::abs(now.daysTo(xmas)) <= 4) { - cat += "-xmas"; - } else if (std::abs(now.daysTo(halloween)) <= 4) { - cat += "-spooky"; - } else if (std::abs(now.daysTo(birthday)) <= 12) { - cat += "-bday"; - } - ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(cat))); + ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(ThemeManager::getCatImage()))); } void ThemeWizardPage::retranslate() diff --git a/launcher/ui/setupwizard/ThemeWizardPage.h b/launcher/ui/setupwizard/ThemeWizardPage.h index 6562ad2e..992ba2ca 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.h +++ b/launcher/ui/setupwizard/ThemeWizardPage.h @@ -24,22 +24,20 @@ namespace Ui { class ThemeWizardPage; } -class ThemeWizardPage : public BaseWizardPage -{ +class ThemeWizardPage : public BaseWizardPage { Q_OBJECT -public: - explicit ThemeWizardPage(QWidget *parent = nullptr); + public: + explicit ThemeWizardPage(QWidget* parent = nullptr); ~ThemeWizardPage(); - void initializePage() override; - bool validatePage() override; + bool validatePage() override { return true; }; void retranslate() override; -private slots: + private slots: void updateIcons(); void updateCat(); -private: - Ui::ThemeWizardPage *ui; + private: + Ui::ThemeWizardPage* ui; }; diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index a6cebc6f..44c13f40 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -31,13 +31,13 @@ ThemeManager::ThemeManager(MainWindow* mainWindow) { m_mainWindow = mainWindow; - InitializeThemes(); + initializeThemes(); } /// @brief Adds the Theme to the list of themes /// @param theme The Theme to add /// @return Theme ID -QString ThemeManager::AddTheme(std::unique_ptr theme) +QString ThemeManager::addTheme(std::unique_ptr theme) { QString id = theme->id(); m_themes.emplace(id, std::move(theme)); @@ -47,12 +47,12 @@ QString ThemeManager::AddTheme(std::unique_ptr theme) /// @brief Gets the Theme from the List via ID /// @param themeId Theme ID of theme to fetch /// @return Theme at themeId -ITheme* ThemeManager::GetTheme(QString themeId) +ITheme* ThemeManager::getTheme(QString themeId) { return m_themes[themeId].get(); } -void ThemeManager::InitializeThemes() +void ThemeManager::initializeThemes() { // Icon themes { @@ -67,10 +67,10 @@ void ThemeManager::InitializeThemes() // Initialize widget themes { themeDebugLog() << "<> Initializing Widget Themes"; - themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique()); - auto darkThemeId = AddTheme(std::make_unique()); + themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); + auto darkThemeId = addTheme(std::make_unique()); themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; - themeDebugLog() << "Loading Built-in Theme:" << AddTheme(std::make_unique()); + themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in // dropdown?) @@ -84,7 +84,7 @@ void ThemeManager::InitializeThemes() if (themeJson.exists()) { // Load "theme.json" based themes themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath(); - AddTheme(std::make_unique(GetTheme(darkThemeId), themeJson, true)); + addTheme(std::make_unique(getTheme(darkThemeId), themeJson, true)); } else { // Load pure QSS Themes QDirIterator stylesheetFileIterator(dir.absoluteFilePath(""), { "*.qss", "*.css" }, QDir::Files); @@ -92,7 +92,7 @@ void ThemeManager::InitializeThemes() QFile customThemeFile(stylesheetFileIterator.next()); QFileInfo customThemeFileInfo(customThemeFile); themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath(); - AddTheme(std::make_unique(GetTheme(darkThemeId), customThemeFileInfo, false)); + addTheme(std::make_unique(getTheme(darkThemeId), customThemeFileInfo, false)); } } } @@ -136,3 +136,20 @@ void ThemeManager::setApplicationTheme(const QString& name) themeWarningLog() << "Tried to set invalid theme:" << name; } } + +QString ThemeManager::getCatImage(QString catName) +{ + QDateTime now = QDateTime::currentDateTime(); + QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); + QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); + QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); + QString cat = catName == "" ? APPLICATION->settings()->get("BackgroundCat").toString() : catName; + if (std::abs(now.daysTo(xmas)) <= 4) { + cat += "-xmas"; + } else if (std::abs(now.daysTo(halloween)) <= 4) { + cat += "-spooky"; + } else if (std::abs(now.daysTo(birthday)) <= 12) { + cat += "-bday"; + } + return cat; +} \ No newline at end of file diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index bb10cd48..4f36bffa 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -40,12 +40,13 @@ class ThemeManager { void applyCurrentlySelectedTheme(); void setApplicationTheme(const QString& name); + static QString getCatImage(QString catName = ""); + private: std::map> m_themes; MainWindow* m_mainWindow; - bool m_firstThemeInitialized; - void InitializeThemes(); - QString AddTheme(std::unique_ptr theme); - ITheme* GetTheme(QString themeId); + void initializeThemes(); + QString addTheme(std::unique_ptr theme); + ITheme* getTheme(QString themeId); }; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index eafcf482..5fb5bd4e 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -20,6 +20,7 @@ #include "Application.h" #include "ui/themes/ITheme.h" +#include "ui/themes/ThemeManager.h" ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ThemeCustomizationWidget) { @@ -72,8 +73,7 @@ void ThemeCustomizationWidget::showFeatures(ThemeFields features) { void ThemeCustomizationWidget::applyIconTheme(int index) { auto settings = APPLICATION->settings(); auto original = settings->get("IconTheme").toString(); - // FIXME: make generic - settings->set("IconTheme", m_iconThemeOptions[index]); + settings->set("IconTheme", m_iconThemeOptions[index].first); if (original != settings->get("IconTheme")) { APPLICATION->applyCurrentlySelectedTheme(); @@ -96,7 +96,7 @@ void ThemeCustomizationWidget::applyWidgetTheme(int index) { void ThemeCustomizationWidget::applyCatTheme(int index) { auto settings = APPLICATION->settings(); - settings->set("BackgroundCat", m_catOptions[index]); + settings->set("BackgroundCat", m_catOptions[index].first); emit currentCatChanged(index); } @@ -111,9 +111,13 @@ void ThemeCustomizationWidget::loadSettings() { auto settings = APPLICATION->settings(); - // FIXME: make generic auto iconTheme = settings->get("IconTheme").toString(); - ui->iconsComboBox->setCurrentIndex(m_iconThemeOptions.indexOf(iconTheme)); + for (auto& iconThemeFromList : m_iconThemeOptions) { + ui->iconsComboBox->addItem(QIcon(QString(":/icons/%1/scalable/settings").arg(iconThemeFromList.first)), iconThemeFromList.second); + if (iconTheme == iconThemeFromList.first) { + ui->iconsComboBox->setCurrentIndex(ui->iconsComboBox->count() - 1); + } + } { auto currentTheme = settings->get("ApplicationTheme").toString(); @@ -129,7 +133,13 @@ void ThemeCustomizationWidget::loadSettings() } auto cat = settings->get("BackgroundCat").toString(); - ui->backgroundCatComboBox->setCurrentIndex(m_catOptions.indexOf(cat)); + for (auto& catFromList : m_catOptions) { + ui->backgroundCatComboBox->addItem(QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first))), + catFromList.second); + if (cat == catFromList.first) { + ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1); + } + } } void ThemeCustomizationWidget::retranslate() diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h index 653e89e7..d450e8df 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.h +++ b/launcher/ui/widgets/ThemeCustomizationWidget.h @@ -18,25 +18,19 @@ #pragma once #include -#include +#include "translations/TranslationsModel.h" -enum ThemeFields { - NONE = 0b0000, - ICONS = 0b0001, - WIDGETS = 0b0010, - CAT = 0b0100 -}; +enum ThemeFields { NONE = 0b0000, ICONS = 0b0001, WIDGETS = 0b0010, CAT = 0b0100 }; namespace Ui { class ThemeCustomizationWidget; } -class ThemeCustomizationWidget : public QWidget -{ +class ThemeCustomizationWidget : public QWidget { Q_OBJECT -public: - explicit ThemeCustomizationWidget(QWidget *parent = nullptr); + public: + explicit ThemeCustomizationWidget(QWidget* parent = nullptr); ~ThemeCustomizationWidget(); void showFeatures(ThemeFields features); @@ -45,21 +39,39 @@ public: void loadSettings(); void retranslate(); - - Ui::ThemeCustomizationWidget *ui; -private slots: + private slots: void applyIconTheme(int index); void applyWidgetTheme(int index); void applyCatTheme(int index); -signals: + signals: int currentIconThemeChanged(int index); int currentWidgetThemeChanged(int index); int currentCatChanged(int index); -private: + private: + Ui::ThemeCustomizationWidget* ui; - QStringList m_iconThemeOptions{ "pe_colored", "pe_light", "pe_dark", "pe_blue", "breeze_light", "breeze_dark", "OSX", "iOS", "flat", "flat_white", "multimc", "custom" }; - QStringList m_catOptions{ "kitteh", "rory", "rory-flat" }; -}; + //TODO finish implementing + QList> m_iconThemeOptions{ + { "pe_colored", QObject::tr("Simple (Colored Icons)") }, + { "pe_light", QObject::tr("Simple (Light Icons)") }, + { "pe_dark", QObject::tr("Simple (Dark Icons)") }, + { "pe_blue", QObject::tr("Simple (Blue Icons)") }, + { "breeze_light", QObject::tr("Breeze Light") }, + { "breeze_dark", QObject::tr("Breeze Dark") }, + { "OSX", QObject::tr("OSX") }, + { "iOS", QObject::tr("iOS") }, + { "flat", QObject::tr("Flat") }, + { "flat_white", QObject::tr("Flat (White)") }, + { "multimc", QObject::tr("Legacy") }, + { "custom", QObject::tr("Custom") } + }; + QList> m_catOptions{ + { "kitteh", QObject::tr("Background Cat (from MultiMC)") }, + { "rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)") }, + { "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") }, + { "teawie", QObject::tr("Teawie (drawn by SympathyTea)") } + }; +}; \ No newline at end of file diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index 9cc5cc76..15ba831e 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -50,66 +50,6 @@ Qt::StrongFocus - - - Simple (Colored Icons) - - - - - Simple (Light Icons) - - - - - Simple (Dark Icons) - - - - - Simple (Blue Icons) - - - - - Breeze Light - - - - - Breeze Dark - - - - - OSX - - - - - iOS - - - - - Flat - - - - - Flat (White) - - - - - Legacy - - - - - Custom - - @@ -156,26 +96,6 @@ Qt::StrongFocus - - - Background Cat (from MultiMC) - - - - - Rory ID 11 (drawn by Ashtaka) - - - - - Rory ID 11 (flat edition, drawn by Ashtaka) - - - - - Teawie (drawn by SympathyTea) - - -- cgit From 689fe1e2c76b8065b9769b4304b1c9b4d81215b1 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 9 Jan 2023 17:01:33 +0100 Subject: CRLF -> LF damn you visual studio for creating CRLF files everywhere... Signed-off-by: Tayou --- launcher/ui/setupwizard/ThemeWizardPage.cpp | 140 ++--- launcher/ui/setupwizard/ThemeWizardPage.h | 86 +-- launcher/ui/setupwizard/ThemeWizardPage.ui | 716 +++++++++++------------ launcher/ui/themes/ThemeManager.cpp | 310 +++++----- launcher/ui/themes/ThemeManager.h | 114 ++-- launcher/ui/widgets/ThemeCustomizationWidget.cpp | 296 +++++----- launcher/ui/widgets/ThemeCustomizationWidget.h | 152 ++--- launcher/ui/widgets/ThemeCustomizationWidget.ui | 210 +++---- 8 files changed, 1012 insertions(+), 1012 deletions(-) (limited to 'launcher/ui/widgets') diff --git a/launcher/ui/setupwizard/ThemeWizardPage.cpp b/launcher/ui/setupwizard/ThemeWizardPage.cpp index cc2d335b..42826aba 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.cpp +++ b/launcher/ui/setupwizard/ThemeWizardPage.cpp @@ -1,70 +1,70 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Tayou - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "ThemeWizardPage.h" -#include "ui_ThemeWizardPage.h" - -#include "Application.h" -#include "ui/themes/ITheme.h" -#include "ui/themes/ThemeManager.h" -#include "ui/widgets/ThemeCustomizationWidget.h" -#include "ui_ThemeCustomizationWidget.h" - -ThemeWizardPage::ThemeWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(new Ui::ThemeWizardPage) -{ - ui->setupUi(this); - - connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentIconThemeChanged, this, &ThemeWizardPage::updateIcons); - connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentCatChanged, this, &ThemeWizardPage::updateCat); - - updateIcons(); - updateCat(); -} - -ThemeWizardPage::~ThemeWizardPage() -{ - delete ui; -} - -void ThemeWizardPage::updateIcons() -{ - qDebug() << "Setting Icons"; - ui->previewIconButton0->setIcon(APPLICATION->getThemedIcon("new")); - ui->previewIconButton1->setIcon(APPLICATION->getThemedIcon("centralmods")); - ui->previewIconButton2->setIcon(APPLICATION->getThemedIcon("viewfolder")); - ui->previewIconButton3->setIcon(APPLICATION->getThemedIcon("launch")); - ui->previewIconButton4->setIcon(APPLICATION->getThemedIcon("copy")); - ui->previewIconButton5->setIcon(APPLICATION->getThemedIcon("export")); - ui->previewIconButton6->setIcon(APPLICATION->getThemedIcon("delete")); - ui->previewIconButton7->setIcon(APPLICATION->getThemedIcon("about")); - ui->previewIconButton8->setIcon(APPLICATION->getThemedIcon("settings")); - ui->previewIconButton9->setIcon(APPLICATION->getThemedIcon("cat")); - update(); - repaint(); - parentWidget()->update(); -} - -void ThemeWizardPage::updateCat() -{ - qDebug() << "Setting Cat"; - ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(ThemeManager::getCatImage()))); -} - -void ThemeWizardPage::retranslate() -{ - ui->retranslateUi(this); -} +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "ThemeWizardPage.h" +#include "ui_ThemeWizardPage.h" + +#include "Application.h" +#include "ui/themes/ITheme.h" +#include "ui/themes/ThemeManager.h" +#include "ui/widgets/ThemeCustomizationWidget.h" +#include "ui_ThemeCustomizationWidget.h" + +ThemeWizardPage::ThemeWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(new Ui::ThemeWizardPage) +{ + ui->setupUi(this); + + connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentIconThemeChanged, this, &ThemeWizardPage::updateIcons); + connect(ui->themeCustomizationWidget, &ThemeCustomizationWidget::currentCatChanged, this, &ThemeWizardPage::updateCat); + + updateIcons(); + updateCat(); +} + +ThemeWizardPage::~ThemeWizardPage() +{ + delete ui; +} + +void ThemeWizardPage::updateIcons() +{ + qDebug() << "Setting Icons"; + ui->previewIconButton0->setIcon(APPLICATION->getThemedIcon("new")); + ui->previewIconButton1->setIcon(APPLICATION->getThemedIcon("centralmods")); + ui->previewIconButton2->setIcon(APPLICATION->getThemedIcon("viewfolder")); + ui->previewIconButton3->setIcon(APPLICATION->getThemedIcon("launch")); + ui->previewIconButton4->setIcon(APPLICATION->getThemedIcon("copy")); + ui->previewIconButton5->setIcon(APPLICATION->getThemedIcon("export")); + ui->previewIconButton6->setIcon(APPLICATION->getThemedIcon("delete")); + ui->previewIconButton7->setIcon(APPLICATION->getThemedIcon("about")); + ui->previewIconButton8->setIcon(APPLICATION->getThemedIcon("settings")); + ui->previewIconButton9->setIcon(APPLICATION->getThemedIcon("cat")); + update(); + repaint(); + parentWidget()->update(); +} + +void ThemeWizardPage::updateCat() +{ + qDebug() << "Setting Cat"; + ui->catImagePreviewButton->setIcon(QIcon(QString(R"(:/backgrounds/%1)").arg(ThemeManager::getCatImage()))); +} + +void ThemeWizardPage::retranslate() +{ + ui->retranslateUi(this); +} diff --git a/launcher/ui/setupwizard/ThemeWizardPage.h b/launcher/ui/setupwizard/ThemeWizardPage.h index 992ba2ca..61a3d0c0 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.h +++ b/launcher/ui/setupwizard/ThemeWizardPage.h @@ -1,43 +1,43 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Tayou - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include -#include "BaseWizardPage.h" - -namespace Ui { -class ThemeWizardPage; -} - -class ThemeWizardPage : public BaseWizardPage { - Q_OBJECT - - public: - explicit ThemeWizardPage(QWidget* parent = nullptr); - ~ThemeWizardPage(); - - bool validatePage() override { return true; }; - void retranslate() override; - - private slots: - void updateIcons(); - void updateCat(); - - private: - Ui::ThemeWizardPage* ui; -}; +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "BaseWizardPage.h" + +namespace Ui { +class ThemeWizardPage; +} + +class ThemeWizardPage : public BaseWizardPage { + Q_OBJECT + + public: + explicit ThemeWizardPage(QWidget* parent = nullptr); + ~ThemeWizardPage(); + + bool validatePage() override { return true; }; + void retranslate() override; + + private slots: + void updateIcons(); + void updateCat(); + + private: + Ui::ThemeWizardPage* ui; +}; diff --git a/launcher/ui/setupwizard/ThemeWizardPage.ui b/launcher/ui/setupwizard/ThemeWizardPage.ui index 95b0f805..1ab04fc8 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.ui +++ b/launcher/ui/setupwizard/ThemeWizardPage.ui @@ -1,358 +1,358 @@ - - - ThemeWizardPage - - - - 0 - 0 - 510 - 552 - - - - WizardPage - - - - - - Select the Theme you wish to use - - - - - - - - 0 - 100 - - - - - - - - Qt::Horizontal - - - - - - - Icon Preview: - - - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - 0 - 0 - - - - - 30 - 30 - - - - - .. - - - false - - - true - - - - - - - - - - 0 - 256 - - - - - - - - 256 - 256 - - - - true - - - - - - - Qt::Vertical - - - - 20 - 193 - - - - - - - - - ThemeCustomizationWidget - QWidget -
ui/widgets/ThemeCustomizationWidget.h
-
-
- - -
+ + + ThemeWizardPage + + + + 0 + 0 + 510 + 552 + + + + WizardPage + + + + + + Select the Theme you wish to use + + + + + + + + 0 + 100 + + + + + + + + Qt::Horizontal + + + + + + + Icon Preview: + + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + .. + + + false + + + true + + + + + + + + + + 0 + 256 + + + + + + + + 256 + 256 + + + + true + + + + + + + Qt::Vertical + + + + 20 + 193 + + + + + + + + + ThemeCustomizationWidget + QWidget +
ui/widgets/ThemeCustomizationWidget.h
+
+
+ + +
diff --git a/launcher/ui/themes/ThemeManager.cpp b/launcher/ui/themes/ThemeManager.cpp index 7ccc946a..13406485 100644 --- a/launcher/ui/themes/ThemeManager.cpp +++ b/launcher/ui/themes/ThemeManager.cpp @@ -1,155 +1,155 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Tayou - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "ThemeManager.h" - -#include -#include -#include -#include -#include "ui/themes/BrightTheme.h" -#include "ui/themes/CustomTheme.h" -#include "ui/themes/DarkTheme.h" -#include "ui/themes/SystemTheme.h" - -#include "Application.h" - -ThemeManager::ThemeManager(MainWindow* mainWindow) -{ - m_mainWindow = mainWindow; - initializeThemes(); -} - -/// @brief Adds the Theme to the list of themes -/// @param theme The Theme to add -/// @return Theme ID -QString ThemeManager::addTheme(std::unique_ptr theme) -{ - QString id = theme->id(); - m_themes.emplace(id, std::move(theme)); - return id; -} - -/// @brief Gets the Theme from the List via ID -/// @param themeId Theme ID of theme to fetch -/// @return Theme at themeId -ITheme* ThemeManager::getTheme(QString themeId) -{ - return m_themes[themeId].get(); -} - -void ThemeManager::initializeThemes() -{ - // Icon themes - { - // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies! - // set icon theme search path! - auto searchPaths = QIcon::themeSearchPaths(); - searchPaths.append("iconthemes"); - QIcon::setThemeSearchPaths(searchPaths); - themeDebugLog() << "<> Icon themes initialized."; - } - - // Initialize widget themes - { - themeDebugLog() << "<> Initializing Widget Themes"; - themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); - auto darkThemeId = addTheme(std::make_unique()); - themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; - themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); - - // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in - // dropdown?) - QString themeFolder = QDir("./themes/").absoluteFilePath(""); - themeDebugLog() << "Theme Folder Path: " << themeFolder; - - QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); - while (directoryIterator.hasNext()) { - QDir dir(directoryIterator.next()); - QFileInfo themeJson(dir.absoluteFilePath("theme.json")); - if (themeJson.exists()) { - // Load "theme.json" based themes - themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath(); - addTheme(std::make_unique(getTheme(darkThemeId), themeJson, true)); - } else { - // Load pure QSS Themes - QDirIterator stylesheetFileIterator(dir.absoluteFilePath(""), { "*.qss", "*.css" }, QDir::Files); - while (stylesheetFileIterator.hasNext()) { - QFile customThemeFile(stylesheetFileIterator.next()); - QFileInfo customThemeFileInfo(customThemeFile); - themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath(); - addTheme(std::make_unique(getTheme(darkThemeId), customThemeFileInfo, false)); - } - } - } - - themeDebugLog() << "<> Widget themes initialized."; - } -} - -QList ThemeManager::getValidApplicationThemes() -{ - QList ret; - ret.reserve(m_themes.size()); - for (auto&& [id, theme] : m_themes) { - ret.append(theme.get()); - } - return ret; -} - -void ThemeManager::setIconTheme(const QString& name) -{ - QIcon::setThemeName(name); -} - -void ThemeManager::applyCurrentlySelectedTheme() -{ - setIconTheme(APPLICATION->settings()->get("IconTheme").toString()); - themeDebugLog() << "<> Icon theme set."; - setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString()); - themeDebugLog() << "<> Application theme set."; -} - -void ThemeManager::setApplicationTheme(const QString& name) -{ - auto systemPalette = qApp->palette(); - auto themeIter = m_themes.find(name); - if (themeIter != m_themes.end()) { - auto& theme = themeIter->second; - themeDebugLog() << "applying theme" << theme->name(); - theme->apply(); - } else { - themeWarningLog() << "Tried to set invalid theme:" << name; - } -} - -QString ThemeManager::getCatImage(QString catName) -{ - QDateTime now = QDateTime::currentDateTime(); - QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); - QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); - QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); - QString cat = !catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString(); - if (std::abs(now.daysTo(xmas)) <= 4) { - cat += "-xmas"; - } else if (std::abs(now.daysTo(halloween)) <= 4) { - cat += "-spooky"; - } else if (std::abs(now.daysTo(birthday)) <= 12) { - cat += "-bday"; - } - return cat; -} +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "ThemeManager.h" + +#include +#include +#include +#include +#include "ui/themes/BrightTheme.h" +#include "ui/themes/CustomTheme.h" +#include "ui/themes/DarkTheme.h" +#include "ui/themes/SystemTheme.h" + +#include "Application.h" + +ThemeManager::ThemeManager(MainWindow* mainWindow) +{ + m_mainWindow = mainWindow; + initializeThemes(); +} + +/// @brief Adds the Theme to the list of themes +/// @param theme The Theme to add +/// @return Theme ID +QString ThemeManager::addTheme(std::unique_ptr theme) +{ + QString id = theme->id(); + m_themes.emplace(id, std::move(theme)); + return id; +} + +/// @brief Gets the Theme from the List via ID +/// @param themeId Theme ID of theme to fetch +/// @return Theme at themeId +ITheme* ThemeManager::getTheme(QString themeId) +{ + return m_themes[themeId].get(); +} + +void ThemeManager::initializeThemes() +{ + // Icon themes + { + // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies! + // set icon theme search path! + auto searchPaths = QIcon::themeSearchPaths(); + searchPaths.append("iconthemes"); + QIcon::setThemeSearchPaths(searchPaths); + themeDebugLog() << "<> Icon themes initialized."; + } + + // Initialize widget themes + { + themeDebugLog() << "<> Initializing Widget Themes"; + themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); + auto darkThemeId = addTheme(std::make_unique()); + themeDebugLog() << "Loading Built-in Theme:" << darkThemeId; + themeDebugLog() << "Loading Built-in Theme:" << addTheme(std::make_unique()); + + // TODO: need some way to differentiate same name themes in different subdirectories (maybe smaller grey text next to theme name in + // dropdown?) + QString themeFolder = QDir("./themes/").absoluteFilePath(""); + themeDebugLog() << "Theme Folder Path: " << themeFolder; + + QDirIterator directoryIterator(themeFolder, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + while (directoryIterator.hasNext()) { + QDir dir(directoryIterator.next()); + QFileInfo themeJson(dir.absoluteFilePath("theme.json")); + if (themeJson.exists()) { + // Load "theme.json" based themes + themeDebugLog() << "Loading JSON Theme from:" << themeJson.absoluteFilePath(); + addTheme(std::make_unique(getTheme(darkThemeId), themeJson, true)); + } else { + // Load pure QSS Themes + QDirIterator stylesheetFileIterator(dir.absoluteFilePath(""), { "*.qss", "*.css" }, QDir::Files); + while (stylesheetFileIterator.hasNext()) { + QFile customThemeFile(stylesheetFileIterator.next()); + QFileInfo customThemeFileInfo(customThemeFile); + themeDebugLog() << "Loading QSS Theme from:" << customThemeFileInfo.absoluteFilePath(); + addTheme(std::make_unique(getTheme(darkThemeId), customThemeFileInfo, false)); + } + } + } + + themeDebugLog() << "<> Widget themes initialized."; + } +} + +QList ThemeManager::getValidApplicationThemes() +{ + QList ret; + ret.reserve(m_themes.size()); + for (auto&& [id, theme] : m_themes) { + ret.append(theme.get()); + } + return ret; +} + +void ThemeManager::setIconTheme(const QString& name) +{ + QIcon::setThemeName(name); +} + +void ThemeManager::applyCurrentlySelectedTheme() +{ + setIconTheme(APPLICATION->settings()->get("IconTheme").toString()); + themeDebugLog() << "<> Icon theme set."; + setApplicationTheme(APPLICATION->settings()->get("ApplicationTheme").toString()); + themeDebugLog() << "<> Application theme set."; +} + +void ThemeManager::setApplicationTheme(const QString& name) +{ + auto systemPalette = qApp->palette(); + auto themeIter = m_themes.find(name); + if (themeIter != m_themes.end()) { + auto& theme = themeIter->second; + themeDebugLog() << "applying theme" << theme->name(); + theme->apply(); + } else { + themeWarningLog() << "Tried to set invalid theme:" << name; + } +} + +QString ThemeManager::getCatImage(QString catName) +{ + QDateTime now = QDateTime::currentDateTime(); + QDateTime birthday(QDate(now.date().year(), 11, 30), QTime(0, 0)); + QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0)); + QDateTime halloween(QDate(now.date().year(), 10, 31), QTime(0, 0)); + QString cat = !catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString(); + if (std::abs(now.daysTo(xmas)) <= 4) { + cat += "-xmas"; + } else if (std::abs(now.daysTo(halloween)) <= 4) { + cat += "-spooky"; + } else if (std::abs(now.daysTo(birthday)) <= 12) { + cat += "-bday"; + } + return cat; +} diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index d5e73bb8..9af44b5a 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -1,57 +1,57 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Tayou - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include - -#include "ui/MainWindow.h" -#include "ui/themes/ITheme.h" - -inline auto themeDebugLog() -{ - return qDebug() << "[Theme]"; -} -inline auto themeWarningLog() -{ - return qWarning() << "[Theme]"; -} - -class ThemeManager { - public: - ThemeManager(MainWindow* mainWindow); - - QList getValidApplicationThemes(); - void setIconTheme(const QString& name); - void applyCurrentlySelectedTheme(); - void setApplicationTheme(const QString& name); - - /// - /// Returns the cat based on selected cat and with events (Birthday, XMas, etc.) - /// - /// Optional, if you need a specific cat. - /// - static QString getCatImage(QString catName = ""); - - private: - std::map> m_themes; - MainWindow* m_mainWindow; - - void initializeThemes(); - QString addTheme(std::unique_ptr theme); - ITheme* getTheme(QString themeId); -}; +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +#include "ui/MainWindow.h" +#include "ui/themes/ITheme.h" + +inline auto themeDebugLog() +{ + return qDebug() << "[Theme]"; +} +inline auto themeWarningLog() +{ + return qWarning() << "[Theme]"; +} + +class ThemeManager { + public: + ThemeManager(MainWindow* mainWindow); + + QList getValidApplicationThemes(); + void setIconTheme(const QString& name); + void applyCurrentlySelectedTheme(); + void setApplicationTheme(const QString& name); + + /// + /// Returns the cat based on selected cat and with events (Birthday, XMas, etc.) + /// + /// Optional, if you need a specific cat. + /// + static QString getCatImage(QString catName = ""); + + private: + std::map> m_themes; + MainWindow* m_mainWindow; + + void initializeThemes(); + QString addTheme(std::unique_ptr theme); + ITheme* getTheme(QString themeId); +}; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index 5fb5bd4e..d0b5be21 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -1,148 +1,148 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Tayou - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include "ThemeCustomizationWidget.h" -#include "ui_ThemeCustomizationWidget.h" - -#include "Application.h" -#include "ui/themes/ITheme.h" -#include "ui/themes/ThemeManager.h" - -ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ThemeCustomizationWidget) -{ - ui->setupUi(this); - loadSettings(); - - connect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); - connect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyWidgetTheme); - connect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme); -} - -ThemeCustomizationWidget::~ThemeCustomizationWidget() -{ - delete ui; -} - -/// -/// The layout was not quite right, so currently this just disables the UI elements, which should be hidden instead -/// TODO FIXME -/// -/// Original Method One: -/// ui->iconsComboBox->setVisible(features& ThemeFields::ICONS); -/// ui->iconsLabel->setVisible(features& ThemeFields::ICONS); -/// ui->widgetStyleComboBox->setVisible(features& ThemeFields::WIDGETS); -/// ui->widgetThemeLabel->setVisible(features& ThemeFields::WIDGETS); -/// ui->backgroundCatComboBox->setVisible(features& ThemeFields::CAT); -/// ui->backgroundCatLabel->setVisible(features& ThemeFields::CAT); -/// -/// original Method Two: -/// if (!(features & ThemeFields::ICONS)) { -/// ui->formLayout->setRowVisible(0, false); -/// } -/// if (!(features & ThemeFields::WIDGETS)) { -/// ui->formLayout->setRowVisible(1, false); -/// } -/// if (!(features & ThemeFields::CAT)) { -/// ui->formLayout->setRowVisible(2, false); -/// } -/// -/// -void ThemeCustomizationWidget::showFeatures(ThemeFields features) { - ui->iconsComboBox->setEnabled(features & ThemeFields::ICONS); - ui->iconsLabel->setEnabled(features & ThemeFields::ICONS); - ui->widgetStyleComboBox->setEnabled(features & ThemeFields::WIDGETS); - ui->widgetThemeLabel->setEnabled(features & ThemeFields::WIDGETS); - ui->backgroundCatComboBox->setEnabled(features & ThemeFields::CAT); - ui->backgroundCatLabel->setEnabled(features & ThemeFields::CAT); -} - -void ThemeCustomizationWidget::applyIconTheme(int index) { - auto settings = APPLICATION->settings(); - auto original = settings->get("IconTheme").toString(); - settings->set("IconTheme", m_iconThemeOptions[index].first); - - if (original != settings->get("IconTheme")) { - APPLICATION->applyCurrentlySelectedTheme(); - } - - emit currentIconThemeChanged(index); -} - -void ThemeCustomizationWidget::applyWidgetTheme(int index) { - auto settings = APPLICATION->settings(); - auto originalAppTheme = settings->get("ApplicationTheme").toString(); - auto newAppTheme = ui->widgetStyleComboBox->currentData().toString(); - if (originalAppTheme != newAppTheme) { - settings->set("ApplicationTheme", newAppTheme); - APPLICATION->applyCurrentlySelectedTheme(); - } - - emit currentWidgetThemeChanged(index); -} - -void ThemeCustomizationWidget::applyCatTheme(int index) { - auto settings = APPLICATION->settings(); - settings->set("BackgroundCat", m_catOptions[index].first); - - emit currentCatChanged(index); -} - -void ThemeCustomizationWidget::applySettings() -{ - applyIconTheme(ui->iconsComboBox->currentIndex()); - applyWidgetTheme(ui->widgetStyleComboBox->currentIndex()); - applyCatTheme(ui->backgroundCatComboBox->currentIndex()); -} -void ThemeCustomizationWidget::loadSettings() -{ - auto settings = APPLICATION->settings(); - - auto iconTheme = settings->get("IconTheme").toString(); - for (auto& iconThemeFromList : m_iconThemeOptions) { - ui->iconsComboBox->addItem(QIcon(QString(":/icons/%1/scalable/settings").arg(iconThemeFromList.first)), iconThemeFromList.second); - if (iconTheme == iconThemeFromList.first) { - ui->iconsComboBox->setCurrentIndex(ui->iconsComboBox->count() - 1); - } - } - - { - auto currentTheme = settings->get("ApplicationTheme").toString(); - auto themes = APPLICATION->getValidApplicationThemes(); - int idx = 0; - for (auto& theme : themes) { - ui->widgetStyleComboBox->addItem(theme->name(), theme->id()); - if (currentTheme == theme->id()) { - ui->widgetStyleComboBox->setCurrentIndex(idx); - } - idx++; - } - } - - auto cat = settings->get("BackgroundCat").toString(); - for (auto& catFromList : m_catOptions) { - ui->backgroundCatComboBox->addItem(QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first))), - catFromList.second); - if (cat == catFromList.first) { - ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1); - } - } -} - -void ThemeCustomizationWidget::retranslate() -{ - ui->retranslateUi(this); -} +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "ThemeCustomizationWidget.h" +#include "ui_ThemeCustomizationWidget.h" + +#include "Application.h" +#include "ui/themes/ITheme.h" +#include "ui/themes/ThemeManager.h" + +ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ThemeCustomizationWidget) +{ + ui->setupUi(this); + loadSettings(); + + connect(ui->iconsComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyIconTheme); + connect(ui->widgetStyleComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyWidgetTheme); + connect(ui->backgroundCatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme); +} + +ThemeCustomizationWidget::~ThemeCustomizationWidget() +{ + delete ui; +} + +/// +/// The layout was not quite right, so currently this just disables the UI elements, which should be hidden instead +/// TODO FIXME +/// +/// Original Method One: +/// ui->iconsComboBox->setVisible(features& ThemeFields::ICONS); +/// ui->iconsLabel->setVisible(features& ThemeFields::ICONS); +/// ui->widgetStyleComboBox->setVisible(features& ThemeFields::WIDGETS); +/// ui->widgetThemeLabel->setVisible(features& ThemeFields::WIDGETS); +/// ui->backgroundCatComboBox->setVisible(features& ThemeFields::CAT); +/// ui->backgroundCatLabel->setVisible(features& ThemeFields::CAT); +/// +/// original Method Two: +/// if (!(features & ThemeFields::ICONS)) { +/// ui->formLayout->setRowVisible(0, false); +/// } +/// if (!(features & ThemeFields::WIDGETS)) { +/// ui->formLayout->setRowVisible(1, false); +/// } +/// if (!(features & ThemeFields::CAT)) { +/// ui->formLayout->setRowVisible(2, false); +/// } +/// +/// +void ThemeCustomizationWidget::showFeatures(ThemeFields features) { + ui->iconsComboBox->setEnabled(features & ThemeFields::ICONS); + ui->iconsLabel->setEnabled(features & ThemeFields::ICONS); + ui->widgetStyleComboBox->setEnabled(features & ThemeFields::WIDGETS); + ui->widgetThemeLabel->setEnabled(features & ThemeFields::WIDGETS); + ui->backgroundCatComboBox->setEnabled(features & ThemeFields::CAT); + ui->backgroundCatLabel->setEnabled(features & ThemeFields::CAT); +} + +void ThemeCustomizationWidget::applyIconTheme(int index) { + auto settings = APPLICATION->settings(); + auto original = settings->get("IconTheme").toString(); + settings->set("IconTheme", m_iconThemeOptions[index].first); + + if (original != settings->get("IconTheme")) { + APPLICATION->applyCurrentlySelectedTheme(); + } + + emit currentIconThemeChanged(index); +} + +void ThemeCustomizationWidget::applyWidgetTheme(int index) { + auto settings = APPLICATION->settings(); + auto originalAppTheme = settings->get("ApplicationTheme").toString(); + auto newAppTheme = ui->widgetStyleComboBox->currentData().toString(); + if (originalAppTheme != newAppTheme) { + settings->set("ApplicationTheme", newAppTheme); + APPLICATION->applyCurrentlySelectedTheme(); + } + + emit currentWidgetThemeChanged(index); +} + +void ThemeCustomizationWidget::applyCatTheme(int index) { + auto settings = APPLICATION->settings(); + settings->set("BackgroundCat", m_catOptions[index].first); + + emit currentCatChanged(index); +} + +void ThemeCustomizationWidget::applySettings() +{ + applyIconTheme(ui->iconsComboBox->currentIndex()); + applyWidgetTheme(ui->widgetStyleComboBox->currentIndex()); + applyCatTheme(ui->backgroundCatComboBox->currentIndex()); +} +void ThemeCustomizationWidget::loadSettings() +{ + auto settings = APPLICATION->settings(); + + auto iconTheme = settings->get("IconTheme").toString(); + for (auto& iconThemeFromList : m_iconThemeOptions) { + ui->iconsComboBox->addItem(QIcon(QString(":/icons/%1/scalable/settings").arg(iconThemeFromList.first)), iconThemeFromList.second); + if (iconTheme == iconThemeFromList.first) { + ui->iconsComboBox->setCurrentIndex(ui->iconsComboBox->count() - 1); + } + } + + { + auto currentTheme = settings->get("ApplicationTheme").toString(); + auto themes = APPLICATION->getValidApplicationThemes(); + int idx = 0; + for (auto& theme : themes) { + ui->widgetStyleComboBox->addItem(theme->name(), theme->id()); + if (currentTheme == theme->id()) { + ui->widgetStyleComboBox->setCurrentIndex(idx); + } + idx++; + } + } + + auto cat = settings->get("BackgroundCat").toString(); + for (auto& catFromList : m_catOptions) { + ui->backgroundCatComboBox->addItem(QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first))), + catFromList.second); + if (cat == catFromList.first) { + ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1); + } + } +} + +void ThemeCustomizationWidget::retranslate() +{ + ui->retranslateUi(this); +} diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h index d450e8df..be2c4492 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.h +++ b/launcher/ui/widgets/ThemeCustomizationWidget.h @@ -1,77 +1,77 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Tayou - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#include -#include "translations/TranslationsModel.h" - -enum ThemeFields { NONE = 0b0000, ICONS = 0b0001, WIDGETS = 0b0010, CAT = 0b0100 }; - -namespace Ui { -class ThemeCustomizationWidget; -} - -class ThemeCustomizationWidget : public QWidget { - Q_OBJECT - - public: - explicit ThemeCustomizationWidget(QWidget* parent = nullptr); - ~ThemeCustomizationWidget(); - - void showFeatures(ThemeFields features); - - void applySettings(); - - void loadSettings(); - void retranslate(); - - private slots: - void applyIconTheme(int index); - void applyWidgetTheme(int index); - void applyCatTheme(int index); - - signals: - int currentIconThemeChanged(int index); - int currentWidgetThemeChanged(int index); - int currentCatChanged(int index); - - private: - Ui::ThemeCustomizationWidget* ui; - - //TODO finish implementing - QList> m_iconThemeOptions{ - { "pe_colored", QObject::tr("Simple (Colored Icons)") }, - { "pe_light", QObject::tr("Simple (Light Icons)") }, - { "pe_dark", QObject::tr("Simple (Dark Icons)") }, - { "pe_blue", QObject::tr("Simple (Blue Icons)") }, - { "breeze_light", QObject::tr("Breeze Light") }, - { "breeze_dark", QObject::tr("Breeze Dark") }, - { "OSX", QObject::tr("OSX") }, - { "iOS", QObject::tr("iOS") }, - { "flat", QObject::tr("Flat") }, - { "flat_white", QObject::tr("Flat (White)") }, - { "multimc", QObject::tr("Legacy") }, - { "custom", QObject::tr("Custom") } - }; - QList> m_catOptions{ - { "kitteh", QObject::tr("Background Cat (from MultiMC)") }, - { "rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)") }, - { "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") }, - { "teawie", QObject::tr("Teawie (drawn by SympathyTea)") } - }; +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Tayou + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "translations/TranslationsModel.h" + +enum ThemeFields { NONE = 0b0000, ICONS = 0b0001, WIDGETS = 0b0010, CAT = 0b0100 }; + +namespace Ui { +class ThemeCustomizationWidget; +} + +class ThemeCustomizationWidget : public QWidget { + Q_OBJECT + + public: + explicit ThemeCustomizationWidget(QWidget* parent = nullptr); + ~ThemeCustomizationWidget(); + + void showFeatures(ThemeFields features); + + void applySettings(); + + void loadSettings(); + void retranslate(); + + private slots: + void applyIconTheme(int index); + void applyWidgetTheme(int index); + void applyCatTheme(int index); + + signals: + int currentIconThemeChanged(int index); + int currentWidgetThemeChanged(int index); + int currentCatChanged(int index); + + private: + Ui::ThemeCustomizationWidget* ui; + + //TODO finish implementing + QList> m_iconThemeOptions{ + { "pe_colored", QObject::tr("Simple (Colored Icons)") }, + { "pe_light", QObject::tr("Simple (Light Icons)") }, + { "pe_dark", QObject::tr("Simple (Dark Icons)") }, + { "pe_blue", QObject::tr("Simple (Blue Icons)") }, + { "breeze_light", QObject::tr("Breeze Light") }, + { "breeze_dark", QObject::tr("Breeze Dark") }, + { "OSX", QObject::tr("OSX") }, + { "iOS", QObject::tr("iOS") }, + { "flat", QObject::tr("Flat") }, + { "flat_white", QObject::tr("Flat (White)") }, + { "multimc", QObject::tr("Legacy") }, + { "custom", QObject::tr("Custom") } + }; + QList> m_catOptions{ + { "kitteh", QObject::tr("Background Cat (from MultiMC)") }, + { "rory", QObject::tr("Rory ID 11 (drawn by Ashtaka)") }, + { "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") }, + { "teawie", QObject::tr("Teawie (drawn by SympathyTea)") } + }; }; \ No newline at end of file diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index 15ba831e..b2772983 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -1,105 +1,105 @@ - - - ThemeCustomizationWidget - - - - 0 - 0 - 400 - 311 - - - - Form - - - - QLayout::SetMinimumSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - &Icons - - - iconsComboBox - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - - - - - &Colors - - - widgetStyleComboBox - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - - - - - C&at - - - backgroundCatComboBox - - - - - - - - 0 - 0 - - - - Qt::StrongFocus - - - - - - - - + + + ThemeCustomizationWidget + + + + 0 + 0 + 400 + 311 + + + + Form + + + + QLayout::SetMinimumSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Icons + + + iconsComboBox + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + &Colors + + + widgetStyleComboBox + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + C&at + + + backgroundCatComboBox + + + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + + + + + + -- cgit From 668b19d11948bedeff6908d76d63f5a5fad4eb02 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 9 Jan 2023 18:40:58 +0100 Subject: Add hint about Cat Signed-off-by: Tayou --- launcher/ui/setupwizard/ThemeWizardPage.ui | 15 ++++++- launcher/ui/widgets/ThemeCustomizationWidget.cpp | 14 ++++--- launcher/ui/widgets/ThemeCustomizationWidget.h | 2 +- launcher/ui/widgets/ThemeCustomizationWidget.ui | 51 ++++++++++++++++++------ 4 files changed, 62 insertions(+), 20 deletions(-) (limited to 'launcher/ui/widgets') diff --git a/launcher/ui/setupwizard/ThemeWizardPage.ui b/launcher/ui/setupwizard/ThemeWizardPage.ui index 1ab04fc8..01394ea4 100644 --- a/launcher/ui/setupwizard/ThemeWizardPage.ui +++ b/launcher/ui/setupwizard/ThemeWizardPage.ui @@ -31,6 +31,16 @@ + + + + Hint: The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. + + + true + + + @@ -41,7 +51,7 @@ - Icon Preview: + Preview: @@ -317,6 +327,9 @@ 256 + + The cat appears in the background and does not serve a purpose, it is purely visual. + diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.cpp b/launcher/ui/widgets/ThemeCustomizationWidget.cpp index d0b5be21..dcf13303 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.cpp +++ b/launcher/ui/widgets/ThemeCustomizationWidget.cpp @@ -72,10 +72,11 @@ void ThemeCustomizationWidget::showFeatures(ThemeFields features) { void ThemeCustomizationWidget::applyIconTheme(int index) { auto settings = APPLICATION->settings(); - auto original = settings->get("IconTheme").toString(); - settings->set("IconTheme", m_iconThemeOptions[index].first); + auto originalIconTheme = settings->get("IconTheme").toString(); + auto& newIconTheme = m_iconThemeOptions[index].first; + settings->set("IconTheme", newIconTheme); - if (original != settings->get("IconTheme")) { + if (originalIconTheme != newIconTheme) { APPLICATION->applyCurrentlySelectedTheme(); } @@ -113,7 +114,8 @@ void ThemeCustomizationWidget::loadSettings() auto iconTheme = settings->get("IconTheme").toString(); for (auto& iconThemeFromList : m_iconThemeOptions) { - ui->iconsComboBox->addItem(QIcon(QString(":/icons/%1/scalable/settings").arg(iconThemeFromList.first)), iconThemeFromList.second); + QIcon iconForComboBox = QIcon(QString(":/icons/%1/scalable/settings").arg(iconThemeFromList.first)); + ui->iconsComboBox->addItem(iconForComboBox, iconThemeFromList.second); if (iconTheme == iconThemeFromList.first) { ui->iconsComboBox->setCurrentIndex(ui->iconsComboBox->count() - 1); } @@ -134,8 +136,8 @@ void ThemeCustomizationWidget::loadSettings() auto cat = settings->get("BackgroundCat").toString(); for (auto& catFromList : m_catOptions) { - ui->backgroundCatComboBox->addItem(QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first))), - catFromList.second); + QIcon catIcon = QIcon(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage(catFromList.first))); + ui->backgroundCatComboBox->addItem(catIcon, catFromList.second); if (cat == catFromList.first) { ui->backgroundCatComboBox->setCurrentIndex(ui->backgroundCatComboBox->count() - 1); } diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.h b/launcher/ui/widgets/ThemeCustomizationWidget.h index be2c4492..d955a266 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.h +++ b/launcher/ui/widgets/ThemeCustomizationWidget.h @@ -74,4 +74,4 @@ class ThemeCustomizationWidget : public QWidget { { "rory-flat", QObject::tr("Rory ID 11 (flat edition, drawn by Ashtaka)") }, { "teawie", QObject::tr("Teawie (drawn by SympathyTea)") } }; -}; \ No newline at end of file +}; diff --git a/launcher/ui/widgets/ThemeCustomizationWidget.ui b/launcher/ui/widgets/ThemeCustomizationWidget.ui index b2772983..f216a610 100644 --- a/launcher/ui/widgets/ThemeCustomizationWidget.ui +++ b/launcher/ui/widgets/ThemeCustomizationWidget.ui @@ -7,7 +7,7 @@ 0 0 400 - 311 + 191 @@ -77,6 +77,9 @@ + + The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. + C&at @@ -86,17 +89,41 @@ - - - - 0 - 0 - - - - Qt::StrongFocus - - + + + + + + 0 + 0 + + + + Qt::StrongFocus + + + The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. + + + + + + + The cat appears in the background and is not shown by default. It is only made visible when pressing the Cat button in the Toolbar. + + + + + + + .. + + + true + + + + -- cgit From 6a1807995390b2a2cbe074ee1f47d3791e0e3f10 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 25 Nov 2022 09:23:46 -0300 Subject: refactor: generalize mod models and APIs to resources Firstly, this abstract away behavior in the mod download models that can also be applied to other types of resources into a superclass, allowing other resource types to be implemented without so much code duplication. For that, this also generalizes the APIs used (currently, ModrinthAPI and FlameAPI) to be able to make requests to other types of resources. It also does a general cleanup of both of those. In particular, this makes use of std::optional instead of invalid values for errors and, well, optional values :p This is a squash of some commits that were becoming too interlaced together to be cleanly separated. Signed-off-by: flow --- launcher/CMakeLists.txt | 37 ++- launcher/ModDownloadTask.cpp | 72 ----- launcher/ModDownloadTask.h | 57 ---- launcher/ResourceDownloadTask.cpp | 80 +++++ launcher/ResourceDownloadTask.h | 57 ++++ launcher/minecraft/PackProfile.cpp | 28 +- launcher/minecraft/PackProfile.h | 4 +- launcher/modplatform/CheckUpdateTask.h | 14 +- launcher/modplatform/EnsureMetadataTask.cpp | 14 +- launcher/modplatform/EnsureMetadataTask.h | 6 +- launcher/modplatform/ModAPI.h | 118 ------- launcher/modplatform/ModIndex.cpp | 24 +- launcher/modplatform/ModIndex.h | 19 +- launcher/modplatform/ResourceAPI.h | 149 +++++++++ launcher/modplatform/flame/FlameAPI.cpp | 16 +- launcher/modplatform/flame/FlameAPI.h | 99 +++--- launcher/modplatform/flame/FlameCheckUpdate.cpp | 11 +- launcher/modplatform/flame/FlameCheckUpdate.h | 2 +- .../flame/FlameInstanceCreationTask.cpp | 4 +- .../modplatform/flame/FlameInstanceCreationTask.h | 2 +- launcher/modplatform/flame/FlameModIndex.cpp | 4 +- launcher/modplatform/helpers/HashUtils.cpp | 16 +- launcher/modplatform/helpers/HashUtils.h | 10 +- launcher/modplatform/helpers/NetworkModAPI.cpp | 97 ------ launcher/modplatform/helpers/NetworkModAPI.h | 17 - .../modplatform/helpers/NetworkResourceAPI.cpp | 124 +++++++ launcher/modplatform/helpers/NetworkResourceAPI.h | 18 ++ launcher/modplatform/modrinth/ModrinthAPI.cpp | 36 ++- launcher/modplatform/modrinth/ModrinthAPI.h | 106 +++--- .../modplatform/modrinth/ModrinthCheckUpdate.cpp | 25 +- .../modplatform/modrinth/ModrinthCheckUpdate.h | 2 +- .../modplatform/modrinth/ModrinthPackIndex.cpp | 4 +- launcher/modplatform/packwiz/Packwiz.cpp | 8 +- launcher/modplatform/packwiz/Packwiz.h | 2 +- launcher/net/NetAction.h | 4 - launcher/net/NetJob.cpp | 5 +- launcher/ui/dialogs/BlockedModsDialog.cpp | 2 +- launcher/ui/dialogs/ChooseProviderDialog.cpp | 6 +- launcher/ui/dialogs/ChooseProviderDialog.h | 6 +- launcher/ui/dialogs/ModDownloadDialog.cpp | 165 +--------- launcher/ui/dialogs/ModDownloadDialog.h | 43 +-- launcher/ui/dialogs/ModUpdateDialog.cpp | 44 +-- launcher/ui/dialogs/ModUpdateDialog.h | 8 +- launcher/ui/dialogs/ResourceDownloadDialog.cpp | 152 +++++++++ launcher/ui/dialogs/ResourceDownloadDialog.h | 55 ++++ launcher/ui/dialogs/ReviewMessageBox.cpp | 4 +- launcher/ui/dialogs/ReviewMessageBox.h | 8 +- launcher/ui/pages/instance/ModFolderPage.cpp | 6 +- launcher/ui/pages/instance/ResourcePackPage.h | 1 + launcher/ui/pages/modplatform/ModModel.cpp | 274 +++------------- launcher/ui/pages/modplatform/ModModel.h | 64 +--- launcher/ui/pages/modplatform/ModPage.cpp | 357 +++------------------ launcher/ui/pages/modplatform/ModPage.h | 78 +---- launcher/ui/pages/modplatform/ModPage.ui | 118 ------- launcher/ui/pages/modplatform/ResourceModel.cpp | 258 +++++++++++++++ launcher/ui/pages/modplatform/ResourceModel.h | 101 ++++++ launcher/ui/pages/modplatform/ResourcePage.cpp | 347 ++++++++++++++++++++ launcher/ui/pages/modplatform/ResourcePage.h | 95 ++++++ launcher/ui/pages/modplatform/ResourcePage.ui | 118 +++++++ .../ui/pages/modplatform/flame/FlameModModel.cpp | 31 -- .../ui/pages/modplatform/flame/FlameModModel.h | 26 -- .../ui/pages/modplatform/flame/FlameModPage.cpp | 97 ------ launcher/ui/pages/modplatform/flame/FlameModPage.h | 70 ---- .../modplatform/flame/FlameResourceModels.cpp | 31 ++ .../pages/modplatform/flame/FlameResourceModels.h | 26 ++ .../pages/modplatform/flame/FlameResourcePages.cpp | 97 ++++++ .../pages/modplatform/flame/FlameResourcePages.h | 71 ++++ .../modplatform/modrinth/ModrinthModModel.cpp | 48 --- .../pages/modplatform/modrinth/ModrinthModModel.h | 44 --- .../pages/modplatform/modrinth/ModrinthModPage.cpp | 84 ----- .../pages/modplatform/modrinth/ModrinthModPage.h | 66 ---- .../modrinth/ModrinthResourceModels.cpp | 53 +++ .../modplatform/modrinth/ModrinthResourceModels.h | 49 +++ .../modplatform/modrinth/ModrinthResourcePages.cpp | 89 +++++ .../modplatform/modrinth/ModrinthResourcePages.h | 72 +++++ launcher/ui/widgets/ProgressWidget.cpp | 6 +- launcher/ui/widgets/ProgressWidget.h | 6 +- tests/Packwiz_test.cpp | 4 +- 78 files changed, 2508 insertions(+), 2063 deletions(-) delete mode 100644 launcher/ModDownloadTask.cpp delete mode 100644 launcher/ModDownloadTask.h create mode 100644 launcher/ResourceDownloadTask.cpp create mode 100644 launcher/ResourceDownloadTask.h delete mode 100644 launcher/modplatform/ModAPI.h create mode 100644 launcher/modplatform/ResourceAPI.h delete mode 100644 launcher/modplatform/helpers/NetworkModAPI.cpp delete mode 100644 launcher/modplatform/helpers/NetworkModAPI.h create mode 100644 launcher/modplatform/helpers/NetworkResourceAPI.cpp create mode 100644 launcher/modplatform/helpers/NetworkResourceAPI.h create mode 100644 launcher/ui/dialogs/ResourceDownloadDialog.cpp create mode 100644 launcher/ui/dialogs/ResourceDownloadDialog.h delete mode 100644 launcher/ui/pages/modplatform/ModPage.ui create mode 100644 launcher/ui/pages/modplatform/ResourceModel.cpp create mode 100644 launcher/ui/pages/modplatform/ResourceModel.h create mode 100644 launcher/ui/pages/modplatform/ResourcePage.cpp create mode 100644 launcher/ui/pages/modplatform/ResourcePage.h create mode 100644 launcher/ui/pages/modplatform/ResourcePage.ui delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.cpp delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModModel.h delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.cpp delete mode 100644 launcher/ui/pages/modplatform/flame/FlameModPage.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourceModels.h create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp create mode 100644 launcher/ui/pages/modplatform/flame/FlameResourcePages.h delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp delete mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp create mode 100644 launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h (limited to 'launcher/ui/widgets') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index eec6c787..a1a68f5b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -38,9 +38,9 @@ set(CORE_SOURCES InstanceImportTask.h InstanceImportTask.cpp - # Mod downloading task - ModDownloadTask.h - ModDownloadTask.cpp + # Resource downloading task + ResourceDownloadTask.h + ResourceDownloadTask.cpp # Use tracking separate from memory management Usable.h @@ -473,7 +473,7 @@ set(API_SOURCES modplatform/ModIndex.h modplatform/ModIndex.cpp - modplatform/ModAPI.h + modplatform/ResourceAPI.h modplatform/EnsureMetadataTask.h modplatform/EnsureMetadataTask.cpp @@ -484,8 +484,8 @@ set(API_SOURCES modplatform/flame/FlameAPI.cpp modplatform/modrinth/ModrinthAPI.h modplatform/modrinth/ModrinthAPI.cpp - modplatform/helpers/NetworkModAPI.h - modplatform/helpers/NetworkModAPI.cpp + modplatform/helpers/NetworkResourceAPI.h + modplatform/helpers/NetworkResourceAPI.cpp modplatform/helpers/HashUtils.h modplatform/helpers/HashUtils.cpp modplatform/helpers/OverrideUtils.h @@ -771,6 +771,11 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/VanillaPage.cpp ui/pages/modplatform/VanillaPage.h + ui/pages/modplatform/ResourcePage.cpp + ui/pages/modplatform/ResourcePage.h + ui/pages/modplatform/ResourceModel.cpp + ui/pages/modplatform/ResourceModel.h + ui/pages/modplatform/ModPage.cpp ui/pages/modplatform/ModPage.h ui/pages/modplatform/ModModel.cpp @@ -803,10 +808,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/flame/FlameModel.h ui/pages/modplatform/flame/FlamePage.cpp ui/pages/modplatform/flame/FlamePage.h - ui/pages/modplatform/flame/FlameModModel.cpp - ui/pages/modplatform/flame/FlameModModel.h - ui/pages/modplatform/flame/FlameModPage.cpp - ui/pages/modplatform/flame/FlameModPage.h + ui/pages/modplatform/flame/FlameResourceModels.cpp + ui/pages/modplatform/flame/FlameResourceModels.h + ui/pages/modplatform/flame/FlameResourcePages.cpp + ui/pages/modplatform/flame/FlameResourcePages.h ui/pages/modplatform/modrinth/ModrinthPage.cpp ui/pages/modplatform/modrinth/ModrinthPage.h @@ -821,10 +826,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ImportPage.cpp ui/pages/modplatform/ImportPage.h - ui/pages/modplatform/modrinth/ModrinthModModel.cpp - ui/pages/modplatform/modrinth/ModrinthModModel.h - ui/pages/modplatform/modrinth/ModrinthModPage.cpp - ui/pages/modplatform/modrinth/ModrinthModPage.h + ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp + ui/pages/modplatform/modrinth/ModrinthResourceModels.h + ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp + ui/pages/modplatform/modrinth/ModrinthResourcePages.h # GUI - dialogs ui/dialogs/AboutDialog.cpp @@ -869,6 +874,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/VersionSelectDialog.h ui/dialogs/SkinUploadDialog.cpp ui/dialogs/SkinUploadDialog.h + ui/dialogs/ResourceDownloadDialog.cpp + ui/dialogs/ResourceDownloadDialog.h ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h ui/dialogs/ScrollMessageBox.cpp @@ -965,7 +972,7 @@ qt_wrap_ui(LAUNCHER_UI ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui ui/pages/modplatform/atlauncher/AtlPage.ui ui/pages/modplatform/VanillaPage.ui - ui/pages/modplatform/ModPage.ui + ui/pages/modplatform/ResourcePage.ui ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/ImportPage.ui diff --git a/launcher/ModDownloadTask.cpp b/launcher/ModDownloadTask.cpp deleted file mode 100644 index 2b0343f4..00000000 --- a/launcher/ModDownloadTask.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#include "ModDownloadTask.h" - -#include "Application.h" -#include "minecraft/mod/ModFolderModel.h" - -ModDownloadTask::ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed) - : m_mod(mod), m_mod_version(version), mods(mods) -{ - if (is_indexed) { - m_update_task.reset(new LocalModUpdateTask(mods->indexDir(), m_mod, m_mod_version)); - connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ModDownloadTask::hasOldMod); - - addTask(m_update_task); - } - - m_filesNetJob.reset(new NetJob(tr("Mod download"), APPLICATION->network())); - m_filesNetJob->setStatus(tr("Downloading mod:\n%1").arg(m_mod_version.downloadUrl)); - - m_filesNetJob->addNetAction(Net::Download::makeFile(m_mod_version.downloadUrl, mods->dir().absoluteFilePath(getFilename()))); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ModDownloadTask::downloadSucceeded); - connect(m_filesNetJob.get(), &NetJob::progress, this, &ModDownloadTask::downloadProgressChanged); - connect(m_filesNetJob.get(), &NetJob::failed, this, &ModDownloadTask::downloadFailed); - - addTask(m_filesNetJob); -} - -void ModDownloadTask::downloadSucceeded() -{ - m_filesNetJob.reset(); - auto name = std::get<0>(to_delete); - auto filename = std::get<1>(to_delete); - if (!name.isEmpty() && filename != m_mod_version.fileName) { - mods->uninstallMod(filename, true); - } -} - -void ModDownloadTask::downloadFailed(QString reason) -{ - emitFailed(reason); - m_filesNetJob.reset(); -} - -void ModDownloadTask::downloadProgressChanged(qint64 current, qint64 total) -{ - emit progress(current, total); -} - -// This indirection is done so that we don't delete a mod before being sure it was -// downloaded successfully! -void ModDownloadTask::hasOldMod(QString name, QString filename) -{ - to_delete = {name, filename}; -} diff --git a/launcher/ModDownloadTask.h b/launcher/ModDownloadTask.h deleted file mode 100644 index 95020470..00000000 --- a/launcher/ModDownloadTask.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#pragma once - -#include "net/NetJob.h" -#include "tasks/SequentialTask.h" - -#include "modplatform/ModIndex.h" -#include "minecraft/mod/tasks/LocalModUpdateTask.h" - -class ModFolderModel; - -class ModDownloadTask : public SequentialTask { - Q_OBJECT -public: - explicit ModDownloadTask(ModPlatform::IndexedPack mod, ModPlatform::IndexedVersion version, const std::shared_ptr mods, bool is_indexed = true); - const QString& getFilename() const { return m_mod_version.fileName; } - -private: - ModPlatform::IndexedPack m_mod; - ModPlatform::IndexedVersion m_mod_version; - const std::shared_ptr mods; - - NetJob::Ptr m_filesNetJob; - LocalModUpdateTask::Ptr m_update_task; - - void downloadProgressChanged(qint64 current, qint64 total); - - void downloadFailed(QString reason); - - void downloadSucceeded(); - - std::tuple to_delete {"", ""}; - -private slots: - void hasOldMod(QString name, QString filename); -}; - - - diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp new file mode 100644 index 00000000..687eaf51 --- /dev/null +++ b/launcher/ResourceDownloadTask.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "ResourceDownloadTask.h" + +#include "Application.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + +ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack, + ModPlatform::IndexedVersion version, + const std::shared_ptr packs, + bool is_indexed) + : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) +{ + if (auto model = dynamic_cast(m_pack_model.get()); model && is_indexed) { + m_update_task.reset(new LocalModUpdateTask(model->indexDir(), m_pack, m_pack_version)); + connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource); + + addTask(m_update_task); + } + + m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); + m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); + + m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()))); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); + connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed); + + addTask(m_filesNetJob); +} + +void ResourceDownloadTask::downloadSucceeded() +{ + m_filesNetJob.reset(); + auto name = std::get<0>(to_delete); + auto filename = std::get<1>(to_delete); + if (!name.isEmpty() && filename != m_pack_version.fileName) { + if (auto model = dynamic_cast(m_pack_model.get()); model) + model->uninstallMod(filename, true); + else + m_pack_model->uninstallResource(filename); + } +} + +void ResourceDownloadTask::downloadFailed(QString reason) +{ + emitFailed(reason); + m_filesNetJob.reset(); +} + +void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) +{ + emit progress(current, total); +} + +// This indirection is done so that we don't delete a mod before being sure it was +// downloaded successfully! +void ResourceDownloadTask::hasOldResource(QString name, QString filename) +{ + to_delete = { name, filename }; +} diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h new file mode 100644 index 00000000..350c2edd --- /dev/null +++ b/launcher/ResourceDownloadTask.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* Copyright (C) 2022 Sefa Eyeoglu +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#pragma once + +#include "net/NetJob.h" +#include "tasks/SequentialTask.h" + +#include "modplatform/ModIndex.h" +#include "minecraft/mod/tasks/LocalModUpdateTask.h" + +class ResourceFolderModel; + +class ResourceDownloadTask : public SequentialTask { + Q_OBJECT +public: + explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr packs, bool is_indexed = true); + const QString& getFilename() const { return m_pack_version.fileName; } + +private: + ModPlatform::IndexedPack m_pack; + ModPlatform::IndexedVersion m_pack_version; + const std::shared_ptr m_pack_model; + + NetJob::Ptr m_filesNetJob; + LocalModUpdateTask::Ptr m_update_task; + + void downloadProgressChanged(qint64 current, qint64 total); + + void downloadFailed(QString reason); + + void downloadSucceeded(); + + std::tuple to_delete {"", ""}; + +private slots: + void hasOldResource(QString name, QString filename); +}; + + + diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 43fa3f8d..42021b3c 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -55,12 +55,13 @@ #include "PackProfile_p.h" #include "ComponentUpdateTask.h" -#include "modplatform/ModAPI.h" +#include "Application.h" +#include "modplatform/ResourceAPI.h" -static const QMap modloaderMapping{ - {"net.minecraftforge", ModAPI::Forge}, - {"net.fabricmc.fabric-loader", ModAPI::Fabric}, - {"org.quiltmc.quilt-loader", ModAPI::Quilt} +static const QMap modloaderMapping{ + {"net.minecraftforge", ResourceAPI::Forge}, + {"net.fabricmc.fabric-loader", ResourceAPI::Fabric}, + {"org.quiltmc.quilt-loader", ResourceAPI::Quilt} }; PackProfile::PackProfile(MinecraftInstance * instance) @@ -1066,19 +1067,22 @@ void PackProfile::disableInteraction(bool disable) } } -ModAPI::ModLoaderTypes PackProfile::getModLoaders() +std::optional PackProfile::getModLoaders() { - ModAPI::ModLoaderTypes result = ModAPI::Unspecified; + ResourceAPI::ModLoaderTypes result; + bool has_any_loader = false; - QMapIterator i(modloaderMapping); + QMapIterator i(modloaderMapping); - while (i.hasNext()) - { + while (i.hasNext()) { i.next(); - Component* c = getComponent(i.key()); - if (c != nullptr && c->isEnabled()) { + if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) { result |= i.value(); + has_any_loader = true; } } + + if (!has_any_loader) + return {}; return result; } diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 2330cca1..67b418f4 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -49,7 +49,7 @@ #include "BaseVersion.h" #include "MojangDownloadInfo.h" #include "net/Mode.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" class MinecraftInstance; struct PackProfileData; @@ -145,7 +145,7 @@ public: // todo(merged): is this the best approach void appendComponent(ComponentPtr component); - ModAPI::ModLoaderTypes getModLoaders(); + std::optional getModLoaders(); private: void scheduleSave(); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index 91922034..932a62d9 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -1,18 +1,18 @@ #pragma once #include "minecraft/mod/Mod.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" #include "modplatform/ModIndex.h" #include "tasks/Task.h" -class ModDownloadTask; +class ResourceDownloadTask; class ModFolderModel; class CheckUpdateTask : public Task { Q_OBJECT public: - CheckUpdateTask(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + CheckUpdateTask(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder) {}; struct UpdatableMod { @@ -21,11 +21,11 @@ class CheckUpdateTask : public Task { QString old_version; QString new_version; QString changelog; - ModPlatform::Provider provider; - ModDownloadTask* download; + ModPlatform::ResourceProvider provider; + ResourceDownloadTask* download; public: - UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::Provider p, ModDownloadTask* t) + UpdatableMod(QString name, QString old_h, QString old_v, QString new_v, QString changelog, ModPlatform::ResourceProvider p, ResourceDownloadTask* t) : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t) {} }; @@ -44,7 +44,7 @@ class CheckUpdateTask : public Task { protected: QList& m_mods; std::list& m_game_versions; - ModAPI::ModLoaderTypes m_loaders; + std::optional m_loaders; std::shared_ptr m_mods_folder; std::vector m_updatable; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 234330a7..9bf81338 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -20,7 +20,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps; static ModrinthAPI modrinth_api; static FlameAPI flame_api; -EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) { auto hash_task = createNewHash(mod); @@ -31,7 +31,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider hash_task->start(); } -EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::Provider prov) +EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::ResourceProvider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10); @@ -110,10 +110,10 @@ void EnsureMetadataTask::executeTask() NetJob::Ptr version_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): version_task = modrinthVersionsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): version_task = flameVersionsTask(); break; } @@ -130,10 +130,10 @@ void EnsureMetadataTask::executeTask() NetJob::Ptr project_task; switch (m_provider) { - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): project_task = modrinthProjectsTask(); break; - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): project_task = flameProjectsTask(); break; } @@ -212,7 +212,7 @@ void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) NetJob::Ptr EnsureMetadataTask::modrinthVersionsTask() { - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); auto* response = new QByteArray(); auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response); diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index a8b0851e..a79e5861 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -14,8 +14,8 @@ class EnsureMetadataTask : public Task { Q_OBJECT public: - EnsureMetadataTask(Mod*, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); - EnsureMetadataTask(QList&, QDir, ModPlatform::Provider = ModPlatform::Provider::MODRINTH); + EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); + EnsureMetadataTask(QList&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH); ~EnsureMetadataTask() = default; @@ -57,7 +57,7 @@ class EnsureMetadataTask : public Task { private: QHash m_mods; QDir m_index_dir; - ModPlatform::Provider m_provider; + ModPlatform::ResourceProvider m_provider; QHash m_temp_versions; ConcurrentTask* m_hashing_task; diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h deleted file mode 100644 index 703de143..00000000 --- a/launcher/modplatform/ModAPI.h +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include "../Version.h" -#include "net/NetJob.h" - -namespace ModPlatform { -class ListModel; -struct IndexedPack; -} - -class ModAPI { - protected: - using CallerType = ModPlatform::ListModel; - - public: - virtual ~ModAPI() = default; - - enum ModLoaderType { - Unspecified = 0, - Forge = 1 << 0, - Cauldron = 1 << 1, - LiteLoader = 1 << 2, - Fabric = 1 << 3, - Quilt = 1 << 4 - }; - Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) - - struct SearchArgs { - int offset; - QString search; - QString sorting; - ModLoaderTypes loaders; - std::list versions; - }; - - virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; - virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) = 0; - - virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0; - virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0; - - - struct VersionSearchArgs { - QString addonId; - std::list mcVersions; - ModLoaderTypes loaders; - }; - - virtual void getVersions(VersionSearchArgs&& args, std::function callback) const = 0; - - static auto getModLoaderString(ModLoaderType type) -> const QString { - switch (type) { - case Unspecified: - break; - case Forge: - return "forge"; - case Cauldron: - return "cauldron"; - case LiteLoader: - return "liteloader"; - case Fabric: - return "fabric"; - case Quilt: - return "quilt"; - } - return ""; - } - - protected: - inline auto getGameVersionsString(std::list mcVersions) const -> QString - { - QString s; - for(auto& ver : mcVersions){ - s += QString("\"%1\",").arg(ver.toString()); - } - s.remove(s.length() - 1, 1); //remove last comma - return s; - } -}; diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 34fd9f30..6a507caf 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -24,47 +24,47 @@ namespace ModPlatform { -auto ProviderCapabilities::name(Provider p) -> const char* +auto ProviderCapabilities::name(ResourceProvider p) -> const char* { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "curseforge"; } return {}; } -auto ProviderCapabilities::readableName(Provider p) -> QString +auto ProviderCapabilities::readableName(ResourceProvider p) -> QString { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return "Modrinth"; - case Provider::FLAME: + case ResourceProvider::FLAME: return "CurseForge"; } return {}; } -auto ProviderCapabilities::hashType(Provider p) -> QStringList +auto ProviderCapabilities::hashType(ResourceProvider p) -> QStringList { switch (p) { - case Provider::MODRINTH: + case ResourceProvider::MODRINTH: return { "sha512", "sha1" }; - case Provider::FLAME: + case ResourceProvider::FLAME: // Try newer formats first, fall back to old format return { "sha1", "md5", "murmur2" }; } return {}; } -auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString +auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString type) -> QString { QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1; switch (p) { - case Provider::MODRINTH: { + case ResourceProvider::MODRINTH: { algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512; break; } - case Provider::FLAME: + case ResourceProvider::FLAME: algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5; break; } diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index 518fed7c..f65a6a4b 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -28,17 +28,16 @@ class QIODevice; namespace ModPlatform { -enum class Provider { - MODRINTH, - FLAME -}; +enum class ResourceProvider { MODRINTH, FLAME }; + +enum class ResourceType { MOD, RESOURCE_PACK }; class ProviderCapabilities { public: - auto name(Provider) -> const char*; - auto readableName(Provider) -> QString; - auto hashType(Provider) -> QStringList; - auto hash(Provider, QIODevice*, QString type = "") -> QString; + auto name(ResourceProvider) -> const char*; + auto readableName(ResourceProvider) -> QString; + auto hashType(ResourceProvider) -> QStringList; + auto hash(ResourceProvider, QIODevice*, QString type = "") -> QString; }; struct ModpackAuthor { @@ -81,7 +80,7 @@ struct ExtraPackData { struct IndexedPack { QVariant addonId; - Provider provider; + ResourceProvider provider; QString name; QString slug; QString description; @@ -101,4 +100,4 @@ struct IndexedPack { } // namespace ModPlatform Q_DECLARE_METATYPE(ModPlatform::IndexedPack) -Q_DECLARE_METATYPE(ModPlatform::Provider) +Q_DECLARE_METATYPE(ModPlatform::ResourceProvider) diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h new file mode 100644 index 00000000..d18a2caa --- /dev/null +++ b/launcher/modplatform/ResourceAPI.h @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +#include "../Version.h" + +#include "modplatform/ModIndex.h" +#include "net/NetJob.h" + +/* Simple class with a common interface for interacting with APIs */ +class ResourceAPI { + public: + virtual ~ResourceAPI() = default; + + enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 }; + Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType) + + struct SearchArgs { + ModPlatform::ResourceType type{}; + int offset = 0; + + std::optional search; + std::optional sorting; + std::optional loaders; + std::optional > versions; + }; + struct SearchCallbacks { + std::function on_succeed; + std::function on_fail; + std::function on_abort; + }; + + struct VersionSearchArgs { + QString addonId; + + std::optional > mcVersions; + std::optional loaders; + }; + struct VersionSearchCallbacks { + std::function on_succeed; + }; + + struct ProjectInfoArgs { + ModPlatform::IndexedPack& pack; + + void operator=(ProjectInfoArgs other) { pack = other.pack; } + }; + struct ProjectInfoCallbacks { + std::function on_succeed; + }; + + public slots: + [[nodiscard]] virtual NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProject(QString addonId, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const + { + qWarning() << "TODO"; + return nullptr; + } + + [[nodiscard]] virtual NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + [[nodiscard]] virtual NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const + { + qWarning() << "TODO"; + return nullptr; + } + + static auto getModLoaderString(ModLoaderType type) -> const QString + { + switch (type) { + case Forge: + return "forge"; + case Cauldron: + return "cauldron"; + case LiteLoader: + return "liteloader"; + case Fabric: + return "fabric"; + case Quilt: + return "quilt"; + default: + break; + } + return ""; + } + + protected: + [[nodiscard]] inline QString debugName() const { return "External resource API"; } + + [[nodiscard]] inline auto getGameVersionsString(std::list mcVersions) const -> QString + { + QString s; + for (auto& ver : mcVersions) { + s += QString("\"%1\",").arg(ver.toString()); + } + s.remove(s.length() - 1, 1); // remove last comma + return s; + } +}; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 4d71da21..ae401399 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -106,13 +106,19 @@ auto FlameAPI::getModDescription(int modId) -> QString auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion { + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return {}; + + auto versions_url = versions_url_optional.value(); + QEventLoop loop; auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.addonId), APPLICATION->network()); auto response = new QByteArray(); ModPlatform::IndexedVersion ver; - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] { QJsonParseError parse_error{}; @@ -161,7 +167,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe return ver; } -auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +NetJob::Ptr FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetProjects"), APPLICATION->network()); @@ -178,13 +184,13 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; } -auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob* +NetJob::Ptr FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const { auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); @@ -201,7 +207,7 @@ auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); - QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::finished, [response] { delete response; }); QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); return netJob; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 4c6ca64c..114a2716 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -1,21 +1,21 @@ #pragma once #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" -class FlameAPI : public NetworkModAPI { +class FlameAPI : public NetworkResourceAPI { public: - auto matchFingerprints(const QList& fingerprints, QByteArray* response) -> NetJob::Ptr; auto getModFileChangelog(int modId, int fileId) -> QString; auto getModDescription(int modId) -> QString; auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; - auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*; + NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; + NetJob::Ptr matchFingerprints(const QList& fingerprints, QByteArray* response); + NetJob::Ptr getFiles(const QStringList& fileIds, QByteArray* response) const; private: - inline auto getSortFieldInt(QString sortString) const -> int + static int getSortFieldInt(QString const& sortString) { return sortString == "Featured" ? 1 : sortString == "Popularity" ? 2 @@ -28,48 +28,16 @@ class FlameAPI : public NetworkModAPI { : 1; } - private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override - { - auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString(); - - return QString( - "https://api.curseforge.com/v1/mods/search?" - "gameId=432&" - "classId=6&" - - "index=%1&" - "pageSize=25&" - "searchFilter=%2&" - "sortField=%3&" - "sortOrder=desc&" - "modLoaderType=%4&" - "%5") - .arg(args.offset) - .arg(args.search) - .arg(getSortFieldInt(args.sorting)) - .arg(getMappedModLoader(args.loaders)) - .arg(gameVersionStr); - }; - - inline auto getModInfoURL(QString& id) const -> QString override + static int getClassId(ModPlatform::ResourceType type) { - return QString("https://api.curseforge.com/v1/mods/%1").arg(id); - }; - - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override - { - QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : ""; - QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders)); - - return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3") - .arg(args.addonId) - .arg(gameVersionQuery) - .arg(modLoaderQuery); - }; + switch (type) { + default: + case ModPlatform::ResourceType::MOD: + return 6; + } + } - public: - static auto getMappedModLoader(const ModLoaderTypes loaders) -> int + static int getMappedModLoader(ModLoaderTypes loaders) { // https://docs.curseforge.com/?http#tocS_ModLoaderType if (loaders & Forge) @@ -81,4 +49,43 @@ class FlameAPI : public NetworkModAPI { return 4; // Quilt would probably be 5 return 0; } + + private: + [[nodiscard]] std::optional getSearchURL(SearchArgs const& args) const override + { + auto gameVersionStr = args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString(); + + QStringList get_arguments; + get_arguments.append(QString("classId=%1").arg(getClassId(args.type))); + get_arguments.append(QString("index=%1").arg(args.offset)); + get_arguments.append("pageSize=25"); + if (args.search.has_value()) + get_arguments.append(QString("searchFilter=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("sortField=%1").arg(getSortFieldInt(args.sorting.value()))); + get_arguments.append("sortOrder=desc"); + if (args.loaders.has_value()) + get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); + get_arguments.append(gameVersionStr); + + return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); + }; + + [[nodiscard]] std::optional getInfoURL(QString const& id) const override + { + return QString("https://api.curseforge.com/v1/mods/%1").arg(id); + }; + + [[nodiscard]] std::optional getVersionsURL(VersionSearchArgs const& args) const override + { + QString url{QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(args.addonId)}; + + QStringList get_parameters; + if (args.mcVersions.has_value()) + get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); + if (args.loaders.has_value()) + get_parameters.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value()))); + + return url + get_parameters.join('&'); + }; }; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 8dd3a846..285fa49f 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -7,7 +7,10 @@ #include "FileSystem.h" #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" static FlameAPI api; @@ -160,7 +163,7 @@ void FlameCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; auto old_version = mod->version(); if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) { @@ -168,10 +171,10 @@ void FlameCheckUpdate::executeTask() old_version = current_ver.version; } - auto download_task = new ModDownloadTask(pack, latest_ver, m_mods_folder); + auto download_task = new ResourceDownloadTask(pack, latest_ver, m_mods_folder); m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version, api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()), - ModPlatform::Provider::FLAME, download_task); + ModPlatform::ResourceProvider::FLAME, download_task); } } diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index 163c706c..4a98d684 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -8,7 +8,7 @@ class FlameCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - FlameCheckUpdate(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + FlameCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index dc69769a..fb6f78e8 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -183,7 +183,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { + connect(job.get(), &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -225,7 +225,7 @@ bool FlameCreationTask::updateInstance() m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); - connect(job, &NetJob::finished, &loop, &QEventLoop::quit); + connect(job.get(), &NetJob::finished, &loop, &QEventLoop::quit); m_process_update_file_info_job = job; job->start(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 498e1d6e..36b62e3e 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -86,7 +86,7 @@ class FlameCreationTask final : public InstanceCreationTask { Flame::Manifest m_pack; // Handle to allow aborting - NetJob* m_process_update_file_info_job = nullptr; + NetJob::Ptr m_process_update_file_info_job = nullptr; NetJob::Ptr m_files_job = nullptr; QString m_managed_id, m_managed_version_id; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 32aa4bdb..617b98ce 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -11,7 +11,7 @@ static ModPlatform::ProviderCapabilities ProviderCaps; void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); - pack.provider = ModPlatform::Provider::FLAME; + pack.provider = ModPlatform::ResourceProvider::FLAME; pack.name = Json::requireString(obj, "name"); pack.slug = Json::requireString(obj, "slug"); pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); @@ -127,7 +127,7 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) -> auto hash_list = Json::ensureArray(obj, "hashes"); for (auto h : hash_list) { auto hash_entry = Json::ensureObject(h); - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::FLAME); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::FLAME); auto hash_algo = enumToString(Json::ensureInteger(hash_entry, "algo", 1, "algorithm")); if (hash_types.contains(hash_algo)) { file.hash = Json::requireString(hash_entry, "value"); diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp index f1e4759e..af484be0 100644 --- a/launcher/modplatform/helpers/HashUtils.cpp +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -12,12 +12,12 @@ namespace Hashing { static ModPlatform::ProviderCapabilities ProviderCaps; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider) { switch (provider) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: return createModrinthHasher(file_path); - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: return createFlameHasher(file_path); default: qCritical() << "[Hashing]" @@ -36,12 +36,12 @@ Hasher::Ptr createFlameHasher(QString file_path) return new FlameHasher(file_path); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) { return new BlockedModHasher(file_path, provider); } -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type) +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type) { auto hasher = new BlockedModHasher(file_path, provider); hasher->useHashType(type); @@ -62,8 +62,8 @@ void ModrinthHasher::executeTask() return; } - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); - m_hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type); + auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); + m_hash = ProviderCaps.hash(ModPlatform::ResourceProvider::MODRINTH, &file, hash_type); file.close(); @@ -92,7 +92,7 @@ void FlameHasher::executeTask() } -BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider) +BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider) : Hasher(file_path), provider(provider) { setObjectName(QString("BlockedModHasher: %1").arg(file_path)); hash_type = ProviderCaps.hashType(provider).first(); diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h index fa3244f6..91146a52 100644 --- a/launcher/modplatform/helpers/HashUtils.h +++ b/launcher/modplatform/helpers/HashUtils.h @@ -42,21 +42,21 @@ class ModrinthHasher : public Hasher { class BlockedModHasher : public Hasher { public: - BlockedModHasher(QString file_path, ModPlatform::Provider provider); + BlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); void executeTask() override; QStringList getHashTypes(); bool useHashType(QString type); private: - ModPlatform::Provider provider; + ModPlatform::ResourceProvider provider; QString hash_type; }; -Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider); +Hasher::Ptr createHasher(QString file_path, ModPlatform::ResourceProvider provider); Hasher::Ptr createFlameHasher(QString file_path); Hasher::Ptr createModrinthHasher(QString file_path); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider); -Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider); +Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::ResourceProvider provider, QString type); } // namespace Hashing diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp deleted file mode 100644 index 7633030e..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "NetworkModAPI.h" - -#include "ui/pages/modplatform/ModModel.h" - -#include "Application.h" -#include "net/NetJob.h" - -void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const -{ - auto netJob = new NetJob(QString("%1::Search").arg(caller->debugName()), APPLICATION->network()); - auto searchUrl = getModSearchURL(args); - - auto response = new QByteArray(); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::started, caller, [caller, netJob] { caller->setActiveJob(netJob); }); - QObject::connect(netJob, &NetJob::failed, caller, &CallerType::searchRequestFailed); - QObject::connect(netJob, &NetJob::aborted, caller, &CallerType::searchRequestAborted); - QObject::connect(netJob, &NetJob::succeeded, caller, [caller, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - caller->searchRequestFinished(doc); - }); - - netJob->start(); -} - -void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::function callback) -{ - auto response = new QByteArray(); - auto job = getProject(pack.addonId.toString(), response); - - QObject::connect(job, &NetJob::succeeded, [callback, &pack, response] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callback(doc, pack); - }); - - job->start(); -} - -void NetworkModAPI::getVersions(VersionSearchArgs&& args, std::function callback) const -{ - auto netJob = new NetJob(QString("ModVersions(%2)").arg(args.addonId), APPLICATION->network()); - auto response = new QByteArray(); - - netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); - - QObject::connect(netJob, &NetJob::succeeded, [response, callback, args] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset - << " reason: " << parse_error.errorString(); - qWarning() << *response; - return; - } - - callback(doc, args.addonId); - }); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - netJob->start(); -} - -auto NetworkModAPI::getProject(QString addonId, QByteArray* response) const -> NetJob* -{ - auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); - auto searchUrl = getModInfoURL(addonId); - - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); - - QObject::connect(netJob, &NetJob::finished, [response, netJob] { - netJob->deleteLater(); - delete response; - }); - - return netJob; -} diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h deleted file mode 100644 index b8af22c7..00000000 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "modplatform/ModAPI.h" - -class NetworkModAPI : public ModAPI { - public: - void searchMods(CallerType* caller, SearchArgs&& args) const override; - void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) override; - void getVersions(VersionSearchArgs&& args, std::function callback) const override; - - auto getProject(QString addonId, QByteArray* response) const -> NetJob* override; - - protected: - virtual auto getModSearchURL(SearchArgs& args) const -> QString = 0; - virtual auto getModInfoURL(QString& id) const -> QString = 0; - virtual auto getVersionsURL(VersionSearchArgs& args) const -> QString = 0; -}; diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp new file mode 100644 index 00000000..eb17008c --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -0,0 +1,124 @@ +#include "NetworkResourceAPI.h" + +#include "Application.h" +#include "net/NetJob.h" + +#include "modplatform/ModIndex.h" + +NetJob::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const +{ + auto search_url_optional = getSearchURL(args); + if (!search_url_optional.has_value()) { + callbacks.on_fail("Failed to create search URL", -1); + return nullptr; + } + + auto search_url = search_url_optional.value(); + + auto response = new QByteArray(); + auto netJob = new NetJob(QString("%1::Search").arg(debugName()), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); + + QObject::connect(netJob, &NetJob::succeeded, [=]{ + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + + callbacks.on_fail(parse_error.errorString(), -1); + + return; + } + + callbacks.on_succeed(doc); + }); + + QObject::connect(netJob, &NetJob::failed, [=](QString reason){ + int network_error_code = -1; + if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply) + network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + callbacks.on_fail(reason, network_error_code); + }); + QObject::connect(netJob, &NetJob::aborted, [=]{ + callbacks.on_abort(); + }); + + return netJob; +} + +NetJob::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfoCallbacks&& callbacks) const +{ + auto response = new QByteArray(); + auto job = getProject(args.pack.addonId.toString(), response); + + QObject::connect(job.get(), &NetJob::succeeded, [response, callbacks, args] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.pack); + }); + + return job; +} + +NetJob::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, VersionSearchCallbacks&& callbacks) const +{ + auto versions_url_optional = getVersionsURL(args); + if (!versions_url_optional.has_value()) + return nullptr; + + auto versions_url = versions_url_optional.value(); + + auto netJob = new NetJob(QString("%1::Versions").arg(args.addonId), APPLICATION->network()); + auto response = new QByteArray(); + + netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); + + QObject::connect(netJob, &NetJob::succeeded, [=] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + callbacks.on_succeed(doc, args.addonId); + }); + + QObject::connect(netJob, &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} + +NetJob::Ptr NetworkResourceAPI::getProject(QString addonId, QByteArray* response) const +{ + auto project_url_optional = getInfoURL(addonId); + if (!project_url_optional.has_value()) + return nullptr; + + auto project_url = project_url_optional.value(); + + auto netJob = new NetJob(QString("%1::GetProject").arg(addonId), APPLICATION->network()); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); + + QObject::connect(netJob, &NetJob::finished, [response] { + delete response; + }); + + return netJob; +} diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.h b/launcher/modplatform/helpers/NetworkResourceAPI.h new file mode 100644 index 00000000..834f274a --- /dev/null +++ b/launcher/modplatform/helpers/NetworkResourceAPI.h @@ -0,0 +1,18 @@ +#pragma once + +#include "modplatform/ResourceAPI.h" + +class NetworkResourceAPI : public ResourceAPI { + public: + NetJob::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const override; + + NetJob::Ptr getProject(QString addonId, QByteArray* response) const override; + + NetJob::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const override; + NetJob::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const override; + + protected: + [[nodiscard]] virtual auto getSearchURL(SearchArgs const& args) const -> std::optional = 0; + [[nodiscard]] virtual auto getInfoURL(QString const& id) const -> std::optional = 0; + [[nodiscard]] virtual auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional = 0; +}; diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 747cf4c3..8e64be09 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -37,21 +37,24 @@ auto ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_format auto ModrinthAPI::latestVersion(QString hash, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); QJsonObject body_obj; - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); @@ -66,8 +69,8 @@ auto ModrinthAPI::latestVersion(QString hash, auto ModrinthAPI::latestVersions(const QStringList& hashes, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr { auto* netJob = new NetJob(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); @@ -77,13 +80,16 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes, Json::writeStringList(body_obj, "hashes", hashes); Json::writeString(body_obj, "algorithm", hash_format); - Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders)); + if (loaders.has_value()) + Json::writeStringList(body_obj, "loaders", getModLoaderStrings(loaders.value())); - QStringList game_versions; - for (auto& ver : mcVersions) { - game_versions.append(ver.toString()); + if (mcVersions.has_value()) { + QStringList game_versions; + for (auto& ver : mcVersions.value()) { + game_versions.append(ver.toString()); + } + Json::writeStringList(body_obj, "game_versions", game_versions); } - Json::writeStringList(body_obj, "game_versions", game_versions); QJsonDocument body(body_obj); auto body_raw = body.toJson(); @@ -95,7 +101,7 @@ auto ModrinthAPI::latestVersions(const QStringList& hashes, return netJob; } -auto ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* +NetJob::Ptr ModrinthAPI::getProjects(QStringList addonIds, QByteArray* response) const { auto netJob = new NetJob(QString("Modrinth::GetProjects"), APPLICATION->network()); auto searchUrl = getMultipleModInfoURL(addonIds); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index e1a18681..bd84fb54 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -19,13 +19,12 @@ #pragma once #include "BuildConfig.h" -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" -#include "modplatform/helpers/NetworkModAPI.h" +#include "modplatform/helpers/NetworkResourceAPI.h" #include -class ModrinthAPI : public NetworkModAPI { +class ModrinthAPI : public NetworkResourceAPI { public: auto currentVersion(QString hash, QString hash_format, @@ -37,17 +36,17 @@ class ModrinthAPI : public NetworkModAPI { auto latestVersion(QString hash, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr; auto latestVersions(const QStringList& hashes, QString hash_format, - std::list mcVersions, - ModLoaderTypes loaders, + std::optional> mcVersions, + std::optional loaders, QByteArray* response) -> NetJob::Ptr; - auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; + NetJob::Ptr getProjects(QStringList addonIds, QByteArray* response) const override; public: inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; }; @@ -55,15 +54,13 @@ class ModrinthAPI : public NetworkModAPI { static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList { QStringList l; - for (auto loader : {Forge, Fabric, Quilt}) - { - if ((types & loader) || types == Unspecified) - { - l << ModAPI::getModLoaderString(loader); + for (auto loader : {Forge, Fabric, Quilt}) { + if (types & loader) { + l << getModLoaderString(loader); } } if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there - l << ModAPI::getModLoaderString(Fabric); + l << getModLoaderString(Fabric); return l; } @@ -78,28 +75,54 @@ class ModrinthAPI : public NetworkModAPI { } private: - inline auto getModSearchURL(SearchArgs& args) const -> QString override + [[nodiscard]] static QString resourceTypeParameter(ModPlatform::ResourceType type) { - if (!validateModLoaders(args.loaders)) { - qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; - return ""; + switch (type) { + case ModPlatform::ResourceType::MOD: + return "mod"; + default: + qWarning() << "Invalid resource type for Modrinth API!"; + break; } - return QString(BuildConfig.MODRINTH_PROD_URL + - "/search?" - "offset=%1&" - "limit=25&" - "query=%2&" - "index=%3&" - "facets=[[%4],%5[\"project_type:mod\"]]") - .arg(args.offset) - .arg(args.search) - .arg(args.sorting) - .arg(getModLoaderFilters(args.loaders)) - .arg(getGameVersionsArray(args.versions)); + return ""; + } + [[nodiscard]] QString createFacets(SearchArgs const& args) const + { + QStringList facets_list; + + if (args.loaders.has_value()) + facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value()))); + if (args.versions.has_value()) + facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value()))); + facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type))); + + return QString("[%1]").arg(facets_list.join(',')); + } + + public: + [[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional override + { + if (args.loaders.has_value()) { + if (!validateModLoaders(args.loaders.value())) { + qWarning() << "Modrinth only have Forge and Fabric-compatible mods!"; + return {}; + } + } + + QStringList get_arguments; + get_arguments.append(QString("offset=%1").arg(args.offset)); + get_arguments.append(QString("limit=25")); + if (args.search.has_value()) + get_arguments.append(QString("query=%1").arg(args.search.value())); + if (args.sorting.has_value()) + get_arguments.append(QString("index=%1").arg(args.sorting.value())); + get_arguments.append(QString("facets=%1").arg(createFacets(args))); + + return BuildConfig.MODRINTH_PROD_URL + "/search?" + get_arguments.join('&'); }; - inline auto getModInfoURL(QString& id) const -> QString override + inline auto getInfoURL(QString const& id) const -> std::optional override { return BuildConfig.MODRINTH_PROD_URL + "/project/" + id; }; @@ -109,15 +132,16 @@ class ModrinthAPI : public NetworkModAPI { return BuildConfig.MODRINTH_PROD_URL + QString("/projects?ids=[\"%1\"]").arg(ids.join("\",\"")); }; - inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override + inline auto getVersionsURL(VersionSearchArgs const& args) const -> std::optional override { - return QString(BuildConfig.MODRINTH_PROD_URL + - "/project/%1/version?" - "game_versions=[%2]&" - "loaders=[\"%3\"]") - .arg(args.addonId, - getGameVersionsString(args.mcVersions), - getModLoaderStrings(args.loaders).join("\",\"")); + QStringList get_arguments; + if (args.mcVersions.has_value()) + get_arguments.append(QString("game_versions=[%1]").arg(getGameVersionsString(args.mcVersions.value()))); + if (args.loaders.has_value()) + get_arguments.append(QString("loaders=[\"%1\"]").arg(getModLoaderStrings(args.loaders.value()).join("\",\""))); + + return QString("%1/project/%2/version%3%4") + .arg(BuildConfig.MODRINTH_PROD_URL, args.addonId, get_arguments.isEmpty() ? "" : "?", get_arguments.join('&')); }; auto getGameVersionsArray(std::list mcVersions) const -> QString @@ -127,12 +151,12 @@ class ModrinthAPI : public NetworkModAPI { s += QString("\"versions:%1\",").arg(ver.toString()); } s.remove(s.length() - 1, 1); //remove last comma - return s.isEmpty() ? QString() : QString("[%1],").arg(s); + return s.isEmpty() ? QString() : s; } inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool { - return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt)); + return loaders & (Forge | Fabric | Quilt); } }; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index e2d27547..7826b33d 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -4,12 +4,15 @@ #include "Json.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" #include "modplatform/helpers/HashUtils.h" #include "tasks/ConcurrentTask.h" +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + static ModrinthAPI api; static ModPlatform::ProviderCapabilities ProviderCaps; @@ -34,7 +37,7 @@ void ModrinthCheckUpdate::executeTask() // Create all hashes QStringList hashes; - auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first(); ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10); for (auto* mod : m_mods) { @@ -108,11 +111,13 @@ void ModrinthCheckUpdate::executeTask() // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it QString loader_filter; - static auto flags = { ModAPI::ModLoaderType::Forge, ModAPI::ModLoaderType::Fabric, ModAPI::ModLoaderType::Quilt }; - for (auto flag : flags) { - if (m_loaders.testFlag(flag)) { - loader_filter = api.getModLoaderString(flag); - break; + if (m_loaders.has_value()) { + static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt }; + for (auto flag : flags) { + if (m_loaders.value().testFlag(flag)) { + loader_filter = api.getModLoaderString(flag); + break; + } } } @@ -152,12 +157,12 @@ void ModrinthCheckUpdate::executeTask() for (auto& author : mod->authors()) pack.authors.append({ author }); pack.description = mod->description(); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; - auto download_task = new ModDownloadTask(pack, project_ver, m_mods_folder); + auto download_task = new ResourceDownloadTask(pack, project_ver, m_mods_folder); m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog, - ModPlatform::Provider::MODRINTH, download_task); + ModPlatform::ResourceProvider::MODRINTH, download_task); } } } catch (Json::JsonException& e) { diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h index abf8ada1..177ce516 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h @@ -8,7 +8,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask { Q_OBJECT public: - ModrinthCheckUpdate(QList& mods, std::list& mcVersions, ModAPI::ModLoaderTypes loaders, std::shared_ptr mods_folder) + ModrinthCheckUpdate(QList& mods, std::list& mcVersions, std::optional loaders, std::shared_ptr mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder) {} diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index aec45a73..a0161089 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -33,7 +33,7 @@ void Modrinth::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) if (pack.addonId.toString().isEmpty()) pack.addonId = Json::requireString(obj, "id"); - pack.provider = ModPlatform::Provider::MODRINTH; + pack.provider = ModPlatform::ResourceProvider::MODRINTH; pack.name = Json::requireString(obj, "title"); pack.slug = Json::ensureString(obj, "slug", ""); @@ -179,7 +179,7 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t file.hash = Json::requireString(hash_list, preferred_hash_type); file.hash_type = preferred_hash_type; } else { - auto hash_types = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH); + auto hash_types = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH); for (auto& hash_type : hash_types) { if (hash_list.contains(hash_type)) { file.hash = Json::requireString(hash_list, hash_type); diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index 0ed29311..510c7309 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -97,7 +97,7 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.name = mod_pack.name; mod.filename = mod_version.fileName; - if (mod_pack.provider == ModPlatform::Provider::FLAME) { + if (mod_pack.provider == ModPlatform::ResourceProvider::FLAME) { mod.mode = "metadata:curseforge"; } else { mod.mode = "url"; @@ -176,11 +176,11 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) in_stream << QString("\n[update]\n"); in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider)); switch (mod.provider) { - case (ModPlatform::Provider::FLAME): + case (ModPlatform::ResourceProvider::FLAME): in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); break; - case (ModPlatform::Provider::MODRINTH): + case (ModPlatform::ResourceProvider::MODRINTH): addToStream("mod-id", mod.mod_id().toString()); addToStream("version", mod.version().toString()); break; @@ -273,7 +273,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod } { // [update] info - using Provider = ModPlatform::Provider; + using Provider = ModPlatform::ResourceProvider; auto update_table = table["update"]; if (!update_table || !update_table.is_table()) { diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h index 9754e5c4..4b096eec 100644 --- a/launcher/modplatform/packwiz/Packwiz.h +++ b/launcher/modplatform/packwiz/Packwiz.h @@ -49,7 +49,7 @@ class V1 { QString hash {}; // [update] - ModPlatform::Provider provider {}; + ModPlatform::ResourceProvider provider {}; QVariant file_id {}; QVariant project_id {}; diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index d9c4fadc..38fe058b 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -52,7 +52,6 @@ class NetAction : public Task { virtual ~NetAction() = default; QUrl url() { return m_url; } - auto index() -> int { return m_index_within_job; } void setNetwork(shared_qobject_ptr network) { m_network = network; } @@ -75,9 +74,6 @@ class NetAction : public Task { public: shared_qobject_ptr m_network; - /// index within the parent job, FIXME: nuke - int m_index_within_job = 0; - /// the network reply unique_qobject_ptr m_reply; diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 9b5d4f1b..4bcd40b5 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -38,11 +38,10 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool { - action->m_index_within_job = m_queue.size(); - m_queue.append(action); - action->setNetwork(m_network); + addTask(action); + return true; } diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index 8b49bd1a..5977fd10 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -230,7 +230,7 @@ void BlockedModsDialog::addHashTask(QString path) /// @param path the path to the local file being hashed void BlockedModsDialog::buildHashTask(QString path) { - auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::Provider::FLAME, "sha1"); + auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, "sha1"); qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path; diff --git a/launcher/ui/dialogs/ChooseProviderDialog.cpp b/launcher/ui/dialogs/ChooseProviderDialog.cpp index 89935d9a..83748e1e 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.cpp +++ b/launcher/ui/dialogs/ChooseProviderDialog.cpp @@ -67,9 +67,9 @@ void ChooseProviderDialog::confirmAll() accept(); } -auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::Provider +auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::ResourceProvider { - return ModPlatform::Provider(m_providers.checkedId()); + return ModPlatform::ResourceProvider(m_providers.checkedId()); } void ChooseProviderDialog::addProviders() @@ -77,7 +77,7 @@ void ChooseProviderDialog::addProviders() int btn_index = 0; QRadioButton* btn; - for (auto& provider : { ModPlatform::Provider::MODRINTH, ModPlatform::Provider::FLAME }) { + for (auto& provider : { ModPlatform::ResourceProvider::MODRINTH, ModPlatform::ResourceProvider::FLAME }) { btn = new QRadioButton(ProviderCaps.readableName(provider), this); m_providers.addButton(btn, btn_index++); ui->providersLayout->addWidget(btn); diff --git a/launcher/ui/dialogs/ChooseProviderDialog.h b/launcher/ui/dialogs/ChooseProviderDialog.h index 4a3b9f29..be9735b5 100644 --- a/launcher/ui/dialogs/ChooseProviderDialog.h +++ b/launcher/ui/dialogs/ChooseProviderDialog.h @@ -8,7 +8,7 @@ class ChooseProviderDialog; } namespace ModPlatform { -enum class Provider; +enum class ResourceProvider; } class Mod; @@ -24,7 +24,7 @@ class ChooseProviderDialog : public QDialog { bool try_others = false; - ModPlatform::Provider chosen; + ModPlatform::ResourceProvider chosen; }; public: @@ -45,7 +45,7 @@ class ChooseProviderDialog : public QDialog { void addProviders(); void disableInput(); - auto getSelectedProvider() const -> ModPlatform::Provider; + auto getSelectedProvider() const -> ModPlatform::ResourceProvider; private: Ui::ChooseProviderDialog* ui; diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 24d23ba9..8a77ef7f 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -19,76 +19,24 @@ #include "ModDownloadDialog.h" -#include -#include -#include - #include "Application.h" -#include "ReviewMessageBox.h" - -#include -#include -#include -#include -#include "ModDownloadTask.h" -#include "ui/pages/modplatform/flame/FlameModPage.h" -#include "ui/pages/modplatform/modrinth/ModrinthModPage.h" -#include "ui/widgets/PageContainer.h" +#include "ui/pages/modplatform/flame/FlameResourcePages.h" +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" -ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance) - : QDialog(parent), mods(mods), m_verticalLayout(new QVBoxLayout(this)), m_instance(instance) +ModDownloadDialog::ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance) + : ResourceDownloadDialog(parent, mods), m_instance(instance) { - setObjectName(QStringLiteral("ModDownloadDialog")); - m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); - - resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); - - setWindowIcon(APPLICATION->getThemedIcon("new")); - // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not - // move this below. - m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - m_container = new PageContainer(this); - m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); - m_container->layout()->setContentsMargins(0, 0, 0, 0); - m_verticalLayout->addWidget(m_container); - - m_container->addButtons(m_buttons); - - connect(m_container, &PageContainer::selectedPageChanged, this, &ModDownloadDialog::selectedPageChanged); - - // Bonk Qt over its stupid head and make sure it understands which button is the default one... - // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button - auto OkButton = m_buttons->button(QDialogButtonBox::Ok); - OkButton->setEnabled(false); - OkButton->setDefault(true); - OkButton->setAutoDefault(true); - OkButton->setText(tr("Review and confirm")); - OkButton->setShortcut(tr("Ctrl+Return")); - OkButton->setToolTip(tr("Opens a new popup to review your selected mods and confirm your selection. Shortcut: Ctrl+Return")); - connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm); - - auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); - CancelButton->setDefault(false); - CancelButton->setAutoDefault(false); - connect(CancelButton, &QPushButton::clicked, this, &ModDownloadDialog::reject); - - auto HelpButton = m_buttons->button(QDialogButtonBox::Help); - HelpButton->setDefault(false); - HelpButton->setAutoDefault(false); - connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); - - QMetaObject::connectSlotsByName(this); - setWindowModality(Qt::WindowModal); - setWindowTitle(dialogTitle()); + initializeContainer(); + connectButtons(); restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("ModDownloadGeometry").toByteArray())); } -QString ModDownloadDialog::dialogTitle() +void ModDownloadDialog::accept() { - return tr("Download mods"); + APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); + QDialog::accept(); } void ModDownloadDialog::reject() @@ -97,106 +45,15 @@ void ModDownloadDialog::reject() QDialog::reject(); } -void ModDownloadDialog::confirm() -{ - auto keys = modTask.keys(); - keys.sort(Qt::CaseInsensitive); - - auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm mods to download")); - - for (auto& task : keys) { - confirm_dialog->appendMod({ task, modTask.find(task).value()->getFilename() }); - } - - if (confirm_dialog->exec()) { - auto deselected = confirm_dialog->deselectedMods(); - for (auto name : deselected) { - modTask.remove(name); - } - - this->accept(); - } -} - -void ModDownloadDialog::accept() -{ - APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); - QDialog::accept(); -} - QList ModDownloadDialog::getPages() { QList pages; - pages.append(ModrinthModPage::create(this, m_instance)); + pages.append(ModrinthModPage::create(this, *m_instance)); if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameModPage::create(this, m_instance)); + pages.append(FlameModPage::create(this, *m_instance)); m_selectedPage = dynamic_cast(pages[0]); return pages; } - -void ModDownloadDialog::addSelectedMod(QString name, ModDownloadTask* task) -{ - removeSelectedMod(name); - modTask.insert(name, task); - - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); -} - -void ModDownloadDialog::removeSelectedMod(QString name) -{ - if (modTask.contains(name)) - delete modTask.find(name).value(); - modTask.remove(name); - - m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); -} - -bool ModDownloadDialog::isModSelected(QString name, QString filename) const -{ - // FIXME: Is there a way to check for versions without checking the filename - // as a heuristic, other than adding such info to ModDownloadTask itself? - auto iter = modTask.find(name); - return iter != modTask.end() && (iter.value()->getFilename() == filename); -} - -bool ModDownloadDialog::isModSelected(QString name) const -{ - auto iter = modTask.find(name); - return iter != modTask.end(); -} - -const QList ModDownloadDialog::getTasks() -{ - return modTask.values(); -} - -void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) -{ - auto* prev_page = dynamic_cast(previous); - if (!prev_page) { - qCritical() << "Page '" << previous->displayName() << "' in ModDownloadDialog is not a ModPage!"; - return; - } - - m_selectedPage = dynamic_cast(selected); - if (!m_selectedPage) { - qCritical() << "Page '" << selected->displayName() << "' in ModDownloadDialog is not a ModPage!"; - return; - } - - // Same effect as having a global search bar - m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); -} - -bool ModDownloadDialog::selectPage(QString pageId) -{ - return m_container->selectPage(pageId); -} - -ModPage* ModDownloadDialog::getSelectedPage() -{ - return m_selectedPage; -} diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index fcf6f4fc..19036042 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -19,60 +19,29 @@ #pragma once -#include -#include - -#include "ModDownloadTask.h" #include "minecraft/mod/ModFolderModel.h" -#include "ui/pages/BasePageProvider.h" -namespace Ui -{ -class ModDownloadDialog; -} +#include "ui/dialogs/ResourceDownloadDialog.h" -class PageContainer; class QDialogButtonBox; -class ModPage; -class ModrinthModPage; -class ModDownloadDialog final : public QDialog, public BasePageProvider +class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance); + explicit ModDownloadDialog(QWidget* parent, const std::shared_ptr& mods, BaseInstance* instance); ~ModDownloadDialog() override = default; - QString dialogTitle() override; - QList getPages() override; - - void addSelectedMod(QString name = QString(), ModDownloadTask* task = nullptr); - void removeSelectedMod(QString name = QString()); - bool isModSelected(QString name, QString filename) const; - bool isModSelected(QString name) const; + //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) + [[nodiscard]] QString resourceString() const override { return tr("mods"); } - const QList getTasks(); - const std::shared_ptr& mods; - - bool selectPage(QString pageId); - ModPage* getSelectedPage(); + QList getPages() override; public slots: - void confirm(); void accept() override; void reject() override; - private slots: - void selectedPageChanged(BasePage* previous, BasePage* selected); - private: - Ui::ModDownloadDialog* ui = nullptr; - PageContainer* m_container = nullptr; - QDialogButtonBox* m_buttons = nullptr; - QVBoxLayout* m_verticalLayout = nullptr; - ModPage* m_selectedPage = nullptr; - - QHash modTask; BaseInstance* m_instance; }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index 2704243e..4ef42d6c 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -21,6 +21,8 @@ #include #include +#include + static ModPlatform::ProviderCapabilities ProviderCaps; static std::list mcVersions(BaseInstance* inst) @@ -28,7 +30,7 @@ static std::list mcVersions(BaseInstance* inst) return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } -static ModAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) +static std::optional mcLoaders(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getModLoaders() }; } @@ -212,14 +214,14 @@ auto ModUpdateDialog::ensureMetadata() -> bool bool confirm_rest = false; bool try_others_rest = false; bool skip_rest = false; - ModPlatform::Provider provider_rest = ModPlatform::Provider::MODRINTH; + ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; - auto addToTmp = [&](Mod* m, ModPlatform::Provider p) { + auto addToTmp = [&](Mod* m, ModPlatform::ResourceProvider p) { switch (p) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: modrinth_tmp.push_back(m); break; - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: flame_tmp.push_back(m); break; } @@ -264,10 +266,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!modrinth_tmp.empty()) { - auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::Provider::MODRINTH); + auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); connect(modrinth_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH); + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); if (modrinth_task->getHashingTask()) @@ -277,10 +279,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool } if (!flame_tmp.empty()) { - auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::Provider::FLAME); + auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); connect(flame_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME); + onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); if (flame_task->getHashingTask()) @@ -306,28 +308,28 @@ void ModUpdateDialog::onMetadataEnsured(Mod* mod) return; switch (mod->metadata()->provider) { - case ModPlatform::Provider::MODRINTH: + case ModPlatform::ResourceProvider::MODRINTH: m_modrinth_to_update.push_back(mod); break; - case ModPlatform::Provider::FLAME: + case ModPlatform::ResourceProvider::FLAME: m_flame_to_update.push_back(mod); break; } } -ModPlatform::Provider next(ModPlatform::Provider p) +ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) { switch (p) { - case ModPlatform::Provider::MODRINTH: - return ModPlatform::Provider::FLAME; - case ModPlatform::Provider::FLAME: - return ModPlatform::Provider::MODRINTH; + case ModPlatform::ResourceProvider::MODRINTH: + return ModPlatform::ResourceProvider::FLAME; + case ModPlatform::ResourceProvider::FLAME: + return ModPlatform::ResourceProvider::MODRINTH; } - return ModPlatform::Provider::FLAME; + return ModPlatform::ResourceProvider::FLAME; } -void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::Provider first_choice) +void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::ResourceProvider first_choice) { if (try_others) { auto index_dir = indexDir(); @@ -368,7 +370,7 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) QString text = info.changelog; switch (info.provider) { - case ModPlatform::Provider::MODRINTH: { + case ModPlatform::ResourceProvider::MODRINTH: { text = markdownToHTML(info.changelog.toUtf8()); break; } @@ -386,9 +388,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) ui->modTreeWidget->addTopLevelItem(item_top); } -auto ModUpdateDialog::getTasks() -> const QList +auto ModUpdateDialog::getTasks() -> const QList { - QList list; + QList list; auto* item = ui->modTreeWidget->topLevelItem(0); diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h index bd486f0d..3e3dd90d 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.h +++ b/launcher/ui/dialogs/ModUpdateDialog.h @@ -1,7 +1,7 @@ #pragma once #include "BaseInstance.h" -#include "ModDownloadTask.h" +#include "ResourceDownloadTask.h" #include "ReviewMessageBox.h" #include "minecraft/mod/ModFolderModel.h" @@ -25,7 +25,7 @@ class ModUpdateDialog final : public ReviewMessageBox { void appendMod(const CheckUpdateTask::UpdatableMod& info); - const QList getTasks(); + const QList getTasks(); auto indexDir() const -> QDir { return m_mod_model->indexDir(); } auto noUpdates() const -> bool { return m_no_updates; }; @@ -36,7 +36,7 @@ class ModUpdateDialog final : public ReviewMessageBox { private slots: void onMetadataEnsured(Mod*); - void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::Provider first_choice = ModPlatform::Provider::MODRINTH); + void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::ResourceProvider first_choice = ModPlatform::ResourceProvider::MODRINTH); private: QWidget* m_parent; @@ -54,7 +54,7 @@ class ModUpdateDialog final : public ReviewMessageBox { QList> m_failed_metadata; QList> m_failed_check_update; - QHash m_tasks; + QHash m_tasks; BaseInstance* m_instance; bool m_no_updates = false; diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp new file mode 100644 index 00000000..7367548f --- /dev/null +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -0,0 +1,152 @@ +#include "ResourceDownloadDialog.h" + +#include + +#include "Application.h" +#include "ResourceDownloadTask.h" + +#include "ui/dialogs/ReviewMessageBox.h" +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/widgets/PageContainer.h" + +ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model) + : QDialog(parent), m_base_model(base_model), m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel), m_vertical_layout(this) +{ + setObjectName(QStringLiteral("ResourceDownloadDialog")); + + resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); + + setWindowIcon(APPLICATION->getThemedIcon("new")); + + // Bonk Qt over its stupid head and make sure it understands which button is the default one... + // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button + auto OkButton = m_buttons.button(QDialogButtonBox::Ok); + OkButton->setEnabled(false); + OkButton->setDefault(true); + OkButton->setAutoDefault(true); + OkButton->setText(tr("Review and confirm")); + OkButton->setShortcut(tr("Ctrl+Return")); + + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + CancelButton->setDefault(false); + CancelButton->setAutoDefault(false); + + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + HelpButton->setDefault(false); + HelpButton->setAutoDefault(false); + + setWindowModality(Qt::WindowModal); + setWindowTitle(dialogTitle()); +} + +// NOTE: We can't have this in the ctor because PageContainer calls a virtual function, and so +// won't work with subclasses if we put it in this ctor. +void ResourceDownloadDialog::initializeContainer() +{ + m_container = new PageContainer(this); + m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); + m_container->layout()->setContentsMargins(0, 0, 0, 0); + m_vertical_layout.addWidget(m_container); + + m_container->addButtons(&m_buttons); + + connect(m_container, &PageContainer::selectedPageChanged, this, &ResourceDownloadDialog::selectedPageChanged); +} + +void ResourceDownloadDialog::connectButtons() +{ + auto OkButton = m_buttons.button(QDialogButtonBox::Ok); + OkButton->setToolTip(tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourceString())); + connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); + + auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); + connect(CancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); + + auto HelpButton = m_buttons.button(QDialogButtonBox::Help); + connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); +} + +void ResourceDownloadDialog::confirm() +{ + auto keys = m_selected.keys(); + keys.sort(Qt::CaseInsensitive); + + auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourceString())); + + for (auto& task : keys) { + confirm_dialog->appendResource({ task, m_selected.find(task).value()->getFilename() }); + } + + if (confirm_dialog->exec()) { + auto deselected = confirm_dialog->deselectedResources(); + for (auto name : deselected) { + m_selected.remove(name); + } + + this->accept(); + } +} + +bool ResourceDownloadDialog::selectPage(QString pageId) +{ + return m_container->selectPage(pageId); +} + +ResourcePage* ResourceDownloadDialog::getSelectedPage() +{ + return m_selectedPage; +} + +void ResourceDownloadDialog::addResource(QString name, ResourceDownloadTask* task) +{ + removeResource(name); + m_selected.insert(name, task); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); +} + +void ResourceDownloadDialog::removeResource(QString name) +{ + if (m_selected.contains(name)) + m_selected.find(name).value()->deleteLater(); + m_selected.remove(name); + + m_buttons.button(QDialogButtonBox::Ok)->setEnabled(!m_selected.isEmpty()); +} + +bool ResourceDownloadDialog::isSelected(QString name, QString filename) const +{ + auto iter = m_selected.constFind(name); + if (iter == m_selected.constEnd()) + return false; + + // FIXME: Is there a way to check for versions without checking the filename + // as a heuristic, other than adding such info to ResourceDownloadTask itself? + if (!filename.isEmpty()) + return iter.value()->getFilename() == filename; + + return true; +} + +const QList ResourceDownloadDialog::getTasks() +{ + return m_selected.values(); +} + +void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) +{ + auto* prev_page = dynamic_cast(previous); + if (!prev_page) { + qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; + return; + } + + m_selectedPage = dynamic_cast(selected); + if (!m_selectedPage) { + qCritical() << "Page '" << selected->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; + return; + } + + // Same effect as having a global search bar + m_selectedPage->setSearchTerm(prev_page->getSearchTerm()); +} diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h new file mode 100644 index 00000000..d6b3938b --- /dev/null +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#include "ui/pages/BasePageProvider.h" + +class ResourceDownloadTask; +class ResourcePage; +class ResourceFolderModel; +class PageContainer; +class QVBoxLayout; +class QDialogButtonBox; + +class ResourceDownloadDialog : public QDialog, public BasePageProvider { + Q_OBJECT + + public: + ResourceDownloadDialog(QWidget* parent, const std::shared_ptr base_model); + + void initializeContainer(); + void connectButtons(); + + //: String that gets appended to the download dialog title ("Download " + resourcesString()) + [[nodiscard]] virtual QString resourceString() const { return tr("resources"); } + + QString dialogTitle() override { return tr("Download %1").arg(resourceString()); }; + + bool selectPage(QString pageId); + ResourcePage* getSelectedPage(); + + void addResource(QString name, ResourceDownloadTask* task); + void removeResource(QString name); + [[nodiscard]] bool isSelected(QString name, QString filename = "") const; + + const QList getTasks(); + [[nodiscard]] const std::shared_ptr getBaseModel() const { return m_base_model; } + + protected slots: + void selectedPageChanged(BasePage* previous, BasePage* selected); + + virtual void confirm(); + + protected: + const std::shared_ptr m_base_model; + + PageContainer* m_container = nullptr; + ResourcePage* m_selectedPage = nullptr; + + QDialogButtonBox m_buttons; + QVBoxLayout m_vertical_layout; + + QHash m_selected; +}; diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index 7c25c91c..f45a9c4a 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -25,7 +25,7 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon) return new ReviewMessageBox(parent, title, icon); } -void ReviewMessageBox::appendMod(ModInformation&& info) +void ReviewMessageBox::appendResource(ResourceInformation&& info) { auto itemTop = new QTreeWidgetItem(ui->modTreeWidget); itemTop->setCheckState(0, Qt::CheckState::Checked); @@ -39,7 +39,7 @@ void ReviewMessageBox::appendMod(ModInformation&& info) ui->modTreeWidget->addTopLevelItem(itemTop); } -auto ReviewMessageBox::deselectedMods() -> QStringList +auto ReviewMessageBox::deselectedResources() -> QStringList { QStringList list; diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h index 9cfa679a..e2d0ce37 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.h +++ b/launcher/ui/dialogs/ReviewMessageBox.h @@ -12,15 +12,15 @@ class ReviewMessageBox : public QDialog { public: static auto create(QWidget* parent, QString&& title, QString&& icon = "") -> ReviewMessageBox*; - using ModInformation = struct { + using ResourceInformation = struct { QString name; QString filename; }; - void appendMod(ModInformation&& info); - auto deselectedMods() -> QStringList; + void appendResource(ResourceInformation&& info); + auto deselectedResources() -> QStringList; - ~ReviewMessageBox(); + ~ReviewMessageBox() override; protected: ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 627e71e5..1bce3c0d 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -59,7 +59,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/ModFolderModel.h" -#include "modplatform/ModAPI.h" +#include "modplatform/ResourceAPI.h" #include "Version.h" #include "tasks/ConcurrentTask.h" @@ -153,12 +153,12 @@ void ModFolderPage::installMods() return; // this is a null instance or a legacy instance auto profile = static_cast(m_instance)->getPackProfile(); - if (profile->getModLoaders() == ModAPI::Unspecified) { + if (!profile->getModLoaders().has_value()) { QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); return; } - ModDownloadDialog mdownload(m_model, this, m_instance); + ModDownloadDialog mdownload(this, m_model, m_instance); if (mdownload.exec()) { ConcurrentTask* tasks = new ConcurrentTask(this); connect(tasks, &Task::failed, [this, tasks](QString reason) { diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 9633e3b4..db8af0c5 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -73,3 +73,4 @@ public: return true; } }; + diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index ed58eb32..31aae746 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -1,226 +1,81 @@ #include "ModModel.h" -#include "BuildConfig.h" #include "Json.h" #include "ModPage.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" - -#include "ui/widgets/ProjectItem.h" #include namespace ModPlatform { -// HACK: We need this to prevent callbacks from calling the ListModel after it has already been deleted. -// This leaks a tiny bit of memory per time the user has opened the mod dialog. How to make this better? -static QHash s_running; - -ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) { s_running.insert(this, true); } - -ListModel::~ListModel() -{ - s_running.find(this).value() = false; -} - -auto ListModel::debugName() const -> QString -{ - return m_parent->debugName(); -} +ListModel::ListModel(ModPage* parent, ResourceAPI* api) : ResourceModel(parent, api) {} /******** Make data requests ********/ -void ListModel::fetchMore(const QModelIndex& parent) +ResourceAPI::SearchArgs ListModel::createSearchArguments() { - if (parent.isValid()) - return; - if (nextSearchOffset == 0) { - qWarning() << "fetchMore with 0 offset is wrong..."; - return; - } - performPaginatedSearch(); + auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); + return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, + getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }; } - -auto ListModel::data(const QModelIndex& index, int role) const -> QVariant +ResourceAPI::SearchCallbacks ListModel::createSearchCallbacks() { - int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { - return QString("INVALID INDEX %1").arg(pos); - } - - ModPlatform::IndexedPack pack = modpacks.at(pos); - switch (role) { - case Qt::ToolTipRole: { - if (pack.description.length() > 100) { - // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); - return edit; - } - return pack.description; - } - case Qt::DecorationRole: { - if (m_logoMap.contains(pack.logoName)) { - return m_logoMap.value(pack.logoName); - } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - // un-const-ify this - ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); - return icon; - } - case Qt::SizeHintRole: - return QSize(0, 58); - case Qt::UserRole: { - QVariant v; - v.setValue(pack); - return v; - } - // Custom data - case UserDataTypes::TITLE: - return pack.name; - case UserDataTypes::DESCRIPTION: - return pack.description; - case UserDataTypes::SELECTED: - return m_parent->getDialog()->isModSelected(pack.name); - default: - break; - } - - return {}; + return { [this](auto& doc) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFinished(doc); + } }; } -bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role) +ResourceAPI::VersionSearchArgs ListModel::createVersionsArguments(QModelIndex& entry) { - int pos = index.row(); - if (pos >= modpacks.size() || pos < 0 || !index.isValid()) - return false; + auto const& pack = m_packs[entry.row()]; + auto profile = static_cast(m_associated_page->m_base_instance).getPackProfile(); - modpacks[pos] = value.value(); - - return true; + return { pack.addonId.toString(), getMineVersions(), profile->getModLoaders() }; } - -void ListModel::requestModVersions(ModPlatform::IndexedPack const& current, QModelIndex index) +ResourceAPI::VersionSearchCallbacks ListModel::createVersionsCallbacks(QModelIndex& entry) { - auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); - - m_parent->apiProvider()->getVersions({ current.addonId.toString(), getMineVersions(), profile->getModLoaders() }, - [this, current, index](QJsonDocument& doc, QString addonId) { - if (!s_running.constFind(this).value()) - return; - versionRequestSucceeded(doc, addonId, index); - }); -} - -void ListModel::performPaginatedSearch() -{ - auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); + auto const& pack = m_packs[entry.row()]; - m_parent->apiProvider()->searchMods( - this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }); + return { [this, pack, entry](auto& doc, auto addonId) { + if (!s_running_models.constFind(this).value()) + return; + versionRequestSucceeded(doc, addonId, entry); + } }; } -void ListModel::requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index) +ResourceAPI::ProjectInfoArgs ListModel::createInfoArguments(QModelIndex& entry) { - m_parent->apiProvider()->getModInfo(current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { - if (!s_running.constFind(this).value()) - return; - infoRequestFinished(doc, pack, index); - }); + auto& pack = m_packs[entry.row()]; + return { pack }; } - -void ListModel::refresh() +ResourceAPI::ProjectInfoCallbacks ListModel::createInfoCallbacks(QModelIndex& entry) { - if (jobPtr) { - jobPtr->abort(); - searchState = ResetRequested; - return; - } else { - beginResetModel(); - modpacks.clear(); - endResetModel(); - searchState = None; - } - nextSearchOffset = 0; - performPaginatedSearch(); + return { [this, entry](auto& doc, auto& pack) { + if (!s_running_models.constFind(this).value()) + return; + infoRequestFinished(doc, pack, entry); + } }; } void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) { - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filter_changed) { + if (m_search_term == term && m_search_term.isNull() == term.isNull() && currentSort == sort && !filter_changed) { return; } - currentSearchTerm = term; + setSearchTerm(term); currentSort = sort; refresh(); } -void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) -{ - if (m_logoMap.contains(logo)) { - callback(APPLICATION->metacache() - ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))) - ->getFullPath()); - } else { - requestLogo(logo, logoUrl); - } -} - -void ListModel::requestLogo(QString logo, QString url) -{ - if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) { - return; - } - - MetaEntryPtr entry = - APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); - auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); - job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); - - auto fullPath = entry->getFullPath(); - QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { - job->deleteLater(); - emit logoLoaded(logo, QIcon(fullPath)); - if (waitingCallbacks.contains(logo)) { - waitingCallbacks.value(logo)(fullPath); - } - }); - - QObject::connect(job, &NetJob::failed, this, [this, logo, job] { - job->deleteLater(); - emit logoFailed(logo); - }); - - job->start(); - m_loadingLogos.append(logo); -} - /******** Request callbacks ********/ -void ListModel::logoLoaded(QString logo, QIcon out) -{ - m_loadingLogos.removeAll(logo); - m_logoMap.insert(logo, out); - for (int i = 0; i < modpacks.size(); i++) { - if (modpacks[i].logoName == logo) { - emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); - } - } -} - -void ListModel::logoFailed(QString logo) -{ - m_failedLogos.append(logo); - m_loadingLogos.removeAll(logo); -} - void ListModel::searchRequestFinished(QJsonDocument& doc) { - jobPtr.reset(); - QList newList; auto packs = documentToArray(doc); @@ -232,62 +87,27 @@ void ListModel::searchRequestFinished(QJsonDocument& doc) loadIndexedPack(pack, packObj); newList.append(pack); } catch (const JSONValidationError& e) { - qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); + qWarning() << "Error while loading mod from " << m_associated_page->debugName() << ": " << e.cause(); continue; } } if (packs.size() < 25) { - searchState = Finished; + m_search_state = SearchState::Finished; } else { - nextSearchOffset += 25; - searchState = CanPossiblyFetchMore; + m_next_search_offset += 25; + m_search_state = SearchState::CanFetchMore; } // When you have a Qt build with assertions turned on, proceeding here will abort the application if (newList.size() == 0) return; - beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); - modpacks.append(newList); + beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + newList.size() - 1); + m_packs.append(newList); endInsertRows(); } -void ListModel::searchRequestFailed(QString reason) -{ - auto failed_action = jobPtr->getFailedActions().at(0); - if (!failed_action->m_reply) { - // Network error - QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); - } else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { - // 409 Gone, notify user to update - QMessageBox::critical(nullptr, tr("Error"), - //: %1 refers to the launcher itself - QString("%1 %2") - .arg(m_parent->displayName()) - .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); - } - - jobPtr.reset(); - searchState = Finished; -} - -void ListModel::searchRequestAborted() -{ - if (searchState != ResetRequested) - qCritical() << "Search task in ModModel aborted by an unknown reason!"; - - // Retry fetching - jobPtr.reset(); - - beginResetModel(); - modpacks.clear(); - endResetModel(); - - nextSearchOffset = 0; - performPaginatedSearch(); -} - void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { qDebug() << "Loading mod info"; @@ -310,12 +130,12 @@ void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack } } - m_parent->updateUi(); + m_associated_page->updateUi(); } void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) { - auto& current = m_parent->getCurrent(); + auto current = m_associated_page->getCurrentPack(); if (addonId != current.addonId) { return; } @@ -336,15 +156,19 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, cons qWarning() << "Failed to cache mod versions!"; } - - m_parent->updateModVersions(); + m_associated_page->updateVersionList(); } } // namespace ModPlatform /******** Helpers ********/ -auto ModPlatform::ListModel::getMineVersions() const -> std::list +#define MOD_PAGE(x) static_cast(x) + +auto ModPlatform::ListModel::getMineVersions() const -> std::optional> { - return m_parent->getFilter()->versions; + auto versions = MOD_PAGE(m_associated_page)->getFilter()->versions; + if (!versions.empty()) + return versions; + return {}; } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 36840649..7c735d90 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -3,90 +3,52 @@ #include #include "modplatform/ModIndex.h" -#include "net/NetJob.h" +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ResourceModel.h" class ModPage; class Version; namespace ModPlatform { -using LogoMap = QMap; -using LogoCallback = std::function; - -class ListModel : public QAbstractListModel { +class ListModel : public ResourceModel { Q_OBJECT public: - ListModel(ModPage* parent); - ~ListModel() override; - - inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); }; - inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; }; - inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; - - auto debugName() const -> QString; - - /* Retrieve information from the model at a given index with the given role */ - auto data(const QModelIndex& index, int role) const -> QVariant override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - - inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } - inline NetJob* activeJob() { return jobPtr.get(); } + ListModel(ModPage* parent, ResourceAPI* api); /* Ask the API for more information */ - void fetchMore(const QModelIndex& parent) override; - void refresh(); void searchWithTerm(const QString& term, const int sort, const bool filter_changed); - void requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index); - void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0; - void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); - - inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; }; - public slots: void searchRequestFinished(QJsonDocument& doc); - void searchRequestFailed(QString reason); - void searchRequestAborted(); void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index); - protected slots: + public slots: + ResourceAPI::SearchArgs createSearchArguments() override; + ResourceAPI::SearchCallbacks createSearchCallbacks() override; - void logoFailed(QString logo); - void logoLoaded(QString logo, QIcon out); + ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) override; + ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) override; - void performPaginatedSearch(); + ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) override; + ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) override; protected: virtual auto documentToArray(QJsonDocument& obj) const -> QJsonArray = 0; virtual auto getSorts() const -> const char** = 0; - void requestLogo(QString file, QString url); - - inline auto getMineVersions() const -> std::list; + inline auto getMineVersions() const -> std::optional>; protected: - ModPage* m_parent; - - QList modpacks; - - LogoMap m_logoMap; - QMap waitingCallbacks; - QStringList m_failedLogos; - QStringList m_loadingLogos; - - QString currentSearchTerm; int currentSort = 0; - int nextSearchOffset = 0; - enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; - - NetJob::Ptr jobPtr; }; } // namespace ModPlatform diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 0f30689e..853f2c54 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -35,59 +35,30 @@ */ #include "ModPage.h" -#include "Application.h" -#include "ui_ModPage.h" +#include "ui_ResourcePage.h" #include #include #include + #include +#include "Application.h" +#include "ResourceDownloadTask.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "ui/dialogs/ModDownloadDialog.h" -#include "ui/widgets/ProjectItem.h" -#include "Markdown.h" - -ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) - : QWidget(dialog) - , m_instance(instance) - , ui(new Ui::ModPage) - , dialog(dialog) - , m_fetch_progress(this, false) - , api(api) -{ - ui->setupUi(this); - - connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); - connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); - connect(ui->packView, &QListView::doubleClicked, this, &ModPage::onModSelected); - - m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); - m_search_timer.setSingleShot(true); - - connect(&m_search_timer, &QTimer::timeout, this, &ModPage::triggerSearch); - - ui->searchEdit->installEventFilter(this); - - ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - - m_fetch_progress.hideIfInactive(true); - m_fetch_progress.setFixedHeight(24); - m_fetch_progress.progressFormat(""); - - ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, ui->gridLayout_3->columnCount()); - ui->packView->setItemDelegate(new ProjectItemDelegate(this)); - ui->packView->installEventFilter(this); +#include "ui/dialogs/ModDownloadDialog.h" - connect(ui->packDescription, &QTextBrowser::anchorClicked, this, &ModPage::openUrl); -} +#include "ui/pages/modplatform/ModModel.h" -ModPage::~ModPage() +ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ResourcePage(dialog, instance) { - delete ui; + connect(m_ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); + connect(m_ui->resourceFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); + connect(m_ui->packView, &QListView::doubleClicked, this, &ModPage::onResourceSelected); } void ModPage::setFilterWidget(unique_qobject_ptr& widget) @@ -97,59 +68,19 @@ void ModPage::setFilterWidget(unique_qobject_ptr& widget) m_filter_widget.swap(widget); - ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, ui->gridLayout_3->columnCount()); + m_ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, m_ui->gridLayout_3->columnCount()); - m_filter_widget->setInstance(static_cast(m_instance)); + m_filter_widget->setInstance(&static_cast(m_base_instance)); m_filter = m_filter_widget->getFilter(); connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: underline"); + m_ui->searchButton->setStyleSheet("text-decoration: underline"); }); connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: none"); + m_ui->searchButton->setStyleSheet("text-decoration: none"); }); } - -/******** Qt things ********/ - -void ModPage::openedImpl() -{ - updateSelectionButton(); - triggerSearch(); -} - -auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool -{ - if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { - auto* keyEvent = dynamic_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - triggerSearch(); - keyEvent->accept(); - return true; - } else { - if (m_search_timer.isActive()) - m_search_timer.stop(); - - m_search_timer.start(350); - } - } else if (watched == ui->packView && event->type() == QEvent::KeyPress) { - auto* keyEvent = dynamic_cast(event); - if (keyEvent->key() == Qt::Key_Return) { - onModSelected(); - - // To have the 'select mod' button outlined instead of the 'review and confirm' one - ui->modSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); - ui->packView->setFocus(Qt::FocusReason::NoFocusReason); - - keyEvent->accept(); - return true; - } - } - return QWidget::eventFilter(watched, event); -} - - /******** Callbacks to events in the UI (set up in the derived classes) ********/ void ModPage::filterMods() @@ -163,176 +94,37 @@ void ModPage::triggerSearch() m_filter = m_filter_widget->getFilter(); if (changed) { - ui->packView->clearSelection(); - ui->packDescription->clear(); - ui->versionSelectionBox->clear(); + m_ui->packView->clearSelection(); + m_ui->packDescription->clear(); + m_ui->versionSelectionBox->clear(); updateSelectionButton(); } - listModel->searchWithTerm(getSearchTerm(), ui->sortByBox->currentIndex(), changed); - m_fetch_progress.watch(listModel->activeJob()); -} - -QString ModPage::getSearchTerm() const -{ - return ui->searchEdit->text(); -} -void ModPage::setSearchTerm(QString term) -{ - ui->searchEdit->setText(term); + static_cast(m_model)->searchWithTerm(getSearchTerm(), m_ui->sortByBox->currentIndex(), changed); + m_fetch_progress.watch(&m_model->activeJob()); } -void ModPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +QMap ModPage::urlHandlers() const { - ui->versionSelectionBox->clear(); - - if (!curr.isValid()) { return; } - - current = listModel->data(curr, Qt::UserRole).value(); - - if (!current.versionsLoaded) { - qDebug() << QString("Loading %1 mod versions").arg(debugName()); - - ui->modSelectionButton->setText(tr("Loading versions...")); - ui->modSelectionButton->setEnabled(false); - - listModel->requestModVersions(current, curr); - } else { - for (int i = 0; i < current.versions.size(); i++) { - ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i)); - } - if (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No valid version found."), QVariant(-1)); } - - updateSelectionButton(); - } - - if(!current.extraDataLoaded){ - qDebug() << QString("Loading %1 mod info").arg(debugName()); - - listModel->requestModInfo(current, curr); - } - - updateUi(); -} - -void ModPage::onVersionSelectionChanged(QString data) -{ - if (data.isNull() || data.isEmpty()) { - selectedVersion = -1; - return; - } - selectedVersion = ui->versionSelectionBox->currentData().toInt(); - updateSelectionButton(); -} - -void ModPage::onModSelected() -{ - if (selectedVersion < 0) - return; - - auto& version = current.versions[selectedVersion]; - if (dialog->isModSelected(current.name, version.fileName)) { - dialog->removeSelectedMod(current.name); - } else { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - dialog->addSelectedMod(current.name, new ModDownloadTask(current, version, dialog->mods, is_indexed)); - } - - updateSelectionButton(); - - /* Force redraw on the mods list when the selection changes */ - ui->packView->adjustSize(); -} - -static const QRegularExpression modrinth(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?")); -static const QRegularExpression curseForge(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?")); -static const QRegularExpression curseForgeOld(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?")); - -void ModPage::openUrl(const QUrl& url) -{ - // do not allow other url schemes for security reasons - if (!(url.scheme() == "http" || url.scheme() == "https")) { - qWarning() << "Unsupported scheme" << url.scheme(); - return; - } - - // detect mod URLs and search instead - - const QString address = url.host() + url.path(); - QRegularExpressionMatch match; - QString page; - - match = modrinth.match(address); - if (match.hasMatch()) - page = "modrinth"; - else if (APPLICATION->capabilities() & Application::SupportsFlame) { - match = curseForge.match(address); - if (!match.hasMatch()) - match = curseForgeOld.match(address); - - if (match.hasMatch()) - page = "curseforge"; - } - - if (!page.isNull()) { - const QString slug = match.captured(1); - - // ensure the user isn't opening the same mod - if (slug != current.slug) { - dialog->selectPage(page); - - ModPage* newPage = dialog->getSelectedPage(); - - QLineEdit* searchEdit = newPage->ui->searchEdit; - ModPlatform::ListModel* model = newPage->listModel; - QListView* view = newPage->ui->packView; - - auto jump = [url, slug, model, view] { - for (int row = 0; row < model->rowCount({}); row++) { - const QModelIndex index = model->index(row); - const auto pack = model->data(index, Qt::UserRole).value(); - - if (pack.slug == slug) { - view->setCurrentIndex(index); - return; - } - } - - // The final fallback. - QDesktopServices::openUrl(url); - }; - - searchEdit->setText(slug); - newPage->triggerSearch(); - - if (model->activeJob()) - connect(model->activeJob(), &Task::finished, jump); - else - jump(); - - return; - } - } - - // open in the user's web browser - QDesktopServices::openUrl(url); + QMap map; + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?"), "modrinth"); + map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + return map; } /******** Make changes to the UI ********/ -void ModPage::retranslate() -{ - ui->retranslateUi(this); -} - -void ModPage::updateModVersions(int prev_count) +void ModPage::updateVersionList() { - auto packProfile = (dynamic_cast(m_instance))->getPackProfile(); + m_ui->versionSelectionBox->clear(); + auto packProfile = (dynamic_cast(m_base_instance)).getPackProfile(); QString mcVersion = packProfile->getComponentVersion("net.minecraft"); - for (int i = 0; i < current.versions.size(); i++) { - auto version = current.versions[i]; + auto current_pack = getCurrentPack(); + for (int i = 0; i < current_pack.versions.size(); i++) { + auto version = current_pack.versions[i]; bool valid = false; for(auto& mcVer : m_filter->versions){ //NOTE: Flame doesn't care about loader, so passing it changes nothing. @@ -344,87 +136,18 @@ void ModPage::updateModVersions(int prev_count) // 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)) - ui->versionSelectionBox->addItem(version.version, QVariant(i)); + m_ui->versionSelectionBox->addItem(version.version, QVariant(i)); } - if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { - ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); - ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); + 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 :(")); } updateSelectionButton(); } - -void ModPage::updateSelectionButton() -{ - if (!isOpened || selectedVersion < 0) { - ui->modSelectionButton->setEnabled(false); - return; - } - - ui->modSelectionButton->setEnabled(true); - auto& version = current.versions[selectedVersion]; - if (!dialog->isModSelected(current.name, version.fileName)) { - ui->modSelectionButton->setText(tr("Select mod for download")); - } else { - ui->modSelectionButton->setText(tr("Deselect mod for download")); - } -} - -void ModPage::updateUi() +void ModPage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) { - QString text = ""; - QString name = current.name; - - if (current.websiteUrl.isEmpty()) - text = name; - else - text = "" + name + ""; - - if (!current.authors.empty()) { - auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { - if (author.url.isEmpty()) { return author.name; } - return QString("%2").arg(author.url, author.name); - }; - QStringList authorStrs; - for (auto& author : current.authors) { - authorStrs.push_back(authorToStr(author)); - } - text += "
" + tr(" by ") + authorStrs.join(", "); - } - - if (current.extraDataLoaded) { - if (!current.extraData.donate.isEmpty()) { - text += "

" + tr("Donate information: "); - auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { - return QString("%2").arg(donate.url, donate.platform); - }; - QStringList donates; - for (auto& donate : current.extraData.donate) { - donates.append(donateToStr(donate)); - } - text += donates.join(", "); - } - - if (!current.extraData.issuesUrl.isEmpty() - || !current.extraData.sourceUrl.isEmpty() - || !current.extraData.wikiUrl.isEmpty() - || !current.extraData.discordUrl.isEmpty()) { - text += "

" + tr("External links:") + "
"; - } - - if (!current.extraData.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current.extraData.issuesUrl) + "
"; - if (!current.extraData.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current.extraData.wikiUrl) + "
"; - if (!current.extraData.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current.extraData.sourceUrl) + "
"; - if (!current.extraData.discordUrl.isEmpty()) - text += "- " + tr("Discord: %1").arg(current.extraData.discordUrl) + "
"; - } - - text += "
"; - - ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : markdownToHTML(current.extraData.body))); - ui->packDescription->flush(); + bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel(), is_indexed)); } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index c9ccbaf2..8c1fec84 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -2,104 +2,58 @@ #include -#include "Application.h" -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" -#include "ui/pages/BasePage.h" -#include "ui/pages/modplatform/ModModel.h" + +#include "ui/pages/modplatform/ResourcePage.h" #include "ui/widgets/ModFilterWidget.h" -#include "ui/widgets/ProgressWidget.h" class ModDownloadDialog; namespace Ui { -class ModPage; +class ResourcePage; } /* This page handles most logic related to browsing and selecting mods to download. */ -class ModPage : public QWidget, public BasePage { +class ModPage : public ResourcePage { Q_OBJECT public: template - static T* create(ModDownloadDialog* dialog, BaseInstance* instance) + static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto filter_widget = ModFilterWidget::create(static_cast(instance)->getPackProfile()->getComponentVersion("net.minecraft"), page); + auto filter_widget = ModFilterWidget::create(static_cast(instance).getPackProfile()->getComponentVersion("net.minecraft"), page); page->setFilterWidget(filter_widget); return page; } - ~ModPage() override; - - /* Affects what the user sees */ - auto displayName() const -> QString override = 0; - auto icon() const -> QIcon override = 0; - auto id() const -> QString override = 0; - auto helpPage() const -> QString override = 0; + ~ModPage() override = default; - /* Used internally */ - virtual auto metaEntryBase() const -> QString = 0; - virtual auto debugName() const -> QString = 0; + [[nodiscard]] inline QString resourceString() const override { return tr("mod"); } + [[nodiscard]] QMap urlHandlers() const override; - void retranslate() override; + void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&) override; - void updateUi(); + virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool = 0; - auto shouldDisplay() const -> bool override = 0; - virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; - virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; - - auto apiProvider() -> ModAPI* { return api.get(); }; + [[nodiscard]] bool supportsFiltering() const override { return true; }; auto getFilter() const -> const std::shared_ptr { return m_filter; } - auto getDialog() const -> const ModDownloadDialog* { return dialog; } - - /** Get the current term in the search bar. */ - auto getSearchTerm() const -> QString; - /** Programatically set the term in the search bar. */ - void setSearchTerm(QString); - void setFilterWidget(unique_qobject_ptr&); - auto getCurrent() -> ModPlatform::IndexedPack& { return current; } - void updateModVersions(int prev_count = -1); - - void openedImpl() override; - auto eventFilter(QObject* watched, QEvent* event) -> bool override; - - BaseInstance* m_instance; + public slots: + void updateVersionList() override; protected: - ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api); - void updateSelectionButton(); + ModPage(ModDownloadDialog* dialog, BaseInstance& instance); protected slots: virtual void filterMods(); - void triggerSearch(); - void onSelectionChanged(QModelIndex first, QModelIndex second); - void onVersionSelectionChanged(QString data); - void onModSelected(); - virtual void openUrl(const QUrl& url); + void triggerSearch() override; protected: - Ui::ModPage* ui = nullptr; - ModDownloadDialog* dialog = nullptr; - unique_qobject_ptr m_filter_widget; std::shared_ptr m_filter; - - ProgressWidget m_fetch_progress; - - ModPlatform::ListModel* listModel = nullptr; - ModPlatform::IndexedPack current; - - std::unique_ptr api; - - int selectedVersion = -1; - - // Used to do instant searching with a delay to cache quick changes - QTimer m_search_timer; }; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui deleted file mode 100644 index 94365aa5..00000000 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ /dev/null @@ -1,118 +0,0 @@ - - - ModPage - - - - 0 - 0 - 837 - 685 - - - - - - - - - false - - - false - - - - - - - Qt::ScrollBarAlwaysOff - - - true - - - - 48 - 48 - - - - - - - - - - Search - - - - - - - Search for mods... - - - - - - - - - - - - Version selected: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - Select mod for download - - - - - - - - - Filter options - - - - - - - Qt::Vertical - - - - - - - - ProjectDescriptionPage - QTextBrowser -
ui/widgets/ProjectDescriptionPage.h
-
-
- - searchEdit - searchButton - packView - packDescription - sortByBox - versionSelectionBox - - - -
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp new file mode 100644 index 00000000..d672a2ac --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -0,0 +1,258 @@ +#include "ResourceModel.h" + +#include +#include +#include +#include +#include + +#include "Application.h" +#include "BuildConfig.h" + +#include "net/Download.h" +#include "net/NetJob.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "modplatform/ModIndex.h" + +#include "ui/pages/modplatform/ResourcePage.h" +#include "ui/widgets/ProjectItem.h" + +QHash ResourceModel::s_running_models; + +ResourceModel::ResourceModel(ResourcePage* parent, ResourceAPI* api) : QAbstractListModel(), m_api(api), m_associated_page(parent) +{ + s_running_models.insert(this, true); +} + +ResourceModel::~ResourceModel() +{ + s_running_models.find(this).value() = false; +} + +auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant +{ + int pos = index.row(); + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) { + return QString("INVALID INDEX %1").arg(pos); + } + + auto pack = m_packs.at(pos); + switch (role) { + case Qt::ToolTipRole: { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; + } + case Qt::DecorationRole: { + if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack.logoUrl); + icon_or_none.has_value()) + return icon_or_none.value(); + + return APPLICATION->getThemedIcon("screenshot-placeholder"); + } + case Qt::SizeHintRole: + return QSize(0, 58); + case Qt::UserRole: { + QVariant v; + v.setValue(pack); + return v; + } + // Custom data + case UserDataTypes::TITLE: + return pack.name; + case UserDataTypes::DESCRIPTION: + return pack.description; + case UserDataTypes::SELECTED: + return isPackSelected(pack); + default: + break; + } + + return {}; +} + +bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + int pos = index.row(); + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) + return false; + + m_packs[pos] = value.value(); + + return true; +} + +QString ResourceModel::debugName() const +{ + return m_associated_page->debugName() + " (Model)"; +} + +void ResourceModel::fetchMore(const QModelIndex& parent) +{ + if (parent.isValid()) + return; + + Q_ASSERT(m_next_search_offset != 0); + + search(); +} + +void ResourceModel::search() +{ + if (!m_current_job.isRunning()) + m_current_job.clear(); + + auto args{ createSearchArguments() }; + + auto callbacks{ createSearchCallbacks() }; + Q_ASSERT(callbacks.on_succeed); + + // Use defaults if no callbacks are set + if (!callbacks.on_fail) + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (!s_running_models.constFind(this).value()) + return; + searchRequestFailed(reason, network_error_code); + }; + if (!callbacks.on_abort) + callbacks.on_abort = [this] { + if (!s_running_models.constFind(this).value()) + return; + searchRequestAborted(); + }; + + if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) + addActiveJob(job); +} + +void ResourceModel::loadEntry(QModelIndex& entry) +{ + auto const& pack = m_packs[entry.row()]; + + if (!m_current_job.isRunning()) + m_current_job.clear(); + + if (!pack.versionsLoaded) { + auto args{ createVersionsArguments(entry) }; + auto callbacks{ createVersionsCallbacks(entry) }; + + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) + addActiveJob(job); + } + + if (!pack.extraDataLoaded) { + auto args{ createInfoArguments(entry) }; + auto callbacks{ createInfoCallbacks(entry) }; + + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) + addActiveJob(job); + } +} + +void ResourceModel::refresh() +{ + if (m_current_job.isRunning()) { + m_current_job.abort(); + m_search_state = SearchState::ResetRequested; + return; + } + + clearData(); + m_search_state = SearchState::None; + + m_next_search_offset = 0; + search(); +} + +void ResourceModel::clearData() +{ + beginResetModel(); + m_packs.clear(); + endResetModel(); +} + +std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) +{ + QPixmap pixmap; + if (QPixmapCache::find(url.toString(), &pixmap)) + return { pixmap }; + + if (!m_current_icon_job) + m_current_icon_job = new NetJob("IconJob", APPLICATION->network()); + + if (m_currently_running_icon_actions.contains(url)) + return {}; + if (m_failed_icon_actions.contains(url)) + return {}; + + auto cache_entry = APPLICATION->metacache()->resolveEntry( + m_associated_page->metaEntryBase(), + QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); + auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); + + auto full_file_path = cache_entry->getFullPath(); + connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { + auto icon = QIcon(full_file_path); + QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); + + m_currently_running_icon_actions.remove(url); + + emit dataChanged(index, index, { Qt::DecorationRole }); + }); + connect(icon_fetch_action.get(), &NetAction::failed, this, [=] { + m_currently_running_icon_actions.remove(url); + m_failed_icon_actions.insert(url); + }); + + m_currently_running_icon_actions.insert(url); + + m_current_icon_job->addNetAction(icon_fetch_action); + if (!m_current_icon_job->isRunning()) + QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start); + + return {}; +} + +bool ResourceModel::isPackSelected(const ModPlatform::IndexedPack& pack) const +{ + return m_associated_page->isPackSelected(pack); +} + +void ResourceModel::searchRequestFailed(QString reason, int network_error_code) +{ + switch (network_error_code) { + default: + // Network error + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); + break; + case 409: + // 409 Gone, notify user to update + QMessageBox::critical(nullptr, tr("Error"), + //: %1 refers to the launcher itself + QString("%1 %2") + .arg(m_associated_page->displayName()) + .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_DISPLAYNAME))); + break; + } + + m_search_state = SearchState::Finished; +} + +void ResourceModel::searchRequestAborted() +{ + if (m_search_state != SearchState::ResetRequested) + qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!"; + + // Retry fetching + clearData(); + + m_next_search_offset = 0; + search(); +} diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h new file mode 100644 index 00000000..af0e9f55 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include + +#include "QObjectPtr.h" +#include "modplatform/ResourceAPI.h" +#include "tasks/ConcurrentTask.h" + +class NetJob; +class ResourcePage; +class ResourceAPI; + +namespace ModPlatform { +struct IndexedPack; +} + + +class ResourceModel : public QAbstractListModel { + Q_OBJECT + + public: + ResourceModel(ResourcePage* parent, ResourceAPI* api); + ~ResourceModel() override; + + [[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override; + bool setData(const QModelIndex& index, const QVariant& value, int role) override; + + [[nodiscard]] auto debugName() const -> QString; + + [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } + [[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }; + [[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; + + inline void addActiveJob(Task::Ptr ptr) { m_current_job.addTask(ptr); if (!m_current_job.isRunning()) m_current_job.start(); } + inline Task const& activeJob() { return m_current_job; } + + public slots: + void fetchMore(const QModelIndex& parent) override; + [[nodiscard]] inline bool canFetchMore(const QModelIndex& parent) const override + { + return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; + } + + void setSearchTerm(QString term) { m_search_term = term; } + + virtual ResourceAPI::SearchArgs createSearchArguments() = 0; + virtual ResourceAPI::SearchCallbacks createSearchCallbacks() = 0; + + virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0; + virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) = 0; + + virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0; + virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) = 0; + + /** Requests the API for more entries. */ + virtual void search(); + + /** Applies any processing / extra requests needed to fully load the specified entry's information. */ + virtual void loadEntry(QModelIndex&); + + /** Schedule a refresh, clearing the current state. */ + void refresh(); + + /** Gets the icon at the URL for the given index. If it's not fetched yet, fetch it and update when fisinhed. */ + std::optional getIcon(QModelIndex&, const QUrl&); + + protected: + /** Resets the model's data. */ + void clearData(); + + [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&) const; + + protected: + /* Basic search parameters */ + enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None; + int m_next_search_offset = 0; + QString m_search_term; + + std::unique_ptr m_api; + + ConcurrentTask m_current_job; + + shared_qobject_ptr m_current_icon_job; + QSet m_currently_running_icon_actions; + QSet m_failed_icon_actions; + + ResourcePage* m_associated_page = nullptr; + + QList m_packs; + + // HACK: We need this to prevent callbacks from calling the model after it has already been deleted. + // This leaks a tiny bit of memory per time the user has opened a resource dialog. How to make this better? + static QHash s_running_models; + + private: + /* Default search request callbacks */ + void searchRequestFailed(QString reason, int network_error_code); + void searchRequestAborted(); +}; diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp new file mode 100644 index 00000000..3b382d20 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -0,0 +1,347 @@ +#include "ResourcePage.h" +#include "ui_ResourcePage.h" + +#include +#include + +#include "Markdown.h" +#include "ResourceDownloadTask.h" + +#include "minecraft/MinecraftInstance.h" + +#include "ui/dialogs/ResourceDownloadDialog.h" +#include "ui/pages/modplatform/ResourceModel.h" +#include "ui/widgets/ProjectItem.h" + +ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) + : QWidget(parent), m_base_instance(base_instance), m_ui(new Ui::ResourcePage), m_parent_dialog(parent), m_fetch_progress(this, false) +{ + m_ui->setupUi(this); + + m_ui->searchEdit->installEventFilter(this); + + m_ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_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, &ResourcePage::triggerSearch); + + m_fetch_progress.hideIfInactive(true); + m_fetch_progress.setFixedHeight(24); + m_fetch_progress.progressFormat(""); + + m_ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, m_ui->gridLayout_3->columnCount()); + + m_ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + m_ui->packView->installEventFilter(this); + + connect(m_ui->packDescription, &QTextBrowser::anchorClicked, this, &ResourcePage::openUrl); +} + +ResourcePage::~ResourcePage() +{ + delete m_ui; +} + +void ResourcePage::retranslate() +{ + m_ui->retranslateUi(this); +} + +void ResourcePage::openedImpl() +{ + if (!supportsFiltering()) + m_ui->resourceFilterButton->setVisible(false); + + updateSelectionButton(); + triggerSearch(); +} + +auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool +{ + if (event->type() == QEvent::KeyPress) { + auto* keyEvent = static_cast(event); + if (watched == m_ui->searchEdit) { + if (keyEvent->key() == Qt::Key_Return) { + triggerSearch(); + keyEvent->accept(); + return true; + } else { + if (m_search_timer.isActive()) + m_search_timer.stop(); + + m_search_timer.start(350); + } + } else if (watched == m_ui->packView) { + if (keyEvent->key() == Qt::Key_Return) { + onResourceSelected(); + + // To have the 'select mod' button outlined instead of the 'review and confirm' one + m_ui->resourceSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); + m_ui->packView->setFocus(Qt::FocusReason::NoFocusReason); + + keyEvent->accept(); + return true; + } + } + } + + return QWidget::eventFilter(watched, event); +} + +QString ResourcePage::getSearchTerm() const +{ + return m_ui->searchEdit->text(); +} + +void ResourcePage::setSearchTerm(QString term) +{ + m_ui->searchEdit->setText(term); +} + +ModPlatform::IndexedPack ResourcePage::getCurrentPack() const +{ + return m_model->data(m_ui->packView->currentIndex(), Qt::UserRole).value(); +} + +bool ResourcePage::isPackSelected(const ModPlatform::IndexedPack& pack, int version) const +{ + if (version < 0 || !pack.versionsLoaded) + return m_parent_dialog->isSelected(pack.name); + + return m_parent_dialog->isSelected(pack.name, pack.versions[version].fileName); +} + +void ResourcePage::updateUi() +{ + auto current_pack = getCurrentPack(); + + QString text = ""; + QString name = current_pack.name; + + if (current_pack.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + + if (!current_pack.authors.empty()) { + auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { + if (author.url.isEmpty()) { + return author.name; + } + return QString("%2").arg(author.url, author.name); + }; + QStringList authorStrs; + for (auto& author : current_pack.authors) { + authorStrs.push_back(authorToStr(author)); + } + text += "
" + tr(" by ") + authorStrs.join(", "); + } + + if (current_pack.extraDataLoaded) { + if (!current_pack.extraData.donate.isEmpty()) { + text += "

" + tr("Donate information: "); + auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { + return QString("%2").arg(donate.url, donate.platform); + }; + QStringList donates; + for (auto& donate : current_pack.extraData.donate) { + donates.append(donateToStr(donate)); + } + text += donates.join(", "); + } + + if (!current_pack.extraData.issuesUrl.isEmpty() || !current_pack.extraData.sourceUrl.isEmpty() || + !current_pack.extraData.wikiUrl.isEmpty() || !current_pack.extraData.discordUrl.isEmpty()) { + text += "

" + tr("External links:") + "
"; + } + + if (!current_pack.extraData.issuesUrl.isEmpty()) + text += "- " + tr("Issues: %1").arg(current_pack.extraData.issuesUrl) + "
"; + if (!current_pack.extraData.wikiUrl.isEmpty()) + text += "- " + tr("Wiki: %1").arg(current_pack.extraData.wikiUrl) + "
"; + if (!current_pack.extraData.sourceUrl.isEmpty()) + text += "- " + tr("Source code: %1").arg(current_pack.extraData.sourceUrl) + "
"; + if (!current_pack.extraData.discordUrl.isEmpty()) + text += "- " + tr("Discord: %1").arg(current_pack.extraData.discordUrl) + "
"; + } + + text += "
"; + + m_ui->packDescription->setHtml( + text + (current_pack.extraData.body.isEmpty() ? current_pack.description : markdownToHTML(current_pack.extraData.body))); + m_ui->packDescription->flush(); +} + +void ResourcePage::updateSelectionButton() +{ + if (!isOpened || m_selected_version_index < 0) { + m_ui->resourceSelectionButton->setEnabled(false); + return; + } + + m_ui->resourceSelectionButton->setEnabled(true); + if (!isPackSelected(getCurrentPack(), m_selected_version_index)) { + m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); + } else { + m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); + } +} + +void ResourcePage::updateVersionList() +{ + auto current_pack = getCurrentPack(); + + m_ui->versionSelectionBox->blockSignals(true); + m_ui->versionSelectionBox->clear(); + m_ui->versionSelectionBox->blockSignals(false); + + for (int i = 0; i < current_pack.versions.size(); i++) { + auto& version = current_pack.versions[i]; + if (optedOut(version)) + continue; + + m_ui->versionSelectionBox->addItem(current_pack.versions[i].version, 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 :(")); + } + + updateSelectionButton(); +} + +void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +{ + if (!curr.isValid()) { + return; + } + + auto current_pack = getCurrentPack(); + + bool request_load = false; + if (!current_pack.versionsLoaded) { + m_ui->resourceSelectionButton->setText(tr("Loading versions...")); + m_ui->resourceSelectionButton->setEnabled(false); + + request_load = true; + } else { + updateVersionList(); + } + + if (!current_pack.extraDataLoaded) + request_load = true; + + if (request_load) + m_model->loadEntry(curr); + + updateUi(); +} + +void ResourcePage::onVersionSelectionChanged(QString data) +{ + if (data.isNull() || data.isEmpty()) { + m_selected_version_index = -1; + return; + } + + m_selected_version_index = m_ui->versionSelectionBox->currentData().toInt(); + updateSelectionButton(); +} + +void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& version) +{ + m_parent_dialog->addResource(pack.name, new ResourceDownloadTask(pack, version, m_parent_dialog->getBaseModel())); +} + +void ResourcePage::removeResourceFromDialog(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion&) +{ + m_parent_dialog->removeResource(pack.name); +} + +void ResourcePage::onResourceSelected() +{ + if (m_selected_version_index < 0) + return; + + auto current_pack = getCurrentPack(); + + auto& version = current_pack.versions[m_selected_version_index]; + if (m_parent_dialog->isSelected(current_pack.name, version.fileName)) + removeResourceFromDialog(current_pack, version); + else + addResourceToDialog(current_pack, version); + + updateSelectionButton(); + + /* Force redraw on the resource list when the selection changes */ + m_ui->packView->adjustSize(); +} + +void ResourcePage::openUrl(const QUrl& url) +{ + // do not allow other url schemes for security reasons + if (!(url.scheme() == "http" || url.scheme() == "https")) { + qWarning() << "Unsupported scheme" << url.scheme(); + return; + } + + // detect URLs and search instead + + const QString address = url.host() + url.path(); + QRegularExpressionMatch match; + QString page; + + for (auto&& [regex, candidate] : urlHandlers().asKeyValueRange()) { + if (match = QRegularExpression(regex).match(address); match.hasMatch()) { + page = candidate; + break; + } + } + + if (!page.isNull()) { + const QString slug = match.captured(1); + + // ensure the user isn't opening the same mod + if (slug != getCurrentPack().slug) { + m_parent_dialog->selectPage(page); + + auto newPage = m_parent_dialog->getSelectedPage(); + + QLineEdit* searchEdit = newPage->m_ui->searchEdit; + auto model = newPage->m_model; + QListView* view = newPage->m_ui->packView; + + auto jump = [url, slug, model, view] { + for (int row = 0; row < model->rowCount({}); row++) { + const QModelIndex index = model->index(row); + const auto pack = model->data(index, Qt::UserRole).value(); + + if (pack.slug == slug) { + view->setCurrentIndex(index); + return; + } + } + + // The final fallback. + QDesktopServices::openUrl(url); + }; + + searchEdit->setText(slug); + newPage->triggerSearch(); + + if (model->activeJob().isRunning()) + connect(&model->activeJob(), &Task::finished, jump); + else + jump(); + + return; + } + } + + // open in the user's web browser + QDesktopServices::openUrl(url); +} diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h new file mode 100644 index 00000000..32aad3d9 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +#include "modplatform/ModIndex.h" +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/BasePage.h" +#include "ui/widgets/ProgressWidget.h" + +namespace Ui { +class ResourcePage; +} + +class BaseInstance; +class ResourceModel; +class ResourceDownloadDialog; + +class ResourcePage : public QWidget, public BasePage { + Q_OBJECT + public: + ~ResourcePage() override; + + /* Affects what the user sees */ + [[nodiscard]] auto displayName() const -> QString override = 0; + [[nodiscard]] auto icon() const -> QIcon override = 0; + [[nodiscard]] auto id() const -> QString override = 0; + [[nodiscard]] auto helpPage() const -> QString override = 0; + [[nodiscard]] bool shouldDisplay() const override = 0; + + /* Used internally */ + [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; + [[nodiscard]] virtual auto debugName() const -> QString = 0; + + [[nodiscard]] virtual inline QString resourceString() const { return tr("resource"); } + + /* Features this resource's page supports */ + [[nodiscard]] virtual bool supportsFiltering() const = 0; + + void retranslate() override; + void openedImpl() override; + auto eventFilter(QObject* watched, QEvent* event) -> bool override; + + /** Get the current term in the search bar. */ + [[nodiscard]] auto getSearchTerm() const -> QString; + /** Programatically set the term in the search bar. */ + void setSearchTerm(QString); + + [[nodiscard]] bool isPackSelected(const ModPlatform::IndexedPack&, int version = -1) const; + [[nodiscard]] auto getCurrentPack() const -> ModPlatform::IndexedPack; + + [[nodiscard]] auto getDialog() const -> const ResourceDownloadDialog* { return m_parent_dialog; } + + protected: + ResourcePage(ResourceDownloadDialog* parent, BaseInstance&); + + public slots: + virtual void updateUi(); + virtual void updateSelectionButton(); + virtual void updateVersionList(); + + virtual void addResourceToDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + virtual void removeResourceFromDialog(ModPlatform::IndexedPack&, ModPlatform::IndexedVersion&); + + protected slots: + virtual void triggerSearch() {} + + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + void onResourceSelected(); + + /** Associates regex expressions to pages in the order they're given in the map. */ + [[nodiscard]] virtual QMap urlHandlers() const = 0; + virtual void openUrl(const QUrl&); + + /** Whether the version is opted out or not. Currently only makes sense in CF. */ + virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; + + public: + BaseInstance& m_base_instance; + + protected: + Ui::ResourcePage* m_ui; + + ResourceDownloadDialog* m_parent_dialog = nullptr; + ResourceModel* m_model = nullptr; + + int m_selected_version_index = -1; + + ProgressWidget m_fetch_progress; + + // Used to do instant searching with a delay to cache quick changes + QTimer m_search_timer; +}; diff --git a/launcher/ui/pages/modplatform/ResourcePage.ui b/launcher/ui/pages/modplatform/ResourcePage.ui new file mode 100644 index 00000000..8fe1d613 --- /dev/null +++ b/launcher/ui/pages/modplatform/ResourcePage.ui @@ -0,0 +1,118 @@ + + + ResourcePage + + + + 0 + 0 + 837 + 685 + + + + + + + + + false + + + false + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + + + + + + + Search + + + + + + + Search for resources... + + + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Select resource for download + + + + + + + + + Filter options + + + + + + + Qt::Vertical + + + + + + + + ProjectDescriptionPage + QTextBrowser +
ui/widgets/ProjectDescriptionPage.h
+
+
+ + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + +
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp deleted file mode 100644 index bc2c686c..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "FlameModModel.h" -#include "Json.h" -#include "modplatform/flame/FlameModIndex.h" - -namespace FlameMod { - -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; - -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadIndexedPack(m, obj); -} - -// We already deal with the URLs when initializing the pack, due to the API response's structure -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - FlameMod::loadBody(m, obj); -} - -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); -} - -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return Json::ensureArray(obj.object(), "data"); -} - -} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h deleted file mode 100644 index 6a6aef2e..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "FlameModPage.h" - -namespace FlameMod { - -class ListModel : public ModPlatform::ListModel { - Q_OBJECT - - public: - ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent) {} - ~ListModel() override = default; - - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[6]; - inline auto getSorts() const -> const char** override { return sorts; }; -}; - -} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp deleted file mode 100644 index bad78c97..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FlameModPage.h" -#include "ui_ModPage.h" - -#include "FlameModModel.h" -#include "ui/dialogs/ModDownloadDialog.h" - -FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) - : ModPage(dialog, instance, new FlameAPI()) -{ - listModel = new FlameMod::ListModel(this); - ui->packView->setModel(listModel); - - // index is used to set the sorting with the flame api - ui->sortByBox->addItem(tr("Sort by Featured")); - ui->sortByBox->addItem(tr("Sort by Popularity")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); - ui->sortByBox->addItem(tr("Sort by Name")); - ui->sortByBox->addItem(tr("Sort by Author")); - ui->sortByBox->addItem(tr("Sort by Downloads")); - - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, - // so it's best not to connect them in the parent's contructor... - connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); - connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected); - - ui->packDescription->setMetaEntry(metaEntryBase()); -} - -auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool -{ - Q_UNUSED(loaders); - return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); -} - -bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const -{ - return ver.downloadUrl.isEmpty(); -} - -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto FlameModPage::shouldDisplay() const -> bool { return true; } - -void FlameModPage::openUrl(const QUrl& url) -{ - if (url.scheme().isEmpty()) { - QString query = url.query(QUrl::FullyDecoded); - - if (query.startsWith("remoteUrl=")) { - // attempt to resolve url from warning page - query.remove(0, 10); - ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary - return; - } - } - - ModPage::openUrl(url); -} diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h deleted file mode 100644 index 58479ab9..00000000 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * Prism Launcher - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * Copyright (C) 2022 TheKodeToad - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "modplatform/ModAPI.h" -#include "ui/pages/modplatform/ModPage.h" - -#include "modplatform/flame/FlameAPI.h" - -class FlameModPage : public ModPage { - Q_OBJECT - - public: - static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) - { - return ModPage::create(dialog, instance); - } - - FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance); - ~FlameModPage() override = default; - - inline auto displayName() const -> QString override { return "CurseForge"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); } - inline auto id() const -> QString override { return "curseforge"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } - - inline auto debugName() const -> QString override { return "Flame"; } - inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; - - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; - bool optedOut(ModPlatform::IndexedVersion& ver) const override; - - auto shouldDisplay() const -> bool override; - - void openUrl(const QUrl& url) override; -}; diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp new file mode 100644 index 00000000..b602dfac --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -0,0 +1,31 @@ +#include "FlameResourceModels.h" +#include "Json.h" +#include "modplatform/flame/FlameModIndex.h" + +namespace FlameMod { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[6]{ "Featured", "Popularity", "LastUpdated", "Name", "Author", "TotalDownloads" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadIndexedPack(m, obj); +} + +// We already deal with the URLs when initializing the pack, due to the API response's structure +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadBody(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return Json::ensureArray(obj.object(), "data"); +} + +} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h new file mode 100644 index 00000000..b94377d3 --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -0,0 +1,26 @@ +#pragma once + +#include "modplatform/flame/FlameAPI.h" + +namespace FlameMod { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(FlameModPage* parent) : ModPlatform::ListModel(parent, new FlameAPI) {} + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[6]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace FlameMod diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp new file mode 100644 index 00000000..490578ad --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FlameResourcePages.h" +#include "ui_ResourcePage.h" + +#include "FlameResourceModels.h" +#include "ui/dialogs/ModDownloadDialog.h" + +FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ModPage(dialog, instance) +{ + m_model = new FlameMod::ListModel(this); + m_ui->packView->setModel(m_model); + + // index is used to set the sorting with the flame api + m_ui->sortByBox->addItem(tr("Sort by Featured")); + m_ui->sortByBox->addItem(tr("Sort by Popularity")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Name")); + m_ui->sortByBox->addItem(tr("Sort by Author")); + m_ui->sortByBox->addItem(tr("Sort by Downloads")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's contructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameModPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders) const -> bool +{ + Q_UNUSED(loaders); + return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); +} + +bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const +{ + return ver.downloadUrl.isEmpty(); +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto FlameModPage::shouldDisplay() const -> bool { return true; } + +void FlameModPage::openUrl(const QUrl& url) +{ + if (url.scheme().isEmpty()) { + QString query = url.query(QUrl::FullyDecoded); + + if (query.startsWith("remoteUrl=")) { + // attempt to resolve url from warning page + query.remove(0, 10); + ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary + return; + } + } + + ModPage::openUrl(url); +} diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h new file mode 100644 index 00000000..597a0c25 --- /dev/null +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Application.h" + +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ModPage.h" + +class FlameModPage : public ModPage { + Q_OBJECT + + public: + static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance& instance) + { + return ModPage::create(dialog, instance); + } + + FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance); + ~FlameModPage() override = default; + + inline auto displayName() const -> QString override { return "CurseForge"; } + inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("flame"); } + inline auto id() const -> QString override { return "curseforge"; } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + inline auto debugName() const -> QString override { return "Flame"; } + inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; + bool optedOut(ModPlatform::IndexedVersion& ver) const override; + + auto shouldDisplay() const -> bool override; + + void openUrl(const QUrl& url) override; +}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp deleted file mode 100644 index af92e63e..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "ModrinthModModel.h" - -#include "modplatform/modrinth/ModrinthPackIndex.h" - -namespace Modrinth { - -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; - -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - Modrinth::loadIndexedPack(m, obj); -} - -void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) -{ - Modrinth::loadExtraPackData(m, obj); -} - -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) -{ - Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); -} - -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray -{ - return obj.object().value("hits").toArray(); -} - -} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h deleted file mode 100644 index 386897fd..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include "ModrinthModPage.h" - -namespace Modrinth { - -class ListModel : public ModPlatform::ListModel { - Q_OBJECT - - public: - ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){}; - ~ListModel() override = default; - - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[5]; - inline auto getSorts() const -> const char** override { return sorts; }; -}; - -} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp deleted file mode 100644 index c531ea90..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "ModrinthModPage.h" -#include "modplatform/modrinth/ModrinthAPI.h" -#include "ui_ModPage.h" - -#include "ModrinthModModel.h" -#include "ui/dialogs/ModDownloadDialog.h" - -ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance) - : ModPage(dialog, instance, new ModrinthAPI()) -{ - listModel = new Modrinth::ListModel(this); - ui->packView->setModel(listModel); - - // index is used to set the sorting with the modrinth api - ui->sortByBox->addItem(tr("Sort by Relevance")); - ui->sortByBox->addItem(tr("Sort by Downloads")); - ui->sortByBox->addItem(tr("Sort by Follows")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); - ui->sortByBox->addItem(tr("Sort by Newest")); - - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, - // so it's best not to connect them in the parent's constructor... - connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); - connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); - connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); - - ui->packDescription->setMetaEntry(metaEntryBase()); -} - -auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool -{ - auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders); - - auto loaderCompatible = false; - for (auto remoteLoader : ver.loaders) - { - if (loaderStrings.contains(remoteLoader)) { - loaderCompatible = true; - break; - } - } - return ver.mcVersion.contains(mineVer) && loaderCompatible; -} - -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto ModrinthModPage::shouldDisplay() const -> bool { return true; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h deleted file mode 100644 index 40d82e6f..00000000 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "modplatform/ModAPI.h" -#include "ui/pages/modplatform/ModPage.h" - -#include "modplatform/modrinth/ModrinthAPI.h" - -class ModrinthModPage : public ModPage { - Q_OBJECT - - public: - static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) - { - return ModPage::create(dialog, instance); - } - - ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); - ~ModrinthModPage() override = default; - - inline auto displayName() const -> QString override { return "Modrinth"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); } - inline auto id() const -> QString override { return "modrinth"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } - - inline auto debugName() const -> QString override { return "Modrinth"; } - inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; }; - - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; - - auto shouldDisplay() const -> bool override; -}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp new file mode 100644 index 00000000..51278546 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ModrinthResourceModels.h" + +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" + +#include "modplatform/modrinth/ModrinthAPI.h" +#include "modplatform/modrinth/ModrinthPackIndex.h" + +namespace Modrinth { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadIndexedPack(m, obj); +} + +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadExtraPackData(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_associated_page->m_base_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + +} // namespace Modrinth + + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h new file mode 100644 index 00000000..bf62d22f --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ui/pages/modplatform/ModModel.h" + +#include "ui/pages/modplatform/modrinth/ModrinthResourcePages.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +namespace Modrinth { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent, new ModrinthAPI){}; + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[5]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace Modrinth + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp new file mode 100644 index 00000000..17f0bc93 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModrinthResourcePages.h" +#include "ui_ResourcePage.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +#include "ModrinthResourceModels.h" +#include "ui/dialogs/ModDownloadDialog.h" + +ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance) + : ModPage(dialog, instance) +{ + m_model = new Modrinth::ListModel(this); + m_ui->packView->setModel(m_model); + + // index is used to set the sorting with the modrinth api + m_ui->sortByBox->addItem(tr("Sort by Relevance")); + m_ui->sortByBox->addItem(tr("Sort by Downloads")); + m_ui->sortByBox->addItem(tr("Sort by Follows")); + m_ui->sortByBox->addItem(tr("Sort by Last Updated")); + m_ui->sortByBox->addItem(tr("Sort by Newest")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); + connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); + connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onResourceSelected); + + m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders) const -> bool +{ + auto loaderCompatible = !loaders.has_value(); + + if (!loaderCompatible) { + auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value()); + for (auto remoteLoader : ver.loaders) + { + if (loaderStrings.contains(remoteLoader)) { + loaderCompatible = true; + break; + } + } + } + + return ver.mcVersion.contains(mineVer) && loaderCompatible; +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto ModrinthModPage::shouldDisplay() const -> bool { return true; } + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h new file mode 100644 index 00000000..6f816cfd --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Application.h" + +#include "modplatform/ResourceAPI.h" + +#include "ui/pages/modplatform/ModPage.h" + +static inline QString displayName() { return "Modrinth"; } +static inline QIcon icon() { return APPLICATION->getThemedIcon("modrinth"); } +static inline QString id() { return "modrinth"; } +static inline QString debugName() { return "Modrinth"; } +static inline QString metaEntryBase() { return "ModrinthPacks"; }; + +class ModrinthModPage : public ModPage { + Q_OBJECT + + public: + static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance& instance) + { + return ModPage::create(dialog, instance); + } + + ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instance); + ~ModrinthModPage() override = default; + + [[nodiscard]] bool shouldDisplay() const override; + + [[nodiscard]] inline auto displayName() const -> QString override { return ::displayName(); } \ + [[nodiscard]] inline auto icon() const -> QIcon override { return ::icon(); } \ + [[nodiscard]] inline auto id() const -> QString override { return ::id(); } \ + [[nodiscard]] inline auto debugName() const -> QString override { return ::debugName(); } \ + [[nodiscard]] inline auto metaEntryBase() const -> QString override { return ::metaEntryBase(); } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional loaders = {}) const -> bool override; +}; diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index b60d9a7a..18b51fc3 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -39,7 +39,7 @@ void ProgressWidget::progressFormat(QString format) m_bar->setFormat(format); } -void ProgressWidget::watch(Task* task) +void ProgressWidget::watch(const Task* task) { if (!task) return; @@ -57,11 +57,11 @@ void ProgressWidget::watch(Task* task) show(); } -void ProgressWidget::start(Task* task) +void ProgressWidget::start(const Task* task) { watch(task); if (!m_task->isRunning()) - QMetaObject::invokeMethod(m_task, "start", Qt::QueuedConnection); + QMetaObject::invokeMethod(const_cast(m_task), "start", Qt::QueuedConnection); } bool ProgressWidget::exec(std::shared_ptr task) diff --git a/launcher/ui/widgets/ProgressWidget.h b/launcher/ui/widgets/ProgressWidget.h index 4d9097b8..b0458f33 100644 --- a/launcher/ui/widgets/ProgressWidget.h +++ b/launcher/ui/widgets/ProgressWidget.h @@ -27,10 +27,10 @@ class ProgressWidget : public QWidget { public slots: /** Watch the progress of a task. */ - void watch(Task* task); + void watch(const Task* task); /** Watch the progress of a task, and start it if needed */ - void start(Task* task); + void start(const Task* task); /** Blocking way of waiting for a task to finish. */ bool exec(std::shared_ptr task); @@ -50,7 +50,7 @@ class ProgressWidget : public QWidget { private: QLabel* m_label = nullptr; QProgressBar* m_bar = nullptr; - Task* m_task = nullptr; + const Task* m_task = nullptr; bool m_hide_if_inactive = false; }; diff --git a/tests/Packwiz_test.cpp b/tests/Packwiz_test.cpp index 098e8f89..29289469 100644 --- a/tests/Packwiz_test.cpp +++ b/tests/Packwiz_test.cpp @@ -48,7 +48,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "sha512"); QCOMPARE(metadata.hash, "c8fe6e15ddea32668822dddb26e1851e5f03834be4bcb2eff9c0da7fdc086a9b6cead78e31a44d3bc66335cba11144ee0337c6d5346f1ba63623064499b3188d"); - QCOMPARE(metadata.provider, ModPlatform::Provider::MODRINTH); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::MODRINTH); QCOMPARE(metadata.version(), "ug2qKTPR"); QCOMPARE(metadata.mod_id(), "kYq5qkSL"); } @@ -76,7 +76,7 @@ class PackwizTest : public QObject { QCOMPARE(metadata.hash_format, "murmur2"); QCOMPARE(metadata.hash, "1781245820"); - QCOMPARE(metadata.provider, ModPlatform::Provider::FLAME); + QCOMPARE(metadata.provider, ModPlatform::ResourceProvider::FLAME); QCOMPARE(metadata.file_id, 3509043); QCOMPARE(metadata.project_id, 327154); } -- cgit From ef87bdf18acb549c1ad9a3eda69d8dff5ad8da8e Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Dec 2022 20:19:33 -0300 Subject: fix(RD): prevent weird behavior of progress widget when i.e. clicking on links or just using the downloader at all, this prevents some flickering and the widget never getting hidden in some cases. Signed-off-by: flow --- launcher/ui/widgets/ProgressWidget.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'launcher/ui/widgets') diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index 18b51fc3..f736af08 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -54,7 +54,10 @@ void ProgressWidget::watch(const Task* task) connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress); connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed); - show(); + if (m_task->isRunning()) + show(); + else + connect(m_task, &Task::started, this, &ProgressWidget::show); } void ProgressWidget::start(const Task* task) -- cgit