diff options
Diffstat (limited to 'launcher/ui/pages')
79 files changed, 1430 insertions, 705 deletions
| diff --git a/launcher/ui/pages/BasePageContainer.h b/launcher/ui/pages/BasePageContainer.h index b750e827..a497ef7b 100644 --- a/launcher/ui/pages/BasePageContainer.h +++ b/launcher/ui/pages/BasePageContainer.h @@ -6,6 +6,7 @@ class BasePageContainer {     public:      virtual ~BasePageContainer(){};      virtual bool selectPage(QString pageId) = 0; +    virtual BasePage* selectedPage() const = 0;      virtual BasePage* getPage(QString pageId) { return nullptr; };      virtual void refreshContainer() = 0;      virtual bool requestClose() = 0; diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index 1d5ecb8d..c95bfabd 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -64,7 +64,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new      ui->setupUi(this);      ui->listView->setEmptyString(          tr("Welcome!\n" -           "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account.")); +           "If you're new here, you can select the \"Add Microsoft\" or \"Add Mojang\" buttons to link your Microsoft and/or Mojang " +           "accounts."));      ui->listView->setEmptyMode(VersionListView::String);      ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); @@ -83,8 +84,10 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new      QItemSelectionModel* selectionModel = ui->listView->selectionModel();      connect(selectionModel, &QItemSelectionModel::selectionChanged, -            [this](const QItemSelection& sel, const QItemSelection& dsel) { updateButtonStates(); }); +            [this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); });      connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu); +    connect(ui->listView, &VersionListView::activated, this, +            [this](const QModelIndex& index) { m_accounts->setDefaultAccount(m_accounts->at(index.row())); });      connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged);      connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged); diff --git a/launcher/ui/pages/global/ExternalToolsPage.ui b/launcher/ui/pages/global/ExternalToolsPage.ui index 3643094d..47c77842 100644 --- a/launcher/ui/pages/global/ExternalToolsPage.ui +++ b/launcher/ui/pages/global/ExternalToolsPage.ui @@ -47,7 +47,7 @@              <item>               <widget class="QPushButton" name="jprofilerPathBtn">                <property name="text"> -               <string notr="true">...</string> +               <string>Browse</string>                </property>               </widget>              </item> @@ -84,7 +84,7 @@              <item>               <widget class="QPushButton" name="jvisualvmPathBtn">                <property name="text"> -               <string notr="true">...</string> +               <string>Browse</string>                </property>               </widget>              </item> @@ -121,7 +121,7 @@              <item>               <widget class="QPushButton" name="mceditPathBtn">                <property name="text"> -               <string notr="true">...</string> +               <string>Browse</string>                </property>               </widget>              </item> @@ -166,7 +166,7 @@            <item row="0" column="2">             <widget class="QToolButton" name="jsonEditorBrowseBtn">              <property name="text"> -             <string notr="true">...</string> +             <string>Browse</string>              </property>             </widget>            </item> diff --git a/launcher/ui/pages/global/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp index a9ede8ed..ac50319e 100644 --- a/launcher/ui/pages/global/JavaPage.cpp +++ b/launcher/ui/pages/global/JavaPage.cpp @@ -166,7 +166,7 @@ void JavaPage::on_javaTestBtn_clicked()      checker->run();  } -void JavaPage::on_maxMemSpinBox_valueChanged(int i) +void JavaPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i)  {      updateThresholds();  } @@ -185,6 +185,7 @@ void JavaPage::updateThresholds()  {      auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;      unsigned int maxMem = ui->maxMemSpinBox->value(); +    unsigned int minMem = ui->minMemSpinBox->value();      QString iconName; @@ -194,6 +195,9 @@ void JavaPage::updateThresholds()      } else if (maxMem > (sysMiB * 0.9)) {          iconName = "status-yellow";          ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); +    } else if (maxMem < minMem) { +        iconName = "status-yellow"; +        ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));      } else {          iconName = "status-good";          ui->labelMaxMemIcon->setToolTip(""); diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui index 561cf79b..5a547637 100644 --- a/launcher/ui/pages/global/JavaPage.ui +++ b/launcher/ui/pages/global/JavaPage.ui @@ -160,37 +160,73 @@            <string>Java Runtime</string>           </property>           <layout class="QGridLayout" name="gridLayout_3"> -          <item row="3" column="1"> -           <widget class="QPushButton" name="javaDetectBtn"> +          <item row="7" column="0" colspan="3"> +           <widget class="QPlainTextEdit" name="jvmArgsTextBox"> +            <property name="enabled"> +             <bool>true</bool> +            </property>              <property name="sizePolicy"> -             <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> +             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">                <horstretch>0</horstretch>                <verstretch>0</verstretch>               </sizepolicy>              </property> -            <property name="text"> -             <string>&Auto-detect...</string> +            <property name="maximumSize"> +             <size> +              <width>16777215</width> +              <height>100</height> +             </size>              </property>             </widget>            </item> -          <item row="2" column="0"> -           <widget class="QLabel" name="labelJVMArgs"> +          <item row="4" column="0"> +           <widget class="QCheckBox" name="skipCompatibilityCheckbox">              <property name="sizePolicy"> -             <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> +             <sizepolicy hsizetype="Preferred" vsizetype="Fixed">                <horstretch>0</horstretch>                <verstretch>0</verstretch>               </sizepolicy>              </property> -            <property name="text"> -             <string>JVM arguments:</string> +            <property name="toolTip"> +             <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>              </property> -            <property name="alignment"> -             <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> +            <property name="text"> +             <string>&Skip Java compatibility checks</string>              </property>             </widget>            </item> -          <item row="0" column="0"> -           <widget class="QLabel" name="labelJavaPath"> +          <item row="2" column="0" colspan="3"> +           <layout class="QHBoxLayout" name="horizontalLayout_2"> +            <item> +             <widget class="QPushButton" name="javaDetectBtn"> +              <property name="sizePolicy"> +               <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> +                <horstretch>0</horstretch> +                <verstretch>0</verstretch> +               </sizepolicy> +              </property> +              <property name="text"> +               <string>&Auto-detect...</string> +              </property> +             </widget> +            </item> +            <item> +             <widget class="QPushButton" name="javaTestBtn"> +              <property name="sizePolicy"> +               <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> +                <horstretch>0</horstretch> +                <verstretch>0</verstretch> +               </sizepolicy> +              </property> +              <property name="text"> +               <string>&Test</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item row="6" column="0"> +           <widget class="QLabel" name="labelJVMArgs">              <property name="sizePolicy">               <sizepolicy hsizetype="Fixed" vsizetype="Preferred">                <horstretch>0</horstretch> @@ -198,69 +234,50 @@               </sizepolicy>              </property>              <property name="text"> -             <string>&Java path:</string> -            </property> -            <property name="buddy"> -             <cstring>javaPathTextBox</cstring> -            </property> -           </widget> -          </item> -          <item row="3" column="2"> -           <widget class="QPushButton" name="javaTestBtn"> -            <property name="sizePolicy"> -             <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> -              <horstretch>0</horstretch> -              <verstretch>0</verstretch> -             </sizepolicy> +             <string>JVM arguments:</string>              </property> -            <property name="text"> -             <string>&Test</string> +            <property name="alignment"> +             <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>              </property>             </widget>            </item> -          <item row="0" column="1" colspan="2"> +          <item row="1" column="0" colspan="3">             <layout class="QHBoxLayout" name="horizontalLayout">              <item> +             <widget class="QLabel" name="labelJavaPath"> +              <property name="sizePolicy"> +               <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> +                <horstretch>0</horstretch> +                <verstretch>0</verstretch> +               </sizepolicy> +              </property> +              <property name="text"> +               <string>&Java path:</string> +              </property> +              <property name="buddy"> +               <cstring>javaPathTextBox</cstring> +              </property> +             </widget> +            </item> +            <item>               <widget class="QLineEdit" name="javaPathTextBox"/>              </item>              <item>               <widget class="QPushButton" name="javaBrowseBtn">                <property name="sizePolicy"> -               <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +               <sizepolicy hsizetype="Maximum" vsizetype="Fixed">                  <horstretch>0</horstretch>                  <verstretch>0</verstretch>                 </sizepolicy>                </property> -              <property name="maximumSize"> -               <size> -                <width>28</width> -                <height>16777215</height> -               </size> -              </property>                <property name="text"> -               <string notr="true">...</string> +               <string>Browse</string>                </property>               </widget>              </item>             </layout>            </item> -          <item row="4" column="1"> -           <widget class="QCheckBox" name="skipCompatibilityCheckbox"> -            <property name="sizePolicy"> -             <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> -              <horstretch>0</horstretch> -              <verstretch>0</verstretch> -             </sizepolicy> -            </property> -            <property name="toolTip"> -             <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string> -            </property> -            <property name="text"> -             <string>&Skip Java compatibility checks</string> -            </property> -           </widget> -          </item> -          <item row="5" column="1"> +          <item row="5" column="0">             <widget class="QCheckBox" name="skipJavaWizardCheckbox">              <property name="toolTip">               <string>If enabled, the launcher will not prompt you to choose a Java version if one isn't found.</string> @@ -270,25 +287,6 @@              </property>             </widget>            </item> -          <item row="2" column="1" colspan="2"> -           <widget class="QPlainTextEdit" name="jvmArgsTextBox"> -            <property name="enabled"> -             <bool>true</bool> -            </property> -            <property name="sizePolicy"> -             <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> -              <horstretch>0</horstretch> -              <verstretch>0</verstretch> -             </sizepolicy> -            </property> -            <property name="maximumSize"> -             <size> -              <width>16777215</width> -              <height>100</height> -             </size> -            </property> -           </widget> -          </item>           </layout>          </widget>         </item> @@ -317,8 +315,6 @@    <tabstop>permGenSpinBox</tabstop>    <tabstop>javaBrowseBtn</tabstop>    <tabstop>javaPathTextBox</tabstop> -  <tabstop>javaDetectBtn</tabstop> -  <tabstop>javaTestBtn</tabstop>    <tabstop>tabWidget</tabstop>   </tabstops>   <resources/> diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 26408f44..bc259a9b 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -99,7 +99,7 @@            <item row="3" column="2">             <widget class="QToolButton" name="downloadsDirBrowseBtn">              <property name="text"> -             <string notr="true">...</string> +             <string>Browse</string>              </property>             </widget>            </item> @@ -109,7 +109,7 @@            <item row="1" column="2">             <widget class="QToolButton" name="modsDirBrowseBtn">              <property name="text"> -             <string notr="true">...</string> +             <string>Browse</string>              </property>             </widget>            </item> @@ -126,14 +126,14 @@            <item row="0" column="2">             <widget class="QToolButton" name="instDirBrowseBtn">              <property name="text"> -             <string notr="true">...</string> +             <string>Browse</string>              </property>             </widget>            </item>            <item row="2" column="2">             <widget class="QToolButton" name="iconsDirBrowseBtn">              <property name="text"> -             <string notr="true">...</string> +             <string>Browse</string>              </property>             </widget>            </item> diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index 866a4121..553cefd8 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -2,7 +2,6 @@  /*   *  Prism Launcher - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> - *  Copyright (C) 2023 seth <getchoo at tuta dot io>   *   *  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 @@ -35,6 +34,7 @@   */  #include "MinecraftPage.h" +#include "BuildConfig.h"  #include "ui_MinecraftPage.h"  #include <QDir> @@ -44,9 +44,15 @@  #include "Application.h"  #include "settings/SettingsObject.h" +#ifdef Q_OS_LINUX +#include "MangoHud.h" +#endif +  MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage)  {      ui->setupUi(this); +    connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeGLFWChanged); +    connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeOpenALChanged);      loadSettings();      updateCheckboxStuff();  } @@ -74,6 +80,16 @@ void MinecraftPage::on_maximizedCheckBox_clicked(bool checked)      updateCheckboxStuff();  } +void MinecraftPage::onUseNativeGLFWChanged(bool checked) +{ +    ui->lineEditGLFWPath->setEnabled(checked); +} + +void MinecraftPage::onUseNativeOpenALChanged(bool checked) +{ +    ui->lineEditOpenALPath->setEnabled(checked); +} +  void MinecraftPage::applySettings()  {      auto s = APPLICATION->settings(); @@ -84,8 +100,10 @@ void MinecraftPage::applySettings()      s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());      // Native library workarounds -    s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());      s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); +    s->set("CustomGLFWPath", ui->lineEditGLFWPath->text()); +    s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); +    s->set("CustomOpenALPath", ui->lineEditOpenALPath->text());      // Peformance related options      s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); @@ -96,13 +114,11 @@ void MinecraftPage::applySettings()      s->set("ShowGameTime", ui->showGameTime->isChecked());      s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());      s->set("RecordGameTime", ui->recordGameTime->isChecked()); +    s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked());      // Miscellaneous      s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());      s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); - -    // Mod loader settings -    s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());  }  void MinecraftPage::loadSettings() @@ -114,8 +130,20 @@ void MinecraftPage::loadSettings()      ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());      ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); -    ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());      ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool()); +    ui->lineEditGLFWPath->setText(s->get("CustomGLFWPath").toString()); +    ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME)); +#ifdef Q_OS_LINUX +    if (!APPLICATION->m_detectedGLFWPath.isEmpty()) +        ui->lineEditGLFWPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedGLFWPath)); +#endif +    ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool()); +    ui->lineEditOpenALPath->setText(s->get("CustomOpenALPath").toString()); +    ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); +#ifdef Q_OS_LINUX +    if (!APPLICATION->m_detectedOpenALPath.isEmpty()) +        ui->lineEditOpenALPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedOpenALPath)); +#endif      ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());      ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); @@ -138,11 +166,10 @@ void MinecraftPage::loadSettings()      ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());      ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());      ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); +    ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool());      ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());      ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); - -    ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool());  }  void MinecraftPage::retranslate() diff --git a/launcher/ui/pages/global/MinecraftPage.h b/launcher/ui/pages/global/MinecraftPage.h index 28c31b5d..5facfbb3 100644 --- a/launcher/ui/pages/global/MinecraftPage.h +++ b/launcher/ui/pages/global/MinecraftPage.h @@ -70,6 +70,9 @@ class MinecraftPage : public QWidget, public BasePage {     private slots:      void on_maximizedCheckBox_clicked(bool checked); +    void onUseNativeGLFWChanged(bool checked); +    void onUseNativeOpenALChanged(bool checked); +     private:      Ui::MinecraftPage* ui;  }; diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index 393b0f35..b5cfa659 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -138,6 +138,13 @@              </property>             </widget>            </item> +           <item> +           <widget class="QCheckBox" name="showGameTimeWithoutDays"> +            <property name="text"> +             <string>Show time spent playing in hours</string> +            </property> +           </widget> +          </item>           </layout>          </widget>         </item> @@ -191,44 +198,59 @@        </attribute>        <layout class="QVBoxLayout" name="verticalLayout_12">         <item> -        <widget class="QGroupBox" name="modLoaderSettingsGroupBox"> -         <property name="title"> -          <string>Mod loader settings</string> -         </property> -         <layout class="QVBoxLayout" name="verticalLayout_13"> -          <item> -           <widget class="QCheckBox" name="disableQuiltBeaconCheckBox"> -            <property name="text"> -             <string>Disable Quilt Loader Beacon</string> -            </property> -            <property name="toolTip"> -             <string>Disable Quilt loader's beacon for counting monthly active users</string> -            </property> -           </widget> -          </item> -         </layout> -        </widget> -       </item> -       <item>          <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">           <property name="title">            <string>Native library workarounds</string>           </property> -         <layout class="QVBoxLayout" name="verticalLayout_11"> -          <item> +         <layout class="QGridLayout" name="gridLayout"> +          <item row="0" column="0">             <widget class="QCheckBox" name="useNativeGLFWCheck">              <property name="text">               <string>Use system installation of &GLFW</string>              </property>             </widget>            </item> -          <item> +          <item row="1" column="0"> +           <widget class="QLabel" name="labelGLFWPath"> +            <property name="text"> +             <string>&GLFW library path</string> +            </property> +            <property name="buddy"> +             <cstring>lineEditGLFWPath</cstring> +            </property> +           </widget> +          </item> +          <item row="2" column="0">             <widget class="QCheckBox" name="useNativeOpenALCheck">              <property name="text">               <string>Use system installation of &OpenAL</string>              </property>             </widget>            </item> +          <item row="3" column="0"> +           <widget class="QLabel" name="labelOpenALPath"> +            <property name="text"> +             <string>&OpenAL library path</string> +            </property> +            <property name="buddy"> +             <cstring>lineEditOpenALPath</cstring> +            </property> +           </widget> +          </item> +          <item row="1" column="1"> +           <widget class="QLineEdit" name="lineEditGLFWPath"> +            <property name="enabled"> +             <bool>false</bool> +            </property> +           </widget> +          </item> +          <item row="3" column="1"> +           <widget class="QLineEdit" name="lineEditOpenALPath"> +            <property name="enabled"> +             <bool>false</bool> +            </property> +           </widget> +          </item>           </layout>          </widget>         </item> diff --git a/launcher/ui/pages/global/ProxyPage.cpp b/launcher/ui/pages/global/ProxyPage.cpp index 19b2bcea..9caffcb3 100644 --- a/launcher/ui/pages/global/ProxyPage.cpp +++ b/launcher/ui/pages/global/ProxyPage.cpp @@ -71,7 +71,7 @@ void ProxyPage::updateCheckboxStuff()      ui->proxyAuthBox->setEnabled(enableEditing);  } -void ProxyPage::proxyGroupChanged(QAbstractButton* button) +void ProxyPage::proxyGroupChanged([[maybe_unused]] QAbstractButton* button)  {      updateCheckboxStuff();  } diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 12038f88..1a8fafa9 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -152,6 +152,7 @@ void ExternalResourcesPage::retranslate()  void ExternalResourcesPage::itemActivated(const QModelIndex&)  {      auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); +    m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);  }  void ExternalResourcesPage::filterTextChanged(const QString& newContents) @@ -305,7 +306,7 @@ bool ExternalResourcesPage::current(const QModelIndex& current, const QModelInde      return onSelectionChanged(current, previous);  } -bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      auto sourceCurrent = m_filterModel->mapToSource(current);      int row = sourceCurrent.row(); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 3c836691..ba703f77 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -168,6 +168,17 @@      <string>Go to mods home page</string>     </property>    </action> +  <action name="actionRemoveItemMetadata"> +   <property name="enabled"> +    <bool>false</bool> +   </property> +   <property name="text"> +    <string>Remove metadata</string> +   </property> +   <property name="toolTip"> +    <string>Remove mod's metadata</string> +   </property> +  </action>   </widget>   <customwidgets>    <customwidget> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 687b82d7..7aa6bd32 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -3,7 +3,6 @@   *  Prism Launcher - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>   *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - *  Copyright (C) 2023 seth <getchoo at tuta dot io>   *   *  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 @@ -48,6 +47,7 @@  #include "ui/widgets/CustomCommands.h"  #include "Application.h" +#include "BuildConfig.h"  #include "JavaCommon.h"  #include "minecraft/auth/AccountList.h" @@ -66,6 +66,10 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent)      connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);      connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,              &InstanceSettingsPage::changeInstanceAccount); + +    connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged); +    connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged); +      loadSettings();      updateThresholds(); @@ -198,11 +202,15 @@ void InstanceSettingsPage::applySettings()      bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();      m_settings->set("OverrideNativeWorkarounds", workarounds);      if (workarounds) { -        m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());          m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); +        m_settings->set("CustomGLFWPath", ui->lineEditGLFWPath->text()); +        m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked()); +        m_settings->set("CustomOpenALPath", ui->lineEditOpenALPath->text());      } else { -        m_settings->reset("UseNativeOpenAL");          m_settings->reset("UseNativeGLFW"); +        m_settings->reset("CustomGLFWPath"); +        m_settings->reset("UseNativeOpenAL"); +        m_settings->reset("CustomOpenALPath");      }      // Performance @@ -245,14 +253,6 @@ void InstanceSettingsPage::applySettings()          m_settings->reset("InstanceAccountId");      } -    bool overrideModLoaderSettings = ui->modLoaderSettingsGroupBox->isChecked(); -    m_settings->set("OverrideModLoaderSettings", overrideModLoaderSettings); -    if (overrideModLoaderSettings) { -        m_settings->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked()); -    } else { -        m_settings->reset("DisableQuiltBeacon"); -    } -      // FIXME: This should probably be called by a signal instead      m_instance->updateRuntimeContext();  } @@ -312,7 +312,19 @@ void InstanceSettingsPage::loadSettings()      // Workarounds      ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());      ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool()); +    ui->lineEditGLFWPath->setText(m_settings->get("CustomGLFWPath").toString()); +#ifdef Q_OS_LINUX +    ui->lineEditGLFWPath->setPlaceholderText(APPLICATION->m_detectedGLFWPath); +#else +    ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME)); +#endif      ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool()); +    ui->lineEditOpenALPath->setText(m_settings->get("CustomOpenALPath").toString()); +#ifdef Q_OS_LINUX +    ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath); +#else +    ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME)); +#endif      // Performance      ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool()); @@ -344,10 +356,6 @@ void InstanceSettingsPage::loadSettings()      ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());      updateAccountsMenu(); - -    // Mod loader specific settings -    ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool()); -    ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool());  }  void InstanceSettingsPage::on_javaDetectBtn_clicked() @@ -408,6 +416,16 @@ void InstanceSettingsPage::on_javaTestBtn_clicked()      checker->run();  } +void InstanceSettingsPage::onUseNativeGLFWChanged(bool checked) +{ +    ui->lineEditGLFWPath->setEnabled(checked); +} + +void InstanceSettingsPage::onUseNativeOpenALChanged(bool checked) +{ +    ui->lineEditOpenALPath->setEnabled(checked); +} +  void InstanceSettingsPage::updateAccountsMenu()  {      ui->instanceAccountSelector->clear(); @@ -440,7 +458,7 @@ void InstanceSettingsPage::changeInstanceAccount(int index)      }  } -void InstanceSettingsPage::on_maxMemSpinBox_valueChanged(int i) +void InstanceSettingsPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i)  {      updateThresholds();  } @@ -460,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()  {      auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;      unsigned int maxMem = ui->maxMemSpinBox->value(); +    unsigned int minMem = ui->minMemSpinBox->value();      QString iconName; @@ -469,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()      } else if (maxMem > (sysMiB * 0.9)) {          iconName = "status-yellow";          ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); +    } else if (maxMem < minMem) { +        iconName = "status-yellow"; +        ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));      } else {          iconName = "status-good";          ui->labelMaxMemIcon->setToolTip(""); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h index 21ecbaf8..8b78dcb7 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.h +++ b/launcher/ui/pages/instance/InstanceSettingsPage.h @@ -71,6 +71,9 @@ class InstanceSettingsPage : public QWidget, public BasePage {      void on_javaBrowseBtn_clicked();      void on_maxMemSpinBox_valueChanged(int i); +    void onUseNativeGLFWChanged(bool checked); +    void onUseNativeOpenALChanged(bool checked); +      void applySettings();      void loadSettings(); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 245433fe..81cf7093 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -61,31 +61,7 @@            <bool>false</bool>           </property>           <layout class="QGridLayout" name="gridLayout"> -          <item row="0" column="0" colspan="3"> -           <widget class="QLineEdit" name="javaPathTextBox"/> -          </item> -          <item row="1" column="0"> -           <widget class="QPushButton" name="javaDetectBtn"> -            <property name="text"> -             <string>Auto-detect...</string> -            </property> -           </widget> -          </item> -          <item row="1" column="1"> -           <widget class="QPushButton" name="javaBrowseBtn"> -            <property name="text"> -             <string>Browse...</string> -            </property> -           </widget> -          </item> -          <item row="1" column="2"> -           <widget class="QPushButton" name="javaTestBtn"> -            <property name="text"> -             <string>Test</string> -            </property> -           </widget> -          </item> -          <item row="2" column="0"> +          <item row="4" column="0">             <widget class="QCheckBox" name="skipCompatibilityCheckbox">              <property name="toolTip">               <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string> @@ -95,6 +71,38 @@              </property>             </widget>            </item> +          <item row="1" column="0"> +           <layout class="QHBoxLayout" name="horizontalLayout"> +            <item> +             <widget class="QLineEdit" name="javaPathTextBox"/> +            </item> +            <item> +             <widget class="QPushButton" name="javaBrowseBtn"> +              <property name="text"> +               <string>Browse</string> +              </property> +             </widget> +            </item> +           </layout> +          </item> +          <item row="2" column="0"> +           <layout class="QHBoxLayout" name="horizontalLayout_2"> +            <item> +             <widget class="QPushButton" name="javaDetectBtn"> +              <property name="text"> +               <string>Auto-detect...</string> +              </property> +             </widget> +            </item> +            <item> +             <widget class="QPushButton" name="javaTestBtn"> +              <property name="text"> +               <string>Test</string> +              </property> +             </widget> +            </item> +           </layout> +          </item>           </layout>          </widget>         </item> @@ -435,18 +443,52 @@           <property name="checked">            <bool>false</bool>           </property> -         <layout class="QVBoxLayout" name="verticalLayout_7"> -          <item> +         <layout class="QGridLayout" name="gridLayout_3"> +          <item row="2" column="0"> +           <widget class="QCheckBox" name="useNativeOpenALCheck"> +            <property name="text"> +             <string>Use system installation of OpenAL</string> +            </property> +           </widget> +          </item> +          <item row="1" column="0"> +           <widget class="QLabel" name="labelGLFWPath"> +            <property name="text"> +             <string>&GLFW library path</string> +            </property> +            <property name="buddy"> +             <cstring>lineEditGLFWPath</cstring> +            </property> +           </widget> +          </item> +          <item row="0" column="0">             <widget class="QCheckBox" name="useNativeGLFWCheck">              <property name="text">               <string>Use system installation of GLFW</string>              </property>             </widget>            </item> -          <item> -           <widget class="QCheckBox" name="useNativeOpenALCheck"> +          <item row="1" column="1"> +           <widget class="QLineEdit" name="lineEditGLFWPath"> +            <property name="enabled"> +             <bool>false</bool> +            </property> +           </widget> +          </item> +          <item row="3" column="0"> +           <widget class="QLabel" name="labelOpenALPath">              <property name="text"> -             <string>Use system installation of OpenAL</string> +             <string>&OpenAL library path</string> +            </property> +            <property name="buddy"> +             <cstring>lineEditOpenALPath</cstring> +            </property> +           </widget> +          </item> +          <item row="3" column="1"> +           <widget class="QLineEdit" name="lineEditOpenALPath"> +            <property name="enabled"> +             <bool>false</bool>              </property>             </widget>            </item> @@ -542,31 +584,6 @@        </attribute>        <layout class="QVBoxLayout" name="verticalLayout_9">         <item> -        <widget class="QGroupBox" name="modLoaderSettingsGroupBox"> -         <property name="checkable"> -          <bool>true</bool> -         </property> -         <property name="checked"> -          <bool>false</bool> -         </property> -         <property name="title"> -          <string>Mod loader settings</string> -         </property> -         <layout class="QVBoxLayout" name="VerticalLayout_16"> -          <item> -           <widget class="QCheckBox" name="disableQuiltBeaconCheckBox"> -            <property name="text"> -             <string>Disable Quilt Loader Beacon</string> -            </property> -            <property name="toolTip"> -             <string>Disable Quilt loader's beacon for counting monthly active users</string> -            </property> -           </widget> -          </item> -         </layout> -        </widget> -       </item> -       <item>          <widget class="QGroupBox" name="gameTimeGroupBox">           <property name="enabled">            <bool>true</bool> @@ -699,10 +716,6 @@    <tabstop>openGlobalJavaSettingsButton</tabstop>    <tabstop>settingsTabs</tabstop>    <tabstop>javaSettingsGroupBox</tabstop> -  <tabstop>javaPathTextBox</tabstop> -  <tabstop>javaDetectBtn</tabstop> -  <tabstop>javaBrowseBtn</tabstop> -  <tabstop>javaTestBtn</tabstop>    <tabstop>memoryGroupBox</tabstop>    <tabstop>minMemSpinBox</tabstop>    <tabstop>maxMemSpinBox</tabstop> diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 0443baf1..8fdaf065 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -26,6 +26,8 @@  #include "ui/dialogs/CustomMessageBox.h"  #include "ui/dialogs/ProgressDialog.h" +#include "net/ApiDownload.h" +  /** This is just to override the combo box popup behavior so that the combo box doesn't take the whole screen.   *  ... thanks Qt.   */ @@ -242,7 +244,7 @@ void ModrinthManagedPackPage::parseManagedPack()      QString id = m_inst->getManagedPackID();      m_fetch_job->addNetAction( -        Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); +        Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));      QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {          QJsonParseError parse_error{}; @@ -392,7 +394,7 @@ void FlameManagedPackPage::parseManagedPack()      QString id = m_inst->getManagedPackID(); -    m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response)); +    m_fetch_job->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));      QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {          QJsonParseError parse_error{}; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index cef292bd..b42bbf46 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -92,6 +92,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>          ui->actionsToolbar->addAction(ui->actionVisitItemPage);          connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages); +        ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata")); +        ui->actionsToolbar->insertActionAfter(ui->actionRemoveItem, ui->actionRemoveItemMetadata); +        connect(ui->actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata); +          auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };          connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] { @@ -104,11 +108,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>              if (selected <= 1) {                  ui->actionVisitItemPage->setText(tr("Visit mod's page"));                  ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page")); + +                ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));              } else {                  ui->actionVisitItemPage->setText(tr("Visit mods' pages"));                  ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods")); + +                ui->actionRemoveItemMetadata->setToolTip(tr("Remove mods' metadata"));              }              ui->actionVisitItemPage->setEnabled(selected != 0); +            ui->actionRemoveItemMetadata->setEnabled(selected != 0);          });          connect(mods.get(), &ModFolderModel::rowsInserted, this, @@ -127,7 +136,7 @@ bool ModFolderPage::shouldDisplay() const      return true;  } -bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      auto sourceCurrent = m_filterModel->mapToSource(current);      int row = sourceCurrent.row(); @@ -297,3 +306,24 @@ void ModFolderPage::visitModPages()              DesktopServices::openUrl(url);      }  } + +void ModFolderPage::deleteModMetadata() +{ +    auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); +    auto selectionCount = m_model->selectedMods(selection).length(); +    if (selectionCount == 0) +        return; +    if (selectionCount > 1) { +        auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), +                                                     tr("You are about to remove the metadata for %1 mods.\n" +                                                        "Are you sure?") +                                                         .arg(selectionCount), +                                                     QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) +                            ->exec(); + +        if (response != QMessageBox::Yes) +            return; +    } + +    m_model->deleteModsMetadata(selection); +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index a23dcae1..0c654d0d 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -61,6 +61,7 @@ class ModFolderPage : public ExternalResourcesPage {     private slots:      void removeItems(const QItemSelection& selection) override; +    void deleteModMetadata();      void installMods();      void updateMods(); diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index b80c08e1..ab5d9828 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -246,20 +246,20 @@ void OtherLogsPage::on_btnClean_clicked()          }      }      if (!failed.empty()) { -        QMessageBox* messageBox = new QMessageBox(this); -        messageBox->setWindowTitle(tr("Error")); +        QMessageBox* messageBoxFailure = new QMessageBox(this); +        messageBoxFailure->setWindowTitle(tr("Error"));          if (failed.size() > 5) { -            messageBox->setText(tr("Couldn't delete some files!")); -            messageBox->setDetailedText(failed.join('\n')); +            messageBoxFailure->setText(tr("Couldn't delete some files!")); +            messageBoxFailure->setDetailedText(failed.join('\n'));          } else { -            messageBox->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n'))); +            messageBoxFailure->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n')));          } -        messageBox->setStandardButtons(QMessageBox::Ok); -        messageBox->setDefaultButton(QMessageBox::Ok); -        messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse); -        messageBox->setIcon(QMessageBox::Critical); -        messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction); -        messageBox->exec(); +        messageBoxFailure->setStandardButtons(QMessageBox::Ok); +        messageBoxFailure->setDefaultButton(QMessageBox::Ok); +        messageBoxFailure->setTextInteractionFlags(Qt::TextSelectableByMouse); +        messageBoxFailure->setIcon(QMessageBox::Critical); +        messageBoxFailure->setTextInteractionFlags(Qt::TextBrowserInteraction); +        messageBoxFailure->exec();      }  } diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index 12b371df..26c14ca4 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -55,7 +55,7 @@ ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr<      ui->actionViewConfigs->setVisible(false);  } -bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      auto sourceCurrent = m_filterModel->mapToSource(current);      int row = sourceCurrent.row(); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 07daca21..2142e6c9 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -3,7 +3,7 @@   *  Prism Launcher - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>   *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> + *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>   *   *  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 @@ -354,14 +354,8 @@ class ServersModel : public QAbstractListModel {          }      } -    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override -    { -        return parent.isValid() ? 0 : m_servers.size(); -    } -    int columnCount(const QModelIndex& parent) const override -    { -        return parent.isValid() ? 0 : COLUMN_COUNT; -    } +    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : m_servers.size(); } +    int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : COLUMN_COUNT; }      Server* at(int index)      { @@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {          qDebug() << "Changed:" << path;          load();      } -    void fileChanged(const QString& path) -    { -        qDebug() << "Changed:" << path; -    } +    void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }     private slots:      void save_internal() @@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {          m_saveTimer.stop();      } -    bool saveIsScheduled() const -    { -        return m_dirty; -    } +    bool saveIsScheduled() const { return m_dirty; }      void updateFSObserver()      { @@ -607,7 +595,7 @@ void ServersPage::runningStateChanged(bool running)      updateState();  } -void ServersPage::currentChanged(const QModelIndex& current, const QModelIndex& previous) +void ServersPage::currentChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      int nextServer = -1;      if (!current.isValid()) { @@ -620,7 +608,7 @@ void ServersPage::currentChanged(const QModelIndex& current, const QModelIndex&  }  // WARNING: this is here because currentChanged is not accurate when removing rows. the current item needs to be fixed up after removal. -void ServersPage::rowsRemoved(const QModelIndex& parent, int first, int last) +void ServersPage::rowsRemoved([[maybe_unused]] const QModelIndex& parent, int first, int last)  {      if (currentServer < first) {          // current was before the removal @@ -743,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered()  void ServersPage::on_actionJoin_triggered()  {      const auto& address = m_model->at(currentServer)->m_address; -    APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address))); +    APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));  }  #include "ServersPage.moc" diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index e477ceda..fa478a9a 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -57,7 +57,7 @@ TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr<Te      ui->actionViewConfigs->setVisible(false);  } -bool TexturePackPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      auto sourceCurrent = m_filterModel->mapToSource(current);      int row = sourceCurrent.row(); diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index a180c804..e22c764c 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -6,7 +6,7 @@   *  Prism Launcher - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>   *  Copyright (C) 2022-2023 Sefa Eyeoglu <contact@scrumplex.net> - *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> + *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>   *   *  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 @@ -51,6 +51,7 @@  #include <QUrl>  #include "VersionPage.h" +#include "ui/dialogs/InstallLoaderDialog.h"  #include "ui_VersionPage.h"  #include "ui/dialogs/CustomMessageBox.h" @@ -165,14 +166,17 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow      ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);      ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu); -    connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);      auto smodel = ui->packageView->selectionModel(); +    connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);      connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); -      connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);      updateVersionControls();      preselect(0);      connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu); +    connect(ui->packageView, &QAbstractItemView::activated, this, [this](const QModelIndex& index) { +        auto component = m_profile->getComponent(index.row()); +        component->setEnabled(!component->isEnabled()); +    });      connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);  } @@ -188,7 +192,7 @@ void VersionPage::showContextMenu(const QPoint& pos)      delete menu;  } -void VersionPage::packageCurrent(const QModelIndex& current, const QModelIndex& previous) +void VersionPage::packageCurrent(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      if (!current.isValid()) {          ui->frame->clear(); @@ -226,18 +230,6 @@ void VersionPage::packageCurrent(const QModelIndex& current, const QModelIndex&  void VersionPage::updateVersionControls()  { -    // FIXME: this is a dirty hack -    auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft")); - -    bool supportsFabric = minecraftVersion >= Version("1.14"); -    ui->actionInstall_Fabric->setEnabled(supportsFabric); - -    bool supportsQuilt = minecraftVersion >= Version("1.14"); -    ui->actionInstall_Quilt->setEnabled(supportsQuilt); - -    bool supportsLiteLoader = minecraftVersion <= Version("1.12.2"); -    ui->actionInstall_LiteLoader->setEnabled(supportsLiteLoader); -      updateButtons();  } @@ -389,20 +381,14 @@ void VersionPage::on_actionChange_version_triggered()          return;      }      auto uid = list->uid(); -    // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata... -    if (uid == "net.minecraftforge") { -        on_actionInstall_Forge_triggered(); -        return; -    } else if (uid == "com.mumfrey.liteloader") { -        on_actionInstall_LiteLoader_triggered(); -        return; -    } +      VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);      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!")); -        vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));      } +    vselect.setExactIfPresentFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); +      auto currentVersion = patch->getVersion();      if (!currentVersion.isEmpty()) {          vselect.setCurrentVersion(currentVersion); @@ -425,7 +411,7 @@ void VersionPage::on_actionDownload_All_triggered()      if (!APPLICATION->accounts()->anyAccountIsValid()) {          CustomMessageBox::selectable(this, tr("Error"),                                       tr("Cannot download Minecraft or update instances unless you have at least " -                                        "one account added.\nPlease add your Mojang or Minecraft account."), +                                        "one account added.\nPlease add your Microsoft or Mojang account."),                                       QMessageBox::Warning)              ->show();          return; @@ -443,79 +429,11 @@ void VersionPage::on_actionDownload_All_triggered()      m_container->refreshContainer();  } -void VersionPage::on_actionInstall_Forge_triggered() -{ -    auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge"); -    if (!vlist) { -        return; -    } -    VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this); -    vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); -    vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + -                           m_profile->getComponentVersion("net.minecraft")); -    vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!")); - -    auto currentVersion = m_profile->getComponentVersion("net.minecraftforge"); -    if (!currentVersion.isEmpty()) { -        vselect.setCurrentVersion(currentVersion); -    } - -    if (vselect.exec() && vselect.selectedVersion()) { -        auto vsn = vselect.selectedVersion(); -        m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor()); -        m_profile->resolve(Net::Mode::Online); -        // m_profile->installVersion(); -        preselect(m_profile->rowCount(QModelIndex()) - 1); -        m_container->refreshContainer(); -    } -} - -void VersionPage::on_actionInstall_Fabric_triggered() -{ -    auto vlist = APPLICATION->metadataIndex()->get("net.fabricmc.fabric-loader"); -    if (!vlist) { -        return; -    } -    VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this); -    vselect.setEmptyString(tr("No Fabric Loader versions are currently available.")); -    vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!")); - -    auto currentVersion = m_profile->getComponentVersion("net.fabricmc.fabric-loader"); -    if (!currentVersion.isEmpty()) { -        vselect.setCurrentVersion(currentVersion); -    } - -    if (vselect.exec() && vselect.selectedVersion()) { -        auto vsn = vselect.selectedVersion(); -        m_profile->setComponentVersion("net.fabricmc.fabric-loader", vsn->descriptor()); -        m_profile->resolve(Net::Mode::Online); -        preselect(m_profile->rowCount(QModelIndex()) - 1); -        m_container->refreshContainer(); -    } -} - -void VersionPage::on_actionInstall_Quilt_triggered() +void VersionPage::on_actionInstall_Loader_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(); -    } +    InstallLoaderDialog dialog(m_inst->getPackProfile(), QString(), this); +    dialog.exec(); +    m_container->refreshContainer();  }  void VersionPage::on_actionAdd_Empty_triggered() @@ -534,33 +452,6 @@ void VersionPage::on_actionAdd_Empty_triggered()      }  } -void VersionPage::on_actionInstall_LiteLoader_triggered() -{ -    auto vlist = APPLICATION->metadataIndex()->get("com.mumfrey.liteloader"); -    if (!vlist) { -        return; -    } -    VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this); -    vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft")); -    vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + -                           m_profile->getComponentVersion("net.minecraft")); -    vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!")); - -    auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader"); -    if (!currentVersion.isEmpty()) { -        vselect.setCurrentVersion(currentVersion); -    } - -    if (vselect.exec() && vselect.selectedVersion()) { -        auto vsn = vselect.selectedVersion(); -        m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor()); -        m_profile->resolve(Net::Mode::Online); -        // m_profile->installVersion(vselect.selectedVersion()); -        preselect(m_profile->rowCount(QModelIndex()) - 1); -        m_container->refreshContainer(); -    } -} -  void VersionPage::on_actionLibrariesFolder_triggered()  {      DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true); @@ -571,7 +462,7 @@ void VersionPage::on_actionMinecraftFolder_triggered()      DesktopServices::openDirectory(m_inst->gameRoot(), true);  } -void VersionPage::versionCurrent(const QModelIndex& current, const QModelIndex& previous) +void VersionPage::versionCurrent(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      currentIdx = current.row();      updateButtons(currentIdx); diff --git a/launcher/ui/pages/instance/VersionPage.h b/launcher/ui/pages/instance/VersionPage.h index 45d383f4..6915be88 100644 --- a/launcher/ui/pages/instance/VersionPage.h +++ b/launcher/ui/pages/instance/VersionPage.h @@ -68,11 +68,8 @@ class VersionPage : public QMainWindow, public BasePage {     private slots:      void on_actionChange_version_triggered(); -    void on_actionInstall_Forge_triggered(); -    void on_actionInstall_Fabric_triggered(); -    void on_actionInstall_Quilt_triggered(); +    void on_actionInstall_Loader_triggered();      void on_actionAdd_Empty_triggered(); -    void on_actionInstall_LiteLoader_triggered();      void on_actionReload_triggered();      void on_actionRemove_triggered();      void on_actionMove_up_triggered(); diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index a73c42d6..9be21d49 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -98,11 +98,7 @@     <addaction name="actionEdit"/>     <addaction name="actionRevert"/>     <addaction name="separator"/> -   <addaction name="actionInstall_Forge"/> -   <addaction name="actionInstall_Fabric"/> -   <addaction name="actionInstall_Quilt"/> -   <addaction name="actionInstall_LiteLoader"/> -   <addaction name="separator"/> +   <addaction name="actionInstall_Loader"/>     <addaction name="actionAdd_to_Minecraft_jar"/>     <addaction name="actionReplace_Minecraft_jar"/>     <addaction name="actionAdd_Agents"/> @@ -116,26 +112,26 @@    </widget>    <action name="actionChange_version">     <property name="text"> -    <string>Change version</string> +    <string>Change Version</string>     </property>     <property name="toolTip"> -    <string>Change version of the selected package.</string> +    <string>Change version of the selected component.</string>     </property>    </action>    <action name="actionMove_up">     <property name="text"> -    <string>Move up</string> +    <string>Move Up</string>     </property>     <property name="toolTip"> -    <string>Make the selected package apply sooner.</string> +    <string>Make the selected component apply sooner.</string>     </property>    </action>    <action name="actionMove_down">     <property name="text"> -    <string>Move down</string> +    <string>Move Down</string>     </property>     <property name="toolTip"> -    <string>Make the selected package apply later.</string> +    <string>Make the selected component apply later.</string>     </property>    </action>    <action name="actionRemove"> @@ -143,7 +139,7 @@      <string>Remove</string>     </property>     <property name="toolTip"> -    <string>Remove selected package from the instance.</string> +    <string>Remove selected component from the instance.</string>     </property>    </action>    <action name="actionCustomize"> @@ -151,7 +147,7 @@      <string>Customize</string>     </property>     <property name="toolTip"> -    <string>Customize selected package.</string> +    <string>Customize selected component.</string>     </property>    </action>    <action name="actionEdit"> @@ -159,7 +155,7 @@      <string>Edit</string>     </property>     <property name="toolTip"> -    <string>Edit selected package.</string> +    <string>Edit selected component.</string>     </property>    </action>    <action name="actionRevert"> @@ -167,39 +163,15 @@      <string>Revert</string>     </property>     <property name="toolTip"> -    <string>Revert the selected package to default.</string> -   </property> -  </action> -  <action name="actionInstall_Forge"> -   <property name="text"> -    <string>Install Forge</string> -   </property> -   <property name="toolTip"> -    <string>Install the Minecraft Forge package.</string> -   </property> -  </action> -  <action name="actionInstall_Fabric"> -   <property name="text"> -    <string>Install Fabric</string> -   </property> -   <property name="toolTip"> -    <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> +    <string>Revert the selected component to default.</string>     </property>    </action> -  <action name="actionInstall_LiteLoader"> +  <action name="actionInstall_Loader">     <property name="text"> -    <string>Install LiteLoader</string> +    <string>Install Loader</string>     </property>     <property name="toolTip"> -    <string>Install the LiteLoader package.</string> +    <string>Install a mod loader.</string>     </property>    </action>    <action name="actionAdd_to_Minecraft_jar"> @@ -228,7 +200,7 @@      <string>Add Empty</string>     </property>     <property name="toolTip"> -    <string>Add an empty custom package.</string> +    <string>Add an empty custom component.</string>     </property>    </action>    <action name="actionReload"> @@ -236,12 +208,12 @@      <string>Reload</string>     </property>     <property name="toolTip"> -    <string>Reload all packages.</string> +    <string>Reload all components.</string>     </property>    </action>    <action name="actionDownload_All">     <property name="text"> -    <string>Download All</string> +    <string>Download all</string>     </property>     <property name="toolTip">      <string>Download the files needed to launch the instance now.</string> diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index fe477616..587bb6ce 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -327,7 +327,7 @@ void WorldListPage::mceditState(LoggedProcess::State state)      }  } -void WorldListPage::worldChanged(const QModelIndex& current, const QModelIndex& previous) +void WorldListPage::worldChanged([[maybe_unused]] const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)  {      QModelIndex index = getSelectedWorld();      bool enable = index.isValid(); diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index 4ac21b01..068fb3a3 100644 --- a/launcher/ui/pages/modplatform/CustomPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -127,6 +127,9 @@ void CustomPage::loaderFilterChanged()          ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));          ui->loaderVersionList->setEmptyMode(VersionListView::String);          return; +    } else if (ui->neoForgeFilter->isChecked()) { +        ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); +        m_selectedLoader = "net.neoforged";      } else if (ui->forgeFilter->isChecked()) {          ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);          m_selectedLoader = "net.minecraftforge"; diff --git a/launcher/ui/pages/modplatform/CustomPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui index 0d89b595..23351ccd 100644 --- a/launcher/ui/pages/modplatform/CustomPage.ui +++ b/launcher/ui/pages/modplatform/CustomPage.ui @@ -195,6 +195,16 @@              </widget>             </item>             <item> +            <widget class="QRadioButton" name="neoForgeFilter"> +             <property name="text"> +              <string>NeoForge</string> +             </property> +             <attribute name="buttonGroup"> +              <string notr="true">loaderBtnGroup</string> +             </attribute> +            </widget> +           </item> +           <item>              <widget class="QRadioButton" name="forgeFilter">               <property name="text">                <string>Forge</string> diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index ba53d033..3e3c36b7 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -35,20 +35,28 @@   */  #include "ImportPage.h" + +#include "ui/dialogs/ProgressDialog.h"  #include "ui_ImportPage.h"  #include <QFileDialog>  #include <QValidator> +#include <utility> +#include "ui/dialogs/CustomMessageBox.h"  #include "ui/dialogs/NewInstanceDialog.h" +#include "modplatform/flame/FlameAPI.h" + +#include "Json.h" +  #include "InstanceImportTask.h"  class UrlValidator : public QValidator {     public:      using QValidator::QValidator; -    State validate(QString& in, int& pos) const +    State validate(QString& in, [[maybe_unused]] int& pos) const      {          const QUrl url(in);          if (url.isValid() && !url.isRelative() && !url.isEmpty()) { @@ -106,10 +114,61 @@ void ImportPage::updateState()              bool isMRPack = fi.suffix() == "mrpack";              if (fi.exists() && (isZip || isMRPack)) { -                QFileInfo fi(url.fileName()); -                dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this)); +                auto extra_info = QMap(m_extra_info); +                qDebug() << "Pack Extra Info" << extra_info << m_extra_info; +                dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));                  dialog->setSuggestedIcon("default");              } +        } else if (url.scheme() == "curseforge") { +            // need to find the download link for the modpack +            // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE +            QUrlQuery query(url); +            auto addonId = query.allQueryItemValues("addonId")[0]; +            auto fileId = query.allQueryItemValues("fileId")[0]; +            auto array = std::make_shared<QByteArray>(); + +            auto api = FlameAPI(); +            auto job = api.getFile(addonId, fileId, array); + +            connect(job.get(), &NetJob::failed, this, +                    [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); +            connect(job.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] { +                qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str(); +                auto doc = Json::requireDocument(*array); +                auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data"); +                // No way to find out if it's a mod or a modpack before here +                // And also we need to check if it ends with .zip, instead of any better way +                auto fileName = Json::ensureString(data, "fileName"); +                if (fileName.endsWith(".zip")) { +                    // Have to use ensureString then use QUrl to get proper url encoding +                    auto dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl")); +                    if (!dl_url.isValid()) { +                        CustomMessageBox::selectable( +                            this, tr("Error"), +                            tr("The modpack %1 is blocked for third-parties! Please download it manually.").arg(fileName), +                            QMessageBox::Critical) +                            ->show(); +                        return; +                    } + +                    QFileInfo dl_file(dl_url.fileName()); +                    QString pack_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName"); + +                    QMap<QString, QString> extra_info; +                    extra_info.insert("pack_id", addonId); +                    extra_info.insert("pack_version_id", fileId); + +                    dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info))); +                    dialog->setSuggestedIcon("default"); + +                } else { +                    CustomMessageBox::selectable(this, tr("Error"), tr("This url isn't a valid modpack !"), QMessageBox::Critical)->show(); +                } +            }); +            ProgressDialog dlUrlDialod(this); +            dlUrlDialod.setSkipButton(true, tr("Abort")); +            dlUrlDialod.execWithTask(job.get()); +            return;          } else {              if (input.endsWith("?client=y")) {                  input.chop(9); @@ -118,7 +177,8 @@ void ImportPage::updateState()              }              // hook, line and sinker.              QFileInfo fi(url.fileName()); -            dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this)); +            auto extra_info = QMap(m_extra_info); +            dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));              dialog->setSuggestedIcon("default");          }      } else { @@ -132,6 +192,12 @@ void ImportPage::setUrl(const QString& url)      updateState();  } +void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info) +{ +    m_extra_info = extra_info; +    updateState(); +} +  void ImportPage::on_modpackBtn_clicked()  {      auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); diff --git a/launcher/ui/pages/modplatform/ImportPage.h b/launcher/ui/pages/modplatform/ImportPage.h index d846d566..70d7736e 100644 --- a/launcher/ui/pages/modplatform/ImportPage.h +++ b/launcher/ui/pages/modplatform/ImportPage.h @@ -62,7 +62,7 @@ class ImportPage : public QWidget, public BasePage {      void setUrl(const QString& url);      void openedImpl() override; - +    void setExtraInfo(const QMap<QString, QString>& extra_info);     private slots:      void on_modpackBtn_clicked();      void updateState(); @@ -73,4 +73,5 @@ class ImportPage : public QWidget, public BasePage {     private:      Ui::ImportPage* ui = nullptr;      NewInstanceDialog* dialog = nullptr; +    QMap<QString, QString> m_extra_info = {};  }; diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui index 3583cf90..9a9736b8 100644 --- a/launcher/ui/pages/modplatform/ImportPage.ui +++ b/launcher/ui/pages/modplatform/ImportPage.ui @@ -40,7 +40,7 @@       <item>        <widget class="QLabel" name="label_5">         <property name="text"> -        <string>- CurseForge modpacks (ZIP)</string> +        <string>- CurseForge modpacks (ZIP / curseforge:// URL)</string>         </property>         <property name="alignment">          <set>Qt::AlignCenter</set> diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index b7537890..c628f74a 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()      auto sort = getCurrentSortingMethodByIndex(); -    return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions }; +    return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };  }  ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry) @@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en      if (!m_filter->versions.empty())          versions = m_filter->versions; -    return { pack, versions, profile->getModLoaders() }; +    return { pack, versions, profile->getSupportedModLoaders() };  }  ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 9b178048..d6cc1fdc 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -124,8 +124,7 @@ void ModPage::updateVersionList()          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. -            if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) { +            if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {                  valid = true;                  break;              } diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 5510c191..5a43e49a 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -55,7 +55,7 @@ class ModPage : public ResourcePage {      virtual auto validateVersion(ModPlatform::IndexedVersion& ver,                                   QString mineVer, -                                 std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0; +                                 std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0;      [[nodiscard]] bool supportsFiltering() const override { return true; };      auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index 49405a02..cb8f1920 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -17,7 +17,7 @@  #include "BuildConfig.h"  #include "Json.h" -#include "net/Download.h" +#include "net/ApiDownload.h"  #include "net/NetJob.h"  #include "modplatform/ModIndex.h" @@ -102,7 +102,7 @@ QHash<int, QByteArray> ResourceModel::roleNames() const      return roles;  } -bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, int role) +bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)  {      int pos = index.row();      if (pos >= m_packs.size() || pos < 0 || !index.isValid()) @@ -132,6 +132,32 @@ void ResourceModel::search()      if (hasActiveSearchJob())          return; +    if (m_search_term.startsWith("#")) { +        auto projectId = m_search_term.mid(1); +        if (!projectId.isEmpty()) { +            ResourceAPI::ProjectInfoCallbacks callbacks; + +            callbacks.on_fail = [this](QString reason) { +                if (!s_running_models.constFind(this).value()) +                    return; +                searchRequestFailed(reason, -1); +            }; +            callbacks.on_abort = [this] { +                if (!s_running_models.constFind(this).value()) +                    return; +                searchRequestAborted(); +            }; + +            callbacks.on_succeed = [this](auto& doc, auto& pack) { +                if (!s_running_models.constFind(this).value()) +                    return; +                searchRequestForOneSucceeded(doc); +            }; +            if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job) +                runSearchJob(job); +            return; +        } +    }      auto args{ createSearchArguments() };      auto callbacks{ createSearchCallbacks() }; @@ -189,11 +215,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)          // Use default if no callbacks are set          if (!callbacks.on_succeed) -            callbacks.on_succeed = [this, entry](auto& doc, auto pack) { +            callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) {                  if (!s_running_models.constFind(this).value())                      return; +                auto pack = newpack;                  infoRequestSucceeded(doc, pack, entry);              }; +        if (!callbacks.on_fail) +            callbacks.on_fail = [this](QString reason) { +                if (!s_running_models.constFind(this).value()) +                    return; +                QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason)); +            };          if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)              runInfoJob(job); @@ -281,7 +314,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)      auto cache_entry = APPLICATION->metacache()->resolveEntry(          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 icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);      auto full_file_path = cache_entry->getFullPath();      connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { @@ -310,7 +343,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)  #define NEED_FOR_CALLBACK_ASSERT(name) \      Q_ASSERT_X(0 != 0, #name, "You NEED to re-implement this if you intend on using the default callbacks.") -QJsonArray ResourceModel::documentToArray(QJsonDocument& doc) const +QJsonArray ResourceModel::documentToArray([[maybe_unused]] QJsonDocument& doc) const  {      NEED_FOR_CALLBACK_ASSERT("documentToArray");      return {}; @@ -372,7 +405,28 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)      endInsertRows();  } -void ResourceModel::searchRequestFailed(QString reason, int network_error_code) +void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc) +{ +    ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>(); + +    try { +        auto obj = Json::requireObject(doc); +        if (obj.contains("data")) +            obj = Json::requireObject(obj, "data"); +        loadIndexedPack(*pack, obj); +    } catch (const JSONValidationError& e) { +        qDebug() << doc; +        qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause(); +    } + +    m_search_state = SearchState::Finished; + +    beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1); +    m_packs.append(pack); +    endInsertRows(); +} + +void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)  {      switch (network_error_code) {          default: diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 6533d9c6..ecf4f8f7 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -42,7 +42,10 @@ class ResourceModel : public QAbstractListModel {      [[nodiscard]] virtual auto debugName() const -> QString;      [[nodiscard]] virtual auto metaEntryBase() const -> QString = 0; -    [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); } +    [[nodiscard]] inline int rowCount(const QModelIndex& parent) const override +    { +        return parent.isValid() ? 0 : static_cast<int>(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); } @@ -146,6 +149,7 @@ class ResourceModel : public QAbstractListModel {     private:      /* Default search request callbacks */      void searchRequestSucceeded(QJsonDocument&); +    void searchRequestForOneSucceeded(QJsonDocument&);      void searchRequestFailed(QString reason, int network_error_code);      void searchRequestAborted(); diff --git a/launcher/ui/pages/modplatform/ResourcePackModel.cpp b/launcher/ui/pages/modplatform/ResourcePackModel.cpp index 18c14bf8..d436f320 100644 --- a/launcher/ui/pages/modplatform/ResourcePackModel.cpp +++ b/launcher/ui/pages/modplatform/ResourcePackModel.cpp @@ -9,7 +9,8 @@  namespace ResourceDownload {  ResourcePackResourceModel::ResourcePackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) -    : ResourceModel(api), m_base_instance(base_inst){}; +    : ResourceModel(api), m_base_instance(base_inst) +{}  /******** Make data requests ********/ diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 2925ec87..44a91003 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -4,7 +4,7 @@  /*   *  Prism Launcher - Minecraft Launcher   *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> - *  Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> + *  Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>   *   *  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 @@ -44,9 +44,6 @@  #include <QKeyEvent>  #include "Markdown.h" -#include "ResourceDownloadTask.h" - -#include "minecraft/MinecraftInstance.h"  #include "ui/dialogs/ResourceDownloadDialog.h"  #include "ui/pages/modplatform/ResourceModel.h" @@ -283,7 +280,7 @@ void ResourcePage::updateVersionList()      updateSelectionButton();  } -void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)  {      if (!curr.isValid()) {          return; @@ -310,9 +307,9 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)      updateUi();  } -void ResourcePage::onVersionSelectionChanged(QString data) +void ResourcePage::onVersionSelectionChanged(QString versionData)  { -    if (data.isNull() || data.isEmpty()) { +    if (versionData.isNull() || versionData.isEmpty()) {          m_selected_version_index = -1;          return;      } @@ -398,7 +395,7 @@ void ResourcePage::openUrl(const QUrl& url)          if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) {              m_parent_dialog->selectPage(page); -            auto newPage = m_parent_dialog->getSelectedPage(); +            auto newPage = m_parent_dialog->selectedPage();              QLineEdit* searchEdit = newPage->m_ui->searchEdit;              auto model = newPage->m_model; diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index b4a87f57..7bec0a37 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -97,7 +97,11 @@ class ResourcePage : public QWidget, public BasePage {      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; }; +    virtual bool optedOut(ModPlatform::IndexedVersion& ver) const +    { +        Q_UNUSED(ver); +        return false; +    };     public:      BaseInstance& m_base_instance; diff --git a/launcher/ui/pages/modplatform/ShaderPackModel.cpp b/launcher/ui/pages/modplatform/ShaderPackModel.cpp index aabd3be6..8c913657 100644 --- a/launcher/ui/pages/modplatform/ShaderPackModel.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackModel.cpp @@ -9,7 +9,8 @@  namespace ResourceDownload {  ShaderPackResourceModel::ShaderPackResourceModel(BaseInstance const& base_inst, ResourceAPI* api) -    : ResourceModel(api), m_base_instance(base_inst){}; +    : ResourceModel(api), m_base_instance(base_inst) +{}  /******** Make data requests ********/ diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index fbf94e84..586dffc5 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -3,6 +3,7 @@  // SPDX-License-Identifier: GPL-3.0-only  #include "ShaderPackPage.h" +#include "modplatform/ModIndex.h"  #include "ui_ResourcePage.h"  #include "ShaderPackModel.h" @@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac                                                 const std::shared_ptr<ResourceFolderModel> base_model)  {      QString custom_target_folder; -    if (version.loaders.contains(QStringLiteral("canvas"))) +    if (version.loaders & ModPlatform::Cauldron)          custom_target_folder = QStringLiteral("resourcepacks");      m_model->addPack(pack, version, base_model, false, custom_target_folder);  } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp index 9cd5eed5..dee3784e 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp @@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen      if (searchTerm.isEmpty()) {          return true;      } -      QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);      ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>(); +    if (searchTerm.startsWith("#")) +        return QString::number(pack.id) == searchTerm.mid(1);      return pack.name.contains(searchTerm, Qt::CaseInsensitive);  } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp index c6b087d6..b6fb7153 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp @@ -20,6 +20,9 @@  #include <BuildConfig.h>  #include <Json.h> +#include "net/ApiDownload.h" +#include "ui/widgets/ProjectItem.h" +  namespace Atl {  ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} @@ -44,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const      }      ATLauncher::IndexedPack pack = modpacks.at(pos); -    if (role == Qt::DisplayRole) { -        return pack.name; -    } else if (role == Qt::ToolTipRole) { -        return pack.name; -    } else if (role == Qt::DecorationRole) { -        if (m_logoMap.contains(pack.safeName)) { -            return (m_logoMap.value(pack.safeName)); +    switch (role) { +        case Qt::ToolTipRole: { +            if (pack.description.length() > 100) { +                // some magic to prevent to long tooltips and replace html linebreaks +                QString edit = pack.description.left(97); +                edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); +                return edit; +            } +            return pack.description;          } -        auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder"); +        case Qt::DecorationRole: { +            if (m_logoMap.contains(pack.safeName)) { +                return (m_logoMap.value(pack.safeName)); +            } +            auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder"); -        auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower()); -        ((ListModel*)this)->requestLogo(pack.safeName, url); +            auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower()); +            ((ListModel*)this)->requestLogo(pack.safeName, url); -        return icon; -    } else if (role == Qt::UserRole) { -        QVariant v; -        v.setValue(pack); -        return v; +            return icon; +        } +        case Qt::UserRole: { +            QVariant v; +            v.setValue(pack); +            return v; +        } +        case Qt::DisplayRole: +            return pack.name; +        case Qt::SizeHintRole: +            return QSize(0, 58); +        // Custom data +        case UserDataTypes::TITLE: +            return pack.name; +        case UserDataTypes::DESCRIPTION: +            return pack.description; +        case UserDataTypes::SELECTED: +            return false; +        case UserDataTypes::INSTALLED: +            return false; +        default: +            break;      } -    return QVariant(); +    return {};  }  void ListModel::request() @@ -75,7 +101,7 @@ void ListModel::request()      auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());      auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json"); -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));      jobPtr = netJob;      jobPtr->start(); @@ -137,8 +163,7 @@ void ListModel::requestFailed(QString reason)  void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)  {      if (m_logoMap.contains(logo)) { -        callback( -            APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); +        callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo))->getFullPath());      } else {          requestLogo(logo, logoUrl);      } @@ -168,9 +193,9 @@ void ListModel::requestLogo(QString file, QString url)          return;      } -    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0))); +    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file));      auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network()); -    job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); +    job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));      auto fullPath = entry->getFullPath();      QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] { diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 62e406d3..6fb86773 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -43,6 +43,8 @@  #include "Json.h"  #include "modplatform/atlauncher/ATLShareCode.h" +#include "net/ApiDownload.h" +  AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)      : QAbstractListModel(parent), m_version(version), m_mods(mods)  { @@ -113,7 +115,7 @@ QVariant AtlOptionalModListModel::data(const QModelIndex& index, int role) const      return {};  } -bool AtlOptionalModListModel::setData(const QModelIndex& index, const QVariant& value, int role) +bool AtlOptionalModListModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)  {      if (role == Qt::CheckStateRole) {          auto row = index.row(); @@ -155,7 +157,7 @@ void AtlOptionalModListModel::useShareCode(const QString& code)  {      m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));      auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code); -    m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response)); +    m_jobPtr->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), m_response));      connect(m_jobPtr.get(), &NetJob::succeeded, this, &AtlOptionalModListModel::shareCodeSuccess);      connect(m_jobPtr.get(), &NetJob::failed, this, &AtlOptionalModListModel::shareCodeFailure); @@ -206,7 +208,7 @@ void AtlOptionalModListModel::shareCodeSuccess()      emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));  } -void AtlOptionalModListModel::shareCodeFailure(const QString& reason) +void AtlOptionalModListModel::shareCodeFailure([[maybe_unused]] const QString& reason)  {      m_jobPtr.reset(); @@ -281,15 +283,15 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool          // if the dependency is 'effectively hidden', then track which mods          // depend on it - so we can efficiently disable it when no more dependents          // depend on it. -        auto dependants = m_dependants[dependencyName]; +        auto dependents = m_dependents[dependencyName];          if (enable) { -            dependants.append(mod.name); +            dependents.append(mod.name);          } else { -            dependants.removeAll(mod.name); +            dependents.removeAll(mod.name);              // if there are no longer any dependents, let's disable the mod -            if (dependencyMod.effectively_hidden && dependants.isEmpty()) { +            if (dependencyMod.effectively_hidden && dependents.isEmpty()) {                  setMod(dependencyMod, dependencyIndex, false, shouldEmit);              }          } @@ -297,8 +299,8 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool      // disable mods that depend on this one, if disabling      if (!enable) { -        auto dependants = m_dependants[mod.name]; -        for (const auto& dependencyName : dependants) { +        auto dependents = m_dependents[mod.name]; +        for (const auto& dependencyName : dependents) {              auto dependencyIndex = m_index[dependencyName];              auto dependencyMod = m_mods.at(dependencyIndex); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 72a946da..55903003 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -90,7 +90,7 @@ class AtlOptionalModListModel : public QAbstractListModel {      QMap<QString, bool> m_selection;      QMap<QString, int> m_index; -    QMap<QString, QVector<QString>> m_dependants; +    QMap<QString, QVector<QString>> m_dependents;  };  class AtlOptionalModDialog : public QDialog { diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 4c9dec58..c7e80027 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -35,11 +35,11 @@   */  #include "AtlPage.h" +#include "ui/widgets/ProjectItem.h"  #include "ui_AtlPage.h"  #include "BuildConfig.h" -#include "AtlOptionalModDialog.h"  #include "AtlUserInteractionSupportImpl.h"  #include "modplatform/atlauncher/ATLPackInstallTask.h"  #include "ui/dialogs/NewInstanceDialog.h" @@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),      connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);      connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);      connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged); + +    ui->packView->setItemDelegate(new ProjectItemDelegate(this));  }  AtlPage::~AtlPage() @@ -123,13 +125,13 @@ void AtlPage::triggerSearch()      filterModel->setSearchTerm(ui->searchEdit->text());  } -void AtlPage::onSortingSelectionChanged(QString data) +void AtlPage::onSortingSelectionChanged(QString sort)  { -    auto toSet = filterModel->getAvailableSortings().value(data); +    auto toSet = filterModel->getAvailableSortings().value(sort);      filterModel->setSorting(toSet);  } -void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second) +void AtlPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)  {      ui->versionSelectionBox->clear(); @@ -151,13 +153,13 @@ void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)      suggestCurrent();  } -void AtlPage::onVersionSelectionChanged(QString data) +void AtlPage::onVersionSelectionChanged(QString version)  { -    if (data.isNull() || data.isEmpty()) { +    if (version.isNull() || version.isEmpty()) {          selectedVersion = "";          return;      } -    selectedVersion = data; +    selectedVersion = version;      suggestCurrent();  } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui index 746aa6d1..8b674733 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui @@ -11,21 +11,28 @@     </rect>    </property>    <layout class="QGridLayout" name="gridLayout"> -   <item row="1" column="0" colspan="2"> -    <layout class="QGridLayout" name="gridLayout_3"> -     <item row="1" column="0"> -      <widget class="QTreeView" name="packView"> -       <property name="alternatingRowColors"> -        <bool>true</bool> +   <item row="3" column="0" colspan="2"> +    <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> +     <item row="0" column="2"> +      <widget class="QComboBox" name="versionSelectionBox"/> +     </item> +     <item row="0" column="1"> +      <widget class="QLabel" name="label"> +       <property name="text"> +        <string>Version selected:</string>         </property> -       <property name="iconSize"> -        <size> -         <width>96</width> -         <height>48</height> -        </size> +       <property name="alignment"> +        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>         </property>        </widget>       </item> +     <item row="0" column="0"> +      <widget class="QComboBox" name="sortByBox"/> +     </item> +    </layout> +   </item> +   <item row="2" column="0" colspan="2"> +    <layout class="QGridLayout" name="gridLayout_3">       <item row="1" column="1">        <widget class="QTextBrowser" name="packDescription">         <property name="openExternalLinks"> @@ -36,39 +43,22 @@         </property>        </widget>       </item> -     <item row="0" column="0" colspan="2"> -      <widget class="QLabel" name="label_2"> -       <property name="text"> -        <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string> -       </property> -       <property name="wordWrap"> +     <item row="1" column="0"> +      <widget class="QTreeView" name="packView"> +       <property name="alternatingRowColors">          <bool>true</bool>         </property> -      </widget> -     </item> -    </layout> -   </item> -   <item row="2" column="0" colspan="2"> -    <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0"> -     <item row="0" column="2"> -      <widget class="QComboBox" name="versionSelectionBox"/> -     </item> -     <item row="0" column="1"> -      <widget class="QLabel" name="label"> -       <property name="text"> -        <string>Version selected:</string> -       </property> -       <property name="alignment"> -        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> +       <property name="iconSize"> +        <size> +         <width>96</width> +         <height>48</height> +        </size>         </property>        </widget>       </item> -     <item row="0" column="0"> -      <widget class="QComboBox" name="sortByBox"/> -     </item>      </layout>     </item> -   <item row="0" column="0"> +   <item row="1" column="0">      <widget class="QLineEdit" name="searchEdit">       <property name="placeholderText">        <string>Search and filter...</string> @@ -78,6 +68,31 @@       </property>      </widget>     </item> +   <item row="1" column="1"> +    <widget class="QPushButton" name="pushButton"> +     <property name="text"> +      <string>Search</string> +     </property> +    </widget> +   </item> +   <item row="0" column="0" colspan="2"> +    <widget class="QLabel" name="label_2"> +     <property name="font"> +      <font> +       <italic>true</italic> +      </font> +     </property> +     <property name="text"> +      <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string> +     </property> +     <property name="alignment"> +      <set>Qt::AlignCenter</set> +     </property> +     <property name="wordWrap"> +      <bool>true</bool> +     </property> +    </widget> +   </item>    </layout>   </widget>   <tabstops> diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 83c1270b..8875a945 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -1,8 +1,12 @@  #include "FlameModel.h"  #include <Json.h>  #include "Application.h" +#include "modplatform/ResourceAPI.h" +#include "modplatform/flame/FlameAPI.h"  #include "ui/widgets/ProjectItem.h" +#include "net/ApiDownload.h" +  #include <Version.h>  #include <QtMath> @@ -70,7 +74,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const      return QVariant();  } -bool ListModel::setData(const QModelIndex& index, const QVariant& value, int role) +bool ListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)  {      int pos = index.row();      if (pos >= modpacks.size() || pos < 0 || !index.isValid()) @@ -104,9 +108,9 @@ void ListModel::requestLogo(QString logo, QString url)          return;      } -    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0))); +    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo));      auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network()); -    job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); +    job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));      auto fullPath = entry->getFullPath();      QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { @@ -130,7 +134,7 @@ void ListModel::requestLogo(QString logo, QString url)  void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)  {      if (m_logoMap.contains(logo)) { -        callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); +        callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo))->getFullPath());      } else {          requestLogo(logo, logoUrl);      } @@ -141,7 +145,7 @@ Qt::ItemFlags ListModel::flags(const QModelIndex& index) const      return QAbstractListModel::flags(index);  } -bool ListModel::canFetchMore(const QModelIndex& parent) const +bool ListModel::canFetchMore([[maybe_unused]] const QModelIndex& parent) const  {      return searchState == CanPossiblyFetchMore;  } @@ -159,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)  void ListModel::performPaginatedSearch()  { +    if (currentSearchTerm.startsWith("#")) { +        auto projectId = currentSearchTerm.mid(1); +        if (!projectId.isEmpty()) { +            ResourceAPI::ProjectInfoCallbacks callbacks; + +            callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; +            callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; +            static const FlameAPI api; +            if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { +                jobPtr = job; +                jobPtr->start(); +            } +            return; +        } +    }      auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());      auto searchUrl = QString(                           "https://api.curseforge.com/v1/mods/search?" @@ -173,7 +192,7 @@ void ListModel::performPaginatedSearch()                           .arg(currentSearchTerm)                           .arg(currentSort + 1); -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));      jobPtr = netJob;      jobPtr->start();      QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); @@ -187,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)      }      currentSearchTerm = term;      currentSort = sort; -    if (jobPtr) { +    if (hasActiveSearchJob()) {          jobPtr->abort();          searchState = ResetRequested;          return; -    } else { -        beginResetModel(); -        modpacks.clear(); -        endResetModel(); -        searchState = None;      } +    beginResetModel(); +    modpacks.clear(); +    endResetModel(); +    searchState = None; +      nextSearchOffset = 0;      performPaginatedSearch();  }  void Flame::ListModel::searchRequestFinished()  { -    jobPtr.reset(); +    if (hasActiveSearchJob()) +        return;      QJsonParseError parse_error;      QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); @@ -244,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()      endInsertRows();  } +void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc) +{ +    jobPtr.reset(); + +    auto packObj = Json::ensureObject(doc.object(), "data"); + +    Flame::IndexedPack pack; +    try { +        Flame::loadIndexedPack(pack, packObj); +    } catch (const JSONValidationError& e) { +        qWarning() << "Error while loading pack from CurseForge: " << e.cause(); +        return; +    } + +    beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1); +    modpacks.append({ pack }); +    endInsertRows(); +} +  void Flame::ListModel::searchRequestFailed(QString reason)  {      jobPtr.reset(); diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h index b3bc96b8..fd8496df 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModel.h @@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {      void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);      void searchWithTerm(const QString& term, const int sort); +    [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } +    [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } +     private slots:      void performPaginatedSearch(); @@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {      void searchRequestFinished();      void searchRequestFailed(QString reason); +    void searchRequestForOneSucceeded(QJsonDocument&);     private:      void requestLogo(QString file, QString url); @@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {      int currentSort = 0;      int nextSearchOffset = 0;      enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; -    NetJob::Ptr jobPtr; +    Task::Ptr jobPtr;      std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();  }; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 9be7ce8b..584d94ad 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -46,9 +46,12 @@  #include "ui/dialogs/NewInstanceDialog.h"  #include "ui/widgets/ProjectItem.h" +#include "net/ApiDownload.h" +  static FlameAPI api; -FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) +FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) +    : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)  {      ui->setupUi(this);      connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch); @@ -59,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren      ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);      ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); +    m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); +    m_search_timer.setSingleShot(true); + +    connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch); + +    m_fetch_progress.hideIfInactive(true); +    m_fetch_progress.setFixedHeight(24); +    m_fetch_progress.progressFormat(""); + +    ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount()); +      // index is used to set the sorting with the curseforge api      ui->sortByBox->addItem(tr("Sort by Featured"));      ui->sortByBox->addItem(tr("Sort by Popularity")); @@ -88,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)              triggerSearch();              keyEvent->accept();              return true; +        } else { +            if (m_search_timer.isActive()) +                m_search_timer.stop(); + +            m_search_timer.start(350);          }      }      return QWidget::eventFilter(watched, event); @@ -112,9 +131,10 @@ void FlamePage::openedImpl()  void FlamePage::triggerSearch()  {      listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); +    m_fetch_progress.watch(listModel->activeSearchJob().get());  } -void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)  {      ui->versionSelectionBox->clear(); @@ -132,7 +152,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)          auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());          auto response = std::make_shared<QByteArray>();          int addonId = current.addonId; -        netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); +        netJob->addNetAction( +            Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));          QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {              if (addonId != current.addonId) { @@ -208,17 +229,17 @@ void FlamePage::suggestCurrent()      dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));      QString editedLogoName; -    editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0); +    editedLogoName = "curseforge_" + current.logoName;      listModel->getLogo(current.logoName, current.logoUrl,                         [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });  } -void FlamePage::onVersionSelectionChanged(QString data) +void FlamePage::onVersionSelectionChanged(QString version)  {      bool is_blocked = false;      ui->versionSelectionBox->currentData().toInt(&is_blocked); -    if (data.isNull() || data.isEmpty() || is_blocked) { +    if (version.isNull() || version.isEmpty() || is_blocked) {          m_selected_version_index = -1;          return;      } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h index ff5c7975..d35858fb 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.h +++ b/launcher/ui/pages/modplatform/flame/FlamePage.h @@ -39,8 +39,9 @@  #include <Application.h>  #include <modplatform/flame/FlamePackIndex.h> -#include "tasks/Task.h" +#include <QTimer>  #include "ui/pages/BasePage.h" +#include "ui/widgets/ProgressWidget.h"  namespace Ui {  class FlamePage; @@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {      Flame::IndexedPack current;      int m_selected_version_index = -1; + +    ProgressWidget m_fetch_progress; + +    // Used to do instant searching with a delay to cache quick changes +    QTimer m_search_timer;  }; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index 71d19513..f9e1fe67 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -47,7 +47,7 @@       </item>      </layout>     </item> -   <item row="2" column="0"> +   <item row="3" column="0">      <layout class="QHBoxLayout">       <item>        <widget class="QListView" name="packView"> @@ -77,7 +77,7 @@       </item>      </layout>     </item> -   <item row="3" column="0"> +   <item row="4" column="0">      <layout class="QHBoxLayout">       <item>        <widget class="QComboBox" name="sortByBox"/> diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index 0fb67c50..7d18e72a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -31,8 +31,8 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr  auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion  { -    return FlameMod::loadDependencyVersions(m, arr); -}; +    return FlameMod::loadDependencyVersions(m, arr, &m_base_instance); +}  auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray  { @@ -121,4 +121,27 @@ auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonAr      return Json::ensureArray(obj.object(), "data");  } +FlameShaderPackModel::FlameShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new FlameAPI) {} + +void FlameShaderPackModel::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 FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ +    FlameMod::loadBody(m, obj); +} + +void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ +    FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +} + +auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ +    return Json::ensureArray(obj.object(), "data"); +} +  }  // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h index 6cfd6a6f..76dbd7b3 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h @@ -68,4 +68,21 @@ class FlameTexturePackModel : public TexturePackResourceModel {      auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;  }; +class FlameShaderPackModel : public ShaderPackResourceModel { +    Q_OBJECT + +   public: +    FlameShaderPackModel(const BaseInstance&); +    ~FlameShaderPackModel() override = default; + +   private: +    [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; } +    [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); } + +    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; +}; +  }  // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index dc17e705..23373ec9 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -68,10 +68,10 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :  auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,                                     QString mineVer, -                                   std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool +                                   std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool  { -    Q_UNUSED(loaders); -    return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); +    return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() && +           (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);  }  bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const @@ -173,6 +173,45 @@ void FlameTexturePackPage::openUrl(const QUrl& url)      TexturePackResourcePage::openUrl(url);  } +FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance) +    : ShaderPackResourcePage(dialog, instance) +{ +    m_model = new FlameShaderPackModel(instance); +    m_ui->packView->setModel(m_model); + +    addSortings(); + +    // 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, &FlameShaderPackPage::onSelectionChanged); +    connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameShaderPackPage::onVersionSelectionChanged); +    connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected); + +    m_ui->packDescription->setMetaEntry(metaEntryBase()); +} + +bool FlameShaderPackPage::optedOut(ModPlatform::IndexedVersion& ver) const +{ +    return isOptedOut(ver); +} + +void FlameShaderPackPage::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); +            ShaderPackResourcePage::openUrl({ QUrl::fromPercentEncoding(query.toUtf8()) });  // double decoding is necessary +            return; +        } +    } + +    ShaderPackResourcePage::openUrl(url); +} +  // 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... @@ -188,5 +227,9 @@ auto FlameTexturePackPage::shouldDisplay() const -> bool  {      return true;  } +auto FlameShaderPackPage::shouldDisplay() const -> bool +{ +    return true; +}  }  // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h index c6ebc1ea..f2f5ceca 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h @@ -44,6 +44,7 @@  #include "ui/pages/modplatform/ModPage.h"  #include "ui/pages/modplatform/ResourcePackPage.h" +#include "ui/pages/modplatform/ShaderPackPage.h"  #include "ui/pages/modplatform/TexturePackPage.h"  namespace ResourceDownload { @@ -95,7 +96,7 @@ class FlameModPage : public ModPage {      bool validateVersion(ModPlatform::IndexedVersion& ver,                           QString mineVer, -                         std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override; +                         std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;      bool optedOut(ModPlatform::IndexedVersion& ver) const override;      void openUrl(const QUrl& url) override; @@ -155,4 +156,31 @@ class FlameTexturePackPage : public TexturePackResourcePage {      void openUrl(const QUrl& url) override;  }; +class FlameShaderPackPage : public ShaderPackResourcePage { +    Q_OBJECT + +   public: +    static FlameShaderPackPage* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance) +    { +        return ShaderPackResourcePage::create<FlameShaderPackPage>(dialog, instance); +    } + +    FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); +    ~FlameShaderPackPage() override = default; + +    [[nodiscard]] bool shouldDisplay() const override; + +    [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); } +    [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); } +    [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); } +    [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); } +    [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); } + +    [[nodiscard]] inline auto helpPage() const -> QString override { return ""; } + +    bool optedOut(ModPlatform::IndexedVersion& ver) const override; + +    void openUrl(const QUrl& url) override; +}; +  }  // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp index 5c9ff63b..d3ead083 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp @@ -17,6 +17,7 @@   */  #include "ImportFTBPage.h" +#include "ui/widgets/ProjectItem.h"  #include "ui_ImportFTBPage.h"  #include <QWidget> @@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg      ui->setupUi(this);      { +        currentModel = new FilterModel(this);          listModel = new ListModel(this); +        currentModel->setSourceModel(listModel); -        ui->modpackList->setModel(listModel); +        ui->modpackList->setModel(currentModel);          ui->modpackList->setSortingEnabled(true);          ui->modpackList->header()->hide();          ui->modpackList->setIndentation(0);          ui->modpackList->setIconSize(QSize(42, 42)); + +        for (int i = 0; i < currentModel->getAvailableSortings().size(); i++) { +            ui->sortByBox->addItem(currentModel->getAvailableSortings().keys().at(i)); +        } + +        ui->sortByBox->setCurrentText(currentModel->translateCurrentSorting());      }      connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged); +    connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged); + +    connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch); + +    ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));      ui->modpackList->selectionModel()->reset();  } @@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr          onPackSelectionChanged();          return;      } -    Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>(); +    Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();      onPackSelectionChanged(&selectedPack);  } @@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)          dialog->setSuggestedPack();  } +void ImportFTBPage::onSortingSelectionChanged(QString sort) +{ +    FilterModel::Sorting toSet = currentModel->getAvailableSortings().value(sort); +    currentModel->setSorting(toSet); +} + +void ImportFTBPage::triggerSearch() +{ +    currentModel->setSearchTerm(ui->searchEdit->text()); +} +  }  // namespace FTBImportAPP diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h index 54c49f7b..8e966127 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h @@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage {      void suggestCurrent();      void onPackSelectionChanged(Modpack* pack = nullptr);     private slots: +    void onSortingSelectionChanged(QString data);      void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second); +    void triggerSearch();     private:      bool initialized = false;      Modpack selected;      ListModel* listModel = nullptr; +    FilterModel* currentModel = nullptr;      NewInstanceDialog* dialog = nullptr;      Ui::ImportFTBPage* ui = nullptr; diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui index 32d548b0..5e09fb6d 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui +++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui @@ -10,8 +10,8 @@      <height>1011</height>     </rect>    </property> -  <layout class="QHBoxLayout" name="horizontalLayout"> -   <item> +  <layout class="QGridLayout" name="gridLayout"> +   <item row="1" column="1">      <widget class="QTreeView" name="modpackList">       <property name="maximumSize">        <size> @@ -21,6 +21,54 @@       </property>      </widget>     </item> +   <item row="0" column="1"> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QLineEdit" name="searchEdit"> +       <property name="placeholderText"> +        <string>Search and filter...</string> +       </property> +       <property name="clearButtonEnabled"> +        <bool>true</bool> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="pushButton"> +       <property name="text"> +        <string>Search</string> +       </property> +      </widget> +     </item> +    </layout> +   </item> +   <item row="2" column="1"> +    <layout class="QHBoxLayout" name="horizontalLayout_2"> +     <item> +      <widget class="QComboBox" name="sortByBox"> +       <property name="minimumSize"> +        <size> +         <width>265</width> +         <height>0</height> +        </size> +       </property> +      </widget> +     </item> +     <item> +      <spacer name="horizontalSpacer"> +       <property name="orientation"> +        <enum>Qt::Horizontal</enum> +       </property> +       <property name="sizeHint" stdset="0"> +        <size> +         <width>40</width> +         <height>20</height> +        </size> +       </property> +      </spacer> +     </item> +    </layout> +   </item>    </layout>   </widget>   <resources/> diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index dc78f451..134bdc0c 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -23,7 +23,9 @@  #include <QIcon>  #include <QProcessEnvironment>  #include "FileSystem.h" +#include "StringUtils.h"  #include "modplatform/import_ftb/PackHelpers.h" +#include "ui/widgets/ProjectItem.h"  namespace FTBImportAPP { @@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const      }      auto pack = modpacks.at(pos); -    if (role == Qt::DisplayRole) { -        return pack.name; -    } else if (role == Qt::DecorationRole) { -        return pack.icon; -    } else if (role == Qt::UserRole) { -        QVariant v; -        v.setValue(pack); -        return v; -    } else if (role == Qt::ToolTipRole) { -        return tr("Minecraft %1").arg(pack.mcVersion); +    if (role == Qt::ToolTipRole) {      } -    return QVariant(); +    switch (role) { +        case Qt::ToolTipRole: +            return tr("Minecraft %1").arg(pack.mcVersion); +        case Qt::DecorationRole: +            return pack.icon; +        case Qt::UserRole: { +            QVariant v; +            v.setValue(pack); +            return v; +        } +        case Qt::DisplayRole: +            return pack.name; +        case Qt::SizeHintRole: +            return QSize(0, 58); +        // Custom data +        case UserDataTypes::TITLE: +            return pack.name; +        case UserDataTypes::DESCRIPTION: +            return tr("Minecraft %1").arg(pack.mcVersion); +        case UserDataTypes::SELECTED: +            return false; +        case UserDataTypes::INSTALLED: +            return false; +        default: +            break; +    } + +    return {}; +} + +FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent) +{ +    currentSorting = Sorting::ByGameVersion; +    sortings.insert(tr("Sort by Name"), Sorting::ByName); +    sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion); +} + +bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ +    Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>(); +    Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>(); + +    if (currentSorting == Sorting::ByGameVersion) { +        Version lv(leftPack.mcVersion); +        Version rv(rightPack.mcVersion); +        return lv < rv; + +    } else if (currentSorting == Sorting::ByName) { +        return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0; +    } + +    // UHM, some inavlid value set?! +    qWarning() << "Invalid sorting set!"; +    return true; +} + +bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const +{ +    if (searchTerm.isEmpty()) { +        return true; +    } +    QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); +    Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>(); +    return pack.name.contains(searchTerm, Qt::CaseInsensitive); +} + +void FilterModel::setSearchTerm(const QString term) +{ +    searchTerm = term.trimmed(); +    invalidate(); +} + +const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings() +{ +    return sortings; +} + +QString FilterModel::translateCurrentSorting() +{ +    return sortings.key(currentSorting); +} + +void FilterModel::setSorting(Sorting s) +{ +    currentSorting = s; +    invalidate(); +} + +FilterModel::Sorting FilterModel::getCurrentSorting() +{ +    return currentSorting;  }  }  // namespace FTBImportAPP
\ No newline at end of file diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h index c67aa896..11192827 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h @@ -20,11 +20,33 @@  #include <QAbstractListModel>  #include <QIcon> +#include <QSortFilterProxyModel>  #include <QVariant>  #include "modplatform/import_ftb/PackHelpers.h"  namespace FTBImportAPP { +class FilterModel : public QSortFilterProxyModel { +    Q_OBJECT +   public: +    FilterModel(QObject* parent = Q_NULLPTR); +    enum Sorting { ByName, ByGameVersion }; +    const QMap<QString, Sorting> getAvailableSortings(); +    QString translateCurrentSorting(); +    void setSorting(Sorting sorting); +    Sorting getCurrentSorting(); +    void setSearchTerm(QString term); + +   protected: +    bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; +    bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; + +   private: +    QMap<QString, Sorting> sortings; +    Sorting currentSorting; +    QString searchTerm; +}; +  class ListModel : public QAbstractListModel {      Q_OBJECT diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 1731ff2c..49666cf6 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -35,11 +35,13 @@  #include "ListModel.h"  #include "Application.h" +#include "net/ApiDownload.h"  #include "net/HttpMetaCache.h"  #include "net/NetJob.h"  #include <Version.h>  #include "StringUtils.h" +#include "ui/widgets/ProjectItem.h"  #include <QLabel>  #include <QtMath> @@ -76,9 +78,22 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co      return true;  } -bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const  { -    return true; +    if (searchTerm.isEmpty()) { +        return true; +    } +    QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); +    Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>(); +    if (searchTerm.startsWith("#")) +        return pack.packCode == searchTerm.mid(1); +    return pack.name.contains(searchTerm, Qt::CaseInsensitive); +} + +void FilterModel::setSearchTerm(const QString term) +{ +    searchTerm = term.trimmed(); +    invalidate();  }  const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings() @@ -138,45 +153,63 @@ QVariant ListModel::data(const QModelIndex& index, int role) const      }      Modpack pack = modpacks.at(pos); -    if (role == Qt::DisplayRole) { -        return pack.name + "\n" + translatePackType(pack.type); -    } else if (role == Qt::ToolTipRole) { -        if (pack.description.length() > 100) { -            // some magic to prevent to long tooltips and replace html linebreaks -            QString edit = pack.description.left(97); -            edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); -            return edit; +    switch (role) { +        case Qt::ToolTipRole: { +            if (pack.description.length() > 100) { +                // some magic to prevent to long tooltips and replace html linebreaks +                QString edit = pack.description.left(97); +                edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); +                return edit; +            } +            return pack.description; +        } +        case Qt::DecorationRole: { +            if (m_logoMap.contains(pack.logo)) { +                return (m_logoMap.value(pack.logo)); +            } +            QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); +            ((ListModel*)this)->requestLogo(pack.logo); +            return icon;          } -        return pack.description; -    } else if (role == Qt::DecorationRole) { -        if (m_logoMap.contains(pack.logo)) { -            return (m_logoMap.value(pack.logo)); +        case Qt::UserRole: { +            QVariant v; +            v.setValue(pack); +            return v;          } -        QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); -        ((ListModel*)this)->requestLogo(pack.logo); -        return icon; -    } else if (role == Qt::ForegroundRole) { -        if (pack.broken) { -            // FIXME: Hardcoded color -            return QColor(255, 0, 50); -        } else if (pack.bugged) { -            // FIXME: Hardcoded color -            // bugged pack, currently only indicates bugged xml -            return QColor(244, 229, 66); +        case Qt::ForegroundRole: { +            if (pack.broken) { +                // FIXME: Hardcoded color +                return QColor(255, 0, 50); +            } else if (pack.bugged) { +                // FIXME: Hardcoded color +                // bugged pack, currently only indicates bugged xml +                return QColor(244, 229, 66); +            }          } -    } else if (role == Qt::UserRole) { -        QVariant v; -        v.setValue(pack); -        return v; +        case Qt::DisplayRole: +            return pack.name; +        case Qt::SizeHintRole: +            return QSize(0, 58); +        // Custom data +        case UserDataTypes::TITLE: +            return pack.name; +        case UserDataTypes::DESCRIPTION: +            return pack.description; +        case UserDataTypes::SELECTED: +            return false; +        case UserDataTypes::INSTALLED: +            return false; +        default: +            break;      } -    return QVariant(); +    return {};  } -void ListModel::fill(ModpackList modpacks) +void ListModel::fill(ModpackList modpacks_)  {      beginResetModel(); -    this->modpacks = modpacks; +    this->modpacks = modpacks_;      endResetModel();  } @@ -229,9 +262,9 @@ void ListModel::requestLogo(QString file)          return;      } -    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); +    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file));      NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network()); -    job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); +    job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));      auto fullPath = entry->getFullPath();      QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] { @@ -255,7 +288,7 @@ void ListModel::requestLogo(QString file)  void ListModel::getLogo(const QString& logo, LogoCallback callback)  {      if (m_logoMap.contains(logo)) { -        callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath()); +        callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo))->getFullPath());      } else {          requestLogo(logo);      } diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h index 51a58d99..c802a4b5 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h @@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel {      QString translateCurrentSorting();      void setSorting(Sorting sorting);      Sorting getCurrentSorting(); +    void setSearchTerm(QString term);     protected:      bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; @@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel {     private:      QMap<QString, Sorting> sortings;      Sorting currentSorting; +    QString searchTerm;  };  class ListModel : public QAbstractListModel { diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index ef8e9892..4104f139 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -35,6 +35,7 @@   */  #include "Page.h" +#include "ui/widgets/ProjectItem.h"  #include "ui_Page.h"  #include <QInputDialog> @@ -110,6 +111,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog      connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);      connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged); +    connect(ui->searchEdit, &QLineEdit::textChanged, this, &Page::triggerSearch); +      connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);      connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);      connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged); @@ -125,6 +128,9 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog      ui->thirdPartyPackList->selectionModel()->reset();      ui->privatePackList->selectionModel()->reset(); +    ui->publicPackList->setItemDelegate(new ProjectItemDelegate(this)); +    ui->thirdPartyPackList->setItemDelegate(new ProjectItemDelegate(this)); +    ui->privatePackList->setItemDelegate(new ProjectItemDelegate(this));      onTabChanged(ui->tabWidget->currentIndex());  } @@ -215,7 +221,7 @@ void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack)      privateListModel->addPack(pack);  } -void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) +void Page::ftbPrivatePackDataDownloadFailed([[maybe_unused]] QString reason, QString packCode)  {      auto reply = QMessageBox::question(this, tr("FTB private packs"),                                         tr("Failed to download pack information for code %1.\nShould it be removed now?").arg(packCode)); @@ -224,7 +230,7 @@ void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)      }  } -void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onPublicPackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)  {      if (!now.isValid()) {          onPackSelectionChanged(); @@ -234,7 +240,7 @@ void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)      onPackSelectionChanged(&selectedPack);  } -void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onThirdPartyPackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)  {      if (!now.isValid()) {          onPackSelectionChanged(); @@ -244,7 +250,7 @@ void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)      onPackSelectionChanged(&selectedPack);  } -void Page::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onPrivatePackSelectionChanged(QModelIndex now, [[maybe_unused]] QModelIndex prev)  {      if (!now.isValid()) {          onPackSelectionChanged(); @@ -284,20 +290,20 @@ void Page::onPackSelectionChanged(Modpack* pack)      suggestCurrent();  } -void Page::onVersionSelectionItemChanged(QString data) +void Page::onVersionSelectionItemChanged(QString version)  { -    if (data.isNull() || data.isEmpty()) { +    if (version.isNull() || version.isEmpty()) {          selectedVersion = "";          return;      } -    selectedVersion = data; +    selectedVersion = version;      suggestCurrent();  } -void Page::onSortingSelectionChanged(QString data) +void Page::onSortingSelectionChanged(QString sort)  { -    FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); +    FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(sort);      publicFilterModel->setSorting(toSet);      thirdPartyFilterModel->setSorting(toSet);      privateFilterModel->setSorting(toSet); @@ -319,6 +325,8 @@ void Page::onTabChanged(int tab)          currentModpackInfo = ui->publicPackDescription;      } +    triggerSearch(); +      currentList->selectionModel()->reset();      QModelIndex idx = currentList->currentIndex();      if (idx.isValid()) { @@ -358,4 +366,9 @@ void Page::onRemovePackClicked()      onPackSelectionChanged();  } +void Page::triggerSearch() +{ +    currentModel->setSearchTerm(ui->searchEdit->text()); +} +  }  // namespace LegacyFTB diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h index a12b0745..4d317b7c 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h @@ -43,7 +43,6 @@  #include "QObjectPtr.h"  #include "modplatform/legacy_ftb/PackFetchTask.h"  #include "modplatform/legacy_ftb/PackHelpers.h" -#include "tasks/Task.h"  #include "ui/pages/BasePage.h"  class NewInstanceDialog; @@ -56,8 +55,6 @@ class Page;  class ListModel;  class FilterModel; -class PrivatePackListModel; -class PrivatePackFilterModel;  class PrivatePackManager;  class Page : public QWidget, public BasePage { @@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage {      void onAddPackClicked();      void onRemovePackClicked(); +    void triggerSearch(); +     private:      FilterModel* currentModel = nullptr;      QTreeView* currentList = nullptr; diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui index ad08dc25..56cba748 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui @@ -10,8 +10,29 @@      <height>602</height>     </rect>    </property> -  <layout class="QVBoxLayout" name="verticalLayout"> -   <item> +  <layout class="QGridLayout" name="gridLayout_5"> +   <item row="0" column="0"> +    <layout class="QHBoxLayout" name="horizontalLayout"> +     <item> +      <widget class="QLineEdit" name="searchEdit"> +       <property name="placeholderText"> +        <string>Search and filter...</string> +       </property> +       <property name="clearButtonEnabled"> +        <bool>true</bool> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="pushButton"> +       <property name="text"> +        <string>Search</string> +       </property> +      </widget> +     </item> +    </layout> +   </item> +   <item row="4" column="0">      <widget class="QTabWidget" name="tabWidget">       <property name="currentIndex">        <number>0</number> @@ -36,9 +57,9 @@         </item>         <item row="0" column="1">          <widget class="QTextBrowser" name="publicPackDescription"> -           <property name="openExternalLinks"> -            <bool>true</bool> -           </property> +         <property name="openExternalLinks"> +          <bool>true</bool> +         </property>          </widget>         </item>        </layout> @@ -50,10 +71,10 @@        <layout class="QGridLayout" name="gridLayout_3">         <item row="0" column="1">          <widget class="QTextBrowser" name="thirdPartyPackDescription"> -               <property name="openExternalLinks"> -            <bool>true</bool> -           </property> -          </widget> +         <property name="openExternalLinks"> +          <bool>true</bool> +         </property> +        </widget>         </item>         <item row="0" column="0">          <widget class="QTreeView" name="thirdPartyPackList"> @@ -104,16 +125,16 @@         </item>         <item row="0" column="1" rowspan="3">          <widget class="QTextBrowser" name="privatePackDescription"> -           <property name="openExternalLinks"> -            <bool>true</bool> -           </property> -          </widget> +         <property name="openExternalLinks"> +          <bool>true</bool> +         </property> +        </widget>         </item>        </layout>       </widget>      </widget>     </item> -   <item> +   <item row="5" column="0">      <layout class="QGridLayout" name="gridLayout_4">       <item row="0" column="1">        <widget class="QLabel" name="label"> diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 761e265d..f691a185 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -38,10 +38,12 @@  #include "BuildConfig.h"  #include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" +#include "modplatform/modrinth/ModrinthAPI.h" +#include "net/NetJob.h"  #include "ui/widgets/ProjectItem.h" +#include "net/ApiDownload.h" +  #include <QMessageBox>  namespace Modrinth { @@ -115,7 +117,7 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian      return {};  } -bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, int role) +bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)  {      int pos = index.row();      if (pos >= modpacks.size() || pos < 0 || !index.isValid()) @@ -128,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,  void ModpackListModel::performPaginatedSearch()  { -    // TODO: Move to standalone API +    if (hasActiveSearchJob()) +        return; + +    if (currentSearchTerm.startsWith("#")) { +        auto projectId = currentSearchTerm.mid(1); +        if (!projectId.isEmpty()) { +            ResourceAPI::ProjectInfoCallbacks callbacks; + +            callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); }; +            callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); }; +            static const ModrinthAPI api; +            if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) { +                jobPtr = job; +                jobPtr->start(); +            } +            return; +        } +    }  // TODO: Move to standalone API      auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());      auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +                                  "/search?" @@ -142,7 +161,7 @@ void ModpackListModel::performPaginatedSearch()                              .arg(currentSearchTerm)                              .arg(currentSort); -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response));      QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {          QJsonParseError parse_error_all{}; @@ -165,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()  void ModpackListModel::refresh()  { -    if (jobPtr) { +    if (hasActiveSearchJob()) {          jobPtr->abort();          searchState = ResetRequested;          return; -    } else { -        beginResetModel(); -        modpacks.clear(); -        endResetModel(); -        searchState = None;      } + +    beginResetModel(); +    modpacks.clear(); +    endResetModel(); +    searchState = None; +      nextSearchOffset = 0;      performPaginatedSearch();  } @@ -194,8 +214,6 @@ static auto sortFromIndex(int index) -> QString          case 4:              return "updated";      } - -    return {};  }  void ModpackListModel::searchWithTerm(const QString& term, const int sort) @@ -218,9 +236,7 @@ void ModpackListModel::searchWithTerm(const QString& term, const int sort)  void ModpackListModel::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()); +        callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo))->getFullPath());      } else {          requestLogo(logo, logoUrl);      } @@ -232,10 +248,9 @@ void ModpackListModel::requestLogo(QString logo, QString url)          return;      } -    MetaEntryPtr entry = -        APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); +    MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo));      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)); +    job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));      auto fullPath = entry->getFullPath();      QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { @@ -310,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)      endInsertRows();  } +void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc) +{ +    jobPtr.reset(); + +    auto packObj = doc.object(); + +    Modrinth::Modpack pack; +    try { +        Modrinth::loadIndexedPack(pack, packObj); +        pack.id = Json::ensureString(packObj, "id", pack.id); +    } catch (const JSONValidationError& e) { +        qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); +        return; +    } + +    beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1); +    modpacks.append({ pack }); +    endInsertRows(); +} +  void ModpackListModel::searchRequestFailed(QString reason)  { -    auto failed_action = jobPtr->getFailedActions().at(0); +    auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);      if (!failed_action->m_reply) {          // Network error          QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 721c69f5..2a9d6226 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -73,6 +73,9 @@ class ModpackListModel : public QAbstractListModel {      void refresh();      void searchWithTerm(const QString& term, const int sort); +    [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } +    [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } +      void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);      inline auto canFetchMore(const QModelIndex& parent) const -> bool override @@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {     public slots:      void searchRequestFinished(QJsonDocument& doc_all);      void searchRequestFailed(QString reason); +    void searchRequestForOneSucceeded(QJsonDocument&);     protected slots: @@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {      int nextSearchOffset = 0;      enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; -    NetJob::Ptr jobPtr; +    Task::Ptr jobPtr;      std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();      QByteArray m_specific_response; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index b4d73b2f..8e2b9a90 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -46,11 +46,14 @@  #include "ui/widgets/ProjectItem.h" +#include "net/ApiDownload.h" +  #include <QComboBox>  #include <QKeyEvent>  #include <QPushButton> -ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog) +ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) +    : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)  {      ui->setupUi(this); @@ -62,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget      ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);      ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); +    m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); +    m_search_timer.setSingleShot(true); + +    connect(&m_search_timer, &QTimer::timeout, this, &ModrinthPage::triggerSearch); + +    m_fetch_progress.hideIfInactive(true); +    m_fetch_progress.setFixedHeight(24); +    m_fetch_progress.progressFormat(""); + +    ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount()); +      ui->sortByBox->addItem(tr("Sort by Relevance"));      ui->sortByBox->addItem(tr("Sort by Total Downloads"));      ui->sortByBox->addItem(tr("Sort by Follows")); @@ -100,12 +114,17 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)              this->triggerSearch();              keyEvent->accept();              return true; +        } else { +            if (m_search_timer.isActive()) +                m_search_timer.stop(); + +            m_search_timer.start(350);          }      }      return QObject::eventFilter(watched, event);  } -void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) +void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)  {      ui->versionSelectionBox->clear(); @@ -127,7 +146,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)          QString id = current.id; -        netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); +        netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));          QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {              if (id != current.id) { @@ -176,7 +195,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)          QString id = current.id;          netJob->addNetAction( -            Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); +            Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));          QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {              if (id != current.id) { @@ -308,11 +327,12 @@ void ModrinthPage::suggestCurrent()  void ModrinthPage::triggerSearch()  {      m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); +    m_fetch_progress.watch(m_model->activeSearchJob().get());  } -void ModrinthPage::onVersionSelectionChanged(QString data) +void ModrinthPage::onVersionSelectionChanged(QString version)  { -    if (data.isNull() || data.isEmpty()) { +    if (version.isNull() || version.isEmpty()) {          selectedVersion = "";          return;      } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index b7054c88..4240dcaf 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -41,7 +41,9 @@  #include "ui/pages/BasePage.h"  #include "modplatform/modrinth/ModrinthPackManifest.h" +#include "ui/widgets/ProgressWidget.h" +#include <QTimer>  #include <QWidget>  namespace Ui { @@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {      Modrinth::Modpack current;      QString selectedVersion; + +    ProgressWidget m_fetch_progress; + +    // Used to do instant searching with a delay to cache quick changes +    QTimer m_search_timer;  }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index 6d8b2b67..78a25fea 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -10,8 +10,8 @@      <height>600</height>     </rect>    </property> -  <layout class="QVBoxLayout"> -   <item> +  <layout class="QGridLayout" name="gridLayout"> +   <item row="0" column="0">      <widget class="QLabel" name="label_2">       <property name="font">        <font> @@ -29,7 +29,7 @@       </property>      </widget>     </item> -   <item> +   <item row="1" column="0">      <layout class="QHBoxLayout">       <item>        <widget class="QLineEdit" name="searchEdit"> @@ -47,7 +47,7 @@       </item>      </layout>     </item> -   <item> +   <item row="3" column="0">      <layout class="QHBoxLayout">       <item>        <widget class="QListView" name="packView"> @@ -77,7 +77,7 @@       </item>      </layout>     </item> -   <item> +   <item row="4" column="0">      <layout class="QHBoxLayout">       <item>        <widget class="QComboBox" name="sortByBox"/> diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 00a0108a..85601829 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -39,13 +39,13 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec  void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)  { -    ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +    ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);  }  auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion  { -    return ::Modrinth::loadDependencyVersions(m, arr); -}; +    return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance); +}  auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray  { @@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q  void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)  { -    ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +    ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);  }  auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray @@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ  void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)  { -    ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +    ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);  }  auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray @@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs  void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)  { -    ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance); +    ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);  }  auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index 616c1a81..a4197b22 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -65,21 +65,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan  auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver,                                        QString mineVer, -                                      std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool +                                      std::optional<ModPlatform::ModLoaderTypes> 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; +    return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);  }  ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h index 86ba1ccb..311bcfe3 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h @@ -93,7 +93,7 @@ class ModrinthModPage : public ModPage {      [[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; } -    auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const +    auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const          -> bool override;  }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index 02d7fd5b..3cd1d9a2 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -38,6 +38,9 @@  #include "BuildConfig.h"  #include "Json.h" +#include "net/ApiDownload.h" +#include "ui/widgets/ProjectItem.h" +  #include <QIcon>  Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} @@ -52,21 +55,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const      }      Modpack pack = modpacks.at(pos); -    if (role == Qt::DisplayRole) { -        return pack.name; -    } else if (role == Qt::DecorationRole) { -        if (m_logoMap.contains(pack.logoName)) { -            return (m_logoMap.value(pack.logoName)); +    switch (role) { +        case Qt::ToolTipRole: { +            if (pack.description.length() > 100) { +                // some magic to prevent to long tooltips and replace html linebreaks +                QString edit = pack.description.left(97); +                edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); +                return edit; +            } +            return pack.description; +        } +        case Qt::DecorationRole: { +            if (m_logoMap.contains(pack.logoName)) { +                return (m_logoMap.value(pack.logoName)); +            } +            QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); +            ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); +            return icon;          } -        QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); -        ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); -        return icon; -    } else if (role == Qt::UserRole) { -        QVariant v; -        v.setValue(pack); -        return v; +        case Qt::UserRole: { +            QVariant v; +            v.setValue(pack); +            return v; +        } +        case Qt::DisplayRole: +            return pack.name; +        case Qt::SizeHintRole: +            return QSize(0, 58); +        // Custom data +        case UserDataTypes::TITLE: +            return pack.name; +        case UserDataTypes::DESCRIPTION: +            return pack.description; +        case UserDataTypes::SELECTED: +            return false; +        case UserDataTypes::INSTALLED: +            return false; +        default: +            break;      } -    return QVariant(); + +    return {};  }  int Technic::ListModel::columnCount(const QModelIndex& parent) const @@ -85,21 +114,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)          return;      }      currentSearchTerm = term; -    if (jobPtr) { +    if (hasActiveSearchJob()) {          jobPtr->abort();          searchState = ResetRequested;          return; -    } else { -        beginResetModel(); -        modpacks.clear(); -        endResetModel(); -        searchState = None;      } + +    beginResetModel(); +    modpacks.clear(); +    endResetModel(); +    searchState = None; +      performSearch();  }  void Technic::ListModel::performSearch()  { +    if (hasActiveSearchJob()) +        return; +      auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());      QString searchUrl = "";      if (currentSearchTerm.isEmpty()) { @@ -111,12 +144,15 @@ void Technic::ListModel::performSearch()      } else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {          searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);          searchMode = Single; +    } else if (currentSearchTerm.startsWith("#")) { +        searchUrl = QString("https://api.technicpack.net/modpack/%1?build=%2").arg(currentSearchTerm.mid(1), BuildConfig.TECHNIC_API_BUILD); +        searchMode = Single;      } else {          searchUrl =              QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);          searchMode = List;      } -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));      jobPtr = netJob;      jobPtr->start();      QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); @@ -157,7 +193,7 @@ void Technic::ListModel::searchRequestFinished()                          pack.logoName = "null";                      } else {                          pack.logoUrl = rawURL; -                        pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); +                        pack.logoName = rawURL.section(QLatin1Char('/'), -1);                      }                      pack.broken = false;                      newList.append(pack); @@ -179,7 +215,7 @@ void Technic::ListModel::searchRequestFinished()                      auto iconUrl = Json::requireString(iconObj, "url");                      pack.logoUrl = iconUrl; -                    pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); +                    pack.logoName = iconUrl.section(QLatin1Char('/'), -1);                  } else {                      pack.logoUrl = "null";                      pack.logoName = "null"; @@ -254,7 +290,7 @@ void Technic::ListModel::requestLogo(QString logo, QString url)      MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));      auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network()); -    job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); +    job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));      auto fullPath = entry->getFullPath(); diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h index d7a635d4..aeb4f308 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.h +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h @@ -58,6 +58,9 @@ class ListModel : public QAbstractListModel {      void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);      void searchWithTerm(const QString& term); +    [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); } +    [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; } +     private slots:      void searchRequestFinished();      void searchRequestFailed(); diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index 88132754..190b7c68 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -34,6 +34,7 @@   */  #include "TechnicPage.h" +#include "ui/widgets/ProjectItem.h"  #include "ui_TechnicPage.h"  #include <QKeyEvent> @@ -49,7 +50,10 @@  #include "Application.h"  #include "modplatform/technic/SolderPackManifest.h" -TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) +#include "net/ApiDownload.h" + +TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) +    : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)  {      ui->setupUi(this);      connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch); @@ -57,8 +61,21 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p      model = new Technic::ListModel(this);      ui->packView->setModel(model); +    m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); +    m_search_timer.setSingleShot(true); + +    connect(&m_search_timer, &QTimer::timeout, this, &TechnicPage::triggerSearch); + +    m_fetch_progress.hideIfInactive(true); +    m_fetch_progress.setFixedHeight(24); +    m_fetch_progress.progressFormat(""); + +    ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount()); +      connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);      connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged); + +    ui->packView->setItemDelegate(new ProjectItemDelegate(this));  }  bool TechnicPage::eventFilter(QObject* watched, QEvent* event) @@ -69,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)              triggerSearch();              keyEvent->accept();              return true; +        } else { +            if (m_search_timer.isActive()) +                m_search_timer.stop(); + +            m_search_timer.start(350);          }      }      return QWidget::eventFilter(watched, event); @@ -98,9 +120,10 @@ void TechnicPage::openedImpl()  void TechnicPage::triggerSearch()  {      model->searchWithTerm(ui->searchEdit->text()); +    m_fetch_progress.watch(model->activeSearchJob().get());  } -void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second) +void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)  {      ui->versionSelectionBox->clear(); @@ -125,7 +148,7 @@ void TechnicPage::suggestCurrent()          return;      } -    QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0); +    QString editedLogoName = "technic_" + current.logoName;      model->getLogo(current.logoName, current.logoUrl,                     [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); @@ -136,7 +159,7 @@ void TechnicPage::suggestCurrent()      auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());      QString slug = current.slug; -    netJob->addNetAction(Net::Download::makeByteArray( +    netJob->addNetAction(Net::ApiDownload::makeByteArray(          QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));      QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug] {          jobPtr.reset(); @@ -232,7 +255,7 @@ void TechnicPage::metadataLoaded()          auto netJob = makeShared<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)); +        netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));          QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); @@ -304,13 +327,13 @@ void TechnicPage::onSolderLoaded()      metadataLoaded();  } -void TechnicPage::onVersionSelectionChanged(QString data) +void TechnicPage::onVersionSelectionChanged(QString version)  { -    if (data.isNull() || data.isEmpty()) { +    if (version.isNull() || version.isEmpty()) {          selectedVersion = "";          return;      } -    selectedVersion = data; +    selectedVersion = version;      selectVersion();  } diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h index 91b61eaf..01439337 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.h +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h @@ -35,13 +35,14 @@  #pragma once +#include <QTimer>  #include <QWidget>  #include <Application.h>  #include "TechnicData.h"  #include "net/NetJob.h" -#include "tasks/Task.h"  #include "ui/pages/BasePage.h" +#include "ui/widgets/ProgressWidget.h"  namespace Ui {  class TechnicPage; @@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {      NetJob::Ptr jobPtr;      std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>(); + +    ProgressWidget m_fetch_progress; + +    // Used to do instant searching with a delay to cache quick changes +    QTimer m_search_timer;  }; diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui index 15bf645f..b988eda2 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui @@ -11,7 +11,7 @@     </rect>    </property>    <layout class="QGridLayout" name="gridLayout"> -   <item row="3" column="0" colspan="2"> +   <item row="4" column="0" colspan="2">      <layout class="QGridLayout" name="gridLayout_3">       <item row="0" column="2">        <widget class="QComboBox" name="versionSelectionBox"/> @@ -44,7 +44,7 @@       </item>      </layout>     </item> -   <item row="2" column="0" colspan="2"> +   <item row="3" column="0" colspan="2">      <layout class="QGridLayout" name="gridLayout_2">       <item row="0" column="0">        <widget class="QListView" name="packView"> | 
