From e6d7e5cdae9ecb058cbd3c5b00013cd67472d658 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 23 Nov 2025 17:23:36 +0000 Subject: [PATCH 1/4] Backport new NetJob failure dialog from Octol1ttle's libcurl PR Co-authored-by: Octol1ttle Signed-off-by: TheKodeToad --- launcher/CMakeLists.txt | 3 + launcher/net/NetJob.cpp | 22 ++--- .../ui/dialogs/NetworkJobFailedDialog.cpp | 65 +++++++++++++ launcher/ui/dialogs/NetworkJobFailedDialog.h | 48 ++++++++++ launcher/ui/dialogs/NetworkJobFailedDialog.ui | 94 +++++++++++++++++++ 5 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 launcher/ui/dialogs/NetworkJobFailedDialog.cpp create mode 100644 launcher/ui/dialogs/NetworkJobFailedDialog.h create mode 100644 launcher/ui/dialogs/NetworkJobFailedDialog.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index dbbe9e85d..62f6f3f34 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1065,6 +1065,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/ImportResourceDialog.h ui/dialogs/MSALoginDialog.cpp ui/dialogs/MSALoginDialog.h + ui/dialogs/NetworkJobFailedDialog.cpp + ui/dialogs/NetworkJobFailedDialog.h ui/dialogs/NewComponentDialog.cpp ui/dialogs/NewComponentDialog.h ui/dialogs/NewInstanceDialog.cpp @@ -1247,6 +1249,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/ProfileSetupDialog.ui ui/dialogs/ProgressDialog.ui ui/dialogs/NewInstanceDialog.ui + ui/dialogs/NetworkJobFailedDialog.ui ui/dialogs/NewComponentDialog.ui ui/dialogs/NewsDialog.ui ui/dialogs/ProfileSelectDialog.ui diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index a40219962..05bbdfa23 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -42,7 +42,7 @@ #if defined(LAUNCHER_APPLICATION) #include "Application.h" #include "settings/SettingsObject.h" -#include "ui/dialogs/CustomMessageBox.h" +#include "ui/dialogs/NetworkJobFailedDialog.h" #endif NetJob::NetJob(QString job_name, QNetworkAccessManager* network, int max_concurrent) : ConcurrentTask(job_name), m_network(network) @@ -164,23 +164,23 @@ void NetJob::emitFailed(QString reason) if (APPLICATION_DYN && m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) { m_manual_try++; - auto response = CustomMessageBox::selectable(nullptr, "Confirm retry", - "The tasks failed.\n" - "Failed urls\n" + - getFailedFiles().join("\n\t") + - ".\n" - "If this continues to happen please check the logs of the application.\n" - "Do you want to retry?", - QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) - ->exec(); + auto failed = getFailedActions(); + auto dialog = NetworkJobFailedDialog(objectName(), m_try, m_done.size(), failed.size(), nullptr); - if (response == QMessageBox::Yes) { + int i = 0; + for (const auto& request : failed) { + dialog.addFailedRequest(i, request->url(), request->errorString()); + ++i; + } + + if (dialog.exec() == QDialog::Accepted) { m_try = 0; executeNextSubTask(); return; } } #endif + ConcurrentTask::emitFailed(reason); } diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.cpp b/launcher/ui/dialogs/NetworkJobFailedDialog.cpp new file mode 100644 index 000000000..314c984d3 --- /dev/null +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Octol1ttle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "NetworkJobFailedDialog.h" + +#include "ui_NetworkJobFailedDialog.h" + +NetworkJobFailedDialog::NetworkJobFailedDialog(QString jobName, int attempts, int requests, int failed, QWidget* parent) : QDialog(parent), ui(new Ui::NetworkJobFailedDialog) +{ + ui->setupUi(this); + ui->failLabel->setText(ui->failLabel->text().arg(jobName)); + if (failed == requests) { + ui->requestCountLabel->setText(tr("All %1 requests have failed after %2 attempts").arg(failed).arg(attempts)); + } else if (failed < requests / 2) { + ui->requestCountLabel->setText(tr("Out of %1 requests, %2 have failed after %3 attempts").arg(requests).arg(failed).arg(attempts)); + } else { + ui->requestCountLabel->setText(tr("Out of %1 requests, only %2 succeeded after %3 attempts").arg(requests).arg(requests - failed).arg(attempts)); + } + ui->detailsTable->setRowCount(failed); + + setShowDetails(failed < 5); + + connect(ui->dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +NetworkJobFailedDialog::~NetworkJobFailedDialog() +{ + delete ui; +} + +void NetworkJobFailedDialog::addFailedRequest(int row, QUrl url, QString error) const +{ + const auto urlItem = new QTableWidgetItem(url.toString()); + const auto errorItem = new QTableWidgetItem(error); + ui->detailsTable->setItem(row, 0, urlItem); + ui->detailsTable->setItem(row, 1, errorItem); +} + +void NetworkJobFailedDialog::setShowDetails(bool showDetails) +{ + m_showDetails = showDetails; + ui->detailsTable->setVisible(m_showDetails); + ui->detailsButton->setText(!m_showDetails ? tr("Show details") : tr("Hide details")); +} + +void NetworkJobFailedDialog::on_detailsButton_clicked() +{ + setShowDetails(!m_showDetails); +} diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.h b/launcher/ui/dialogs/NetworkJobFailedDialog.h new file mode 100644 index 000000000..e0bd01b00 --- /dev/null +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2025 Octol1ttle + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class NetworkJobFailedDialog; +} +QT_END_NAMESPACE + +class NetworkJobFailedDialog : public QDialog { + Q_OBJECT + + public: + explicit NetworkJobFailedDialog(QString jobName, int attempts, int requests, int failed, QWidget* parent = nullptr); + ~NetworkJobFailedDialog() override; + + void addFailedRequest(int row, QUrl url, QString error) const; + + private: + void setShowDetails(bool showDetails); + + private slots: + void on_detailsButton_clicked(); + + private: + Ui::NetworkJobFailedDialog* ui; + + bool m_showDetails = false; +}; diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.ui b/launcher/ui/dialogs/NetworkJobFailedDialog.ui new file mode 100644 index 000000000..50deb5fb7 --- /dev/null +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.ui @@ -0,0 +1,94 @@ + + + NetworkJobFailedDialog + + + + 0 + 0 + 450 + 350 + + + + Network error + + + + + + + + + + 0 + 0 + + + + A network operation has failed: %1 + + + + + + + + 0 + 0 + + + + TextLabel + + + + + + + PushButton + + + + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + + URL + + + + + Error + + + + + + + + + 0 + 0 + + + + What would you like to do? + + + + + + + QDialogButtonBox::StandardButton::Abort|QDialogButtonBox::StandardButton::Retry + + + + + + + + From 4bc72ccca47d121a4979c3926eefa3d2464dc52f Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 23 Nov 2025 20:08:55 +0000 Subject: [PATCH 2/4] My tweaks Use a tree view instead of table view, remove toggle button (janky) Signed-off-by: TheKodeToad --- launcher/net/NetJob.cpp | 4 +- .../ui/dialogs/NetworkJobFailedDialog.cpp | 47 ++++++++----------- launcher/ui/dialogs/NetworkJobFailedDialog.h | 12 +---- launcher/ui/dialogs/NetworkJobFailedDialog.ui | 14 ++---- 4 files changed, 27 insertions(+), 50 deletions(-) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 05bbdfa23..3e310d0dc 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -167,10 +167,8 @@ void NetJob::emitFailed(QString reason) auto failed = getFailedActions(); auto dialog = NetworkJobFailedDialog(objectName(), m_try, m_done.size(), failed.size(), nullptr); - int i = 0; for (const auto& request : failed) { - dialog.addFailedRequest(i, request->url(), request->errorString()); - ++i; + dialog.addFailedRequest(request->url(), request->errorString()); } if (dialog.exec() == QDialog::Accepted) { diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.cpp b/launcher/ui/dialogs/NetworkJobFailedDialog.cpp index 314c984d3..6dbbedf57 100644 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.cpp +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.cpp @@ -20,46 +20,37 @@ #include "ui_NetworkJobFailedDialog.h" -NetworkJobFailedDialog::NetworkJobFailedDialog(QString jobName, int attempts, int requests, int failed, QWidget* parent) : QDialog(parent), ui(new Ui::NetworkJobFailedDialog) +#include + +NetworkJobFailedDialog::NetworkJobFailedDialog(QString jobName, int attempts, int requests, int failed, QWidget* parent) + : QDialog(parent), m_ui(new Ui::NetworkJobFailedDialog) { - ui->setupUi(this); - ui->failLabel->setText(ui->failLabel->text().arg(jobName)); + m_ui->setupUi(this); + m_ui->failLabel->setText(m_ui->failLabel->text().arg(jobName)); if (failed == requests) { - ui->requestCountLabel->setText(tr("All %1 requests have failed after %2 attempts").arg(failed).arg(attempts)); + m_ui->requestCountLabel->setText(tr("All %1 requests have failed after %2 attempts").arg(failed).arg(attempts)); } else if (failed < requests / 2) { - ui->requestCountLabel->setText(tr("Out of %1 requests, %2 have failed after %3 attempts").arg(requests).arg(failed).arg(attempts)); + m_ui->requestCountLabel->setText( + tr("Out of %1 requests, %2 have failed after %3 attempts").arg(requests).arg(failed).arg(attempts)); } else { - ui->requestCountLabel->setText(tr("Out of %1 requests, only %2 succeeded after %3 attempts").arg(requests).arg(requests - failed).arg(attempts)); + m_ui->requestCountLabel->setText( + tr("Out of %1 requests, only %2 succeeded after %3 attempts").arg(requests).arg(requests - failed).arg(attempts)); } - ui->detailsTable->setRowCount(failed); - setShowDetails(failed < 5); + m_ui->detailsTable->header()->setSectionResizeMode(0, QHeaderView::Stretch); + m_ui->detailsTable->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - connect(ui->dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_ui->dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } NetworkJobFailedDialog::~NetworkJobFailedDialog() { - delete ui; + delete m_ui; } -void NetworkJobFailedDialog::addFailedRequest(int row, QUrl url, QString error) const +void NetworkJobFailedDialog::addFailedRequest(QUrl url, QString error) const { - const auto urlItem = new QTableWidgetItem(url.toString()); - const auto errorItem = new QTableWidgetItem(error); - ui->detailsTable->setItem(row, 0, urlItem); - ui->detailsTable->setItem(row, 1, errorItem); -} - -void NetworkJobFailedDialog::setShowDetails(bool showDetails) -{ - m_showDetails = showDetails; - ui->detailsTable->setVisible(m_showDetails); - ui->detailsButton->setText(!m_showDetails ? tr("Show details") : tr("Hide details")); -} - -void NetworkJobFailedDialog::on_detailsButton_clicked() -{ - setShowDetails(!m_showDetails); + auto item = new QTreeWidgetItem(m_ui->detailsTable, { url.toString(), error }); + m_ui->detailsTable->addTopLevelItem(item); } diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.h b/launcher/ui/dialogs/NetworkJobFailedDialog.h index e0bd01b00..eff117679 100644 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.h +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.h @@ -33,16 +33,8 @@ class NetworkJobFailedDialog : public QDialog { explicit NetworkJobFailedDialog(QString jobName, int attempts, int requests, int failed, QWidget* parent = nullptr); ~NetworkJobFailedDialog() override; - void addFailedRequest(int row, QUrl url, QString error) const; + void addFailedRequest(QUrl url, QString error) const; private: - void setShowDetails(bool showDetails); - - private slots: - void on_detailsButton_clicked(); - - private: - Ui::NetworkJobFailedDialog* ui; - - bool m_showDetails = false; + Ui::NetworkJobFailedDialog* m_ui; }; diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.ui b/launcher/ui/dialogs/NetworkJobFailedDialog.ui index 50deb5fb7..26e364da6 100644 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.ui +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.ui @@ -39,22 +39,18 @@ - TextLabel + (request count) - - - PushButton - - - - - + QAbstractItemView::EditTrigger::NoEditTriggers + + false + URL From 7bb746dfab5c59e77f50bc12fff1f4a73ab40b87 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Wed, 26 Nov 2025 12:05:37 +0000 Subject: [PATCH 3/4] Safer dialog Signed-off-by: TheKodeToad --- launcher/net/NetJob.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 3e310d0dc..dae149ab3 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -165,17 +165,25 @@ void NetJob::emitFailed(QString reason) if (APPLICATION_DYN && m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) { m_manual_try++; auto failed = getFailedActions(); - auto dialog = NetworkJobFailedDialog(objectName(), m_try, m_done.size(), failed.size(), nullptr); + auto dialog = new NetworkJobFailedDialog(objectName(), m_try, m_done.size(), failed.size(), nullptr); + dialog->setAttribute(Qt::WA_DeleteOnClose); for (const auto& request : failed) { - dialog.addFailedRequest(request->url(), request->errorString()); + dialog->addFailedRequest(request->url(), request->errorString()); } - if (dialog.exec() == QDialog::Accepted) { - m_try = 0; - executeNextSubTask(); - return; - } + dialog->open(); + + connect(dialog, &QDialog::finished, this, [this, reason = std::move(reason)](int result) { + if (result == QDialog::Accepted) { + m_try = 0; + executeNextSubTask(); + } else { + ConcurrentTask::emitFailed(reason); + } + }); + + return; } #endif From 3a48d13c071c51cc83847660ac730f5dc01e0efa Mon Sep 17 00:00:00 2001 From: Octol1ttle Date: Thu, 26 Mar 2026 10:43:20 +0500 Subject: [PATCH 4/4] feat(NetworkJobFailedDialog): implement URL copying Signed-off-by: Octol1ttle --- .../ui/dialogs/NetworkJobFailedDialog.cpp | 33 ++++++++++++++++--- launcher/ui/dialogs/NetworkJobFailedDialog.h | 7 ++-- launcher/ui/dialogs/NetworkJobFailedDialog.ui | 9 +++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.cpp b/launcher/ui/dialogs/NetworkJobFailedDialog.cpp index 6dbbedf57..2d8efb866 100644 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.cpp +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.cpp @@ -18,11 +18,14 @@ #include "NetworkJobFailedDialog.h" +#include +#include +#include +#include + #include "ui_NetworkJobFailedDialog.h" -#include - -NetworkJobFailedDialog::NetworkJobFailedDialog(QString jobName, int attempts, int requests, int failed, QWidget* parent) +NetworkJobFailedDialog::NetworkJobFailedDialog(const QString& jobName, const int attempts, const int requests, const int failed, QWidget* parent) : QDialog(parent), m_ui(new Ui::NetworkJobFailedDialog) { m_ui->setupUi(this); @@ -40,6 +43,12 @@ NetworkJobFailedDialog::NetworkJobFailedDialog(QString jobName, int attempts, in m_ui->detailsTable->header()->setSectionResizeMode(0, QHeaderView::Stretch); m_ui->detailsTable->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + const auto* copyShortcut = new QShortcut(QKeySequence::Copy, m_ui->detailsTable); + connect(copyShortcut, &QShortcut::activated, this, &NetworkJobFailedDialog::copyUrl); + + const auto* copyButton = m_ui->dialogButtonBox->addButton(tr("Copy URL"), QDialogButtonBox::ActionRole); + connect(copyButton, &QPushButton::clicked, this, &NetworkJobFailedDialog::copyUrl); + connect(m_ui->dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(m_ui->dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } @@ -49,8 +58,22 @@ NetworkJobFailedDialog::~NetworkJobFailedDialog() delete m_ui; } -void NetworkJobFailedDialog::addFailedRequest(QUrl url, QString error) const +void NetworkJobFailedDialog::addFailedRequest(const QUrl& url, QString error) const { - auto item = new QTreeWidgetItem(m_ui->detailsTable, { url.toString(), error }); + auto* item = new QTreeWidgetItem(m_ui->detailsTable, { url.toString(), std::move(error) }); m_ui->detailsTable->addTopLevelItem(item); + if (m_ui->detailsTable->selectedItems().isEmpty()) { + m_ui->detailsTable->setCurrentItem(item); + } +} + +void NetworkJobFailedDialog::copyUrl() const +{ + auto items = m_ui->detailsTable->selectedItems(); + if (items.isEmpty()) { + return; + } + + auto* clipboard = QGuiApplication::clipboard(); + clipboard->setText(items.first()->text(0)); } diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.h b/launcher/ui/dialogs/NetworkJobFailedDialog.h index eff117679..9bfb7c439 100644 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.h +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.h @@ -30,10 +30,13 @@ class NetworkJobFailedDialog : public QDialog { Q_OBJECT public: - explicit NetworkJobFailedDialog(QString jobName, int attempts, int requests, int failed, QWidget* parent = nullptr); + explicit NetworkJobFailedDialog(const QString& jobName, int attempts, int requests, int failed, QWidget* parent = nullptr); ~NetworkJobFailedDialog() override; - void addFailedRequest(QUrl url, QString error) const; + void addFailedRequest(const QUrl& url, QString error) const; + + private slots: + void copyUrl() const; private: Ui::NetworkJobFailedDialog* m_ui; diff --git a/launcher/ui/dialogs/NetworkJobFailedDialog.ui b/launcher/ui/dialogs/NetworkJobFailedDialog.ui index 26e364da6..b133052eb 100644 --- a/launcher/ui/dialogs/NetworkJobFailedDialog.ui +++ b/launcher/ui/dialogs/NetworkJobFailedDialog.ui @@ -48,6 +48,15 @@ QAbstractItemView::EditTrigger::NoEditTriggers + + true + + + QAbstractItemView::ScrollMode::ScrollPerPixel + + + 0 + false