#pragma once #include #include #include #include #include "Common/Flag.h" class ParallelProgressDialog final : public QObject { Q_OBJECT public: // Only use this from the main thread template ParallelProgressDialog(Args&&... args) : m_dialog{std::forward(args)...} { setParent(m_dialog.parent()); m_dialog.setWindowFlags(m_dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); ConnectSignalsAndSlots(); } // Only use this from the main thread QProgressDialog* GetRaw() { return &m_dialog; } // All of the following can be called from any thread void Cancel() { emit CancelSignal(); } void Reset() { emit ResetSignal(); } void SetCancelButtonText(const QString& text) { emit SetCancelButtonTextSignal(text); } void SetLabelText(const QString& text) { emit SetLabelTextSignal(text); } void SetMaximum(int maximum) { emit SetMaximumSignal(maximum); } void SetMinimum(int minimum) { emit SetMinimumSignal(minimum); } void SetMinimumDuration(int ms) { emit SetMinimumDurationSignal(ms); } void SetRange(int minimum, int maximum) { emit SetRangeSignal(minimum, maximum); } void SetValue(int progress) { emit SetValueSignal(progress); } // Can be called from any thread bool WasCanceled() { return m_was_cancelled.IsSet(); } signals: void CancelSignal(); void ResetSignal(); void SetCancelButtonTextSignal(const QString& cancel_button_text); void SetLabelTextSignal(const QString& text); void SetMaximumSignal(int maximum); void SetMinimumSignal(int minimum); void SetMinimumDurationSignal(int ms); void SetRangeSignal(int minimum, int maximum); void SetValueSignal(int progress); void Canceled(); void Finished(int result); private slots: void OnCancelled() { m_was_cancelled.Set(); } void SetValueSlot(int progress) { // Normally we would've been able to just call setValue instead of having this wrapper // around it, but due to the https://bugreports.qt.io/browse/QTBUG-10561 stack overflow, // we can't. Short summary of the stack overflow: setValue calls processEvents, // which calls SetValueSlot if there is another SetValueSlot event in the queue, // which would call setValue if it wasn't for the check below, and so on. m_last_received_progress = progress; if (m_is_setting_value) return; m_is_setting_value = true; int last_set_progress; do { last_set_progress = m_last_received_progress; m_dialog.setValue(m_last_received_progress); } while (m_last_received_progress != last_set_progress); m_is_setting_value = false; } private: template void ConnectSignal(Func1 signal, Func2 slot) { QObject::connect(this, signal, &m_dialog, slot); } template void ConnectSlot(Func1 signal, Func2 slot) { QObject::connect(&m_dialog, signal, this, slot); } void ConnectSignalsAndSlots() { ConnectSignal(&ParallelProgressDialog::CancelSignal, &QProgressDialog::cancel); ConnectSignal(&ParallelProgressDialog::ResetSignal, &QProgressDialog::reset); ConnectSignal(&ParallelProgressDialog::SetCancelButtonTextSignal, &QProgressDialog::setCancelButtonText); ConnectSignal(&ParallelProgressDialog::SetLabelTextSignal, &QProgressDialog::setLabelText); ConnectSignal(&ParallelProgressDialog::SetMaximumSignal, &QProgressDialog::setMaximum); ConnectSignal(&ParallelProgressDialog::SetMinimumSignal, &QProgressDialog::setMinimum); ConnectSignal(&ParallelProgressDialog::SetMinimumDurationSignal, &QProgressDialog::setMinimumDuration); ConnectSignal(&ParallelProgressDialog::SetRangeSignal, &QProgressDialog::setRange); QObject::connect(this, &ParallelProgressDialog::SetValueSignal, this, &ParallelProgressDialog::SetValueSlot); ConnectSlot(&QProgressDialog::canceled, &ParallelProgressDialog::OnCancelled); ConnectSlot(&QProgressDialog::canceled, &ParallelProgressDialog::Canceled); ConnectSlot(&QProgressDialog::finished, &ParallelProgressDialog::Finished); } QProgressDialog m_dialog; Common::Flag m_was_cancelled; int m_last_received_progress; bool m_is_setting_value = false; };