Add generic progress dialog, and derived download dialog

This commit is contained in:
Dan Paulat 2024-03-25 00:30:28 -05:00
parent 4ac2626b65
commit 2f397106f9
6 changed files with 374 additions and 0 deletions

View file

@ -221,6 +221,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
source/scwx/qt/ui/animation_dock_widget.hpp source/scwx/qt/ui/animation_dock_widget.hpp
source/scwx/qt/ui/collapsible_group.hpp source/scwx/qt/ui/collapsible_group.hpp
source/scwx/qt/ui/county_dialog.hpp source/scwx/qt/ui/county_dialog.hpp
source/scwx/qt/ui/download_dialog.hpp
source/scwx/qt/ui/flow_layout.hpp source/scwx/qt/ui/flow_layout.hpp
source/scwx/qt/ui/imgui_debug_dialog.hpp source/scwx/qt/ui/imgui_debug_dialog.hpp
source/scwx/qt/ui/imgui_debug_widget.hpp source/scwx/qt/ui/imgui_debug_widget.hpp
@ -232,6 +233,7 @@ set(HDR_UI source/scwx/qt/ui/about_dialog.hpp
source/scwx/qt/ui/open_url_dialog.hpp source/scwx/qt/ui/open_url_dialog.hpp
source/scwx/qt/ui/placefile_dialog.hpp source/scwx/qt/ui/placefile_dialog.hpp
source/scwx/qt/ui/placefile_settings_widget.hpp source/scwx/qt/ui/placefile_settings_widget.hpp
source/scwx/qt/ui/progress_dialog.hpp
source/scwx/qt/ui/radar_site_dialog.hpp source/scwx/qt/ui/radar_site_dialog.hpp
source/scwx/qt/ui/settings_dialog.hpp source/scwx/qt/ui/settings_dialog.hpp
source/scwx/qt/ui/update_dialog.hpp) source/scwx/qt/ui/update_dialog.hpp)
@ -241,6 +243,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
source/scwx/qt/ui/animation_dock_widget.cpp source/scwx/qt/ui/animation_dock_widget.cpp
source/scwx/qt/ui/collapsible_group.cpp source/scwx/qt/ui/collapsible_group.cpp
source/scwx/qt/ui/county_dialog.cpp source/scwx/qt/ui/county_dialog.cpp
source/scwx/qt/ui/download_dialog.cpp
source/scwx/qt/ui/flow_layout.cpp source/scwx/qt/ui/flow_layout.cpp
source/scwx/qt/ui/imgui_debug_dialog.cpp source/scwx/qt/ui/imgui_debug_dialog.cpp
source/scwx/qt/ui/imgui_debug_widget.cpp source/scwx/qt/ui/imgui_debug_widget.cpp
@ -252,6 +255,7 @@ set(SRC_UI source/scwx/qt/ui/about_dialog.cpp
source/scwx/qt/ui/open_url_dialog.cpp source/scwx/qt/ui/open_url_dialog.cpp
source/scwx/qt/ui/placefile_dialog.cpp source/scwx/qt/ui/placefile_dialog.cpp
source/scwx/qt/ui/placefile_settings_widget.cpp source/scwx/qt/ui/placefile_settings_widget.cpp
source/scwx/qt/ui/progress_dialog.cpp
source/scwx/qt/ui/radar_site_dialog.cpp source/scwx/qt/ui/radar_site_dialog.cpp
source/scwx/qt/ui/settings_dialog.cpp source/scwx/qt/ui/settings_dialog.cpp
source/scwx/qt/ui/update_dialog.cpp) source/scwx/qt/ui/update_dialog.cpp)
@ -266,6 +270,7 @@ set(UI_UI source/scwx/qt/ui/about_dialog.ui
source/scwx/qt/ui/open_url_dialog.ui source/scwx/qt/ui/open_url_dialog.ui
source/scwx/qt/ui/placefile_dialog.ui source/scwx/qt/ui/placefile_dialog.ui
source/scwx/qt/ui/placefile_settings_widget.ui source/scwx/qt/ui/placefile_settings_widget.ui
source/scwx/qt/ui/progress_dialog.ui
source/scwx/qt/ui/radar_site_dialog.ui source/scwx/qt/ui/radar_site_dialog.ui
source/scwx/qt/ui/settings_dialog.ui source/scwx/qt/ui/settings_dialog.ui
source/scwx/qt/ui/update_dialog.ui) source/scwx/qt/ui/update_dialog.ui)

View file

@ -0,0 +1,134 @@
#include <scwx/qt/ui/download_dialog.hpp>
#include <scwx/util/strings.hpp>
#include <boost/timer/timer.hpp>
#include <fmt/chrono.h>
#include <fmt/format.h>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QTimer>
namespace scwx
{
namespace qt
{
namespace ui
{
class DownloadDialog::Impl
{
public:
explicit Impl(DownloadDialog* self) : self_ {self}
{
updateTimer_.setSingleShot(true);
updateTimer_.setInterval(0);
QObject::connect(&updateTimer_,
&QTimer::timeout,
self_,
[this]() { UpdateProgress(); });
};
~Impl() = default;
void UpdateProgress();
DownloadDialog* self_;
boost::timer::cpu_timer timer_ {};
QTimer updateTimer_ {};
std::ptrdiff_t downloadedBytes_ {};
std::ptrdiff_t totalBytes_ {};
};
DownloadDialog::DownloadDialog(QWidget* parent) :
ProgressDialog(parent), p {std::make_unique<Impl>(this)}
{
auto buttonBox = button_box();
buttonBox->setStandardButtons(QDialogButtonBox::StandardButton::Ok |
QDialogButtonBox::StandardButton::Cancel);
buttonBox->button(QDialogButtonBox::StandardButton::Ok)
->setText("Install Now");
SetRange(0, 100);
}
DownloadDialog::~DownloadDialog() {}
void DownloadDialog::set_filename(const std::string& filename)
{
QString label = tr("Downloading %1...").arg(filename.c_str());
SetTopLabelText(label);
}
void DownloadDialog::StartDownload()
{
// Hide the OK button until the download is finished
button_box()
->button(QDialogButtonBox::StandardButton::Ok)
->setVisible(false);
SetBottomLabelText(tr("Waiting for download to begin..."));
p->timer_.start();
show();
}
void DownloadDialog::UpdateProgress(std::ptrdiff_t downloadedBytes,
std::ptrdiff_t totalBytes)
{
p->downloadedBytes_ = downloadedBytes;
p->totalBytes_ = totalBytes;
// Use a one-shot timer to trigger an update, preventing multiple updates per
// frame
p->updateTimer_.start();
}
void DownloadDialog::FinishDownload()
{
button_box()->button(QDialogButtonBox::StandardButton::Ok)->setVisible(true);
}
void DownloadDialog::CancelDownload()
{
SetValue(0);
SetBottomLabelText(tr("Error occurred while downloading"));
}
void DownloadDialog::Impl::UpdateProgress()
{
using namespace std::chrono_literals;
const std::ptrdiff_t downloadedBytes = downloadedBytes_;
const std::ptrdiff_t totalBytes = totalBytes_;
const std::chrono::nanoseconds elapsed {timer_.elapsed().wall};
const double percentComplete =
(totalBytes > 0.0) ? static_cast<double>(downloadedBytes) / totalBytes :
0.0;
const int progressValue = static_cast<int>(percentComplete * 100.0);
self_->SetValue(progressValue);
const std::chrono::seconds timeRemaining =
(percentComplete > 0.0) ?
std::chrono::duration_cast<std::chrono::seconds>(
elapsed / percentComplete - elapsed) :
0s;
const std::chrono::hours hoursRemaining =
std::chrono::duration_cast<std::chrono::hours>(timeRemaining);
const std::string progressText =
fmt::format("{} of {} downloaded ({}:{:%M:%S} remaining)",
util::BytesToString(downloadedBytes),
util::BytesToString(totalBytes),
hoursRemaining.count(),
timeRemaining);
self_->SetBottomLabelText(QString::fromStdString(progressText));
}
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,38 @@
#pragma once
#include <scwx/qt/ui/progress_dialog.hpp>
#include <cstddef>
namespace scwx
{
namespace qt
{
namespace ui
{
class DownloadDialog : public ProgressDialog
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(DownloadDialog)
public:
explicit DownloadDialog(QWidget* parent = nullptr);
~DownloadDialog();
void set_filename(const std::string& filename);
public slots:
void StartDownload();
void UpdateProgress(std::ptrdiff_t downloadedBytes,
std::ptrdiff_t totalBytes);
void FinishDownload();
void CancelDownload();
private:
class Impl;
std::unique_ptr<Impl> p;
};
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,66 @@
#include "progress_dialog.hpp"
#include "ui_progress_dialog.h"
namespace scwx
{
namespace qt
{
namespace ui
{
class ProgressDialog::Impl
{
public:
explicit Impl() = default;
~Impl() = default;
};
ProgressDialog::ProgressDialog(QWidget* parent) :
QDialog(parent), p {std::make_unique<Impl>()}, ui(new Ui::ProgressDialog)
{
ui->setupUi(this);
}
ProgressDialog::~ProgressDialog()
{
delete ui;
}
QDialogButtonBox* ProgressDialog::button_box() const
{
return ui->buttonBox;
}
void ProgressDialog::SetTopLabelText(const QString& text)
{
ui->topLabel->setText(text);
}
void ProgressDialog::SetBottomLabelText(const QString& text)
{
ui->bottomLabel->setText(text);
}
void ProgressDialog::SetMinimum(int minimum)
{
ui->progressBar->setMinimum(minimum);
}
void ProgressDialog::SetMaximum(int maximum)
{
ui->progressBar->setMaximum(maximum);
}
void ProgressDialog::SetRange(int minimum, int maximum)
{
ui->progressBar->setRange(minimum, maximum);
}
void ProgressDialog::SetValue(int value)
{
ui->progressBar->setValue(value);
}
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,46 @@
#pragma once
#include <QDialog>
class QDialogButtonBox;
namespace Ui
{
class ProgressDialog;
}
namespace scwx
{
namespace qt
{
namespace ui
{
class ProgressDialog : public QDialog
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(ProgressDialog)
public:
explicit ProgressDialog(QWidget* parent = nullptr);
~ProgressDialog();
protected:
QDialogButtonBox* button_box() const;
public slots:
void SetTopLabelText(const QString& text);
void SetBottomLabelText(const QString& text);
void SetMinimum(int minimum);
void SetMaximum(int maximum);
void SetRange(int minimum, int maximum);
void SetValue(int value);
private:
class Impl;
std::unique_ptr<Impl> p;
Ui::ProgressDialog* ui;
};
} // namespace ui
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProgressDialog</class>
<widget class="QDialog" name="ProgressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>394</width>
<height>116</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="topLabel">
<property name="text">
<string>Downloading supercell-wx-v0.4.4-windows-x64.msi...</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="bottomLabel">
<property name="text">
<string>25.3 MB of 69.1 MB downloaded (00:00:04 remaining)</string>
</property>
</widget>
</item>
<item>
<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>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ProgressDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ProgressDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>