diff options
Diffstat (limited to 'launcher/ui')
92 files changed, 2193 insertions, 524 deletions
| diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp index b1ea5ee9..5a62e4d0 100644 --- a/launcher/ui/GuiUtil.cpp +++ b/launcher/ui/GuiUtil.cpp @@ -39,7 +39,6 @@  #include <QClipboard>  #include <QApplication>  #include <QFileDialog> -#include <QStandardPaths>  #include "ui/dialogs/ProgressDialog.h"  #include "ui/dialogs/CustomMessageBox.h" diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index ae765c3c..0ad8c594 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include "InstanceWindow.h" @@ -97,9 +117,9 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent)      // set up instance and launch process recognition      {          auto launchTask = m_instance->getLaunchTask(); -        on_InstanceLaunchTask_changed(launchTask); -        connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::on_InstanceLaunchTask_changed); -        connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::on_RunningState_changed); +        instanceLaunchTaskChanged(launchTask); +        connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &InstanceWindow::instanceLaunchTaskChanged); +        connect(m_instance.get(), &BaseInstance::runningStatusChanged, this, &InstanceWindow::runningStateChanged);      }      // set up instance destruction detection @@ -152,12 +172,12 @@ void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()      APPLICATION->launch(m_instance, false, nullptr);  } -void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc) +void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)  {      m_proc = proc;  } -void InstanceWindow::on_RunningState_changed(bool running) +void InstanceWindow::runningStateChanged(bool running)  {      updateLaunchButtons();      m_container->refreshContainer(); diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h index 1acf684e..aec07868 100644 --- a/launcher/ui/InstanceWindow.h +++ b/launcher/ui/InstanceWindow.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #pragma once @@ -55,8 +75,8 @@ slots:      void on_btnKillMinecraft_clicked();      void on_btnLaunchMinecraftOffline_clicked(); -    void on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc); -    void on_RunningState_changed(bool running); +    void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc); +    void runningStateChanged(bool running);      void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);  protected: diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f68cf61a..299401f5 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -42,31 +42,31 @@  #include "MainWindow.h" -#include <QtCore/QVariant> -#include <QtCore/QUrl> -#include <QtCore/QDir> -#include <QtCore/QFileInfo> - -#include <QtGui/QKeyEvent> - -#include <QtWidgets/QAction> -#include <QtWidgets/QApplication> -#include <QtWidgets/QButtonGroup> -#include <QtWidgets/QHBoxLayout> -#include <QtWidgets/QHeaderView> -#include <QtWidgets/QMainWindow> -#include <QtWidgets/QStatusBar> -#include <QtWidgets/QToolBar> -#include <QtWidgets/QWidget> -#include <QtWidgets/QMenu> -#include <QtWidgets/QMenuBar> -#include <QtWidgets/QMessageBox> -#include <QtWidgets/QInputDialog> -#include <QtWidgets/QLabel> -#include <QtWidgets/QToolButton> -#include <QtWidgets/QWidgetAction> -#include <QtWidgets/QProgressDialog> -#include <QtWidgets/QShortcut> +#include <QVariant> +#include <QUrl> +#include <QDir> +#include <QFileInfo> + +#include <QKeyEvent> +#include <QAction> + +#include <QApplication> +#include <QButtonGroup> +#include <QHBoxLayout> +#include <QHeaderView> +#include <QMainWindow> +#include <QStatusBar> +#include <QToolBar> +#include <QWidget> +#include <QMenu> +#include <QMenuBar> +#include <QMessageBox> +#include <QInputDialog> +#include <QLabel> +#include <QToolButton> +#include <QWidgetAction> +#include <QProgressDialog> +#include <QShortcut>  #include <BaseInstance.h>  #include <InstanceList.h> @@ -235,7 +235,6 @@ public:      TranslatedAction actionMods;      TranslatedAction actionViewSelectedInstFolder;      TranslatedAction actionViewSelectedMCFolder; -    TranslatedAction actionViewSelectedModsFolder;      TranslatedAction actionDeleteInstance;      TranslatedAction actionConfig_Folder;      TranslatedAction actionCAT; @@ -253,6 +252,9 @@ public:      TranslatedAction actionViewInstanceFolder;      TranslatedAction actionViewCentralModsFolder; +    QMenu * editMenu = nullptr; +    TranslatedAction actionUndoTrashInstance; +      QMenu * helpMenu = nullptr;      TranslatedToolButton helpMenuButton;      TranslatedAction actionReportBug; @@ -285,7 +287,7 @@ public:      TranslatedToolbar newsToolBar;      QVector<TranslatedToolbar *> all_toolbars; -    void createMainToolbarActions(QMainWindow *MainWindow) +    void createMainToolbarActions(MainWindow *MainWindow)      {          actionAddInstance = TranslatedAction(MainWindow);          actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); @@ -336,6 +338,14 @@ public:          actionSettings->setShortcut(QKeySequence::Preferences);          all_actions.append(&actionSettings); +        actionUndoTrashInstance = TranslatedAction(MainWindow); +        connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), MainWindow, SLOT(undoTrashInstance())); +        actionUndoTrashInstance->setObjectName(QStringLiteral("actionUndoTrashInstance")); +        actionUndoTrashInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Undo Last Instance Deletion")); +        actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); +        actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z")); +        all_actions.append(&actionUndoTrashInstance); +          if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {              actionReportBug = TranslatedAction(MainWindow);              actionReportBug->setObjectName(QStringLiteral("actionReportBug")); @@ -509,6 +519,9 @@ public:          fileMenu->addSeparator();          fileMenu->addAction(actionSettings); +        editMenu = menuBar->addMenu(tr("&Edit")); +        editMenu->addAction(actionUndoTrashInstance); +          viewMenu = menuBar->addMenu(tr("&View"));          viewMenu->setSeparatorsCollapsible(false);          viewMenu->addAction(actionCAT); @@ -709,14 +722,6 @@ public:          actionViewSelectedMCFolder->setShortcut(QKeySequence(tr("Ctrl+M")));          all_actions.append(&actionViewSelectedMCFolder); -        /* -        actionViewSelectedModsFolder = TranslatedAction(MainWindow); -        actionViewSelectedModsFolder->setObjectName(QStringLiteral("actionViewSelectedModsFolder")); -        actionViewSelectedModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Mods Folder")); -        actionViewSelectedModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's mods folder in a file browser.")); -        all_actions.append(&actionViewSelectedModsFolder); -        */ -          actionConfig_Folder = TranslatedAction(MainWindow);          actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder"));          actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Confi&g Folder")); @@ -741,9 +746,10 @@ public:          actionDeleteInstance = TranslatedAction(MainWindow);          actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); -        actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance...")); +        actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance"));          actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));          actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete}); +        actionDeleteInstance->setAutoRepeat(false);          all_actions.append(&actionDeleteInstance);          actionCopyInstance = TranslatedAction(MainWindow); @@ -793,9 +799,6 @@ public:          instanceToolBar->addSeparator();          instanceToolBar->addAction(actionViewSelectedMCFolder); -        /* -        instanceToolBar->addAction(actionViewSelectedModsFolder); -        */          instanceToolBar->addAction(actionConfig_Folder);          instanceToolBar->addAction(actionViewSelectedInstFolder); @@ -1024,7 +1027,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow      } -#ifdef LAUNCHER_WITH_UPDATER      if(BuildConfig.UPDATER_ENABLED)      {          bool updatesAllowed = APPLICATION->updatesAreAllowed(); @@ -1042,8 +1044,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow          {              updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false);          } + +        if (APPLICATION->updateChecker()->getExternalUpdater()) +        { +            connect(APPLICATION->updateChecker()->getExternalUpdater(), +                    &ExternalUpdater::canCheckForUpdatesChanged, +                    this, +                    &MainWindow::updatesAllowedChanged); +        }      } -#endif      setSelectedInstanceById(APPLICATION->settings()->get("SelectedInstance").toString()); @@ -1156,6 +1165,11 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)              connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));              actions.append(actionDeleteGroup);          } + +        QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this); +        connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance())); +        actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); +        actions.append(actionUndoTrashInstance);      }      QMenu myMenu;      myMenu.addActions(actions); @@ -1355,7 +1369,6 @@ void MainWindow::repopulateAccountsMenu()      ui->profileMenu->addAction(ui->actionManageAccounts);  } -#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::updatesAllowedChanged(bool allowed)  {      if(!BuildConfig.UPDATER_ENABLED) @@ -1364,7 +1377,6 @@ void MainWindow::updatesAllowedChanged(bool allowed)      }      ui->actionCheckUpdate->setEnabled(allowed);  } -#endif  /*   * Assumes the sender is a QAction @@ -1453,6 +1465,7 @@ void MainWindow::updateNewsLabel()      {          newsLabel->setText(tr("Loading news..."));          newsLabel->setEnabled(false); +        ui->actionMoreNews->setVisible(false);      }      else      { @@ -1461,16 +1474,17 @@ void MainWindow::updateNewsLabel()          {              newsLabel->setText(entries[0]->title);              newsLabel->setEnabled(true); +            ui->actionMoreNews->setVisible(true);          }          else          {              newsLabel->setText(tr("No news available."));              newsLabel->setEnabled(false); +            ui->actionMoreNews->setVisible(false);          }      }  } -#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::updateAvailable(GoUpdate::Status status)  {      if(!APPLICATION->updatesAreAllowed()) @@ -1496,11 +1510,14 @@ void MainWindow::updateNotAvailable()      UpdateDialog dlg(false, this);      dlg.exec();  } -#endif  QList<int> stringToIntList(const QString &string)  { +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) +    QStringList split = string.split(',', Qt::SkipEmptyParts); +#else      QStringList split = string.split(',', QString::SkipEmptyParts); +#endif      QList<int> out;      for (int i = 0; i < split.size(); ++i)      { @@ -1518,7 +1535,6 @@ QString intListToString(const QList<int> &list)      return slist.join(',');  } -#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::downloadUpdates(GoUpdate::Status status)  {      if(!APPLICATION->updatesAreAllowed()) @@ -1552,7 +1568,6 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)          CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show();      }  } -#endif  void MainWindow::onCatToggled(bool state)  { @@ -1840,6 +1855,11 @@ void MainWindow::deleteGroup()      }  } +void MainWindow::undoTrashInstance() +{ +    APPLICATION->instances()->undoTrashInstance(); +} +  void MainWindow::on_actionViewInstanceFolder_triggered()  {      QString str = APPLICATION->settings()->get("InstanceDir").toString(); @@ -1865,7 +1885,6 @@ void MainWindow::on_actionConfig_Folder_triggered()      }  } -#ifdef LAUNCHER_WITH_UPDATER  void MainWindow::checkForUpdates()  {      if(BuildConfig.UPDATER_ENABLED) @@ -1878,7 +1897,6 @@ void MainWindow::checkForUpdates()          qWarning() << "Updater not set up. Cannot check for updates.";      }  } -#endif  void MainWindow::on_actionSettings_triggered()  { @@ -1900,11 +1918,6 @@ void MainWindow::globalSettingsClosed()      update();  } -void MainWindow::on_actionInstanceSettings_triggered() -{ -    APPLICATION->showInstanceWindow(m_selectedInstance, "settings"); -} -  void MainWindow::on_actionEditInstNotes_triggered()  {      APPLICATION->showInstanceWindow(m_selectedInstance, "notes"); @@ -1972,7 +1985,12 @@ void MainWindow::on_actionDeleteInstance_triggered()      {          return;      } +      auto id = m_selectedInstance->id(); +    if (APPLICATION->instances()->trashInstance(id)) { +        return; +    } +          auto response = CustomMessageBox::selectable(          this,          tr("CAREFUL!"), @@ -2027,20 +2045,6 @@ void MainWindow::on_actionViewSelectedMCFolder_triggered()      }  } -void MainWindow::on_actionViewSelectedModsFolder_triggered() -{ -    if (m_selectedInstance) -    { -        QString str = m_selectedInstance->modsRoot(); -        if (!FS::ensureFilePathExists(str)) -        { -            // TODO: report error -            return; -        } -        DesktopServices::openDirectory(QDir(str).absolutePath()); -    } -} -  void MainWindow::closeEvent(QCloseEvent *event)  {      // Save the window state and geometry. diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 4615975e..dde3d02c 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -78,9 +78,7 @@ public:      void checkInstancePathForProblems(); -#ifdef LAUNCHER_WITH_UPDATER      void updatesAllowedChanged(bool allowed); -#endif      void droppedURLs(QList<QUrl> urls);  signals: @@ -120,20 +118,14 @@ private slots:      void on_actionViewSelectedMCFolder_triggered(); -    void on_actionViewSelectedModsFolder_triggered(); -      void refreshInstances();      void on_actionViewCentralModsFolder_triggered(); -#ifdef LAUNCHER_WITH_UPDATER      void checkForUpdates(); -#endif      void on_actionSettings_triggered(); -    void on_actionInstanceSettings_triggered(); -      void on_actionManageAccounts_triggered();      void on_actionReportBug_triggered(); @@ -153,6 +145,7 @@ private slots:      void on_actionDeleteInstance_triggered();      void deleteGroup(); +    void undoTrashInstance();      void on_actionExportInstance_triggered(); @@ -197,11 +190,9 @@ private slots:      void startTask(Task *task); -#ifdef LAUNCHER_WITH_UPDATER      void updateAvailable(GoUpdate::Status status);      void updateNotAvailable(); -#endif      void defaultAccountChanged(); @@ -211,12 +202,10 @@ private slots:      void updateNewsLabel(); -#ifdef LAUNCHER_WITH_UPDATER      /*!       * Runs the DownloadTask and installs updates.       */      void downloadUpdates(GoUpdate::Status status); -#endif      void konamiTriggered(); diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 8dadb755..743c34f1 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -64,7 +64,9 @@ QString getCreditsHtml()  {      QString output;      QTextStream stream(&output); +#if QT_VERSION <= QT_VERSION_CHECK(6, 0, 0)      stream.setCodec(QTextCodec::codecForName("UTF-8")); +#endif      stream << "<center>\n";      //: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers" @@ -145,10 +147,15 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia      else          ui->platformLabel->setVisible(false); -    if (BuildConfig.VERSION_BUILD >= 0) -        ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD)); +    if (!BuildConfig.GIT_COMMIT.isEmpty()) +        ui->commitLabel->setText(tr("Commit: %1").arg(BuildConfig.GIT_COMMIT));      else -        ui->buildNumLabel->setVisible(false); +        ui->commitLabel->setVisible(false); + +    if (!BuildConfig.BUILD_DATE.isEmpty()) +        ui->buildDateLabel->setText(tr("Build date: %1").arg(BuildConfig.BUILD_DATE)); +    else +        ui->buildDateLabel->setVisible(false);      if (!BuildConfig.VERSION_CHANNEL.isEmpty())          ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL); diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index 6323992b..6eaa0c4e 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -184,12 +184,28 @@          </widget>         </item>         <item> -        <widget class="QLabel" name="buildNumLabel"> +        <widget class="QLabel" name="buildDateLabel">           <property name="cursor">            <cursorShape>IBeamCursor</cursorShape>           </property>           <property name="text"> -          <string>Build Number:</string> +          <string>Build Date:</string> +         </property> +         <property name="alignment"> +          <set>Qt::AlignCenter</set> +         </property> +         <property name="textInteractionFlags"> +          <set>Qt::TextSelectableByMouse</set> +         </property> +        </widget> +       </item> +       <item> +        <widget class="QLabel" name="commitLabel"> +         <property name="cursor"> +          <cursorShape>IBeamCursor</cursorShape> +         </property> +         <property name="text"> +          <string>Commit:</string>           </property>           <property name="alignment">            <set>Qt::AlignCenter</set> diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp new file mode 100644 index 00000000..fe87b517 --- /dev/null +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -0,0 +1,28 @@ +#include "BlockedModsDialog.h" +#include "ui_BlockedModsDialog.h" +#include <QPushButton> +#include <QDialogButtonBox> +#include <QDesktopServices> + + +BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls) : +        QDialog(parent), ui(new Ui::BlockedModsDialog), urls(urls) { +    ui->setupUi(this); + +    auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole); +    connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll); + +    this->setWindowTitle(title); +    ui->label->setText(text); +    ui->textBrowser->setText(body); +} + +BlockedModsDialog::~BlockedModsDialog() { +    delete ui; +} + +void BlockedModsDialog::openAll() { +    for(auto &url : urls) { +        QDesktopServices::openUrl(url); +    } +} diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h new file mode 100644 index 00000000..5f5bd61b --- /dev/null +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -0,0 +1,22 @@ +#pragma once + +#include <QDialog> + + +QT_BEGIN_NAMESPACE +namespace Ui { class BlockedModsDialog; } +QT_END_NAMESPACE + +class BlockedModsDialog : public QDialog { +Q_OBJECT + +public: +    BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls); + +    ~BlockedModsDialog() override; + +private: +    Ui::BlockedModsDialog *ui; +    const QList<QUrl> &urls; +    void openAll(); +}; diff --git a/launcher/ui/dialogs/BlockedModsDialog.ui b/launcher/ui/dialogs/BlockedModsDialog.ui new file mode 100644 index 00000000..f4ae95b6 --- /dev/null +++ b/launcher/ui/dialogs/BlockedModsDialog.ui @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BlockedModsDialog</class> + <widget class="QDialog" name="BlockedModsDialog"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>400</width> +    <height>455</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string notr="true">BlockedModsDialog</string> +  </property> +  <layout class="QGridLayout" name="gridLayout"> +   <item row="0" column="0"> +    <widget class="QLabel" name="label"> +     <property name="text"> +      <string notr="true"/> +     </property> +     <property name="textFormat"> +      <enum>Qt::RichText</enum> +     </property> +    </widget> +   </item> +   <item row="2" column="0"> +    <widget class="QDialogButtonBox" name="buttonBox"> +     <property name="orientation"> +      <enum>Qt::Horizontal</enum> +     </property> +     <property name="standardButtons"> +      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> +     </property> +    </widget> +   </item> +   <item row="1" column="0"> +    <widget class="QTextBrowser" name="textBrowser"> +     <property name="acceptRichText"> +      <bool>true</bool> +     </property> +     <property name="openExternalLinks"> +      <bool>true</bool> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections> +  <connection> +   <sender>buttonBox</sender> +   <signal>accepted()</signal> +   <receiver>BlockedModsDialog</receiver> +   <slot>accept()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>199</x> +     <y>425</y> +    </hint> +    <hint type="destinationlabel"> +     <x>199</x> +     <y>227</y> +    </hint> +   </hints> +  </connection> +  <connection> +   <sender>buttonBox</sender> +   <signal>rejected()</signal> +   <receiver>BlockedModsDialog</receiver> +   <slot>reject()</slot> +   <hints> +    <hint type="sourcelabel"> +     <x>199</x> +     <y>425</y> +    </hint> +    <hint type="destinationlabel"> +     <x>199</x> +     <y>227</y> +    </hint> +   </hints> +  </connection> + </connections> +</ui> diff --git a/launcher/ui/dialogs/ChooseProviderDialog.cpp b/launcher/ui/dialogs/ChooseProviderDialog.cpp new file mode 100644 index 00000000..89935d9a --- /dev/null +++ b/launcher/ui/dialogs/ChooseProviderDialog.cpp @@ -0,0 +1,96 @@ +#include "ChooseProviderDialog.h" +#include "ui_ChooseProviderDialog.h" + +#include <QPushButton> +#include <QRadioButton> + +#include "modplatform/ModIndex.h" + +static ModPlatform::ProviderCapabilities ProviderCaps; + +ChooseProviderDialog::ChooseProviderDialog(QWidget* parent, bool single_choice, bool allow_skipping) +    : QDialog(parent), ui(new Ui::ChooseProviderDialog) +{ +    ui->setupUi(this); + +    addProviders(); +    m_providers.button(0)->click(); + +    connect(ui->skipOneButton, &QPushButton::clicked, this, &ChooseProviderDialog::skipOne); +    connect(ui->skipAllButton, &QPushButton::clicked, this, &ChooseProviderDialog::skipAll); + +    connect(ui->confirmOneButton, &QPushButton::clicked, this, &ChooseProviderDialog::confirmOne); +    connect(ui->confirmAllButton, &QPushButton::clicked, this, &ChooseProviderDialog::confirmAll); + +    if (single_choice) { +        ui->providersLayout->removeWidget(ui->skipAllButton); +        ui->providersLayout->removeWidget(ui->confirmAllButton); +    } + +    if (!allow_skipping) { +        ui->providersLayout->removeWidget(ui->skipOneButton); +        ui->providersLayout->removeWidget(ui->skipAllButton); +    } +} + +ChooseProviderDialog::~ChooseProviderDialog() +{ +    delete ui; +} + +void ChooseProviderDialog::setDescription(QString desc) +{ +    ui->explanationLabel->setText(desc); +} + +void ChooseProviderDialog::skipOne() +{ +    reject(); +} +void ChooseProviderDialog::skipAll() +{ +    m_response.skip_all = true; +    reject(); +} + +void ChooseProviderDialog::confirmOne() +{ +    m_response.chosen = getSelectedProvider(); +    m_response.try_others = ui->tryOthersCheckbox->isChecked(); +    accept(); +} +void ChooseProviderDialog::confirmAll() +{ +    m_response.chosen = getSelectedProvider(); +    m_response.confirm_all = true; +    m_response.try_others = ui->tryOthersCheckbox->isChecked(); +    accept(); +} + +auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::Provider +{ +    return ModPlatform::Provider(m_providers.checkedId()); +} + +void ChooseProviderDialog::addProviders() +{ +    int btn_index = 0; +    QRadioButton* btn; + +    for (auto& provider : { ModPlatform::Provider::MODRINTH, ModPlatform::Provider::FLAME }) { +        btn = new QRadioButton(ProviderCaps.readableName(provider), this); +        m_providers.addButton(btn, btn_index++); +        ui->providersLayout->addWidget(btn); +    } +} + +void ChooseProviderDialog::disableInput() +{ +    for (auto& btn : m_providers.buttons()) +        btn->setEnabled(false); + +    ui->skipOneButton->setEnabled(false); +    ui->skipAllButton->setEnabled(false); +    ui->confirmOneButton->setEnabled(false); +    ui->confirmAllButton->setEnabled(false); +} diff --git a/launcher/ui/dialogs/ChooseProviderDialog.h b/launcher/ui/dialogs/ChooseProviderDialog.h new file mode 100644 index 00000000..4a3b9f29 --- /dev/null +++ b/launcher/ui/dialogs/ChooseProviderDialog.h @@ -0,0 +1,56 @@ +#pragma once + +#include <QButtonGroup> +#include <QDialog> + +namespace Ui { +class ChooseProviderDialog; +} + +namespace ModPlatform { +enum class Provider; +} + +class Mod; +class NetJob; +class ModUpdateDialog; + +class ChooseProviderDialog : public QDialog { +    Q_OBJECT + +    struct Response { +        bool skip_all = false; +        bool confirm_all = false; + +        bool try_others = false; + +        ModPlatform::Provider chosen; +    }; + +   public: +    explicit ChooseProviderDialog(QWidget* parent, bool single_choice = false, bool allow_skipping = true); +    ~ChooseProviderDialog(); + +    auto getResponse() const -> Response { return m_response; } + +    void setDescription(QString desc); + +   private slots: +    void skipOne(); +    void skipAll(); +    void confirmOne(); +    void confirmAll(); + +   private: +    void addProviders(); +    void disableInput(); + +    auto getSelectedProvider() const -> ModPlatform::Provider; + +   private: +    Ui::ChooseProviderDialog* ui; + +    QButtonGroup m_providers; + +    Response m_response; +}; diff --git a/launcher/ui/dialogs/ChooseProviderDialog.ui b/launcher/ui/dialogs/ChooseProviderDialog.ui new file mode 100644 index 00000000..78cd9613 --- /dev/null +++ b/launcher/ui/dialogs/ChooseProviderDialog.ui @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ChooseProviderDialog</class> + <widget class="QDialog" name="ChooseProviderDialog"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>453</width> +    <height>197</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Choose a mod provider</string> +  </property> +  <layout class="QGridLayout" name="gridLayout"> +   <item row="0" column="0" colspan="2"> +    <widget class="QLabel" name="explanationLabel"> +     <property name="alignment"> +      <set>Qt::AlignJustify|Qt::AlignTop</set> +     </property> +     <property name="wordWrap"> +      <bool>true</bool> +     </property> +     <property name="indent"> +      <number>-1</number> +     </property> +    </widget> +   </item> +   <item row="1" column="0" colspan="2"> +    <layout class="QFormLayout" name="providersLayout"> +     <property name="labelAlignment"> +      <set>Qt::AlignHCenter|Qt::AlignTop</set> +     </property> +     <property name="formAlignment"> +      <set>Qt::AlignHCenter|Qt::AlignTop</set> +     </property> +    </layout> +   </item> +   <item row="4" column="0" colspan="2"> +    <layout class="QHBoxLayout" name="buttonsLayout"> +     <item> +      <widget class="QPushButton" name="skipOneButton"> +       <property name="text"> +        <string>Skip this mod</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="skipAllButton"> +       <property name="text"> +        <string>Skip all</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="confirmAllButton"> +       <property name="text"> +        <string>Confirm for all</string> +       </property> +      </widget> +     </item> +     <item> +      <widget class="QPushButton" name="confirmOneButton"> +       <property name="text"> +        <string>Confirm</string> +       </property> +       <property name="default"> +        <bool>true</bool> +       </property> +      </widget> +     </item> +    </layout> +   </item> +   <item row="3" column="0"> +    <widget class="QCheckBox" name="tryOthersCheckbox"> +     <property name="text"> +      <string>Try to automatically use other providers if the chosen one fails</string> +     </property> +     <property name="checked"> +      <bool>true</bool> +     </property> +    </widget> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp index e5113981..9ec341bc 100644 --- a/launcher/ui/dialogs/CopyInstanceDialog.cpp +++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include <QLayout> @@ -39,8 +59,14 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)      ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));      ui->instNameTextBox->setText(original->name());      ui->instNameTextBox->setFocus(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) +    auto groupList = APPLICATION->instances()->getGroups(); +    QSet<QString> groups(groupList.begin(), groupList.end()); +    groupList = QStringList(groups.values()); +#else      auto groups = APPLICATION->instances()->getGroups().toSet();      auto groupList = QStringList(groups.toList()); +#endif      groupList.sort(Qt::CaseInsensitive);      groupList.removeOne("");      groupList.push_front(""); diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp index 8631edf6..9f32dd8e 100644 --- a/launcher/ui/dialogs/ExportInstanceDialog.cpp +++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp @@ -488,7 +488,11 @@ void ExportInstanceDialog::loadPackIgnore()      }      auto data = ignoreFile.readAll();      auto string = QString::fromUtf8(data); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) +    proxyModel->setBlockedPaths(string.split('\n', Qt::SkipEmptyParts)); +#else      proxyModel->setBlockedPaths(string.split('\n', QString::SkipEmptyParts)); +#endif  }  void ExportInstanceDialog::savePackIgnore() diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp index 194315a7..30394b72 100644 --- a/launcher/ui/dialogs/LoginDialog.cpp +++ b/launcher/ui/dialogs/LoginDialog.cpp @@ -115,5 +115,5 @@ MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg)      {          return dlg.m_account;      } -    return 0; +    return nullptr;  } diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index b11b6980..be49babb 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -169,5 +169,5 @@ MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg)      {          return dlg.m_account;      } -    return 0; +    return nullptr;  } diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index f01c9c07..e4fc3ecc 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -1,9 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ +  #include "ModDownloadDialog.h"  #include <BaseVersion.h>  #include <icons/IconList.h>  #include <InstanceList.h> +#include "Application.h"  #include "ProgressDialog.h"  #include "ReviewMessageBox.h" @@ -100,13 +119,13 @@ void ModDownloadDialog::accept()  QList<BasePage *> ModDownloadDialog::getPages()  { -    modrinthPage = new ModrinthModPage(this, m_instance); -    flameModPage = new FlameModPage(this, m_instance); -    return -    { -        modrinthPage, -        flameModPage -    }; +    QList<BasePage *> pages; + +    pages.append(new ModrinthModPage(this, m_instance)); +    if (APPLICATION->currentCapabilities() & Application::SupportsFlame) +        pages.append(new FlameModPage(this, m_instance)); + +    return pages;  }  void ModDownloadDialog::addSelectedMod(const QString& name, ModDownloadTask* task) diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 5c565ad3..1fa1f058 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ +  #pragma once  #include <QDialog> @@ -48,9 +66,6 @@ private:      QDialogButtonBox * m_buttons = nullptr;      QVBoxLayout *m_verticalLayout = nullptr; - -    ModrinthModPage *modrinthPage = nullptr; -    FlameModPage *flameModPage = nullptr;      QHash<QString, ModDownloadTask*> modTask;      BaseInstance *m_instance;  }; diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp new file mode 100644 index 00000000..d73c8ebb --- /dev/null +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -0,0 +1,409 @@ +#include "ModUpdateDialog.h" +#include "ChooseProviderDialog.h" +#include "CustomMessageBox.h" +#include "ProgressDialog.h" +#include "ScrollMessageBox.h" +#include "ui_ReviewMessageBox.h" + +#include "FileSystem.h" +#include "Json.h" + +#include "tasks/ConcurrentTask.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "modplatform/EnsureMetadataTask.h" +#include "modplatform/flame/FlameCheckUpdate.h" +#include "modplatform/modrinth/ModrinthCheckUpdate.h" + +#include <HoeDown.h> +#include <QTextBrowser> +#include <QTreeWidgetItem> + +static ModPlatform::ProviderCapabilities ProviderCaps; + +static std::list<Version> mcVersions(BaseInstance* inst) +{ +    return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; +} + +static ModAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) +{ +    return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() }; +} + +ModUpdateDialog::ModUpdateDialog(QWidget* parent, +                                 BaseInstance* instance, +                                 const std::shared_ptr<ModFolderModel> mods, +                                 QList<Mod::Ptr>& search_for) +    : ReviewMessageBox(parent, tr("Confirm mods to update"), "") +    , m_parent(parent) +    , m_mod_model(mods) +    , m_candidates(search_for) +    , m_second_try_metadata(new ConcurrentTask()) +    , m_instance(instance) +{ +    ReviewMessageBox::setGeometry(0, 0, 800, 600); + +    ui->explainLabel->setText(tr("You're about to update the following mods:")); +    ui->onlyCheckedLabel->setText(tr("Only mods with a check will be updated!")); +} + +void ModUpdateDialog::checkCandidates() +{ +    // Ensure mods have valid metadata +    auto went_well = ensureMetadata(); +    if (!went_well) { +        m_aborted = true; +        return; +    } + +    // Report failed metadata generation +    if (!m_failed_metadata.empty()) { +        QString text; +        for (const auto& failed : m_failed_metadata) { +            const auto& mod = std::get<0>(failed); +            const auto& reason = std::get<1>(failed); +            text += tr("Mod name: %1<br>File name: %2<br>Reason: %3<br><br>").arg(mod->name(), mod->fileinfo().fileName(), reason); +        } + +        ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"), +                                        tr("Could not generate metadata for the following mods:<br>" +                                           "Do you wish to proceed without those mods?"), +                                        text); +        message_dialog.setModal(true); +        if (message_dialog.exec() == QDialog::Rejected) { +            m_aborted = true; +            QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); +            return; +        } +    } + +    auto versions = mcVersions(m_instance); +    auto loaders = mcLoaders(m_instance); + +    SequentialTask check_task(m_parent, tr("Checking for updates")); + +    if (!m_modrinth_to_update.empty()) { +        m_modrinth_check_task = new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model); +        connect(m_modrinth_check_task, &CheckUpdateTask::checkFailed, this, +                [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({mod, reason, recover_url}); }); +        check_task.addTask(m_modrinth_check_task); +    } + +    if (!m_flame_to_update.empty()) { +        m_flame_check_task = new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model); +        connect(m_flame_check_task, &CheckUpdateTask::checkFailed, this, +                [this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({mod, reason, recover_url}); }); +        check_task.addTask(m_flame_check_task); +    } + +    connect(&check_task, &Task::failed, this, +            [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + +    connect(&check_task, &Task::succeeded, this, [&]() { +        QStringList warnings = check_task.warnings(); +        if (warnings.count()) { +            CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); +        } +    }); + +    // Check for updates +    ProgressDialog progress_dialog(m_parent); +    progress_dialog.setSkipButton(true, tr("Abort")); +    progress_dialog.setWindowTitle(tr("Checking for updates...")); +    auto ret = progress_dialog.execWithTask(&check_task); + +    // If the dialog was skipped / some download error happened +    if (ret == QDialog::DialogCode::Rejected) { +        m_aborted = true; +        QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); +        return; +    } + +    // Add found updates for Modrinth +    if (m_modrinth_check_task) { +        auto modrinth_updates = m_modrinth_check_task->getUpdatable(); +        for (auto& updatable : modrinth_updates) { +            qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); + +            appendMod(updatable); +            m_tasks.insert(updatable.name, updatable.download); +        } +    } + +    // Add found updated for Flame +    if (m_flame_check_task) { +        auto flame_updates = m_flame_check_task->getUpdatable(); +        for (auto& updatable : flame_updates) { +            qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); + +            appendMod(updatable); +            m_tasks.insert(updatable.name, updatable.download); +        } +    } + +    // Report failed update checking +    if (!m_failed_check_update.empty()) { +        QString text; +        for (const auto& failed : m_failed_check_update) { +            const auto& mod = std::get<0>(failed); +            const auto& reason = std::get<1>(failed); +            const auto& recover_url = std::get<2>(failed); + +            qDebug() << mod->name() << " failed to check for updates!"; + +            text += tr("Mod name: %1").arg(mod->name()) + "<br>"; +            if (!reason.isEmpty()) +                text += tr("Reason: %1").arg(reason) + "<br>"; +            if (!recover_url.isEmpty()) +                //: %1 is the link to download it manually +                text += tr("Possible solution: Getting the latest version manually:<br>%1<br>") +                    .arg(QString("<a href='%1'>%1</a>").arg(recover_url.toString())); +            text += "<br>"; +        } + +        ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"), +                                        tr("Could not check or get the following mods for updates:<br>" +                                           "Do you wish to proceed without those mods?"), +                                        text); +        message_dialog.setModal(true); +        if (message_dialog.exec() == QDialog::Rejected) { +            m_aborted = true; +            QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); +            return; +        } +    } + +    // If there's no mod to be updated +    if (ui->modTreeWidget->topLevelItemCount() == 0) { +        m_no_updates = true; +    } else { +        // FIXME: Find a more efficient way of doing this! + +        // Sort major items in alphabetical order (also sorts the children unfortunately) +        ui->modTreeWidget->sortItems(0, Qt::SortOrder::AscendingOrder); + +        // Re-sort the children +        auto* item = ui->modTreeWidget->topLevelItem(0); +        for (int i = 1; item != nullptr; ++i) { +            item->sortChildren(0, Qt::SortOrder::DescendingOrder); +            item = ui->modTreeWidget->topLevelItem(i); +        } +    } + +    if (m_aborted || m_no_updates) +        QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); +} + +// Part 1: Ensure we have a valid metadata +auto ModUpdateDialog::ensureMetadata() -> bool +{ +    auto index_dir = indexDir(); + +    SequentialTask seq(m_parent, tr("Looking for metadata")); + +    // A better use of data structures here could remove the need for this QHash +    QHash<QString, bool> should_try_others; +    QList<Mod*> modrinth_tmp; +    QList<Mod*> flame_tmp; + +    bool confirm_rest = false; +    bool try_others_rest = false; +    bool skip_rest = false; +    ModPlatform::Provider provider_rest = ModPlatform::Provider::MODRINTH; + +    auto addToTmp = [&](Mod* m, ModPlatform::Provider p) { +        switch (p) { +            case ModPlatform::Provider::MODRINTH: +                modrinth_tmp.push_back(m); +                break; +            case ModPlatform::Provider::FLAME: +                flame_tmp.push_back(m); +                break; +        } +    }; + +    for (auto candidate : m_candidates) { +        auto* candidate_ptr = candidate.get(); +        if (candidate->status() != ModStatus::NoMetadata) { +            onMetadataEnsured(candidate_ptr); +            continue; +        } + +        if (skip_rest) +            continue; + +        if (confirm_rest) { +            addToTmp(candidate_ptr, provider_rest); +            should_try_others.insert(candidate->internal_id(), try_others_rest); +            continue; +        } + +        ChooseProviderDialog chooser(this); +        chooser.setDescription(tr("The mod '%1' does not have a metadata yet. We need to generate it in order to track relevant " +                                  "information on how to update this mod. " +                                  "To do this, please select a mod provider which we can use to check for updates for this mod.") +                                   .arg(candidate->name())); +        auto confirmed = chooser.exec() == QDialog::DialogCode::Accepted; + +        auto response = chooser.getResponse(); + +        if (response.skip_all) +            skip_rest = true; +        if (response.confirm_all) { +            confirm_rest = true; +            provider_rest = response.chosen; +            try_others_rest = response.try_others; +        } + +        should_try_others.insert(candidate->internal_id(), response.try_others); + +        if (confirmed) +            addToTmp(candidate_ptr, response.chosen); +    } + +    if (!modrinth_tmp.empty()) { +        auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::Provider::MODRINTH); +        connect(modrinth_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); +        connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { +            onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH); +        }); +        seq.addTask(modrinth_task); +    } + +    if (!flame_tmp.empty()) { +        auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::Provider::FLAME); +        connect(flame_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); +        connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { +            onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME); +        }); +        seq.addTask(flame_task); +    } + +    seq.addTask(m_second_try_metadata); + +    ProgressDialog checking_dialog(m_parent); +    checking_dialog.setSkipButton(true, tr("Abort")); +    checking_dialog.setWindowTitle(tr("Generating metadata...")); +    auto ret_metadata = checking_dialog.execWithTask(&seq); + +    return (ret_metadata != QDialog::DialogCode::Rejected); +} + +void ModUpdateDialog::onMetadataEnsured(Mod* mod) +{ +    // When the mod is a folder, for instance +    if (!mod->metadata()) +        return; + +    switch (mod->metadata()->provider) { +        case ModPlatform::Provider::MODRINTH: +            m_modrinth_to_update.push_back(mod); +            break; +        case ModPlatform::Provider::FLAME: +            m_flame_to_update.push_back(mod); +            break; +    } +} + +ModPlatform::Provider next(ModPlatform::Provider p) +{ +    switch (p) { +        case ModPlatform::Provider::MODRINTH: +            return ModPlatform::Provider::FLAME; +        case ModPlatform::Provider::FLAME: +            return ModPlatform::Provider::MODRINTH; +    } + +    return ModPlatform::Provider::FLAME; +} + +void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::Provider first_choice) +{ +    if (try_others) { +        auto index_dir = indexDir(); + +        auto* task = new EnsureMetadataTask(mod, index_dir, next(first_choice)); +        connect(task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); }); +        connect(task, &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); }); + +        m_second_try_metadata->addTask(task); +    } else { +        QString reason{ tr("Couldn't find a valid version on the selected mod provider(s)") }; + +        m_failed_metadata.append({mod, reason}); +    } +} + +void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info) +{ +    auto item_top = new QTreeWidgetItem(ui->modTreeWidget); +    item_top->setCheckState(0, Qt::CheckState::Checked); +    item_top->setText(0, info.name); +    item_top->setExpanded(true); + +    auto provider_item = new QTreeWidgetItem(item_top); +    provider_item->setText(0, tr("Provider: %1").arg(ProviderCaps.readableName(info.provider))); + +    auto old_version_item = new QTreeWidgetItem(item_top); +    old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version)); + +    auto new_version_item = new QTreeWidgetItem(item_top); +    new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); + +    auto changelog_item = new QTreeWidgetItem(item_top); +    changelog_item->setText(0, tr("Changelog of the latest version")); + +    auto changelog = new QTreeWidgetItem(changelog_item); +    auto changelog_area = new QTextBrowser(); + +    switch (info.provider) { +        case ModPlatform::Provider::MODRINTH: { +            HoeDown h; +            // HoeDown bug?: \n aren't converted to <br> +            auto text = h.process(info.changelog.toUtf8()); + +            // Don't convert if there's an HTML tag right after (Qt rendering weirdness) +            text.remove(QRegularExpression("(\n+)(?=<)")); +            text.replace('\n', "<br>"); + +            changelog_area->setHtml(text); +            break; +        } +        case ModPlatform::Provider::FLAME: { +            changelog_area->setHtml(info.changelog); +            break; +        } +    } + +    changelog_area->setOpenExternalLinks(true); +    changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::NoWrap); +    changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + +    // HACK: Is there a better way of achieving this? +    auto font_height = QFontMetrics(changelog_area->font()).height(); +    changelog_area->setMaximumHeight((changelog_area->toPlainText().count(QRegularExpression("\n|<br>")) + 2) * font_height); + +    ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area); + +    ui->modTreeWidget->addTopLevelItem(item_top); +} + +auto ModUpdateDialog::getTasks() -> const QList<ModDownloadTask*> +{ +    QList<ModDownloadTask*> list; + +    auto* item = ui->modTreeWidget->topLevelItem(0); + +    for (int i = 1; item != nullptr; ++i) { +        if (item->checkState(0) == Qt::CheckState::Checked) { +            list.push_back(m_tasks.find(item->text(0)).value()); +        } + +        item = ui->modTreeWidget->topLevelItem(i); +    } + +    return list; +} diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h new file mode 100644 index 00000000..76aaab36 --- /dev/null +++ b/launcher/ui/dialogs/ModUpdateDialog.h @@ -0,0 +1,62 @@ +#pragma once + +#include "BaseInstance.h" +#include "ModDownloadTask.h" +#include "ReviewMessageBox.h" + +#include "minecraft/mod/ModFolderModel.h" + +#include "modplatform/CheckUpdateTask.h" + +class Mod; +class ModrinthCheckUpdate; +class FlameCheckUpdate; +class ConcurrentTask; + +class ModUpdateDialog final : public ReviewMessageBox { +    Q_OBJECT +   public: +    explicit ModUpdateDialog(QWidget* parent, +                             BaseInstance* instance, +                             const std::shared_ptr<ModFolderModel> mod_model, +                             QList<Mod::Ptr>& search_for); + +    void checkCandidates(); + +    void appendMod(const CheckUpdateTask::UpdatableMod& info); + +    const QList<ModDownloadTask*> getTasks(); +    auto indexDir() const -> QDir { return m_mod_model->indexDir(); } + +    auto noUpdates() const -> bool { return m_no_updates; }; +    auto aborted() const -> bool { return m_aborted; }; + +   private: +    auto ensureMetadata() -> bool; + +   private slots: +    void onMetadataEnsured(Mod*); +    void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::Provider first_choice = ModPlatform::Provider::MODRINTH); + +   private: +    QWidget* m_parent; + +    ModrinthCheckUpdate* m_modrinth_check_task = nullptr; +    FlameCheckUpdate* m_flame_check_task = nullptr; + +    const std::shared_ptr<ModFolderModel> m_mod_model; + +    QList<Mod::Ptr>& m_candidates; +    QList<Mod*> m_modrinth_to_update; +    QList<Mod*> m_flame_to_update; + +    ConcurrentTask* m_second_try_metadata; +    QList<std::tuple<Mod*, QString>> m_failed_metadata; +    QList<std::tuple<Mod*, QString, QUrl>> m_failed_check_update; + +    QHash<QString, ModDownloadTask*> m_tasks; +    BaseInstance* m_instance; + +    bool m_no_updates = false; +    bool m_aborted = false; +}; diff --git a/launcher/ui/dialogs/NewComponentDialog.cpp b/launcher/ui/dialogs/NewComponentDialog.cpp index 1bbafb0c..ea790e8c 100644 --- a/launcher/ui/dialogs/NewComponentDialog.cpp +++ b/launcher/ui/dialogs/NewComponentDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include "Application.h" @@ -46,7 +66,6 @@ NewComponentDialog::NewComponentDialog(const QString & initialName, const QStrin      connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState);      connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState); -    auto groups = APPLICATION->instances()->getGroups().toSet();      ui->nameTextBox->setFocus();      originalPlaceholderText = ui->uidTextBox->placeholderText(); diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 05ea091d..35bba9be 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include "Application.h" @@ -54,8 +74,14 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString      InstIconKey = "default";      ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) +    auto groupList = APPLICATION->instances()->getGroups(); +    auto groups = QSet<QString>(groupList.begin(), groupList.end()); +    groupList = groups.values(); +#else      auto groups = APPLICATION->instances()->getGroups().toSet();      auto groupList = QStringList(groups.toList()); +#endif      groupList.sort(Qt::CaseInsensitive);      groupList.removeOne("");      groupList.push_front(initialGroup); @@ -124,20 +150,21 @@ void NewInstanceDialog::accept()  QList<BasePage *> NewInstanceDialog::getPages()  { +    QList<BasePage *> pages; +      importPage = new ImportPage(this); -    flamePage = new FlamePage(this); -    auto technicPage = new TechnicPage(this); -    return -    { -        new VanillaPage(this), -        importPage, -        new AtlPage(this), -        flamePage, -        new FtbPage(this), -        new LegacyFTB::Page(this), -        new ModrinthPage(this), -        technicPage -    }; + +    pages.append(new VanillaPage(this)); +    pages.append(importPage); +    pages.append(new AtlPage(this)); +    if (APPLICATION->currentCapabilities() & Application::SupportsFlame) +        pages.append(new FlamePage(this)); +    pages.append(new FtbPage(this)); +    pages.append(new LegacyFTB::Page(this)); +    pages.append(new ModrinthPage(this)); +    pages.append(new TechnicPage(this)); + +    return pages;  }  QString NewInstanceDialog::dialogTitle() diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h index ef74634e..a3c8cd1c 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.h +++ b/launcher/ui/dialogs/NewInstanceDialog.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #pragma once @@ -69,7 +89,6 @@ private:      QString InstIconKey;      ImportPage *importPage = nullptr; -    FlamePage *flamePage = nullptr;      std::unique_ptr<InstanceTask> creationTask;      bool importIcon = false; diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp index df620464..d3b21627 100644 --- a/launcher/ui/dialogs/NewsDialog.cpp +++ b/launcher/ui/dialogs/NewsDialog.cpp @@ -16,7 +16,7 @@ NewsDialog::NewsDialog(QList<NewsEntryPtr> entries, QWidget* parent) : QDialog(p      m_article_list_hidden = ui->articleListWidget->isHidden();      auto first_item = ui->articleListWidget->item(0); -    ui->articleListWidget->setItemSelected(first_item, true); +    first_item->setSelected(true);      auto article_entry = m_entries.constFind(first_item->text()).value();      ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, first_item->text())); diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index 4f3d8be4..a69537ab 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -103,5 +103,5 @@ MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg)      {          return dlg.m_account;      } -    return 0; +    return nullptr;  } diff --git a/launcher/ui/dialogs/ProfileSetupDialog.cpp b/launcher/ui/dialogs/ProfileSetupDialog.cpp index 76b6af49..64c0b924 100644 --- a/launcher/ui/dialogs/ProfileSetupDialog.cpp +++ b/launcher/ui/dialogs/ProfileSetupDialog.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include "ProfileSetupDialog.h" @@ -18,7 +38,7 @@  #include <QPushButton>  #include <QAction> -#include <QRegExpValidator> +#include <QRegularExpressionValidator>  #include <QJsonDocument>  #include <QDebug> @@ -39,9 +59,9 @@ ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidg      yellowIcon = APPLICATION->getThemedIcon("status-yellow");      badIcon = APPLICATION->getThemedIcon("status-bad"); -    QRegExp permittedNames("[a-zA-Z0-9_]{3,16}"); +    QRegularExpression permittedNames("[a-zA-Z0-9_]{3,16}");      auto nameEdit = ui->nameEdit; -    nameEdit->setValidator(new QRegExpValidator(permittedNames)); +    nameEdit->setValidator(new QRegularExpressionValidator(permittedNames));      nameEdit->setClearButtonEnabled(true);      validityAction = nameEdit->addAction(yellowIcon, QLineEdit::LeadingPosition);      connect(nameEdit, &QLineEdit::textEdited, this, &ProfileSetupDialog::nameEdited); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index e5226016..3c7f53d3 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -43,8 +43,8 @@ void ProgressDialog::setSkipButton(bool present, QString label)  void ProgressDialog::on_skipButton_clicked(bool checked)  {      Q_UNUSED(checked); -    task->abort(); -    QDialog::reject(); +    if (task->abort()) +        QDialog::reject();  }  ProgressDialog::~ProgressDialog() @@ -62,24 +62,24 @@ void ProgressDialog::updateSize()  int ProgressDialog::execWithTask(Task* task)  {      this->task = task; -    QDialog::DialogCode result;      if (!task) { -        qDebug() << "Programmer error: progress dialog created with null task."; -        return Accepted; +        qDebug() << "Programmer error: Progress dialog created with null task."; +        return QDialog::DialogCode::Accepted;      } +    QDialog::DialogCode result;      if (handleImmediateResult(result)) {          return result;      }      // Connect signals. -    connect(task, SIGNAL(started()), SLOT(onTaskStarted())); -    connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString))); -    connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded())); -    connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString&))); -    connect(task, SIGNAL(stepStatus(QString)), SLOT(changeStatus(const QString&))); -    connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64))); +    connect(task, &Task::started, this, &ProgressDialog::onTaskStarted); +    connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed); +    connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded); +    connect(task, &Task::status, this, &ProgressDialog::changeStatus); +    connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); +    connect(task, &Task::progress, this, &ProgressDialog::changeProgress);      connect(task, &Task::aborted, [this] { onTaskFailed(tr("Aborted by user")); }); @@ -89,19 +89,15 @@ int ProgressDialog::execWithTask(Task* task)          ui->globalProgressBar->setHidden(true);      } -    // if this didn't connect to an already running task, invoke start +    // It's a good idea to start the task after we entered the dialog's event loop :^)      if (!task->isRunning()) { -        task->start(); -    } -    if (task->isRunning()) { -        changeProgress(task->getProgress(), task->getTotalProgress()); -        changeStatus(task->getStatus()); -        return QDialog::exec(); -    } else if (handleImmediateResult(result)) { -        return result; +        QMetaObject::invokeMethod(task, &Task::start, Qt::QueuedConnection);      } else { -        return QDialog::Rejected; +        changeStatus(task->getStatus()); +        changeProgress(task->getProgress(), task->getTotalProgress());      } + +    return QDialog::exec();  }  // TODO: only provide the unique_ptr overloads diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index c92234a4..e664e566 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -40,7 +40,7 @@ auto ReviewMessageBox::deselectedMods() -> QStringList      auto* item = ui->modTreeWidget->topLevelItem(0); -    for (int i = 0; item != nullptr; ++i) { +    for (int i = 1; item != nullptr; ++i) {          if (item->checkState(0) == Qt::CheckState::Unchecked) {              list.append(item->text(0));          } diff --git a/launcher/ui/dialogs/ScrollMessageBox.ui b/launcher/ui/dialogs/ScrollMessageBox.ui index 299d2ecc..e684185f 100644 --- a/launcher/ui/dialogs/ScrollMessageBox.ui +++ b/launcher/ui/dialogs/ScrollMessageBox.ui @@ -6,7 +6,7 @@     <rect>      <x>0</x>      <y>0</y> -    <width>400</width> +    <width>500</width>      <height>455</height>     </rect>    </property> diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index 8d137afc..8180ac1f 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License. + */ +  #include <QFileInfo>  #include <QFileDialog>  #include <QPainter> @@ -22,68 +57,72 @@ void SkinUploadDialog::on_buttonBox_accepted()  {      QString fileName;      QString input = ui->skinPathTextBox->text(); -    QRegExp urlPrefixMatcher("^([a-z]+)://.+$"); -    bool isLocalFile = false; -    // it has an URL prefix -> it is an URL -    if(urlPrefixMatcher.exactMatch(input)) -    { -        QUrl fileURL = input; -        if(fileURL.isValid()) +    ProgressDialog prog(this); +    SequentialTask skinUpload; + +    if (!input.isEmpty()) { +        QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$")); +        bool isLocalFile = false; +        // it has an URL prefix -> it is an URL +        if(urlPrefixMatcher.match(input).hasMatch())          { -            // local? -            if(fileURL.isLocalFile()) +            QUrl fileURL = input; +            if(fileURL.isValid())              { -                isLocalFile = true; -                fileName = fileURL.toLocalFile(); +                // local? +                if(fileURL.isLocalFile()) +                { +                    isLocalFile = true; +                    fileName = fileURL.toLocalFile(); +                } +                else +                { +                    CustomMessageBox::selectable( +                        this, +                        tr("Skin Upload"), +                        tr("Using remote URLs for setting skins is not implemented yet."), +                        QMessageBox::Warning +                        )->exec(); +                    close(); +                    return; +                }              }              else              {                  CustomMessageBox::selectable(                      this,                      tr("Skin Upload"), -                    tr("Using remote URLs for setting skins is not implemented yet."), +                    tr("You cannot use an invalid URL for uploading skins."),                      QMessageBox::Warning -                )->exec(); +                    )->exec();                  close();                  return;              }          }          else          { -            CustomMessageBox::selectable( -                this, -                tr("Skin Upload"), -                tr("You cannot use an invalid URL for uploading skins."), -                QMessageBox::Warning -            )->exec(); +            // just assume it's a path then +            isLocalFile = true; +            fileName = ui->skinPathTextBox->text(); +        } +        if (isLocalFile && !QFile::exists(fileName)) +        { +            CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();              close();              return;          } +        SkinUpload::Model model = SkinUpload::STEVE; +        if (ui->steveBtn->isChecked()) +        { +            model = SkinUpload::STEVE; +        } +        else if (ui->alexBtn->isChecked()) +        { +            model = SkinUpload::ALEX; +        } +        skinUpload.addTask(shared_qobject_ptr<SkinUpload>(new SkinUpload(this, m_acct->accessToken(), FS::read(fileName), model)));      } -    else -    { -        // just assume it's a path then -        isLocalFile = true; -        fileName = ui->skinPathTextBox->text(); -    } -    if (isLocalFile && !QFile::exists(fileName)) -    { -        CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec(); -        close(); -        return; -    } -    SkinUpload::Model model = SkinUpload::STEVE; -    if (ui->steveBtn->isChecked()) -    { -        model = SkinUpload::STEVE; -    } -    else if (ui->alexBtn->isChecked()) -    { -        model = SkinUpload::ALEX; -    } -    ProgressDialog prog(this); -    SequentialTask skinUpload; -    skinUpload.addTask(shared_qobject_ptr<SkinUpload>(new SkinUpload(this, m_acct->accessToken(), FS::read(fileName), model))); +      auto selectedCape = ui->capeCombo->currentData().toString();      if(selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {          skinUpload.addTask(shared_qobject_ptr<CapeChange>(new CapeChange(this, m_acct->accessToken(), selectedCape))); diff --git a/launcher/ui/dialogs/SkinUploadDialog.ui b/launcher/ui/dialogs/SkinUploadDialog.ui index f4b0ed0a..c7b16645 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.ui +++ b/launcher/ui/dialogs/SkinUploadDialog.ui @@ -21,7 +21,11 @@       </property>       <layout class="QHBoxLayout" name="horizontalLayout">        <item> -       <widget class="QLineEdit" name="skinPathTextBox"/> +       <widget class="QLineEdit" name="skinPathTextBox"> +        <property name="placeholderText"> +         <string>Leave empty to keep current skin</string> +        </property> +       </widget>        </item>        <item>         <widget class="QPushButton" name="skinBrowseBtn"> diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp index ec77d146..e0c5a495 100644 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ b/launcher/ui/dialogs/UpdateDialog.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License. + */ +  #include "UpdateDialog.h"  #include "ui_UpdateDialog.h"  #include <QDebug> @@ -58,7 +93,7 @@ QString reprocessMarkdown(QByteArray markdown)      QString output = hoedown.process(markdown);      // HACK: easier than customizing hoedown -    output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/issues/\\1\">GH-\\1</a>"); +    output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/issues/\\1\">GH-\\1</a>");      qDebug() << output;      return output;  } diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp index b446e39d..137cc8d5 100644 --- a/launcher/ui/instanceview/InstanceDelegate.cpp +++ b/launcher/ui/instanceview/InstanceDelegate.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include "InstanceDelegate.h" @@ -24,7 +44,7 @@  #include "InstanceView.h"  #include "BaseInstance.h"  #include "InstanceList.h" -#include <xdgicon.h> +#include <QIcon>  #include <QTextEdit>  // Origin: Qt @@ -61,7 +81,7 @@ void drawSelectionRect(QPainter *painter, const QStyleOptionViewItem &option,          painter->fillRect(rect, option.palette.brush(QPalette::Highlight));      else      { -        QColor backgroundColor = option.palette.color(QPalette::Background); +        QColor backgroundColor = option.palette.color(QPalette::Window);          backgroundColor.setAlpha(160);          painter->fillRect(rect, QBrush(backgroundColor));      } @@ -142,7 +162,7 @@ void drawBadges(QPainter *painter, const QStyleOptionViewItem &option, BaseInsta                  return;              }              // FIXME: inject this. -            auto icon = XdgIcon::fromTheme(it.next()); +            auto icon = QIcon::fromTheme(it.next());              // opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state);              const QPixmap pixmap;              // itemSide diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index 25aec1ab..fbeffe35 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include "InstanceView.h" @@ -425,7 +445,12 @@ void InstanceView::mouseReleaseEvent(QMouseEvent *event)          {              emit clicked(index);          } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +        QStyleOptionViewItem option; +        initViewItemOption(&option); +#else          QStyleOptionViewItem option = viewOptions(); +#endif          if (m_pressedAlreadySelected)          {              option.state |= QStyle::State_Selected; @@ -461,7 +486,12 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent *event)      QPersistentModelIndex persistent = index;      emit doubleClicked(persistent); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +    QStyleOptionViewItem option; +    initViewItemOption(&option); +#else      QStyleOptionViewItem option = viewOptions(); +#endif      if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this))      {          emit activated(index); @@ -474,7 +504,12 @@ void InstanceView::paintEvent(QPaintEvent *event)      QPainter painter(this->viewport()); -    QStyleOptionViewItem option(viewOptions()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +    QStyleOptionViewItem option; +    initViewItemOption(&option); +#else +    QStyleOptionViewItem option = viewOptions(); +#endif      option.widget = this;      int wpWidth = viewport()->width(); @@ -528,9 +563,9 @@ void InstanceView::paintEvent(QPaintEvent *event)  #if 0      if (!m_lastDragPosition.isNull())      { -        QPair<Group *, int> pair = rowDropPos(m_lastDragPosition); -        Group *category = pair.first; -        int row = pair.second; +        std::pair<VisualGroup *, VisualGroup::HitResults> pair = rowDropPos(m_lastDragPosition); +        VisualGroup *category = pair.first; +        VisualGroup::HitResults row = pair.second;          if (category)          {              int internalRow = row - category->firstItemIndex; @@ -618,7 +653,7 @@ void InstanceView::dropEvent(QDropEvent *event)      {          if(event->possibleActions() & Qt::MoveAction)          { -            QPair<VisualGroup *, VisualGroup::HitResults> dropPos = rowDropPos(event->pos()); +            std::pair<VisualGroup *, VisualGroup::HitResults> dropPos = rowDropPos(event->pos());              const VisualGroup *group = dropPos.first;              auto hitresult = dropPos.second; @@ -709,10 +744,18 @@ QRect InstanceView::geometryRect(const QModelIndex &index) const      int x = pos.first;      // int y = pos.second; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +    QStyleOptionViewItem option; +    initViewItemOption(&option); +#else +    QStyleOptionViewItem option = viewOptions(); +#endif +      QRect out;      out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index));      out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); -    out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); +    out.setSize(itemDelegate()->sizeHint(option, index));      geometryCache.insert(row, new QRect(out));      return out;  } @@ -759,7 +802,12 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList &indices, QRect *r) c      QPixmap pixmap(r->size());      pixmap.fill(Qt::transparent);      QPainter painter(&pixmap); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +    QStyleOptionViewItem option; +    initViewItemOption(&option); +#else      QStyleOptionViewItem option = viewOptions(); +#endif      option.state |= QStyle::State_Selected;      for (int j = 0; j < paintPairs.count(); ++j)      { @@ -770,16 +818,16 @@ QPixmap InstanceView::renderToPixmap(const QModelIndexList &indices, QRect *r) c      return pixmap;  } -QList<QPair<QRect, QModelIndex>> InstanceView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +QList<std::pair<QRect, QModelIndex>> InstanceView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const  {      Q_ASSERT(r);      QRect &rect = *r; -    QList<QPair<QRect, QModelIndex>> ret; +    QList<std::pair<QRect, QModelIndex>> ret;      for (int i = 0; i < indices.count(); ++i)      {          const QModelIndex &index = indices.at(i);          const QRect current = geometryRect(index); -        ret += qMakePair(current, index); +        ret += std::make_pair(current, index);          rect |= current;      }      return ret; @@ -790,11 +838,11 @@ bool InstanceView::isDragEventAccepted(QDropEvent *event)      return true;  } -QPair<VisualGroup *, VisualGroup::HitResults> InstanceView::rowDropPos(const QPoint &pos) +std::pair<VisualGroup *, VisualGroup::HitResults> InstanceView::rowDropPos(const QPoint &pos)  {      VisualGroup::HitResults hitresult;      auto group = categoryAt(pos + offset(), hitresult); -    return qMakePair<VisualGroup*, int>(group, hitresult); +    return std::make_pair(group, hitresult);  }  QPoint InstanceView::offset() const diff --git a/launcher/ui/instanceview/InstanceView.h b/launcher/ui/instanceview/InstanceView.h index 406362e6..ac338274 100644 --- a/launcher/ui/instanceview/InstanceView.h +++ b/launcher/ui/instanceview/InstanceView.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #pragma once @@ -143,11 +163,11 @@ private: /* methods */      int calculateItemsPerRow() const;      int verticalScrollToValue(const QModelIndex &index, const QRect &rect, QListView::ScrollHint hint) const;      QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; -    QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; +    QList<std::pair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, QRect *r) const;      bool isDragEventAccepted(QDropEvent *event); -    QPair<VisualGroup *, VisualGroup::HitResults> rowDropPos(const QPoint &pos); +    std::pair<VisualGroup *, VisualGroup::HitResults> rowDropPos(const QPoint &pos);      QPoint offset() const;  }; diff --git a/launcher/ui/instanceview/VisualGroup.cpp b/launcher/ui/instanceview/VisualGroup.cpp index 8991fb2d..e6bca17d 100644 --- a/launcher/ui/instanceview/VisualGroup.cpp +++ b/launcher/ui/instanceview/VisualGroup.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include "VisualGroup.h" @@ -55,7 +75,14 @@ void VisualGroup::update()              positionInRow = 0;              maxRowHeight = 0;          } -        auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +        QStyleOptionViewItem viewItemOption; +        view->initViewItemOption(&viewItemOption); +#else +        QStyleOptionViewItem viewItemOption = view->viewOptions(); +#endif + +        auto itemHeight = view->itemDelegate()->sizeHint(viewItemOption, item).height();          if(itemHeight > maxRowHeight)          {              maxRowHeight = itemHeight; diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index b889e6f7..e3d30475 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -40,8 +40,10 @@  #include <QMessageBox>  #include <QFileDialog> +#include <QRegularExpression>  #include <QStandardPaths>  #include <QTabBar> +#include <QValidator>  #include <QVariant>  #include "settings/SettingsObject.h" @@ -63,6 +65,10 @@ APIPage::APIPage(QWidget *parent) :      };      static QRegularExpression validUrlRegExp("https?://.+"); +    static QRegularExpression validMSAClientID(QRegularExpression::anchoredPattern( +                "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}")); +    static QRegularExpression validFlameKey(QRegularExpression::anchoredPattern( +                "\\$2[ayb]\\$.{56}"));      ui->setupUi(this); @@ -75,6 +81,8 @@ APIPage::APIPage(QWidget *parent) :      // This function needs to be called even when the ComboBox's index is still in its default state.      updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());      ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry)); +    ui->msaClientID->setValidator(new QRegularExpressionValidator(validMSAClientID, ui->msaClientID)); +    ui->flameKey->setValidator(new QRegularExpressionValidator(validFlameKey, ui->flameKey));      ui->metaURL->setPlaceholderText(BuildConfig.META_URL);      ui->userAgentLineEdit->setPlaceholderText(BuildConfig.USER_AGENT); @@ -137,8 +145,8 @@ void APIPage::loadSettings()      ui->msaClientID->setText(msaClientID);      QString metaURL = s->get("MetaURLOverride").toString();      ui->metaURL->setText(metaURL); -    QString curseKey = s->get("CFKeyOverride").toString(); -    ui->curseKey->setText(curseKey); +    QString flameKey = s->get("FlameKeyOverride").toString(); +    ui->flameKey->setText(flameKey);      QString customUserAgent = s->get("UserAgentOverride").toString();      ui->userAgentLineEdit->setText(customUserAgent);  } @@ -167,8 +175,8 @@ void APIPage::applySettings()      }      s->set("MetaURLOverride", metaURL); -    QString curseKey = ui->curseKey->text(); -    s->set("CFKeyOverride", curseKey); +    QString flameKey = ui->flameKey->text(); +    s->set("FlameKeyOverride", flameKey);      s->set("UserAgentOverride", ui->userAgentLineEdit->text());  } diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 5327771c..1ae788c7 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -196,7 +196,7 @@          </widget>         </item>         <item> -        <widget class="QGroupBox" name="groupBox_curse"> +        <widget class="QGroupBox" name="groupBox_flame">           <property name="enabled">            <bool>true</bool>           </property> @@ -214,7 +214,7 @@            <item row="2" column="0">             <widget class="QLabel" name="label_7">              <property name="text"> -             <string>Enter a custom API Key for CurseForge here. </string> +             <string>Enter a custom API Key for CurseForge here.</string>              </property>              <property name="textFormat">               <enum>Qt::RichText</enum> @@ -228,7 +228,7 @@             </widget>            </item>            <item row="1" column="0"> -           <widget class="QLineEdit" name="curseKey"> +           <widget class="QLineEdit" name="flameKey">              <property name="enabled">               <bool>true</bool>              </property> diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index a608771e..fcc43add 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -96,7 +96,7 @@ AccountListPage::AccountListPage(QWidget *parent)      updateButtonStates();      // Xbox authentication won't work without a client identifier, so disable the button if it is missing -    if (APPLICATION->getMSAClientID().isEmpty()) { +    if (~APPLICATION->currentCapabilities() & Application::SupportsMSA) {          ui->actionAddMicrosoft->setVisible(false);          ui->actionAddMicrosoft->setToolTip(tr("No Microsoft Authentication client ID was set."));      } diff --git a/launcher/ui/pages/global/CustomCommandsPage.cpp b/launcher/ui/pages/global/CustomCommandsPage.cpp index 436d766e..df1420ca 100644 --- a/launcher/ui/pages/global/CustomCommandsPage.cpp +++ b/launcher/ui/pages/global/CustomCommandsPage.cpp @@ -2,7 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index edbf609f..73ef0024 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -78,7 +78,6 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch      m_languageModel = APPLICATION->translations();      loadSettings(); -#ifdef LAUNCHER_WITH_UPDATER      if(BuildConfig.UPDATER_ENABLED)      {          QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList); @@ -91,9 +90,18 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch          {              APPLICATION->updateChecker()->updateChanList(false);          } -        ui->updateSettingsBox->setHidden(false); + +        if (APPLICATION->updateChecker()->getExternalUpdater()) +        { +            ui->updateChannelComboBox->setVisible(false); +            ui->updateChannelDescLabel->setVisible(false); +            ui->updateChannelLabel->setVisible(false); +        } +    } +    else +    { +        ui->updateSettingsBox->setHidden(true);      } -#endif      connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));      connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));  } @@ -188,7 +196,6 @@ void LauncherPage::on_metadataDisableBtn_clicked()      ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());  } -#ifdef LAUNCHER_WITH_UPDATER  void LauncherPage::refreshUpdateChannelList()  {      // Stop listening for selection changes. It's going to change a lot while we update it and @@ -260,14 +267,22 @@ void LauncherPage::refreshUpdateChannelDesc()          m_currentUpdateChannel = selected.id;      }  } -#endif  void LauncherPage::applySettings()  {      auto s = APPLICATION->settings();      // Updates -    s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); +    if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater()) +    { +        APPLICATION->updateChecker()->getExternalUpdater()->setAutomaticallyChecksForUpdates( +                ui->autoUpdateCheckBox->isChecked()); +    } +    else +    { +        s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked()); +    } +      s->set("UpdateChannel", m_currentUpdateChannel);      auto original = s->get("IconTheme").toString();      //FIXME: make generic @@ -352,7 +367,16 @@ void LauncherPage::loadSettings()  {      auto s = APPLICATION->settings();      // Updates -    ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); +    if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater()) +    { +        ui->autoUpdateCheckBox->setChecked( +                APPLICATION->updateChecker()->getExternalUpdater()->getAutomaticallyChecksForUpdates()); +    } +    else +    { +        ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool()); +    } +      m_currentUpdateChannel = s->get("UpdateChannel").toString();      //FIXME: make generic      auto theme = s->get("IconTheme").toString(); diff --git a/launcher/ui/pages/global/LauncherPage.h b/launcher/ui/pages/global/LauncherPage.h index ccfd7e9e..f38c922e 100644 --- a/launcher/ui/pages/global/LauncherPage.h +++ b/launcher/ui/pages/global/LauncherPage.h @@ -90,7 +90,6 @@ slots:      void on_iconsDirBrowseBtn_clicked();      void on_metadataDisableBtn_clicked(); -#ifdef LAUNCHER_WITH_UPDATER      /*!       * Updates the list of update channels in the combo box.       */ @@ -101,13 +100,13 @@ slots:       */      void refreshUpdateChannelDesc(); -    void updateChannelSelectionChanged(int index); -#endif      /*!       * Updates the font preview       */      void refreshFontPreview(); +    void updateChannelSelectionChanged(int index); +  private:      Ui::LauncherPage *ui; diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index ceb68c5b..645f7ef6 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -50,14 +50,11 @@           <property name="title">            <string>Update Settings</string>           </property> -         <property name="visible"> -          <bool>false</bool> -         </property>           <layout class="QVBoxLayout" name="verticalLayout_7">            <item>             <widget class="QCheckBox" name="autoUpdateCheckBox">              <property name="text"> -             <string>Check for updates on start?</string> +             <string>Check for updates automatically</string>              </property>             </widget>            </item> diff --git a/launcher/ui/pages/global/ProxyPage.cpp b/launcher/ui/pages/global/ProxyPage.cpp index aefd1e74..ffff8456 100644 --- a/launcher/ui/pages/global/ProxyPage.cpp +++ b/launcher/ui/pages/global/ProxyPage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -36,11 +37,11 @@  #include "ProxyPage.h"  #include "ui_ProxyPage.h" +#include <QButtonGroup>  #include <QTabBar>  #include "settings/SettingsObject.h"  #include "Application.h" -#include "Application.h"  ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)  { @@ -49,7 +50,8 @@ ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)      loadSettings();      updateCheckboxStuff(); -    connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int))); +    connect(ui->proxyGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), +            this, &ProxyPage::proxyGroupChanged);  }  ProxyPage::~ProxyPage() @@ -65,13 +67,13 @@ bool ProxyPage::apply()  void ProxyPage::updateCheckboxStuff()  { -    ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() && -                                 !ui->proxyDefaultBtn->isChecked()); -    ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() && -                                 !ui->proxyDefaultBtn->isChecked()); +    bool enableEditing = ui->proxyHTTPBtn->isChecked() +        || ui->proxySOCKS5Btn->isChecked(); +    ui->proxyAddrBox->setEnabled(enableEditing); +    ui->proxyAuthBox->setEnabled(enableEditing);  } -void ProxyPage::proxyChanged(int) +void ProxyPage::proxyGroupChanged(QAbstractButton *button)  {      updateCheckboxStuff();  } diff --git a/launcher/ui/pages/global/ProxyPage.h b/launcher/ui/pages/global/ProxyPage.h index e3677774..279a9029 100644 --- a/launcher/ui/pages/global/ProxyPage.h +++ b/launcher/ui/pages/global/ProxyPage.h @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -36,6 +37,7 @@  #pragma once  #include <memory> +#include <QAbstractButton>  #include <QDialog>  #include "ui/pages/BasePage.h" @@ -73,15 +75,14 @@ public:      bool apply() override;      void retranslate() override; +private slots: +    void proxyGroupChanged(QAbstractButton *button); +  private:      void updateCheckboxStuff();      void applySettings();      void loadSettings(); -private -slots: -    void proxyChanged(int); -  private:      Ui::ProxyPage *ui;  }; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 02eeae3d..39fbe3e2 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -32,13 +32,13 @@ class SortProxy : public QSortFilterProxyModel {          const auto& mod = model->at(source_row); -        if (mod.name().contains(filterRegExp())) +        if (filterRegularExpression().match(mod.name()).hasMatch())              return true; -        if (mod.description().contains(filterRegExp())) +        if (filterRegularExpression().match(mod.description()).hasMatch())              return true;          for (auto& author : mod.authors()) { -            if (author.contains(filterRegExp())) { +            if (filterRegularExpression().match(author).hasMatch()) {                  return true;              }          } @@ -101,7 +101,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared  {      ui->setupUi(this); -    runningStateChanged(m_instance && m_instance->isRunning()); +    ExternalResourcesPage::runningStateChanged(m_instance && m_instance->isRunning());      ui->actionsToolbar->insertSpacer(ui->actionViewConfigs); @@ -182,7 +182,7 @@ void ExternalResourcesPage::retranslate()  void ExternalResourcesPage::filterTextChanged(const QString& newContents)  {      m_viewFilter = newContents; -    m_filterModel->setFilterFixedString(m_viewFilter); +    m_filterModel->setFilterRegularExpression(m_viewFilter);  }  void ExternalResourcesPage::runningStateChanged(bool running) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index 41237139..ff294678 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -46,7 +46,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {     protected slots:      void itemActivated(const QModelIndex& index);      void filterTextChanged(const QString& newContents); -    void runningStateChanged(bool running); +    virtual void runningStateChanged(bool running);      virtual void addItem();      virtual void removeItem(); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index 17bf455a..a13666b2 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -147,6 +147,17 @@      <string>Download a new resource</string>     </property>    </action> +  <action name="actionUpdateItem"> +   <property name="enabled"> +    <bool>false</bool> +   </property> +   <property name="text"> +    <string>Check for &Updates</string> +   </property> +   <property name="toolTip"> +    <string>Try to check or update all selected resources (all resources if none are selected)</string> +   </property> +  </action>   </widget>   <customwidgets>    <customwidget> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 1d8cd1d7..f11cf992 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -2,7 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -349,7 +349,7 @@ void InstanceSettingsPage::loadSettings()      ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());      #if !defined(Q_OS_LINUX) -    ui->perfomanceGroupBox->setVisible(false); +    ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false);      #endif      // Miscellanous diff --git a/launcher/ui/pages/instance/LogPage.cpp b/launcher/ui/pages/instance/LogPage.cpp index a6c98c08..3d9fb025 100644 --- a/launcher/ui/pages/instance/LogPage.cpp +++ b/launcher/ui/pages/instance/LogPage.cpp @@ -40,7 +40,6 @@  #include "Application.h"  #include <QIcon> -#include <QIdentityProxyModel>  #include <QScrollBar>  #include <QShortcut> @@ -64,7 +63,7 @@ public:          {              case Qt::FontRole:                  return m_font; -            case Qt::TextColorRole: +            case Qt::ForegroundRole:              {                  MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();                  return m_colors->getFront(level); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 4432ccc8..1b2cde0c 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -49,6 +49,7 @@  #include "ui/GuiUtil.h"  #include "ui/dialogs/CustomMessageBox.h"  #include "ui/dialogs/ModDownloadDialog.h" +#include "ui/dialogs/ModUpdateDialog.h"  #include "DesktopServices.h" @@ -78,6 +79,36 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>          ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);          connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods); + +        ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)")); +        ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem); +        connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); + +        auto check_allow_update = [this] { +            return (!m_instance || !m_instance->isRunning()) && +                   (ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); +        }; + +        connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] { +            ui->actionUpdateItem->setEnabled(check_allow_update()); +        }); + +        connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] { +            ui->actionUpdateItem->setEnabled(check_allow_update()); +        }); + +        connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this, check_allow_update] { +            ui->actionUpdateItem->setEnabled(check_allow_update()); +        }); + +        connect(mods.get(), &ModFolderModel::updateFinished, this, [this, check_allow_update, mods] { +            ui->actionUpdateItem->setEnabled(check_allow_update()); + +            // Prevent a weird crash when trying to open the mods page twice in a session o.O +            disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0); +        }); + +        ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning());      }  } @@ -85,6 +116,13 @@ CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFold      : ModFolderPage(inst, mods, parent)  {} +void ModFolderPage::runningStateChanged(bool running) +{ +    ExternalResourcesPage::runningStateChanged(running); +    ui->actionDownloadItem->setEnabled(!running); +    ui->actionUpdateItem->setEnabled(!running); +} +  bool ModFolderPage::shouldDisplay() const  {      return true; @@ -107,7 +145,6 @@ bool CoreModFolderPage::shouldDisplay() const              return false;          if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)              return true; -              }      return false;  } @@ -118,7 +155,7 @@ void ModFolderPage::installMods()          return;      if (m_instance->typeName() != "Minecraft")          return;  // this is a null instance or a legacy instance -     +      auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();      if (profile->getModLoaders() == ModAPI::Unspecified) {          QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!")); @@ -140,7 +177,7 @@ void ModFolderPage::installMods()              QStringList warnings = tasks->warnings();              if (warnings.count())                  CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); -             +              tasks->deleteLater();          }); @@ -155,3 +192,63 @@ void ModFolderPage::installMods()          m_model->update();      }  } + +void ModFolderPage::updateMods() +{ +    auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); + +    auto mods_list = m_model->selectedMods(selection); +    bool use_all = mods_list.empty(); +    if (use_all) +        mods_list = m_model->allMods(); + +    ModUpdateDialog update_dialog(this, m_instance, m_model, mods_list); +    update_dialog.checkCandidates(); + +    if (update_dialog.aborted()) { +        CustomMessageBox::selectable(this, tr("Aborted"), tr("The mod updater was aborted!"), QMessageBox::Warning)->show(); +        return; +    } +    if (update_dialog.noUpdates()) { +        QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; +        if (mods_list.size() > 1) { +            if (use_all) { +                message = tr("All mods are up-to-date! :)"); +            } else { +                message = tr("All selected mods are up-to-date! :)"); +            } +        } +        CustomMessageBox::selectable(this, tr("Update checker"), message) +            ->exec(); +        return; +    } + +    if (update_dialog.exec()) { +        ConcurrentTask* tasks = new ConcurrentTask(this); +        connect(tasks, &Task::failed, [this, tasks](QString reason) { +            CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); +            tasks->deleteLater(); +        }); +        connect(tasks, &Task::aborted, [this, tasks]() { +            CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show(); +            tasks->deleteLater(); +        }); +        connect(tasks, &Task::succeeded, [this, tasks]() { +            QStringList warnings = tasks->warnings(); +            if (warnings.count()) { +                CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); +            } +            tasks->deleteLater(); +        }); + +        for (auto task : update_dialog.getTasks()) { +            tasks->addTask(task); +        } + +        ProgressDialog loadDialog(this); +        loadDialog.setSkipButton(true, tr("Abort")); +        loadDialog.execWithTask(tasks); + +        m_model->update(); +    } +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 1a9ed7db..93889707 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -52,9 +53,11 @@ class ModFolderPage : public ExternalResourcesPage {      virtual QString helpPage() const override { return "Loader-mods"; }      virtual bool shouldDisplay() const override; +    void runningStateChanged(bool running) override;     private slots:      void installMods(); +    void updateMods();  };  class CoreModFolderPage : public ModFolderPage { diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 51163e28..c97253e4 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -50,6 +50,7 @@  #include <QClipboard>  #include <QKeyEvent>  #include <QMenu> +#include <QRegularExpression>  #include <Application.h> @@ -154,7 +155,7 @@ public:          if (role == Qt::DisplayRole || role == Qt::EditRole)          {              QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); -            return result.toString().remove(QRegExp("\\.png$")); +            return result.toString().remove(QRegularExpression("\\.png$"));          }          if (role == Qt::DecorationRole)          { @@ -270,7 +271,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)      ui->listView->setViewMode(QListView::IconMode);      ui->listView->setResizeMode(QListView::Adjust);      ui->listView->installEventFilter(this); -    ui->listView->setEditTriggers(0); +    ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);      ui->listView->setItemDelegate(new CenteredEditingDelegate(this));      ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);      connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index c34c9755..c22706af 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -35,7 +35,6 @@  #pragma once -#include <QItemSelection>  #include <QMainWindow>  #include "ui/pages/BasePage.h" diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index c3bde612..e5cce96c 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -48,7 +48,6 @@  #include <QFileSystemWatcher>  #include <QMenu> -#include <QTimer>  static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things. @@ -289,7 +288,11 @@ public:              return false;          }          beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1); +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) +        m_servers.swapItemsAt(row-1, row); +#else          m_servers.swap(row-1, row); +#endif          endMoveRows();          scheduleSave();          return true; @@ -307,7 +310,11 @@ public:              return false;          }          beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2); +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) +        m_servers.swapItemsAt(row+1, row); +#else          m_servers.swap(row+1, row); +#endif          endMoveRows();          scheduleSave();          return true; @@ -616,7 +623,7 @@ ServersPage::ServersPage(InstancePtr inst, QWidget* parent)      auto selectionModel = ui->serversView->selectionModel();      connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged); -    connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed); +    connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::runningStateChanged);      connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited);      connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited);      connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int))); @@ -656,7 +663,7 @@ QMenu * ServersPage::createPopupMenu()      return filteredMenu;  } -void ServersPage::on_RunningState_changed(bool running) +void ServersPage::runningStateChanged(bool running)  {      if(m_locked == running)      { diff --git a/launcher/ui/pages/instance/ServersPage.h b/launcher/ui/pages/instance/ServersPage.h index 5173712c..37399d49 100644 --- a/launcher/ui/pages/instance/ServersPage.h +++ b/launcher/ui/pages/instance/ServersPage.h @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -97,7 +98,7 @@ private slots:      void on_actionMove_Down_triggered();      void on_actionJoin_triggered(); -    void on_RunningState_changed(bool running); +    void runningStateChanged(bool running);      void nameEdited(const QString & name);      void addressEdited(const QString & address); diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 23e2367b..468ff35c 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -2,7 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index ff30dd82..85cc01ff 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -47,7 +47,6 @@  #include <QInputDialog>  #include <QProcess>  #include <Qt> -#include <QSortFilterProxyModel>  #include "tools/MCEditTool.h"  #include "FileSystem.h" @@ -212,7 +211,7 @@ void WorldListPage::on_actionDatapacks_triggered()          return;      } -    if(!worldSafetyNagQuestion()) +    if(!worldSafetyNagQuestion(tr("Open World Datapacks Folder")))          return;      auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); @@ -270,7 +269,7 @@ void WorldListPage::on_actionMCEdit_triggered()          return;      } -    if(!worldSafetyNagQuestion()) +    if(!worldSafetyNagQuestion(tr("Open World in MCEdit")))          return;      auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); @@ -374,11 +373,11 @@ bool WorldListPage::isWorldSafe(QModelIndex)      return !m_inst->isRunning();  } -bool WorldListPage::worldSafetyNagQuestion() +bool WorldListPage::worldSafetyNagQuestion(const QString &actionType)  {      if(!isWorldSafe(getSelectedWorld()))      { -        auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); +        auto result = QMessageBox::question(this, actionType, tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?"));          if(result == QMessageBox::No)          {              return false; @@ -396,7 +395,7 @@ void WorldListPage::on_actionCopy_triggered()          return;      } -    if(!worldSafetyNagQuestion()) +    if(!worldSafetyNagQuestion(tr("Copy World")))          return;      auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); @@ -418,7 +417,7 @@ void WorldListPage::on_actionRename_triggered()          return;      } -    if(!worldSafetyNagQuestion()) +    if(!worldSafetyNagQuestion(tr("Rename World")))          return;      auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 17e36a08..1dc9e53e 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -93,7 +93,7 @@ protected:  private:      QModelIndex getSelectedWorld();      bool isWorldSafe(QModelIndex index); -    bool worldSafetyNagQuestion(); +    bool worldSafetyNagQuestion(const QString &actionType);      void mceditError();  private: diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 0b8577b1..30196aad 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -2,7 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui index 77bc5da5..0a50e871 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)</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 4917b890..94b1f099 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -219,6 +219,10 @@ void ListModel::searchRequestFinished(QJsonDocument& doc)          searchState = CanPossiblyFetchMore;      } +    // When you have a Qt build with assertions turned on, proceeding here will abort the application +    if (newList.size() == 0) +        return; +      beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);      modpacks.append(newList);      endInsertRows(); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 8de5211c..7901b90b 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -37,13 +37,12 @@  #include "AtlPage.h"  #include "ui_AtlPage.h" -#include "modplatform/atlauncher/ATLPackInstallTask.h" +#include "BuildConfig.h"  #include "AtlOptionalModDialog.h" +#include "AtlUserInteractionSupportImpl.h" +#include "modplatform/atlauncher/ATLPackInstallTask.h"  #include "ui/dialogs/NewInstanceDialog.h" -#include "ui/dialogs/VersionSelectDialog.h" - -#include <BuildConfig.h>  #include <QMessageBox> @@ -117,7 +116,9 @@ void AtlPage::suggestCurrent()          return;      } -    dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion)); +    auto uiSupport = new AtlUserInteractionSupportImpl(this); +    dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion)); +      auto editedLogoName = selected.safeName;      auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());      listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo) @@ -172,51 +173,3 @@ void AtlPage::onVersionSelectionChanged(QString data)      selectedVersion = data;      suggestCurrent();  } - -QVector<QString> AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) -{ -    AtlOptionalModDialog optionalModDialog(this, version, mods); -    optionalModDialog.exec(); -    return optionalModDialog.getResult(); -} - -QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) { -    VersionSelectDialog vselect(vlist.get(), "Choose Version", APPLICATION->activeWindow(), false); -    if (minecraftVersion != Q_NULLPTR) { -        vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); -        vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion)); -    } -    else { -        vselect.setEmptyString(tr("No versions are currently available")); -    } -    vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!")); - -    // select recommended build -    for (int i = 0; i < vlist->versions().size(); i++) { -        auto version = vlist->versions().at(i); -        auto reqs = version->requires(); - -        // filter by minecraft version, if the loader depends on a certain version. -        if (minecraftVersion != Q_NULLPTR) { -            auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) { -                return req.uid == "net.minecraft"; -            }); -            if (iter == reqs.end()) continue; -            if (iter->equalsVersion != minecraftVersion) continue; -        } - -        // first recommended build we find, we use. -        if (version->isRecommended()) { -            vselect.setCurrentVersion(version->descriptor()); -            break; -        } -    } - -    vselect.exec(); -    return vselect.selectedVersion()->descriptor(); -} - -void AtlPage::displayMessage(QString message) -{ -    QMessageBox::information(this, tr("Installing"), message); -} diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h index aa6d5da1..1b3b15c1 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h @@ -52,7 +52,7 @@ namespace Ui  class NewInstanceDialog; -class AtlPage : public QWidget, public BasePage, public ATLauncher::UserInteractionSupport +class AtlPage : public QWidget, public BasePage  {  Q_OBJECT @@ -83,10 +83,6 @@ public:  private:      void suggestCurrent(); -    QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; -    QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override; -    void displayMessage(QString message) override; -  private slots:      void triggerSearch(); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp new file mode 100644 index 00000000..03196685 --- /dev/null +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License. + */ + +#include <QMessageBox> +#include "AtlUserInteractionSupportImpl.h" + +#include "AtlOptionalModDialog.h" +#include "ui/dialogs/VersionSelectDialog.h" + +AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget *parent) : m_parent(parent) +{ +} + +QVector<QString> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) +{ +    AtlOptionalModDialog optionalModDialog(m_parent, version, mods); +    optionalModDialog.exec(); +    return optionalModDialog.getResult(); +} + +QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) +{ +    VersionSelectDialog vselect(vlist.get(), "Choose Version", m_parent, false); +    if (minecraftVersion != nullptr) { +        vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); +        vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion)); +    } +    else { +        vselect.setEmptyString(tr("No versions are currently available")); +    } +    vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!")); + +    // select recommended build +    for (int i = 0; i < vlist->versions().size(); i++) { +        auto version = vlist->versions().at(i); +        auto reqs = version->requires(); + +        // filter by minecraft version, if the loader depends on a certain version. +        if (minecraftVersion != nullptr) { +            auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) { +                return req.uid == "net.minecraft"; +            }); +            if (iter == reqs.end()) +                continue; +            if (iter->equalsVersion != minecraftVersion) +                continue; +        } + +        // first recommended build we find, we use. +        if (version->isRecommended()) { +            vselect.setCurrentVersion(version->descriptor()); +            break; +        } +    } + +    vselect.exec(); +    return vselect.selectedVersion()->descriptor(); +} + +void AtlUserInteractionSupportImpl::displayMessage(QString message) +{ +    QMessageBox::information(m_parent, tr("Installing"), message); +} diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h new file mode 100644 index 00000000..aa22fc73 --- /dev/null +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org> + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License. + */ + +#pragma once + +#include <QObject> + +#include "modplatform/atlauncher/ATLPackInstallTask.h" + +class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport { +    Q_OBJECT + +public: +    AtlUserInteractionSupportImpl(QWidget* parent); + +private: +    QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; +    QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override; +    void displayMessage(QString message) override; + +private: +    QWidget* m_parent; + +}; diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 1c160fd4..772fd2e0 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-3.0-only  /*   *  PolyMC - Minecraft Launcher - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -64,7 +64,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)  auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool  {      Q_UNUSED(loaders); -    return ver.mcVersion.contains(mineVer); +    return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();  }  // I don't know why, but doing this on the parent class makes it so that diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 86e1a17b..445d0368 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-3.0-only  /*   *  PolyMC - Minecraft Launcher - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index f97536e8..b9804681 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -57,6 +57,17 @@ QVariant ListModel::data(const QModelIndex& index, int role) const      return QVariant();  } +bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ +    int pos = index.row(); +    if (pos >= modpacks.size() || pos < 0 || !index.isValid()) +        return false; + +    modpacks[pos] = value.value<Flame::IndexedPack>(); + +    return true; +} +  void ListModel::logoLoaded(QString logo, QIcon out)  {      m_loadingLogos.removeAll(logo); @@ -210,6 +221,11 @@ void Flame::ListModel::searchRequestFinished()          nextSearchOffset += 25;          searchState = CanPossiblyFetchMore;      } + +    // When you have a Qt build with assertions turned on, proceeding here will abort the application +    if (newList.size() == 0) +        return; +      beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);      modpacks.append(newList);      endInsertRows(); diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h index 536f6add..cab666cc 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModel.h @@ -34,6 +34,7 @@ public:      int rowCount(const QModelIndex &parent) const override;      int columnCount(const QModelIndex &parent) const override;      QVariant data(const QModelIndex &index, int role) const override; +    bool setData(const QModelIndex &index, const QVariant &value, int role) override;      Qt::ItemFlags flags(const QModelIndex &index) const override;      bool canFetchMore(const QModelIndex & parent) const override;      void fetchMore(const QModelIndex & parent) override; diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index b65ace6b..7d2ba2e2 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -107,18 +107,18 @@ void FlamePage::triggerSearch()      listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());  } -void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second) +void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)  {      ui->versionSelectionBox->clear(); -    if (!first.isValid()) { +    if (!curr.isValid()) {          if (isOpened) {              dialog->setSuggestedPack();          }          return;      } -    current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>(); +    current = listModel->data(curr, Qt::UserRole).value<Flame::IndexedPack>();      if (current.versionsLoaded == false) {          qDebug() << "Loading flame modpack versions"; @@ -127,7 +127,7 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)          int addonId = current.addonId;          netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); -        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId] { +        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {              if (addonId != current.addonId) {                  return;  // wrong request              } @@ -151,6 +151,16 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)                  ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));              } +            QVariant current_updated; +            current_updated.setValue(current); + +            if (!listModel->setData(curr, current_updated, Qt::UserRole)) +                qWarning() << "Failed to cache versions for the current pack!"; + +            // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution. +            if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) { +                ui->versionSelectionBox->addItem(tr("No version is available!"), -1); +            }              suggestCurrent();          });          QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { @@ -166,6 +176,11 @@ void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)          suggestCurrent();      } +    // TODO: Check whether it's a connection issue or the project disabled 3rd-party distribution. +    if (current.versionsLoaded && ui->versionSelectionBox->count() < 1) { +        ui->versionSelectionBox->addItem(tr("No version is available!"), -1); +    } +      updateUi();  } @@ -175,7 +190,7 @@ void FlamePage::suggestCurrent()          return;      } -    if (selectedVersion.isEmpty()) { +    if (selectedVersion.isEmpty() || selectedVersion == "-1") {          dialog->setSuggestedPack();          return;      } diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index 8a93bc2e..504d7f7b 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -126,7 +127,7 @@ void FtbPage::suggestCurrent()          return;      } -    dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion)); +    dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this));      for(auto art : selected.art) {          if(art.type == "square") {              QString editedLogoName; diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 06e9db4f..2d135e59 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -46,8 +46,6 @@  #include <BuildConfig.h> -#include <net/NetJob.h> -  namespace LegacyFTB {  FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) @@ -170,7 +168,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const          ((ListModel *)this)->requestLogo(pack.logo);          return icon;      } -    else if(role == Qt::TextColorRole) +    else if(role == Qt::ForegroundRole)      {          if(pack.broken)          { diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 7667d169..6ffbd312 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -2,6 +2,7 @@  /*   *  PolyMC - Minecraft Launcher   *  Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 @@ -151,7 +152,7 @@ void Page::openedImpl()          ftbFetchTask->fetch();          ftbPrivatePacks->load(); -        ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().toList()); +        ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().values());          initialized = true;      }      suggestCurrent(); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui index 15e5d432..f4231d8d 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui @@ -25,7 +25,7 @@          <widget class="QTreeView" name="publicPackList">           <property name="maximumSize">            <size> -           <width>250</width> +           <width>16777215</width>             <height>16777215</height>            </size>           </property> @@ -51,7 +51,7 @@          <widget class="QTreeView" name="thirdPartyPackList">           <property name="maximumSize">            <size> -           <width>250</width> +           <width>16777215</width>             <height>16777215</height>            </size>           </property> @@ -71,7 +71,7 @@          <widget class="QTreeView" name="privatePackList">           <property name="maximumSize">            <size> -           <width>250</width> +           <width>16777215</width>             <height>16777215</height>            </size>           </property> diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp index 0b81ea93..5fa00b9b 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-3.0-only  /*   *  PolyMC - Minecraft Launcher - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h index c39acaa0..94985f63 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-3.0-only  /*   *  PolyMC - Minecraft Launcher - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 39b935a6..3633d575 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -104,6 +104,17 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian      return {};  } +bool ModpackListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ +    int pos = index.row(); +    if (pos >= modpacks.size() || pos < 0 || !index.isValid()) +        return false; + +    modpacks[pos] = value.value<Modrinth::Modpack>(); + +    return true; +} +  void ModpackListModel::performPaginatedSearch()  {      // TODO: Move to standalone API @@ -279,6 +290,10 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)          searchState = CanPossiblyFetchMore;      } +    // When you have a Qt build with assertions turned on, proceeding here will abort the application +    if (newList.size() == 0) +        return; +      beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);      modpacks.append(newList);      endInsertRows(); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 1b4d8da4..6f33e11e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -39,7 +39,6 @@  #include "modplatform/modrinth/ModrinthPackManifest.h"  #include "ui/pages/modplatform/modrinth/ModrinthPage.h" -#include "net/NetJob.h"  class ModPage;  class Version; @@ -64,6 +63,7 @@ class ModpackListModel : public QAbstractListModel {      /* Retrieve information from the model at a given index with the given role */      auto data(const QModelIndex& index, int role) const -> QVariant override; +    bool setData(const QModelIndex &index, const QVariant &value, int role) override;      inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index d8500674..df29c0c3 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -101,18 +101,18 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)      return QObject::eventFilter(watched, event);  } -void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) +void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)  {      ui->versionSelectionBox->clear(); -    if (!first.isValid()) { +    if (!curr.isValid()) {          if (isOpened) {              dialog->setSuggestedPack();          }          return;      } -    current = m_model->data(first, Qt::UserRole).value<Modrinth::Modpack>(); +    current = m_model->data(curr, Qt::UserRole).value<Modrinth::Modpack>();      auto name = current.name;      if (!current.extraInfoLoaded) { @@ -125,7 +125,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)          netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); -        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] { +        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {              if (id != current.id) {                  return;  // wrong request?              } @@ -149,6 +149,13 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)              }              updateUI(); + +            QVariant current_updated; +            current_updated.setValue(current); + +            if (!m_model->setData(curr, current_updated, Qt::UserRole)) +                qWarning() << "Failed to cache extra info for the current pack!"; +              suggestCurrent();          });          QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { @@ -170,7 +177,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)          netJob->addNetAction(              Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); -        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] { +        QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {              if (id != current.id) {                  return;  // wrong request?              } @@ -192,9 +199,18 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)              }              for (auto version : current.versions) { -                ui->versionSelectionBox->addItem(version.version, QVariant(version.id)); +                if (!version.name.contains(version.version)) +                    ui->versionSelectionBox->addItem(QString("%1 — %2").arg(version.name, version.version), QVariant(version.id)); +                else +                    ui->versionSelectionBox->addItem(version.name, QVariant(version.id));              } +            QVariant current_updated; +            current_updated.setValue(current); + +            if (!m_model->setData(curr, current_updated, Qt::UserRole)) +                qWarning() << "Failed to cache versions for the current pack!"; +              suggestCurrent();          });          QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { @@ -205,7 +221,10 @@ void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)      } else {          for (auto version : current.versions) { -            ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id)); +            if (!version.name.contains(version.version)) +                ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id)); +            else +                ui->versionSelectionBox->addItem(version.name, QVariant(version.id));          }          suggestCurrent(); @@ -224,7 +243,7 @@ void ModrinthPage::updateUI()      // TODO: Implement multiple authors with links      text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); -    if(current.extraInfoLoaded) { +    if (current.extraInfoLoaded) {          if (!current.extra.donate.isEmpty()) {              text += "<br><br>" + tr("Donate information: ");              auto donateToStr = [](Modrinth::DonationData& donate) -> QString { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index ae9556ed..6a34701d 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -63,9 +63,6 @@           <height>48</height>          </size>         </property> -       <property name="uniformItemSizes"> -        <bool>true</bool> -       </property>        </widget>       </item>       <item> diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp index 9c9d1e75..742f4f2a 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp @@ -217,6 +217,11 @@ void Technic::ListModel::searchRequestFinished()          return;      }      searchState = Finished; + +    // When you have a Qt build with assertions turned on, proceeding here will abort the application +    if (newList.size() == 0) +        return; +      beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);      modpacks.append(newList);      endInsertRows(); diff --git a/launcher/ui/themes/DarkTheme.cpp b/launcher/ui/themes/DarkTheme.cpp index 31ecd559..712a9d3e 100644 --- a/launcher/ui/themes/DarkTheme.cpp +++ b/launcher/ui/themes/DarkTheme.cpp @@ -31,6 +31,7 @@ QPalette DarkTheme::colorScheme()      darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));      darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));      darkPalette.setColor(QPalette::HighlightedText, Qt::black); +    darkPalette.setColor(QPalette::PlaceholderText, Qt::darkGray);      return fadeInactive(darkPalette, fadeAmount(),  fadeColor());  } diff --git a/launcher/ui/widgets/CustomCommands.cpp b/launcher/ui/widgets/CustomCommands.cpp index 5a718b54..5ab90395 100644 --- a/launcher/ui/widgets/CustomCommands.cpp +++ b/launcher/ui/widgets/CustomCommands.cpp @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-3.0-only  /*   *  PolyMC - Minecraft Launcher - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/widgets/CustomCommands.h b/launcher/ui/widgets/CustomCommands.h index 4a7a17ef..ed10ba95 100644 --- a/launcher/ui/widgets/CustomCommands.h +++ b/launcher/ui/widgets/CustomCommands.h @@ -1,7 +1,7 @@  // SPDX-License-Identifier: GPL-3.0-only  /*   *  PolyMC - Minecraft Launcher - *  Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net> + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   *   *  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 diff --git a/launcher/ui/widgets/LabeledToolButton.cpp b/launcher/ui/widgets/LabeledToolButton.cpp index ab2d3278..f52e49c9 100644 --- a/launcher/ui/widgets/LabeledToolButton.cpp +++ b/launcher/ui/widgets/LabeledToolButton.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *          http://www.apache.org/licenses/LICENSE-2.0   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include <QLabel> @@ -80,9 +100,7 @@ QSize LabeledToolButton::sizeHint() const      if (popupMode() == MenuButtonPopup)          w += style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, this); -    QSize rawSize = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this); -    QSize sizeHint = rawSize.expandedTo(QApplication::globalStrut()); -    return sizeHint; +    return style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), this);  } diff --git a/launcher/ui/widgets/LogView.cpp b/launcher/ui/widgets/LogView.cpp index 26a2a527..9c46438d 100644 --- a/launcher/ui/widgets/LogView.cpp +++ b/launcher/ui/widgets/LogView.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License. + */ +  #include "LogView.h"  #include <QTextBlock>  #include <QScrollBar> @@ -102,7 +137,7 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last)          {              format.setFont(font.value<QFont>());          } -        auto fg = m_model->data(idx, Qt::TextColorRole); +        auto fg = m_model->data(idx, Qt::ForegroundRole);          if(fg.isValid())          {              format.setForeground(fg.value<QColor>()); diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 2af7d731..419ccb66 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -66,7 +66,7 @@ public:  protected:      bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const      { -        const QString pattern = filterRegExp().pattern(); +        const QString pattern = filterRegularExpression().pattern();          const auto model = static_cast<PageModel *>(sourceModel());          const auto page = model->pages().at(sourceRow);          if (!page->shouldDisplay()) @@ -171,7 +171,7 @@ void PageContainer::createUI()      headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));      headerHLayout->setContentsMargins(0, 6, 0, 0); -    m_pageStack->setMargin(0); +    m_pageStack->setContentsMargins(0, 0, 0, 0);      m_pageStack->addWidget(new QWidget(this));      m_layout = new QGridLayout; diff --git a/launcher/ui/widgets/VersionListView.cpp b/launcher/ui/widgets/VersionListView.cpp index aba0b1a1..0e126c65 100644 --- a/launcher/ui/widgets/VersionListView.cpp +++ b/launcher/ui/widgets/VersionListView.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + *  PolyMC - Minecraft Launcher + *  Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>   * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3.   * - *     http://www.apache.org/licenses/LICENSE-2.0 + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details.   * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + *      Copyright 2013-2021 MultiMC Contributors + * + *      Licensed under the Apache License, Version 2.0 (the "License"); + *      you may not use this file except in compliance with the License. + *      You may obtain a copy of the License at + * + *          http://www.apache.org/licenses/LICENSE-2.0 + * + *      Unless required by applicable law or agreed to in writing, software + *      distributed under the License is distributed on an "AS IS" BASIS, + *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + *      See the License for the specific language governing permissions and + *      limitations under the License.   */  #include <QHeaderView> @@ -136,7 +156,7 @@ void VersionListView::paintInfoLabel(QPaintEvent *event) const      auto innerBounds = bounds;      innerBounds.adjust(10, 10, -10, -10); -    QColor background = QApplication::palette().color(QPalette::Foreground); +    QColor background = QApplication::palette().color(QPalette::WindowText);      QColor foreground = QApplication::palette().color(QPalette::Base);      foreground.setAlpha(190);      painter.setFont(font); diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp index 8d5bd12d..79f1e0c9 100644 --- a/launcher/ui/widgets/WideBar.cpp +++ b/launcher/ui/widgets/WideBar.cpp @@ -76,13 +76,20 @@ void WideBar::addSeparator()      m_entries.push_back(entry);  } -void WideBar::insertActionBefore(QAction* before, QAction* action){ -    auto iter = std::find_if(m_entries.begin(), m_entries.end(), [before](BarEntry * entry) { -        return entry->wideAction == before; +auto WideBar::getMatching(QAction* act) -> QList<BarEntry*>::iterator +{ +    auto iter = std::find_if(m_entries.begin(), m_entries.end(), [act](BarEntry * entry) { +        return entry->wideAction == act;      }); -    if(iter == m_entries.end()) { +     +    return iter; +} + +void WideBar::insertActionBefore(QAction* before, QAction* action){ +    auto iter = getMatching(before); +    if(iter == m_entries.end())          return; -    } +      auto entry = new BarEntry();      entry->qAction = insertWidget((*iter)->qAction, new ActionButton(action, this));      entry->wideAction = action; @@ -90,14 +97,24 @@ void WideBar::insertActionBefore(QAction* before, QAction* action){      m_entries.insert(iter, entry);  } +void WideBar::insertActionAfter(QAction* after, QAction* action){ +    auto iter = getMatching(after); +    if(iter == m_entries.end()) +        return; + +    auto entry = new BarEntry(); +    entry->qAction = insertWidget((*(iter+1))->qAction, new ActionButton(action, this)); +    entry->wideAction = action; +    entry->type = BarEntry::Action; +    m_entries.insert(iter + 1, entry); +} +  void WideBar::insertSpacer(QAction* action)  { -    auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) { -        return entry->wideAction == action; -    }); -    if(iter == m_entries.end()) { +    auto iter = getMatching(action); +    if(iter == m_entries.end())          return; -    } +      QWidget* spacer = new QWidget();      spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -107,6 +124,18 @@ void WideBar::insertSpacer(QAction* action)      m_entries.insert(iter, entry);  } +void WideBar::insertSeparator(QAction* before) +{ +    auto iter = getMatching(before); +    if(iter == m_entries.end()) +        return; + +    auto entry = new BarEntry(); +    entry->qAction = QToolBar::insertSeparator(before); +    entry->type = BarEntry::Separator; +    m_entries.insert(iter, entry); +} +  QMenu * WideBar::createContextMenu(QWidget *parent, const QString & title)  {      QMenu *contextMenu = new QMenu(title, parent); diff --git a/launcher/ui/widgets/WideBar.h b/launcher/ui/widgets/WideBar.h index 2b676a8c..8ff62ef2 100644 --- a/launcher/ui/widgets/WideBar.h +++ b/launcher/ui/widgets/WideBar.h @@ -1,27 +1,34 @@  #pragma once -#include <QToolBar>  #include <QAction>  #include <QMap> +#include <QToolBar>  class QMenu; -class WideBar : public QToolBar -{ +class WideBar : public QToolBar {      Q_OBJECT -public: -    explicit WideBar(const QString &title, QWidget * parent = nullptr); -    explicit WideBar(QWidget * parent = nullptr); +   public: +    explicit WideBar(const QString& title, QWidget* parent = nullptr); +    explicit WideBar(QWidget* parent = nullptr);      virtual ~WideBar(); -    void addAction(QAction *action); +    void addAction(QAction* action);      void addSeparator(); -    void insertSpacer(QAction *action); -    void insertActionBefore(QAction *before, QAction *action); -    QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString()); -private: +    void insertSpacer(QAction* action); +    void insertSeparator(QAction* before); +    void insertActionBefore(QAction* before, QAction* action); +    void insertActionAfter(QAction* after, QAction* action); + +    QMenu* createContextMenu(QWidget* parent = nullptr, const QString& title = QString()); + +   private:      struct BarEntry; -    QList<BarEntry *> m_entries; + +    auto getMatching(QAction* act) -> QList<BarEntry*>::iterator; + +   private: +    QList<BarEntry*> m_entries;  }; | 
