diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp
index 5ec8dcf16..ca55c7b41 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_update.cpp
@@ -171,11 +171,13 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Releas
publishDate = release->getPublishDate().toString(QLocale().dateFormat(QLocale::LongFormat));
if (isCompatible) {
- if (QtUtils::confirm(window(), tr("Update Available"),
- tr("A new version of Cockatrice is available!") + "
" + "" + tr("New version") +
- QString(": %1
").arg(release->getName()) + "" + tr("Released") +
- QString(": %1 (").arg(publishDate, release->getDescriptionUrl()) +
- tr("Changelog") + ")
" + tr("Do you want to update now?"))) {
+ const QString message = tr("A new version of Cockatrice is available!") + "
" + "" +
+ tr("New version") + QString(": %1
").arg(release->getName()) + "" +
+ tr("Released") +
+ QString(": %1 (").arg(publishDate, release->getDescriptionUrl()) +
+ tr("Changelog") + ")
" + tr("Do you want to update now?");
+
+ if (QtUtils::confirm(window(), tr("Update Available"), message)) {
downloadUpdate(release->getName());
} else {
closeDialog();
diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt
index cc0168cb8..748f6b90f 100644
--- a/libcockatrice_utility/CMakeLists.txt
+++ b/libcockatrice_utility/CMakeLists.txt
@@ -21,6 +21,7 @@ set(UTILITY_HEADERS
libcockatrice/utility/card_ref.h
libcockatrice/utility/color.h
libcockatrice/utility/cryptoutil.h
+ libcockatrice/utility/debouncer.h
libcockatrice/utility/expression.h
libcockatrice/utility/levenshtein.h
libcockatrice/utility/local_server_name.h
diff --git a/libcockatrice_utility/libcockatrice/utility/debouncer.h b/libcockatrice_utility/libcockatrice/utility/debouncer.h
new file mode 100644
index 000000000..54635db4f
--- /dev/null
+++ b/libcockatrice_utility/libcockatrice/utility/debouncer.h
@@ -0,0 +1,62 @@
+#ifndef COCKATRICE_DEBOUNCER_H
+#define COCKATRICE_DEBOUNCER_H
+
+#include
+#include
+#include
+#include
+
+namespace QtUtils
+{
+/**
+ * @brief Runs the most recent call() after a period without further calls.
+ *
+ * Typical use is coalescing rapid model or editor updates into one refresh:
+ *
+ * debouncer.call([this] { refresh(); });
+ *
+ * The pending callback is owned by the debouncer and replaced when call() is invoked
+ * again. The debouncer must outlive any callback it schedules; parenting it to the
+ * owning widget is usually enough.
+ */
+class Debouncer : public QObject
+{
+public:
+ explicit Debouncer(int intervalMs, QObject *parent = nullptr) : QObject(parent)
+ {
+ timer.setSingleShot(true);
+ timer.setInterval(intervalMs);
+ connect(&timer, &QTimer::timeout, this, [this] {
+ if (callback) {
+ auto pending = std::move(callback);
+ callback = nullptr;
+ pending();
+ }
+ });
+ }
+
+ /**
+ * @brief Schedules @p newCallback, replacing any callback not run yet.
+ */
+ void call(std::function newCallback)
+ {
+ callback = std::move(newCallback);
+ timer.start();
+ }
+
+ /**
+ * @brief Drops the pending callback and stops the timer.
+ */
+ void cancel()
+ {
+ timer.stop();
+ callback = nullptr;
+ }
+
+private:
+ QTimer timer;
+ std::function callback;
+};
+} // namespace QtUtils
+
+#endif // COCKATRICE_DEBOUNCER_H