diff --git a/src/common/RefreshDeferrer.cpp b/src/common/RefreshDeferrer.cpp index 6b928716..a72cb095 100644 --- a/src/common/RefreshDeferrer.cpp +++ b/src/common/RefreshDeferrer.cpp @@ -18,7 +18,9 @@ bool RefreshDeferrer::attemptRefresh(RefreshDeferrerParams params) return true; } else { dirty = true; - acc->accumulate(params); + if (acc) { + acc->accumulate(params); + } return false; } } @@ -28,8 +30,10 @@ void RefreshDeferrer::registerFor(CutterDockWidget *dockWidget) this->dockWidget = dockWidget; connect(dockWidget, &CutterDockWidget::becameVisibleToUser, this, [this]() { if(dirty) { - emit refreshNow(acc->result()); - acc->clear(); + emit refreshNow(acc ? acc->result() : nullptr); + if (acc) { + acc->clear(); + } dirty = false; } }); diff --git a/src/common/RefreshDeferrer.h b/src/common/RefreshDeferrer.h index 1052116e..178b65a4 100644 --- a/src/common/RefreshDeferrer.h +++ b/src/common/RefreshDeferrer.h @@ -10,6 +10,9 @@ class RefreshDeferrer; using RefreshDeferrerParams = void *; using RefreshDeferrerParamsResult = void *; +/*! + * \brief Abstract class for accumulating params in RefreshDeferrer + */ class RefreshDeferrerAccumulator { friend class RefreshDeferrer; @@ -18,12 +21,34 @@ public: virtual ~RefreshDeferrerAccumulator() = default; protected: + /*! + * \brief Add a new param to the accumulator + */ virtual void accumulate(RefreshDeferrerParams params) =0; + + /*! + * \brief Ignore the incoming params. Useful for freeing if necessary. + */ virtual void ignoreParams(RefreshDeferrerParams params) =0; + + /*! + * \brief Clear the current accumulator + */ virtual void clear() =0; + + /*! + * \brief Return the final result of the accumulation + */ virtual RefreshDeferrerParamsResult result() =0; }; + +/*! + * \brief Accumulator which simply replaces the current value by an incoming new one + * \tparam T The type of the param to store + * + * This accumulator takes the ownership of all params passed to it and deletes them automatically if not needed anymore! + */ template class ReplacingRefreshDeferrerAccumulator: public RefreshDeferrerAccumulator { @@ -32,6 +57,9 @@ private: bool replaceIfNull; public: + /*! + * \param Determines whether, if nullptr is passed, the current value should be replaced or kept. + */ explicit ReplacingRefreshDeferrerAccumulator(bool replaceIfNull = true) : replaceIfNull(replaceIfNull) {} @@ -67,6 +95,39 @@ protected: } }; +/*! + * \brief Helper class for deferred refreshing in Widgets + * + * This class can handle the logic necessary to defer the refreshing of widgets when they are not visible. + * It contains an optional RefreshDeferrerAccumulator, which can be used to accumulate incoming events while + * refreshing is deferred. + * + * Example (don't write it like this in practice, use the convenience methods in CutterDockWidget): + * ``` + * // in the constructor of a widget + * this->refreshDeferrer = new RefreshDeferrer(new ReplacingRefreshDeferrerAccumulator(false), this); + * this->refreshDeferrer->registerFor(this); + * connect(this->refreshDeferrer, &RefreshDeferrer::refreshNow, this, [this](MyParam *param) { + * // We attempted a refresh some time before, but it got deferred. + * // Now the RefreshDeferrer tells us to do the refresh and gives us the accumulated param. + * this->doRefresh(*param); + * } + * + * // ... + * + * void MyWidget::doRefresh(MyParam param) + * { + * if (!this->refreshDeferrer->attemptRefresh(new MyParam(param))) { + * // We shouldn't refresh right now. + * // The RefreshDeferrer takes over the param we passed it in attemptRefresh() + * // and gives it to the ReplacingRefreshDeferrerAccumulator. + * return; + * } + * // do the actual refresh depending on param + * } + * ``` + * + */ class RefreshDeferrer : public QObject { Q_OBJECT @@ -77,8 +138,11 @@ private: bool dirty = false; public: - RefreshDeferrer(RefreshDeferrerAccumulator *acc, QObject *parent = nullptr); - virtual ~RefreshDeferrer(); + /*! + * \param acc The accumulator (can be nullptr). The RefreshDeferrer takes the ownership! + */ + explicit RefreshDeferrer(RefreshDeferrerAccumulator *acc, QObject *parent = nullptr); + ~RefreshDeferrer() override; bool attemptRefresh(RefreshDeferrerParams params); void registerFor(CutterDockWidget *dockWidget); diff --git a/src/widgets/CutterDockWidget.h b/src/widgets/CutterDockWidget.h index 8dd926ab..814d98cf 100644 --- a/src/widgets/CutterDockWidget.h +++ b/src/widgets/CutterDockWidget.h @@ -32,6 +32,26 @@ private: protected: void closeEvent(QCloseEvent *event) override; + /*! + * \brief Convenience method for creating and registering a RefreshDeferrer without any parameters + * \param refreshNowFunc lambda taking no parameters, called when a refresh should occur + */ + template + RefreshDeferrer *createRefreshDeferrer(Func refreshNowFunc) + { + auto *deferrer = new RefreshDeferrer(nullptr, this); + deferrer->registerFor(this); + connect(deferrer, &RefreshDeferrer::refreshNow, this, [refreshNowFunc](const RefreshDeferrerParamsResult) { + refreshNowFunc(); + }); + return deferrer; + } + + /*! + * \brief Convenience method for creating and registering a RefreshDeferrer with a replacing Accumulator + * \param replaceIfNull passed to the ReplacingRefreshDeferrerAccumulator + * \param refreshNowFunc lambda taking a single parameter of type ParamResult, called when a refresh should occur + */ template RefreshDeferrer *createReplacingRefreshDeferrer(bool replaceIfNull, Func refreshNowFunc) {