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; }; |