ProgressCallback: Add message logging

This commit is contained in:
Stenzek
2025-12-21 00:54:34 +10:00
parent bfb9ba1c6d
commit 461aaeda4d
9 changed files with 187 additions and 64 deletions

View File

@@ -49,7 +49,6 @@
X(PerfMon) \
X(PlatformMisc) \
X(PostProcessing) \
X(ProgressCallback) \
X(PIO) \
X(ReShadeFXShader) \
X(Recompiler) \

View File

@@ -9,8 +9,6 @@
#include <cstdio>
#include <limits>
LOG_CHANNEL(ProgressCallback);
static ProgressCallback s_nullProgressCallbacks;
ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks;
@@ -113,3 +111,18 @@ bool ProgressCallbackWithPrompt::ConfirmPrompt(PromptIcon icon, std::string_view
{
return false;
}
void ProgressCallbackWithPrompt::AppendMessage(std::string_view message)
{
Log::Write(Log::PackCategory(Log::Channel::Host, Log::Level::Info, Log::Color::StrongOrange), message);
}
void ProgressCallbackWithPrompt::SetAutoClose(bool enabled)
{
}
void ProgressCallbackWithPrompt::SetStatusTextAndAppendMessage(std::string_view message)
{
SetStatusText(message);
AppendMessage(message);
}

View File

@@ -11,6 +11,15 @@
#include <memory>
#include <string>
#define MAKE_PROGRESS_CALLBACK_FORWARDER(from, to) \
template<typename... T> \
void from(fmt::format_string<T...> fmt, T&&... args) \
{ \
TinyString str; \
fmt::vformat_to(std::back_inserter(str), fmt, fmt::make_format_args(args...)); \
to(str.view()); \
}
class ProgressCallback
{
public:
@@ -30,19 +39,8 @@ public:
virtual void SetProgressValue(u32 value);
virtual void IncrementProgressValue();
#define MAKE_PROGRESS_CALLBACK_FORWARDER(from, to) \
template<typename... T> \
void from(fmt::format_string<T...> fmt, T&&... args) \
{ \
TinyString str; \
fmt::vformat_to(std::back_inserter(str), fmt, fmt::make_format_args(args...)); \
to(str.view()); \
}
MAKE_PROGRESS_CALLBACK_FORWARDER(FormatStatusText, SetStatusText);
#undef MAKE_PROGRESS_CALLBACK_FORWARDER
protected:
struct State
{
@@ -85,4 +83,15 @@ public:
virtual void AlertPrompt(PromptIcon icon, std::string_view message);
virtual bool ConfirmPrompt(PromptIcon icon, std::string_view message, std::string_view yes_text = {},
std::string_view no_text = {});
virtual void AppendMessage(std::string_view message);
virtual void SetAutoClose(bool enabled);
void SetStatusTextAndAppendMessage(std::string_view message);
MAKE_PROGRESS_CALLBACK_FORWARDER(AppendFormatMessage, AppendMessage);
MAKE_PROGRESS_CALLBACK_FORWARDER(FormatStatusTextAndAppendMessage, SetStatusTextAndAppendMessage);
};
#undef MAKE_PROGRESS_CALLBACK_FORWARDER

View File

@@ -453,8 +453,8 @@ void GameSummaryWidget::onComputeHashClicked()
m_ui.computeHashes->setEnabled(false);
QtAsyncTaskWithProgressDialog::create(this, TRANSLATE_SV("GameSummaryWidget", "Verifying Image"), {}, true, 1, 0,
0.0f, [this, path = m_path](ProgressCallback* progress) {
QtAsyncTaskWithProgressDialog::create(this, TRANSLATE_SV("GameSummaryWidget", "Verifying Image"), {}, false, true, 1,
0, 0.0f, true, [this, path = m_path](ProgressCallback* progress) {
Error error;
CDImageHasher::TrackHashes track_hashes;
const bool result = computeImageHash(path, track_hashes, progress, &error);

View File

@@ -189,7 +189,7 @@ void ISOBrowserWindow::extractFile(const QString& path, IsoReader::ReadMode mode
const std::string status_text =
fmt::format(TRANSLATE_FS("ISOBrowserWindow", "Extracting {}..."), Path::GetFileName(spath));
QtAsyncTaskWithProgressDialog::create(
this, windowTitle().toStdString(), status_text, true, 0, 0, 0.15f,
this, windowTitle().toStdString(), status_text, false, true, 0, 0, 0.15f, true,
[this, spath = std::move(spath), save_path = std::move(save_path), mode](ProgressCallback* const progress) mutable {
Error error;
std::optional<IsoReader::ISODirectoryEntry> de = m_iso.LocateFile(spath, &error);

View File

@@ -1322,7 +1322,8 @@ void MainWindow::onChangeDiscMenuAboutToShow()
QString path = QString::fromStdString(glentry->path);
action->setCheckable(true);
action->setChecked(path == s_locals.current_game_path);
connect(action, &QAction::triggered, [path = std::move(path)]() { g_core_thread->changeDisc(path, false, true); });
connect(action, &QAction::triggered,
[path = std::move(path)]() { g_core_thread->changeDisc(path, false, true); });
}
}
}
@@ -3290,7 +3291,8 @@ void MainWindow::onToolsDownloadAchievementGameIconsTriggered()
{
QtAsyncTaskWithProgressDialog::create(
this, TRANSLATE_STR("GameListWidget", "Download Game Icons"),
TRANSLATE_STR("GameListWidget", "Downloading game icons..."), true, 0, 0, 0.0f, [](ProgressCallback* progress) {
TRANSLATE_STR("GameListWidget", "Downloading game icons..."), false, true, 0, 0, 0.0f, true,
[](ProgressCallback* progress) {
Error error;
const bool result = Achievements::DownloadGameIcons(progress, &error);
return [error = std::move(error), result]() {
@@ -3305,7 +3307,7 @@ void MainWindow::onToolsDownloadAchievementGameIconsTriggered()
void MainWindow::refreshAchievementProgress()
{
QtAsyncTaskWithProgressDialog::create(
this, TRANSLATE_STR("MainWindow", "Refresh Achievement Progress"), {}, true, 0, 0, 0.0f,
this, TRANSLATE_STR("MainWindow", "Refresh Achievement Progress"), {}, false, true, 0, 0, 0.0f, true,
[](ProgressCallback* progress) {
Error error;
const bool result = Achievements::RefreshAllProgressDatabase(progress, &error);

View File

@@ -469,7 +469,7 @@ void QtHost::DownloadFile(QWidget* parent, std::string url, std::string path,
std::string_view(url).substr((url_file_part_pos >= 0) ? (url_file_part_pos + 1) : 0));
QtAsyncTaskWithProgressDialog::create(
parent, TRANSLATE_SV("QtHost", "File Download"), status_text, true, 0, 0, 0.0f,
parent, TRANSLATE_SV("QtHost", "File Download"), status_text, false, true, 0, 0, 0.0f, true,
[url = std::move(url), path = std::move(path),
completion_callback = std::move(completion_callback)](ProgressCallback* const progress) mutable {
Error error;

View File

@@ -7,11 +7,14 @@
#include "common/assert.h"
#include "common/log.h"
#include "common/small_string.h"
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPlainTextEdit>
#include <QtWidgets/QProgressBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QVBoxLayout>
#include <array>
@@ -151,7 +154,7 @@ QtAsyncTaskWithProgress* QtAsyncTaskWithProgress::create(QWidget* const callback
self->m_callback = std::move(callback);
connect(self, &QtAsyncTaskWithProgress::completed, callback_parent, [self]() {
const CompletionCallback& cb = std::get<CompletionCallback>(self->m_callback);
CompletionCallback& cb = std::get<CompletionCallback>(self->m_callback);
if (cb)
cb();
});
@@ -185,15 +188,18 @@ void QtAsyncTaskWithProgress::cancel()
}
QtAsyncTaskWithProgressDialog::QtAsyncTaskWithProgressDialog(const QString& initial_title,
const QString& initial_status_text, bool cancellable,
int range, int value, float show_delay,
QWidget* dialog_parent, WorkCallback callback)
: m_callback(std::move(callback)), m_show_delay(show_delay)
const QString& initial_status_text,
bool initial_message_log, bool initial_cancellable,
int initial_range, int initial_value, float show_delay,
bool auto_close, QWidget* dialog_parent,
WorkCallback callback)
: m_callback(std::move(callback)), m_show_delay(show_delay), m_auto_close(auto_close)
{
m_dialog = new ProgressDialog(initial_title, initial_status_text, cancellable, range, value, *this, dialog_parent);
m_cancellable = cancellable;
m_progress_range = range;
m_progress_value = value;
m_dialog = new ProgressDialog(initial_title, initial_status_text, initial_message_log, initial_cancellable,
initial_range, initial_value, this, dialog_parent);
m_cancellable = initial_cancellable;
m_progress_range = initial_range;
m_progress_value = initial_value;
if (show_delay <= 0.0f)
{
@@ -206,16 +212,24 @@ QtAsyncTaskWithProgressDialog::~QtAsyncTaskWithProgressDialog()
{
if (m_dialog)
{
// should null out itself
delete m_dialog;
DebugAssert(!m_dialog);
if (m_auto_close)
{
// should null out itself
delete m_dialog;
DebugAssert(!m_dialog);
}
else
{
m_dialog->taskFinished();
}
}
}
QtAsyncTaskWithProgressDialog::ProgressDialog::ProgressDialog(const QString& initial_title,
const QString& initial_status_text, bool cancellable,
int range, int value, QtAsyncTaskWithProgressDialog& task,
QWidget* parent)
const QString& initial_status_text,
bool initial_message_log, bool initial_cancellable,
int initial_range, int initial_value,
QtAsyncTaskWithProgressDialog* task, QWidget* parent)
: QDialog(parent), m_task(task)
{
if (!initial_title.isEmpty())
@@ -224,13 +238,13 @@ QtAsyncTaskWithProgressDialog::ProgressDialog::ProgressDialog(const QString& ini
setWindowTitle(QStringLiteral("DuckStation"));
setWindowFlag(Qt::CustomizeWindowHint, true);
setWindowFlag(Qt::WindowCloseButtonHint, cancellable);
setWindowFlag(Qt::WindowCloseButtonHint, initial_cancellable);
setWindowModality(Qt::WindowModal);
setMinimumSize(MINIMUM_WIDTH, cancellable ? MINIMUM_HEIGHT_WITH_CANCEL : MINIMUM_HEIGHT_WITHOUT_CANCEL);
setMinimumWidth(MINIMUM_WIDTH);
m_progress_bar = new QProgressBar(this);
m_progress_bar->setRange(0, range);
m_progress_bar->setValue(value);
m_progress_bar->setRange(0, initial_range);
m_progress_bar->setValue(initial_value);
m_status_label = new QLabel(this);
m_status_label->setAlignment(Qt::AlignCenter);
@@ -239,19 +253,27 @@ QtAsyncTaskWithProgressDialog::ProgressDialog::ProgressDialog(const QString& ini
m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::close);
m_button_box->setVisible(cancellable);
m_button_box->setVisible(initial_cancellable);
QVBoxLayout* const layout = new QVBoxLayout(this);
layout->setSpacing(8);
layout->addWidget(m_status_label);
layout->addWidget(m_progress_bar);
layout->addWidget(m_button_box);
m_layout = new QVBoxLayout(this);
m_layout->setSpacing(8);
m_layout->addWidget(m_status_label);
m_layout->addWidget(m_progress_bar);
m_layout->addWidget(m_button_box);
if (initial_message_log)
addMessageLog();
updateMinimumHeight();
}
QtAsyncTaskWithProgressDialog::ProgressDialog::~ProgressDialog()
{
DebugAssert(m_task.m_dialog == this);
m_task.m_dialog = nullptr;
if (m_task)
{
DebugAssert(m_task->m_dialog == this);
m_task->m_dialog = nullptr;
}
}
void QtAsyncTaskWithProgressDialog::ProgressDialog::setCancellable(bool cancellable)
@@ -260,7 +282,7 @@ void QtAsyncTaskWithProgressDialog::ProgressDialog::setCancellable(bool cancella
return;
setWindowFlag(Qt::WindowCloseButtonHint, cancellable);
setMinimumHeight(cancellable ? MINIMUM_HEIGHT_WITH_CANCEL : MINIMUM_HEIGHT_WITHOUT_CANCEL);
updateMinimumHeight();
m_button_box->setVisible(cancellable);
}
@@ -271,24 +293,59 @@ void QtAsyncTaskWithProgressDialog::ProgressDialog::closeEvent(QCloseEvent* even
QDialog::closeEvent(event);
}
void QtAsyncTaskWithProgressDialog::ProgressDialog::updateMinimumHeight()
{
setMinimumHeight((m_button_box->isVisible() ? MINIMUM_HEIGHT_WITH_CANCEL : MINIMUM_HEIGHT_WITHOUT_CANCEL) +
(m_message_log ? MESSAGE_LOG_HEIGHT : 0));
}
void QtAsyncTaskWithProgressDialog::ProgressDialog::cancelled()
{
m_task.m_ts_cancelled.store(true, std::memory_order_release);
if (m_task)
m_task->m_ts_cancelled.store(true, std::memory_order_release);
}
void QtAsyncTaskWithProgressDialog::ProgressDialog::taskFinished()
{
DebugAssert(m_task);
m_task = nullptr;
m_button_box->setStandardButtons(QDialogButtonBox::Close);
if (!m_button_box->isVisible())
{
m_button_box->setVisible(true);
updateMinimumHeight();
}
}
void QtAsyncTaskWithProgressDialog::ProgressDialog::addMessageLog()
{
DebugAssert(!m_message_log);
m_message_log = new QPlainTextEdit(this);
m_message_log->setReadOnly(true);
m_message_log->setMinimumHeight(100);
m_layout->insertWidget(2, m_message_log);
}
QtAsyncTaskWithProgressDialog* QtAsyncTaskWithProgressDialog::create(QWidget* parent, std::string_view initial_title,
std::string_view initial_status_text,
bool cancellable, int range, int value,
float show_delay, WorkCallback callback)
bool initial_message_log, bool initial_cancellable,
int initial_range, int initial_value,
float show_delay, bool auto_close,
WorkCallback callback)
{
DebugAssert(parent);
// NOTE: Must get connected before queuing, because otherwise you risk a race.
QtAsyncTaskWithProgressDialog* task = new QtAsyncTaskWithProgressDialog(
QtUtils::StringViewToQString(initial_title), QtUtils::StringViewToQString(initial_status_text), cancellable, range,
value, show_delay, parent, std::move(callback));
connect(task, &QtAsyncTaskWithProgressDialog::completed, parent,
[task]() { std::get<CompletionCallback>(task->m_callback)(); });
QtUtils::StringViewToQString(initial_title), QtUtils::StringViewToQString(initial_status_text), initial_message_log,
initial_cancellable, initial_range, initial_value, show_delay, auto_close, parent, std::move(callback));
connect(task, &QtAsyncTaskWithProgressDialog::completed, parent, [task]() {
CompletionCallback& cb = std::get<CompletionCallback>(task->m_callback);
if (cb)
cb();
});
Host::QueueAsyncTask([task]() {
task->m_callback = std::get<WorkCallback>(task->m_callback)(task);
@@ -304,7 +361,7 @@ QtAsyncTaskWithProgressDialog* QtAsyncTaskWithProgressDialog::create(QWidget* pa
QtAsyncTaskWithProgressDialog* QtAsyncTaskWithProgressDialog::create(QWidget* parent, float show_delay,
WorkCallback callback)
{
return create(parent, {}, {}, false, 0, 1, show_delay, std::move(callback));
return create(parent, {}, {}, false, false, 0, 1, show_delay, true, std::move(callback));
}
void QtAsyncTaskWithProgressDialog::cancel()
@@ -485,6 +542,34 @@ bool QtAsyncTaskWithProgressDialog::ConfirmPrompt(PromptIcon icon, std::string_v
return m_prompt_result.load(std::memory_order_relaxed);
}
void QtAsyncTaskWithProgressDialog::AppendMessage(std::string_view message)
{
Log::Write(Log::PackCategory(Log::Channel::Host, Log::Level::Info, Log::Color::StrongOrange), message);
EnsureShown();
Host::RunOnUIThread([this, message = QtUtils::StringViewToQString(message)]() {
if (!m_dialog)
return;
if (!m_dialog->m_message_log)
m_dialog->addMessageLog();
m_dialog->m_message_log->appendPlainText(message);
QScrollBar* const scrollbar = m_dialog->m_message_log->verticalScrollBar();
const bool cursor_at_end = m_dialog->m_message_log->textCursor().atEnd();
const bool scroll_at_end = scrollbar->sliderPosition() == scrollbar->maximum();
if (cursor_at_end && scroll_at_end)
m_dialog->m_message_log->centerCursor();
});
}
void QtAsyncTaskWithProgressDialog::SetAutoClose(bool enabled)
{
m_auto_close = enabled;
}
void QtAsyncTaskWithProgressDialog::EnsureShown()
{
if (!m_shown)

View File

@@ -14,6 +14,8 @@ class QDialogButtonBox;
class QLabel;
class QProgressBar;
class QPushButton;
class QPlainTextEdit;
class QVBoxLayout;
class QtProgressCallback final : public QObject, public ProgressCallback
{
@@ -97,8 +99,9 @@ public:
using WorkCallback = std::function<CompletionCallback(ProgressCallbackWithPrompt*)>;
static QtAsyncTaskWithProgressDialog* create(QWidget* parent, std::string_view initial_title,
std::string_view initial_status_text, bool cancellable, int range,
int value, float show_delay, WorkCallback callback);
std::string_view initial_status_text, bool initial_message_log,
bool initial_cancellable, int initial_range, int initial_value,
float show_delay, bool auto_close, WorkCallback callback);
static QtAsyncTaskWithProgressDialog* create(QWidget* parent, float show_delay, WorkCallback callback);
/// Asynchronously cancel the task. Should only be called from the UI thread.
@@ -115,8 +118,9 @@ private:
friend QtAsyncTaskWithProgressDialog;
public:
ProgressDialog(const QString& initial_title, const QString& initial_status_text, bool cancellable, int range,
int value, QtAsyncTaskWithProgressDialog& task, QWidget* parent);
ProgressDialog(const QString& initial_title, const QString& initial_status_text, bool initial_message_log,
bool initial_cancellable, int initial_range, int initial_value, QtAsyncTaskWithProgressDialog* task,
QWidget* parent);
~ProgressDialog() override;
void setCancellable(bool cancellable);
@@ -128,20 +132,28 @@ private:
static constexpr int MINIMUM_WIDTH = 500;
static constexpr int MINIMUM_HEIGHT_WITHOUT_CANCEL = 70;
static constexpr int MINIMUM_HEIGHT_WITH_CANCEL = 100;
static constexpr int MESSAGE_LOG_HEIGHT = 150;
void updateMinimumHeight();
void cancelled();
void taskFinished();
void addMessageLog();
QtAsyncTaskWithProgressDialog& m_task;
QtAsyncTaskWithProgressDialog* m_task;
QLabel* m_status_label = nullptr;
QProgressBar* m_progress_bar = nullptr;
QDialogButtonBox* m_button_box = nullptr;
QPlainTextEdit* m_message_log = nullptr;
QVBoxLayout* m_layout = nullptr;
};
friend ProgressDialog;
// constructor hidden, clients should not be creating this directly
QtAsyncTaskWithProgressDialog(const QString& initial_title, const QString& initial_status_text, bool cancellable,
int range, int value, float show_delay, QWidget* dialog_parent, WorkCallback callback);
QtAsyncTaskWithProgressDialog(const QString& initial_title, const QString& initial_status_text,
bool initial_message_log, bool initial_cancellable, int initial_range,
int initial_value, float show_delay, bool auto_close, QWidget* dialog_parent,
WorkCallback callback);
~QtAsyncTaskWithProgressDialog();
// progress callback overrides
@@ -155,6 +167,8 @@ private:
void AlertPrompt(PromptIcon icon, std::string_view message) override;
bool ConfirmPrompt(PromptIcon icon, std::string_view message, std::string_view yes_text = {},
std::string_view no_text = {}) override;
void AppendMessage(std::string_view message) override;
void SetAutoClose(bool enabled) override;
void CheckForDelayedShow();
void EnsureShown();
@@ -165,6 +179,7 @@ private:
Timer m_show_timer;
float m_show_delay;
bool m_shown = false;
bool m_auto_close = true;
std::atomic_bool m_ts_cancelled{false};
std::atomic_bool m_prompt_result{false};
std::atomic_flag m_prompt_waiting = ATOMIC_FLAG_INIT;