diff options
Diffstat (limited to 'launcher/ui')
34 files changed, 1145 insertions, 333 deletions
diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index f9665c30..70c5009d 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -80,7 +80,7 @@ </font> </property> <property name="text"> - <string notr="true">PolyMC</string> + <string notr="true">Launcher</string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index a53f93e8..d02ea476 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -22,7 +22,9 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods : QDialog(parent), mods(mods), m_instance(instance) { setObjectName(QStringLiteral("ModDownloadDialog")); - resize(400, 347); + + resize(std::max(0.5*parent->width(), 400.0), std::max(0.75*parent->height(), 400.0)); + m_verticalLayout = new QVBoxLayout(this); m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); @@ -80,7 +82,7 @@ void ModDownloadDialog::confirm() tr("Confirm mods to download") ); - for(auto task : keys){ + for(auto& task : keys){ confirm_dialog->appendMod(task, modTask.find(task).value()->getFilename()); } diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 4b092859..648bd88b 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -81,6 +81,12 @@ int ProgressDialog::execWithTask(Task *task) connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString &))); connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); + m_is_multi_step = task->isMultiStep(); + if(!m_is_multi_step){ + ui->globalStatusLabel->setHidden(true); + ui->globalProgressBar->setHidden(true); + } + // if this didn't connect to an already running task, invoke start if(!task->isRunning()) { @@ -152,14 +158,24 @@ void ProgressDialog::onTaskSucceeded() void ProgressDialog::changeStatus(const QString &status) { - ui->statusLabel->setText(status); + ui->statusLabel->setText(task->getStepStatus()); + ui->globalStatusLabel->setText(status); updateSize(); } void ProgressDialog::changeProgress(qint64 current, qint64 total) { - ui->taskProgressBar->setMaximum(total); - ui->taskProgressBar->setValue(current); + ui->globalProgressBar->setMaximum(total); + ui->globalProgressBar->setValue(current); + + if(!m_is_multi_step){ + ui->taskProgressBar->setMaximum(total); + ui->taskProgressBar->setValue(current); + } + else{ + ui->taskProgressBar->setMaximum(task->getStepProgress()); + ui->taskProgressBar->setValue(task->getStepTotalProgress()); + } } void ProgressDialog::keyPressEvent(QKeyEvent *e) diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index b28ad4fa..0b4b78a4 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -19,6 +19,7 @@ #include <memory> class Task; +class SequentialTask; namespace Ui { @@ -35,7 +36,7 @@ public: void updateSize(); - int execWithTask(Task *task); + int execWithTask(Task* task); int execWithTask(std::unique_ptr<Task> &&task); int execWithTask(std::unique_ptr<Task> &task); @@ -68,4 +69,6 @@ private: Ui::ProgressDialog *ui; Task *task; + + bool m_is_multi_step = false; }; diff --git a/launcher/ui/dialogs/ProgressDialog.ui b/launcher/ui/dialogs/ProgressDialog.ui index 04b8fef3..bf119a78 100644 --- a/launcher/ui/dialogs/ProgressDialog.ui +++ b/launcher/ui/dialogs/ProgressDialog.ui @@ -2,14 +2,6 @@ <ui version="4.0"> <class>ProgressDialog</class> <widget class="QDialog" name="ProgressDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>400</width> - <height>100</height> - </rect> - </property> <property name="minimumSize"> <size> <width>400</width> @@ -26,7 +18,27 @@ <string>Please wait...</string> </property> <layout class="QGridLayout" name="gridLayout"> + <item row="4" column="0"> + <widget class="QPushButton" name="skipButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Skip</string> + </property> + </widget> + </item> <item row="0" column="0"> + <widget class="QLabel" name="globalStatusLabel"> + <property name="text"> + <string>Global Task Status...</string> + </property> + </widget> + </item> + <item row="2" column="0"> <widget class="QLabel" name="statusLabel"> <property name="text"> <string>Task Status...</string> @@ -36,7 +48,7 @@ </property> </widget> </item> - <item row="1" column="0"> + <item row="3" column="0"> <widget class="QProgressBar" name="taskProgressBar"> <property name="value"> <number>24</number> @@ -46,16 +58,13 @@ </property> </widget> </item> - <item row="2" column="0"> - <widget class="QPushButton" name="skipButton"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <item row="1" column="0"> + <widget class="QProgressBar" name="globalProgressBar"> + <property name="enabled"> + <bool>true</bool> </property> - <property name="text"> - <string>Skip</string> + <property name="value"> + <number>24</number> </property> </widget> </item> diff --git a/launcher/ui/dialogs/UpdateDialog.ui b/launcher/ui/dialogs/UpdateDialog.ui index bd94a554..5eb9d88a 100644 --- a/launcher/ui/dialogs/UpdateDialog.ui +++ b/launcher/ui/dialogs/UpdateDialog.ui @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>PolyMC Update</string> + <string>Launcher Update</string> </property> <property name="windowIcon"> <iconset> diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index decc9b8b..c18ab34b 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -173,20 +173,20 @@ <item> <widget class="QCheckBox" name="closeAfterLaunchCheck"> <property name="toolTip"> - <string><html><head/><body><p>PolyMC will automatically reopen when the game crashes or exits.</p></body></html></string> + <string><html><head/><body><p>The launcher will automatically reopen when the game crashes or exits.</p></body></html></string> </property> <property name="text"> - <string>Close PolyMC after game window opens</string> + <string>Close the launcher after game window opens</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="quitAfterGameStopCheck"> <property name="toolTip"> - <string><html><head/><body><p>PolyMC will automatically exit if the game crashes or exists.</p></body></html></string> + <string><html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html></string> </property> <property name="text"> - <string>Quit PolyMC after game window stops</string> + <string>Quit the launcher after game window closes</string> </property> </widget> </item> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index a5985741..a48c4d69 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -179,7 +179,6 @@ void InstanceSettingsPage::applySettings() if(javaArgs) { m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " ")); - JavaCommon::checkJVMArgs(m_settings->get("JvmArgs").toString(), this->parentWidget()); } else { diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 599f0e11..8113fe85 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -56,8 +56,11 @@ #include "minecraft/VersionFilterData.h" #include "minecraft/PackProfile.h" +#include "modplatform/ModAPI.h" + #include "Version.h" #include "ui/dialogs/ProgressDialog.h" +#include "tasks/SequentialTask.h" namespace { // FIXME: wasteful @@ -387,32 +390,31 @@ void ModFolderPage::on_actionInstall_mods_triggered() if(m_inst->typeName() != "Minecraft"){ return; //this is a null instance or a legacy instance } - bool hasFabric = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); - bool hasForge = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.minecraftforge").isEmpty(); - if (!hasFabric && !hasForge) { + auto profile = ((MinecraftInstance *)m_inst)->getPackProfile(); + if (profile->getModLoader() == ModAPI::Unspecified) { QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!")); return; } ModDownloadDialog mdownload(m_mods, this, m_inst); - if(mdownload.exec()) { - for(auto task : mdownload.getTasks()){ - connect(task, &Task::failed, [this, task](QString reason) { - task->deleteLater(); - CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); - }); - connect(task, &Task::succeeded, [this, task]() { - QStringList warnings = task->warnings(); - if (warnings.count()) { - CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), - QMessageBox::Warning)->show(); - } - task->deleteLater(); - }); - ProgressDialog loadDialog(this); - loadDialog.setSkipButton(true, tr("Abort")); - loadDialog.execWithTask(task); - m_mods->update(); + if (mdownload.exec()) { + SequentialTask* tasks = new SequentialTask(this); + connect(tasks, &Task::failed, [this, tasks](QString reason) { + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); + tasks->deleteLater(); + }); + connect(tasks, &Task::succeeded, [this, tasks]() { + QStringList warnings = tasks->warnings(); + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); } + tasks->deleteLater(); + }); + + for (auto task : mdownload.getTasks()) { + tasks->addTask(task); } + ProgressDialog loadDialog(this); + loadDialog.setSkipButton(true, tr("Abort")); + loadDialog.execWithTask(tasks); + m_mods->update(); } } diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index ed37dd1a..23e2367b 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -243,6 +243,9 @@ void VersionPage::updateVersionControls() bool supportsFabric = minecraftVersion >= Version("1.14"); ui->actionInstall_Fabric->setEnabled(controlsEnabled && supportsFabric); + bool supportsQuilt = minecraftVersion >= Version("1.14"); + ui->actionInstall_Quilt->setEnabled(controlsEnabled && supportsQuilt); + bool supportsLiteLoader = minecraftVersion <= Version("1.12.2"); ui->actionInstall_LiteLoader->setEnabled(controlsEnabled && supportsLiteLoader); @@ -392,7 +395,7 @@ void VersionPage::on_actionChange_version_triggered() return; } VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); - if (uid == "net.fabricmc.intermediary") + if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed") { vselect.setEmptyString(tr("No intermediary mappings versions are currently available.")); vselect.setEmptyErrorString(tr("Couldn't load or download the intermediary mappings version lists!")); @@ -498,6 +501,33 @@ void VersionPage::on_actionInstall_Fabric_triggered() } } +void VersionPage::on_actionInstall_Quilt_triggered() +{ + auto vlist = APPLICATION->metadataIndex()->get("org.quiltmc.quilt-loader"); + if(!vlist) + { + return; + } + VersionSelectDialog vselect(vlist.get(), tr("Select Quilt Loader version"), this); + vselect.setEmptyString(tr("No Quilt Loader versions are currently available.")); + vselect.setEmptyErrorString(tr("Couldn't load or download the Quilt Loader version lists!")); + + auto currentVersion = m_profile->getComponentVersion("org.quiltmc.quilt-loader"); + if(!currentVersion.isEmpty()) + { + vselect.setCurrentVersion(currentVersion); + } + + if (vselect.exec() && vselect.selectedVersion()) + { + auto vsn = vselect.selectedVersion(); + m_profile->setComponentVersion("org.quiltmc.quilt-loader", vsn->descriptor()); + m_profile->resolve(Net::Mode::Online); + preselect(m_profile->rowCount(QModelIndex())-1); + m_container->refreshContainer(); + } +} + void VersionPage::on_actionAdd_Empty_triggered() { NewComponentDialog compdialog(QString(), QString(), this); diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index 2d37af43..979311fc 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -73,6 +73,7 @@ private slots: void on_actionChange_version_triggered(); void on_actionInstall_Forge_triggered(); void on_actionInstall_Fabric_triggered(); + void on_actionInstall_Quilt_triggered(); void on_actionAdd_Empty_triggered(); void on_actionInstall_LiteLoader_triggered(); void on_actionReload_triggered(); diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index a4990ff3..489f7218 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -107,6 +107,7 @@ <addaction name="separator"/> <addaction name="actionInstall_Forge"/> <addaction name="actionInstall_Fabric"/> + <addaction name="actionInstall_Quilt"/> <addaction name="actionInstall_LiteLoader"/> <addaction name="actionInstall_mods"/> <addaction name="separator"/> @@ -192,6 +193,14 @@ <string>Install the Fabric Loader package.</string> </property> </action> + <action name="actionInstall_Quilt"> + <property name="text"> + <string>Install Quilt</string> + </property> + <property name="toolTip"> + <string>Install the Quilt Loader package.</string> + </property> + </action> <action name="actionInstall_LiteLoader"> <property name="text"> <string>Install LiteLoader</string> diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 01b5d247..da0331b5 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -61,21 +61,22 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) { + auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile(); + m_parent->apiProvider()->getVersions(this, - { current.addonId.toString(), getMineVersions(), hasFabric() ? ModAPI::ModLoaderType::Fabric : ModAPI::ModLoaderType::Forge }); + { current.addonId.toString(), getMineVersions(), profile->getModLoader() }); } void ListModel::performPaginatedSearch() { + auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile(); + m_parent->apiProvider()->searchMods(this, - { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], hasFabric() ? ModAPI::Fabric : ModAPI::Forge, getMineVersions().at(0) }); + { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() }); } -void ListModel::searchWithTerm(const QString& term, const int sort) +void ListModel::refresh() { - if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; } - currentSearchTerm = term; - currentSort = sort; if (jobPtr) { jobPtr->abort(); searchState = ResetRequested; @@ -90,6 +91,20 @@ void ListModel::searchWithTerm(const QString& term, const int sort) performPaginatedSearch(); } +void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed) +{ + if (currentSearchTerm == term + && currentSearchTerm.isNull() == term.isNull() + && currentSort == sort + && !filter_changed) + { return; } + + currentSearchTerm = term; + currentSort = sort; + + refresh(); +} + void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) { if (m_logoMap.contains(logo)) { @@ -218,17 +233,8 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) } // namespace ModPlatform /******** Helpers ********/ -auto ModPlatform::ListModel::hasFabric() const -> bool -{ - return !(dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance)) - ->getPackProfile() - ->getComponentVersion("net.fabricmc.fabric-loader") - .isEmpty(); -} -auto ModPlatform::ListModel::getMineVersions() const -> QList<QString> +auto ModPlatform::ListModel::getMineVersions() const -> std::list<Version> { - return { (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance)) - ->getPackProfile() - ->getComponentVersion("net.minecraft") }; + return m_parent->getFilter()->versions; } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 64cfa71e..d460cff2 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -7,6 +7,7 @@ #include "net/NetJob.h" class ModPage; +class Version; namespace ModPlatform { @@ -33,7 +34,8 @@ class ListModel : public QAbstractListModel { /* Ask the API for more information */ void fetchMore(const QModelIndex& parent) override; - void searchWithTerm(const QString& term, const int sort); + void refresh(); + void searchWithTerm(const QString& term, const int sort, const bool filter_changed); void requestModVersions(const ModPlatform::IndexedPack& current); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; @@ -62,8 +64,7 @@ class ListModel : public QAbstractListModel { void requestLogo(QString file, QString url); - inline auto hasFabric() const -> bool; - inline auto getMineVersions() const -> QList<QString>; + inline auto getMineVersions() const -> std::list<Version>; protected: ModPage* m_parent; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 3a116d3c..29f6b601 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -2,21 +2,39 @@ #include "ui_ModPage.h" #include <QKeyEvent> +#include <memory> #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) - : QWidget(dialog), m_instance(instance), ui(new Ui::ModPage), dialog(dialog), api(api) + : QWidget(dialog) + , m_instance(instance) + , ui(new Ui::ModPage) + , dialog(dialog) + , filter_widget(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this) + , api(api) { ui->setupUi(this); connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); + connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); ui->searchEdit->installEventFilter(this); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); + ui->gridLayout_3->addWidget(&filter_widget, 0, 0, 1, ui->gridLayout_3->columnCount()); + + filter_widget.setInstance(static_cast<MinecraftInstance*>(m_instance)); + m_filter = filter_widget.getFilter(); + + connect(&filter_widget, &ModFilterWidget::filterChanged, this, [&]{ + ui->searchButton->setStyleSheet("text-decoration: underline"); + }); + connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{ + ui->searchButton->setStyleSheet("text-decoration: none"); + }); } ModPage::~ModPage() @@ -49,9 +67,24 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool /******** Callbacks to events in the UI (set up in the derived classes) ********/ +void ModPage::filterMods() +{ + filter_widget.setHidden(!filter_widget.isHidden()); +} + void ModPage::triggerSearch() { - listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); + auto changed = filter_widget.changed(); + m_filter = filter_widget.getFilter(); + + if(changed){ + ui->packView->clearSelection(); + ui->packDescription->clear(); + ui->versionSelectionBox->clear(); + updateSelectionButton(); + } + + listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), changed); } void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) @@ -68,7 +101,7 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) text = name; else text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; - + if (!current.authors.empty()) { auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { if (author.url.isEmpty()) { return author.name; } @@ -128,27 +161,35 @@ void ModPage::onModSelected() void ModPage::retranslate() { - ui->retranslateUi(this); + ui->retranslateUi(this); } -void ModPage::updateModVersions() +void ModPage::updateModVersions(int prev_count) { auto packProfile = (dynamic_cast<MinecraftInstance*>(m_instance))->getPackProfile(); QString mcVersion = packProfile->getComponentVersion("net.minecraft"); - QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge"; + + QString loaderString = ModAPI::getModLoaderString(packProfile->getModLoader()); for (int i = 0; i < current.versions.size(); i++) { auto version = current.versions[i]; - //NOTE: Flame doesn't care about loaderString, so passing it changes nothing. - if (!validateVersion(version, mcVersion, loaderString)) { - continue; + bool valid = false; + for(auto& mcVer : m_filter->versions){ + //NOTE: Flame doesn't care about loaderString, so passing it changes nothing. + if (validateVersion(version, mcVer.toString(), loaderString)) { + valid = true; + break; + } } - ui->versionSelectionBox->addItem(version.version, QVariant(i)); + if(valid || m_filter->versions.size() == 0) + 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 (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); } - ui->modSelectionButton->setText(tr("Cannot select invalid version :(")); updateSelectionButton(); } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 0cd13f37..85aaede9 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -7,6 +7,7 @@ #include "modplatform/ModIndex.h" #include "ui/pages/BasePage.h" #include "ui/pages/modplatform/ModModel.h" +#include "ui/widgets/ModFilterWidget.h" class ModDownloadDialog; @@ -39,9 +40,10 @@ class ModPage : public QWidget, public BasePage { virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, QString loaderVer = "") const -> bool = 0; auto apiProvider() const -> const ModAPI* { return api.get(); }; + auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } auto getCurrent() -> ModPlatform::IndexedPack& { return current; } - void updateModVersions(); + void updateModVersions(int prev_count = -1); void openedImpl() override; auto eventFilter(QObject* watched, QEvent* event) -> bool override; @@ -52,6 +54,7 @@ class ModPage : public QWidget, public BasePage { void updateSelectionButton(); protected slots: + virtual void filterMods(); void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); void onVersionSelectionChanged(QString data); @@ -60,6 +63,10 @@ class ModPage : public QWidget, public BasePage { protected: Ui::ModPage* ui = nullptr; ModDownloadDialog* dialog = nullptr; + + ModFilterWidget filter_widget; + std::shared_ptr<ModFilterWidget::Filter> m_filter; + ModPlatform::ListModel* listModel = nullptr; ModPlatform::IndexedPack current; diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui index 508f1bac..afcd9bb7 100644 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ b/launcher/ui/pages/modplatform/ModPage.ui @@ -11,7 +11,7 @@ </rect> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="0" colspan="2"> + <item row="1" column="0" colspan="4"> <layout class="QGridLayout" name="gridLayout_3"> <item row="1" column="2"> <widget class="QTextBrowser" name="packDescription"> @@ -41,7 +41,7 @@ </item> </layout> </item> - <item row="0" column="1"> + <item row="0" column="3"> <widget class="QPushButton" name="searchButton"> <property name="text"> <string>Search</string> @@ -51,12 +51,12 @@ <item row="0" column="0"> <widget class="QLineEdit" name="searchEdit"> <property name="placeholderText"> - <string>Search and filter...</string> + <string>Search for mods...</string> </property> </widget> </item> - <item row="2" column="0" colspan="2"> - <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0,0"> + <item row="2" column="0" colspan="4"> + <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0"> <item row="0" column="2"> <widget class="QComboBox" name="versionSelectionBox"/> </item> @@ -82,6 +82,20 @@ </item> </layout> </item> + <item row="0" column="1"> + <widget class="QPushButton" name="modFilterButton"> + <property name="text"> + <string>Filter options</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + </widget> + </item> </layout> </widget> <tabstops> diff --git a/launcher/ui/pages/modplatform/VanillaPage.cpp b/launcher/ui/pages/modplatform/VanillaPage.cpp index c691128f..207d0130 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.cpp +++ b/launcher/ui/pages/modplatform/VanillaPage.cpp @@ -44,6 +44,7 @@ #include "ui/dialogs/NewInstanceDialog.h" #include "Filter.h" #include "InstanceCreationTask.h" +#include "Version.h" VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) @@ -59,6 +60,15 @@ VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged); connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh); + + connect(ui->loaderVersionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedLoaderVersion); + connect(ui->noneFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); + connect(ui->forgeFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); + connect(ui->fabricFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); + connect(ui->quiltFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); + connect(ui->liteLoaderFilter, &QRadioButton::toggled, this, &VanillaPage::loaderFilterChanged); + connect(ui->loaderRefreshBtn, &QPushButton::clicked, this, &VanillaPage::loaderRefresh); + } void VanillaPage::openedImpl() @@ -80,6 +90,13 @@ void VanillaPage::refresh() ui->versionList->loadList(); } +void VanillaPage::loaderRefresh() +{ + if(ui->noneFilter->isChecked()) + return; + ui->loaderVersionList->loadList(); +} + void VanillaPage::filterChanged() { QStringList out; @@ -99,6 +116,52 @@ void VanillaPage::filterChanged() ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false)); } +void VanillaPage::loaderFilterChanged() +{ + auto minecraftVersion = m_selectedVersion->descriptor(); + if(ui->noneFilter->isChecked()) + { + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, "AAA"); // empty list + ui->loaderVersionList->setEmptyString(tr("No mod loader is selected.")); + ui->loaderVersionList->setEmptyMode(VersionListView::String); + return; + } + else if(ui->forgeFilter->isChecked()) + { + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); + m_selectedLoader = "net.minecraftforge"; + } + else if(ui->fabricFilter->isChecked()) + { + // FIXME: dirty hack because the launcher is unaware of Fabric's dependencies + if (Version(minecraftVersion) >= Version("1.14")) // Fabric/Quilt supported + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, ""); + else // Fabric/Quilt unsupported + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, "AAA"); // clear list + m_selectedLoader = "net.fabricmc.fabric-loader"; + } + else if(ui->quiltFilter->isChecked()) + { + // FIXME: dirty hack because the launcher is unaware of Quilt's dependencies (same as Fabric) + if (Version(minecraftVersion) >= Version("1.14")) // Fabric/Quilt supported + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, ""); + else // Fabric/Quilt unsupported + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, "AAA"); // clear list + m_selectedLoader = "org.quiltmc.quilt-loader"; + } + else if(ui->liteLoaderFilter->isChecked()) + { + ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); + m_selectedLoader = "com.mumfrey.liteloader"; + } + + auto vlist = APPLICATION->metadataIndex()->get(m_selectedLoader); + ui->loaderVersionList->initialize(vlist.get()); + ui->loaderVersionList->selectRecommended(); + suggestCurrent(); + ui->loaderVersionList->setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion)); +} + VanillaPage::~VanillaPage() { delete ui; @@ -119,6 +182,16 @@ BaseVersionPtr VanillaPage::selectedVersion() const return m_selectedVersion; } +BaseVersionPtr VanillaPage::selectedLoaderVersion() const +{ + return m_selectedLoaderVersion; +} + +QString VanillaPage::selectedLoader() const +{ + return m_selectedLoader; +} + void VanillaPage::suggestCurrent() { if (!isOpened) @@ -132,7 +205,15 @@ void VanillaPage::suggestCurrent() return; } - dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + // List is empty if either no mod loader is selected, or no versions are available + if(!ui->loaderVersionList->hasVersions()) + dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + else + { + dialog->setSuggestedPack(m_selectedVersion->descriptor(), + new InstanceCreationTask(m_selectedVersion, m_selectedLoader, + m_selectedLoaderVersion)); + } dialog->setSuggestedIcon("default"); } @@ -140,4 +221,11 @@ void VanillaPage::setSelectedVersion(BaseVersionPtr version) { m_selectedVersion = version; suggestCurrent(); + loaderFilterChanged(); +} + +void VanillaPage::setSelectedLoaderVersion(BaseVersionPtr version) +{ + m_selectedLoaderVersion = version; + suggestCurrent(); } diff --git a/launcher/ui/pages/modplatform/VanillaPage.h b/launcher/ui/pages/modplatform/VanillaPage.h index 4e7479df..7193597d 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.h +++ b/launcher/ui/pages/modplatform/VanillaPage.h @@ -77,15 +77,20 @@ public: void openedImpl() override; BaseVersionPtr selectedVersion() const; + BaseVersionPtr selectedLoaderVersion() const; + QString selectedLoader() const; public slots: void setSelectedVersion(BaseVersionPtr version); + void setSelectedLoaderVersion(BaseVersionPtr version); private slots: void filterChanged(); + void loaderFilterChanged(); private: void refresh(); + void loaderRefresh(); void suggestCurrent(); private: @@ -94,4 +99,6 @@ private: Ui::VanillaPage *ui = nullptr; bool m_versionSetByUser = false; BaseVersionPtr m_selectedVersion; + BaseVersionPtr m_selectedLoaderVersion; + QString m_selectedLoader; }; diff --git a/launcher/ui/pages/modplatform/VanillaPage.ui b/launcher/ui/pages/modplatform/VanillaPage.ui index 870ff161..43110927 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.ui +++ b/launcher/ui/pages/modplatform/VanillaPage.ui @@ -33,113 +33,231 @@ <string notr="true"/> </attribute> <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Filter</string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="releaseFilter"> - <property name="text"> - <string>Releases</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="snapshotFilter"> - <property name="text"> - <string>Snapshots</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="oldSnapshotFilter"> - <property name="text"> - <string>Old Snapshots</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - </widget> - </item> + <item row="2" column="0"> + <widget class="Line" name="line"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="minecraftLayout"> <item> - <widget class="QCheckBox" name="betaFilter"> - <property name="text"> - <string>Betas</string> - </property> - <property name="checkable"> - <bool>true</bool> + <widget class="VersionSelectWidget" name="versionList" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> </widget> </item> <item> - <widget class="QCheckBox" name="alphaFilter"> - <property name="text"> - <string>Alphas</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - </widget> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Filter</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="releaseFilter"> + <property name="text"> + <string>Releases</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="snapshotFilter"> + <property name="text"> + <string>Snapshots</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="oldSnapshotFilter"> + <property name="text"> + <string>Old Snapshots</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="betaFilter"> + <property name="text"> + <string>Betas</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="alphaFilter"> + <property name="text"> + <string>Alphas</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="experimentsFilter"> + <property name="text"> + <string>Experiments</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="refreshBtn"> + <property name="text"> + <string>Refresh</string> + </property> + </widget> + </item> + </layout> </item> + </layout> + </item> + <item row="4" column="0"> + <layout class="QHBoxLayout" name="loaderLayout"> <item> - <widget class="QCheckBox" name="experimentsFilter"> - <property name="text"> - <string>Experiments</string> - </property> - <property name="checkable"> - <bool>true</bool> + <widget class="VersionSelectWidget" name="loaderVersionList" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> </widget> </item> <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="refreshBtn"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="loaderLabel"> + <property name="text"> + <string>Mod Loader</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="noneFilter"> + <property name="text"> + <string>None</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <attribute name="buttonGroup"> + <string notr="true">loaderBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="forgeFilter"> + <property name="text"> + <string>Forge</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">loaderBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="fabricFilter"> + <property name="text"> + <string>Fabric</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">loaderBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="quiltFilter"> + <property name="text"> + <string>Quilt</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">loaderBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <widget class="QRadioButton" name="liteLoaderFilter"> + <property name="text"> + <string>LiteLoader</string> + </property> + <attribute name="buttonGroup"> + <string notr="true">loaderBtnGroup</string> + </attribute> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="loaderRefreshBtn"> + <property name="text"> + <string>Refresh</string> + </property> + </widget> + </item> + </layout> </item> </layout> </item> - <item row="0" column="0"> - <widget class="VersionSelectWidget" name="versionList" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - </widget> - </item> </layout> </widget> </widget> @@ -166,4 +284,7 @@ </tabstops> <resources/> <connections/> + <buttongroups> + <buttongroup name="loaderBtnGroup"/> + </buttongroups> </ui> diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index dc58fd7f..d96a0720 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -49,7 +49,7 @@ class FlameModPage : public ModPage { 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 "Flame-platform"; } + 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"; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index aa5ed793..0bde43eb 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -49,7 +49,7 @@ class ModrinthPage : public ModPage { 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 "Modrinth-platform"; } + 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"; }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicData.h b/launcher/ui/pages/modplatform/technic/TechnicData.h index 50fd75e8..cd2ea8e1 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicData.h +++ b/launcher/ui/pages/modplatform/technic/TechnicData.h @@ -1,22 +1,43 @@ -/* Copyright 2020-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-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 <QList> #include <QString> +#include <QVector> namespace Technic { struct Modpack { @@ -36,6 +57,11 @@ struct Modpack { QString websiteUrl; QString author; QString description; + QString currentVersion; + + bool versionsLoaded = false; + QString recommended; + QVector<QString> versions; }; } diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index 0167f746..9c9d1e75 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -1,20 +1,41 @@ -/* Copyright 2020-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-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 "TechnicModel.h" #include "Application.h" +#include "BuildConfig.h" #include "Json.h" #include <QIcon> @@ -94,13 +115,24 @@ void Technic::ListModel::performSearch() NetJob *netJob = new NetJob("Technic::Search", APPLICATION->network()); QString searchUrl = ""; if (currentSearchTerm.isEmpty()) { - searchUrl = "https://api.technicpack.net/trending?build=multimc"; + searchUrl = QString("%1trending?build=%2") + .arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD); + searchMode = List; } - else - { + else if (currentSearchTerm.startsWith("http://api.technicpack.net/modpack/")) { + searchUrl = QString("https://%1?build=%2") + .arg(currentSearchTerm.mid(7), BuildConfig.TECHNIC_API_BUILD); + searchMode = Single; + } + else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) { + searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD); + searchMode = Single; + } + else { searchUrl = QString( - "https://api.technicpack.net/search?build=multimc&q=%1" - ).arg(currentSearchTerm); + "%1search?build=%2&q=%3" + ).arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); + searchMode = List; } netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; @@ -125,26 +157,58 @@ void Technic::ListModel::searchRequestFinished() QList<Modpack> newList; try { auto root = Json::requireObject(doc); - auto objs = Json::requireArray(root, "modpacks"); - for (auto technicPack: objs) { - Modpack pack; - auto technicPackObject = Json::requireObject(technicPack); - pack.name = Json::requireString(technicPackObject, "name"); - pack.slug = Json::requireString(technicPackObject, "slug"); - if (pack.slug == "vanilla") - continue; - - auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); - if(rawURL == "null") { - pack.logoUrl = "null"; - pack.logoName = "null"; + + switch (searchMode) { + case List: { + auto objs = Json::requireArray(root, "modpacks"); + for (auto technicPack: objs) { + Modpack pack; + auto technicPackObject = Json::requireObject(technicPack); + pack.name = Json::requireString(technicPackObject, "name"); + pack.slug = Json::requireString(technicPackObject, "slug"); + if (pack.slug == "vanilla") + continue; + + auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null"); + if(rawURL == "null") { + pack.logoUrl = "null"; + pack.logoName = "null"; + } + else { + pack.logoUrl = rawURL; + pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + } + pack.broken = false; + newList.append(pack); + } + break; } - else { - pack.logoUrl = rawURL; - pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + case Single: { + if (root.contains("error")) { + // Invalid API url + break; + } + + Modpack pack; + pack.name = Json::requireString(root, "displayName"); + pack.slug = Json::requireString(root, "name"); + + if (root.contains("icon")) { + auto iconObj = Json::requireObject(root, "icon"); + auto iconUrl = Json::requireString(iconObj, "url"); + + pack.logoUrl = iconUrl; + pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); + } + else { + pack.logoUrl = "null"; + pack.logoName = "null"; + } + + pack.broken = false; + newList.append(pack); + break; } - pack.broken = false; - newList.append(pack); } } catch (const JSONValidationError &err) diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h index e80e6e7c..5eea124c 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.h +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h @@ -1,16 +1,36 @@ -/* Copyright 2020-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2021 Jamie Mansfield <jmansfield@cadixdev.org> * - * 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 + * 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. * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-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 @@ -63,6 +83,10 @@ private: ResetRequested, Finished } searchState = None; + enum SearchMode { + List, + Single, + } searchMode = List; NetJob::Ptr jobPtr; QByteArray response; }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index c3807269..b8c1e00a 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * * 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 @@ -40,12 +40,14 @@ #include "ui/dialogs/NewInstanceDialog.h" +#include "BuildConfig.h" #include "TechnicModel.h" #include "modplatform/technic/SingleZipPackInstallTask.h" #include "modplatform/technic/SolderPackInstallTask.h" #include "Json.h" #include "Application.h" +#include "modplatform/technic/SolderPackManifest.h" TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) @@ -55,7 +57,9 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) ui->searchEdit->installEventFilter(this); model = new Technic::ListModel(this); ui->packView->setModel(model); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged); } bool TechnicPage::eventFilter(QObject* watched, QEvent* event) @@ -98,13 +102,14 @@ void TechnicPage::triggerSearch() { void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second) { + ui->versionSelectionBox->clear(); + if(!first.isValid()) { if(isOpened) { dialog->setSuggestedPack(); } - //ui->frame->clear(); return; } @@ -137,17 +142,19 @@ void TechnicPage::suggestCurrent() } NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); - std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); QString slug = current.slug; - netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get())); - QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug] + netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), &response)); + QObject::connect(netJob, &NetJob::succeeded, this, [this, slug] { + jobPtr.reset(); + if (current.slug != slug) { return; } - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + + QJsonParseError parse_error {}; + QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); QJsonObject obj = doc.object(); if(parse_error.error != QJsonParseError::NoError) { @@ -189,10 +196,14 @@ void TechnicPage::suggestCurrent() current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__"); current.author = Json::ensureString(obj, "user", QString(), "__placeholder__"); current.description = Json::ensureString(obj, "description", QString(), "__placeholder__"); + current.currentVersion = Json::ensureString(obj, "version", QString(), "__placeholder__"); current.metadataLoaded = true; + metadataLoaded(); }); - netJob->start(); + + jobPtr = netJob; + jobPtr->start(); } // expects current.metadataLoaded to be true @@ -202,25 +213,119 @@ void TechnicPage::metadataLoaded() QString name = current.name; if (current.websiteUrl.isEmpty()) - // This allows injecting HTML here. - text = name; + text = name.toHtmlEscaped(); else - // URL not properly escaped for inclusion in HTML. The name allows for injecting HTML. - text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>"; + text = "<a href=\"" + current.websiteUrl.toHtmlEscaped() + "\">" + name.toHtmlEscaped() + "</a>"; + if (!current.author.isEmpty()) { - // This allows injecting HTML here - text += tr(" by ") + current.author; + text += "<br>" + tr(" by ") + current.author.toHtmlEscaped(); + } + + text += "<br><br>"; + + ui->packDescription->setHtml(text + current.description); + + // Strip trailing forward-slashes from Solder URL's + if (current.isSolder) { + while (current.url.endsWith('/')) current.url.chop(1); + } + + // Display versions from Solder + if (!current.isSolder) { + // If the pack isn't a Solder pack, it only has the single version + ui->versionSelectionBox->addItem(current.currentVersion); + } + else if (current.versionsLoaded) { + // reverse foreach, so that the newest versions are first + for (auto i = current.versions.size(); i--;) { + ui->versionSelectionBox->addItem(current.versions.at(i)); + } + ui->versionSelectionBox->setCurrentText(current.recommended); + } + else { + // For now, until the versions are pulled from the Solder instance, display the current + // version so we can display something quicker + ui->versionSelectionBox->addItem(current.currentVersion); + + auto* netJob = new NetJob(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); + auto url = QString("%1/modpack/%2").arg(current.url, current.slug); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response)); + + QObject::connect(netJob, &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); + + jobPtr = netJob; + jobPtr->start(); + } + + selectVersion(); +} + +void TechnicPage::selectVersion() { + if (!isOpened) { + return; + } + if (current.broken) { + dialog->setSuggestedPack(); + return; } - ui->frame->setModText(text); - ui->frame->setModDescription(current.description); if (!current.isSolder) { - dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); } else { - while (current.url.endsWith('/')) current.url.chop(1); - dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, current.minecraftVersion)); + dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url, current.slug, selectedVersion, current.minecraftVersion)); + } +} + +void TechnicPage::onSolderLoaded() { + jobPtr.reset(); + + auto fallback = [this]() { + current.versionsLoaded = true; + + current.versions.clear(); + current.versions.append(current.currentVersion); + }; + + current.versions.clear(); + + QJsonParseError parse_error {}; + auto doc = QJsonDocument::fromJson(response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Solder at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + fallback(); + return; + } + auto obj = doc.object(); + + TechnicSolder::Pack pack; + try { + TechnicSolder::loadPack(pack, obj); + } + catch (const JSONValidationError& err) { + qCritical() << "Couldn't parse Solder pack metadata:" << err.cause(); + fallback(); + return; + } + + current.versionsLoaded = true; + current.recommended = pack.recommended; + current.versions.append(pack.builds); + + // Finally, let's reload :) + ui->versionSelectionBox->clear(); + metadataLoaded(); +} + +void TechnicPage::onVersionSelectionChanged(QString data) { + if (data.isNull() || data.isEmpty()) { + selectedVersion = ""; + return; } + + selectedVersion = data; + selectVersion(); } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h index bf4baa58..f4a3b61d 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.h +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * Copyright (c) 2021-2022 Jamie Mansfield <jmansfield@cadixdev.org> * * 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 @@ -39,6 +39,7 @@ #include "ui/pages/BasePage.h" #include <Application.h> +#include "net/NetJob.h" #include "tasks/Task.h" #include "TechnicData.h" @@ -86,14 +87,22 @@ public: private: void suggestCurrent(); void metadataLoaded(); + void selectVersion(); private slots: void triggerSearch(); void onSelectionChanged(QModelIndex first, QModelIndex second); + void onSolderLoaded(); + void onVersionSelectionChanged(QString data); private: Ui::TechnicPage *ui = nullptr; NewInstanceDialog* dialog = nullptr; Technic::ListModel* model = nullptr; + Technic::Modpack current; + QString selectedVersion; + + NetJob::Ptr jobPtr; + QByteArray response; }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui index 62ab6154..ca6a9b7e 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui @@ -10,86 +10,76 @@ <height>405</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QWidget" name="widget" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QLineEdit" name="searchEdit"> - <property name="placeholderText"> - <string>Search and filter...</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="searchButton"> - <property name="text"> - <string>Search</string> - </property> - </widget> - </item> - </layout> - </widget> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="2"> + <widget class="QComboBox" name="versionSelectionBox"/> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Version selected:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Preferred</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>1</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> </item> - <item> - <widget class="QListView" name="packView"> - <property name="horizontalScrollBarPolicy"> - <enum>Qt::ScrollBarAlwaysOff</enum> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="iconSize"> - <size> - <width>48</width> - <height>48</height> - </size> + <item row="2" column="0" colspan="2"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QListView" name="packView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="iconSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QTextBrowser" name="packDescription"/> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QLineEdit" name="searchEdit"> + <property name="placeholderText"> + <string>Search and filter...</string> </property> </widget> </item> - <item> - <widget class="MCModInfoFrame" name="frame"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> + <item row="1" column="1"> + <widget class="QPushButton" name="searchButton"> + <property name="text"> + <string>Search</string> </property> </widget> </item> </layout> </widget> - <customwidgets> - <customwidget> - <class>MCModInfoFrame</class> - <extends>QFrame</extends> - <header>ui/widgets/MCModInfoFrame.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <tabstops> - <tabstop>searchEdit</tabstop> - <tabstop>searchButton</tabstop> - <tabstop>packView</tabstop> - </tabstops> <resources/> <connections/> </ui> diff --git a/launcher/ui/widgets/CustomCommands.ui b/launcher/ui/widgets/CustomCommands.ui index dbd54431..650a9cc1 100644 --- a/launcher/ui/widgets/CustomCommands.ui +++ b/launcher/ui/widgets/CustomCommands.ui @@ -74,7 +74,7 @@ <item> <widget class="QLabel" name="labelCustomCmdsDescription"> <property name="text"> - <string><html><head/><body><p>Pre-launch command runs before the instance launches and post-exit command runs after it exits.</p><p>Both will be run in the launcher's working folder with extra environment variables:</p><ul><li>$INST_NAME - Name of the instance</li><li>$INST_ID - ID of the instance (its folder name)</li><li>$INST_DIR - absolute path of the instance</li><li>$INST_MC_DIR - absolute path of Minecraft</li><li>$INST_JAVA - Java binary used for launch</li><li>$INST_JAVA_ARGS - command-line parameters used for launch</li></ul><p>Wrapper command allows launching using an extra wrapper program (like 'optirun' on Linux)</p></body></html></string> + <string><html><head/><body><p>Pre-launch command runs before the instance launches and post-exit command runs after it exits.</p><p>Both will be run in the launcher's working folder with extra environment variables:</p><ul><li>$INST_NAME - Name of the instance</li><li>$INST_ID - ID of the instance (its folder name)</li><li>$INST_DIR - absolute path of the instance</li><li>$INST_MC_DIR - absolute path of Minecraft</li><li>$INST_JAVA - Java binary used for launch</li><li>$INST_JAVA_ARGS - command-line parameters used for launch (warning: will not work correctly if arguments contain spaces)</li></ul><p>Wrapper command allows launching using an extra wrapper program (like 'optirun' on Linux)</p></body></html></string> </property> <property name="alignment"> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp new file mode 100644 index 00000000..ffc8d05d --- /dev/null +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -0,0 +1,109 @@ +#include "ModFilterWidget.h" +#include "ui_ModFilterWidget.h" + +ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) + : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget) +{ + ui->setupUi(this); + + m_mcVersion_buttons.addButton(ui->strictVersionButton, VersionButtonID::Strict); + ui->strictVersionButton->click(); + m_mcVersion_buttons.addButton(ui->majorVersionButton, VersionButtonID::Major); + m_mcVersion_buttons.addButton(ui->allVersionsButton, VersionButtonID::All); + //m_mcVersion_buttons.addButton(ui->betweenVersionsButton, VersionButtonID::Between); + + connect(&m_mcVersion_buttons, SIGNAL(idClicked(int)), this, SLOT(onVersionFilterChanged(int))); + + m_filter->versions.push_front(def); + + setHidden(true); +} + +void ModFilterWidget::setInstance(MinecraftInstance* instance) +{ + m_instance = instance; + + auto mcVersionSplit = mcVersionStr().split("."); + + ui->strictVersionButton->setText( + tr("Strict match (= %1)").arg(mcVersionStr())); + ui->majorVersionButton->setText( + tr("Major version match (= %1.%2.x)").arg(mcVersionSplit[0], mcVersionSplit[1])); + ui->allVersionsButton->setText( + tr("Any version")); + //ui->betweenVersionsButton->setText( + // tr("Between two versions")); +} + +auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter> +{ + m_last_version_id = m_version_id; + emit filterUnchanged(); + return m_filter; +} + +void ModFilterWidget::disableVersionButton(VersionButtonID id) +{ + switch(id){ + case(VersionButtonID::Strict): + ui->strictVersionButton->setEnabled(false); + break; + case(VersionButtonID::Major): + ui->majorVersionButton->setEnabled(false); + break; + case(VersionButtonID::All): + ui->allVersionsButton->setEnabled(false); + break; + case(VersionButtonID::Between): + // ui->betweenVersionsButton->setEnabled(false); + break; + default: + break; + } +} + +void ModFilterWidget::onVersionFilterChanged(int id) +{ + //ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between); + //ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between); + + auto versionSplit = mcVersionStr().split("."); + int index = 0; + + auto cast_id = (VersionButtonID) id; + if (cast_id != m_version_id) { + m_version_id = cast_id; + } else { + return; + } + + m_filter->versions.clear(); + + switch(cast_id){ + case(VersionButtonID::Strict): + m_filter->versions.push_front(mcVersion()); + break; + case(VersionButtonID::Major): + for(auto i = Version(QString("%1.%2").arg(versionSplit[0], versionSplit[1])); i <= mcVersion(); index++){ + m_filter->versions.push_front(i); + i = Version(QString("%1.%2.%3").arg(versionSplit[0], versionSplit[1], QString("%1").arg(index))); + } + break; + case(VersionButtonID::All): + // Empty list to avoid enumerating all versions :P + break; + case(VersionButtonID::Between): + // TODO + break; + } + + if(changed()) + emit filterChanged(); + else + emit filterUnchanged(); +} + +ModFilterWidget::~ModFilterWidget() +{ + delete ui; +} diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h new file mode 100644 index 00000000..334fc672 --- /dev/null +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -0,0 +1,69 @@ +#pragma once + +#include <QTabWidget> +#include <QButtonGroup> + +#include "Version.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +class MinecraftInstance; + +namespace Ui { +class ModFilterWidget; +} + +class ModFilterWidget : public QTabWidget +{ + Q_OBJECT +public: + enum VersionButtonID { + Strict = 0, + Major = 1, + All = 2, + Between = 3 + }; + + struct Filter { + std::list<Version> versions; + + bool operator==(const Filter& other) const { return versions == other.versions; } + bool operator!=(const Filter& other) const { return !(*this == other); } + }; + + std::shared_ptr<Filter> m_filter; + +public: + explicit ModFilterWidget(Version def, QWidget* parent = nullptr); + ~ModFilterWidget(); + + void setInstance(MinecraftInstance* instance); + + /// By default all buttons are enabled + void disableVersionButton(VersionButtonID); + + auto getFilter() -> std::shared_ptr<Filter>; + auto changed() const -> bool { return m_last_version_id != m_version_id; } + +private: + inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; } + inline auto mcVersion() const -> Version { return { mcVersionStr() }; } + +private slots: + void onVersionFilterChanged(int id); + +public: signals: + void filterChanged(); + void filterUnchanged(); + +private: + Ui::ModFilterWidget* ui; + + MinecraftInstance* m_instance = nullptr; + + QButtonGroup m_mcVersion_buttons; + + /* Used to tell if the filter was changed since the last getFilter() call */ + VersionButtonID m_last_version_id = VersionButtonID::Strict; + VersionButtonID m_version_id = VersionButtonID::Strict; +}; diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui new file mode 100644 index 00000000..ad1090e2 --- /dev/null +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ModFilterWidget</class> + <widget class="QTabWidget" name="ModFilterWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <widget class="QWidget" name="VersionPage"> + <attribute name="title"> + <string>Minecraft versions</string> + </attribute> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QFormLayout" name="formLayout"> + <item row="2" column="0"> + <widget class="QRadioButton" name="allVersionsButton"> + <property name="text"> + <string>allVersions</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QRadioButton" name="strictVersionButton"> + <property name="text"> + <string>strictVersion</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QRadioButton" name="majorVersionButton"> + <property name="text"> + <string>majorVersion</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp index 1209f118..cc4fc6a2 100644 --- a/launcher/ui/widgets/VersionSelectWidget.cpp +++ b/launcher/ui/widgets/VersionSelectWidget.cpp @@ -4,7 +4,6 @@ #include <QVBoxLayout> #include <QHeaderView> -#include "VersionListView.h" #include "VersionProxyModel.h" #include "ui/dialogs/CustomMessageBox.h" @@ -57,6 +56,11 @@ void VersionSelectWidget::setEmptyErrorString(QString emptyErrorString) listView->setEmptyErrorString(emptyErrorString); } +void VersionSelectWidget::setEmptyMode(VersionListView::EmptyMode mode) +{ + listView->setEmptyMode(mode); +} + VersionSelectWidget::~VersionSelectWidget() { } diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h index 0a649408..f56daa8a 100644 --- a/launcher/ui/widgets/VersionSelectWidget.h +++ b/launcher/ui/widgets/VersionSelectWidget.h @@ -18,6 +18,7 @@ #include <QWidget> #include <QSortFilterProxyModel> #include "BaseVersionList.h" +#include "VersionListView.h" class VersionProxyModel; class VersionListView; @@ -49,6 +50,7 @@ public: void setFilter(BaseVersionList::ModelRoles role, Filter *filter); void setEmptyString(QString emptyString); void setEmptyErrorString(QString emptyErrorString); + void setEmptyMode(VersionListView::EmptyMode mode); void setResizeOn(int column); signals: |