Relative-Hook: rectify the design

Partially as a leftover from the way more ambitious initial design,
we ended up with CanvasHook as an elaboration/specialisation of the
ViewHook abstraction. However, as it stands, this design is tilted,
since CanvasHook is not just an elaboration, but rather a variation
of the same basic idea.

And this is now more like a building pattern and less of a generic
framework, it seems adequate to separate these two variations completely,
even if this incurs a small amount of code duplication.


Actually this refactoring is necessary to resolve a bug, where
we ended up with the same Clip widgets attached two times to the
same Canvas control, one time through the ViewHook baseclass,
and a second time by the ctor of the "derived" CanvasHook
This commit is contained in:
Fischlurch 2021-01-30 13:29:50 +01:00
parent e175805481
commit 2e4cd56f4f
8 changed files with 97 additions and 64 deletions

View file

@ -23,12 +23,14 @@
/** @file canvas-hook.hpp
** Specialised (abstracted) presentation context with positioning by coordinates.
** This extends the generic abstraction of a ViewHook, and works in a similar way,
** This expands the idea behind the ViewHook abstraction, and works in a similar way,
** in close collaboration with the corresponding CanvasHooked entity (abstraction).
** Elements relying on those abstractions maintain an attachment to "their view",
** while remaining agnostic about the view's implementation details. But the key
** point with this extended abstraction is that elements can be placed onto a
** coordinate system or canvas, and they can be moved to a different position.
** while remaining agnostic about the view's implementation details. However,
** the key point with this extended variant of the abstraction is that elements
** can be placed onto a coordinate system or canvas, and they can be moved to a
** different position.
**
** A CanvasHooked element is basically a decorator directly attached to the
** element, adding automatic detachment on destruction, similar to a smart-ptr.
** So the "hooked" widget will live within the common allocation, together with
@ -50,7 +52,6 @@
#ifndef STAGE_MODEL_CANVAS_HOOK_H
#define STAGE_MODEL_CANVAS_HOOK_H
#include "stage/model/view-hook.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
@ -67,24 +68,25 @@ namespace model {
/**
* Interface to represent _"some presentation layout entity",_
* with the ability to _place_ widgets (managed elsewhere), to
* relocate those widgets to another position, and to re-establish
* a different sequence of the widgets (whatever this means).
* with the ability to _place_ widgets (managed elsewhere) onto it,
* to relocate those widgets to another position.
* @remark the canonical example is a _canvas widget,_ (e.g. `Gtk::Layout`),
* allowing to attach child widgets at specific positions, together with
* custom drawing.
* @warning please ensure the CanvasHook outlives any attached CanvasHooked.
* @see ViewHook embodies the same scheme for widgets just "added" into
* the presentation without the notion of explicit coordinates.
*/
template<class WID>
class CanvasHook
: public ViewHook<WID>
{
public:
virtual ~CanvasHook() { } ///< this is an interface
virtual void hook (WID& widget, int xPos, int yPos) =0;
virtual void move (WID& widget, int xPos, int yPos) =0;
virtual void hook (WID& widget, int xPos, int yPos) =0;
virtual void move (WID& widget, int xPos, int yPos) =0;
virtual void remove (WID& widget) =0;
/** Anchor point to build chains of related View Hooks */
virtual CanvasHook<WID>&
getAnchorHook() noexcept
@ -94,14 +96,14 @@ namespace model {
struct Pos
{
CanvasHook& view;
CanvasHook* view;
int x,y;
};
Pos
hookedAt (int x, int y)
{
return Pos{*this, x,y};
return Pos{this, x,y};
}
/** build the "construction hook" for a \ref ViewHooked element,
@ -118,20 +120,16 @@ namespace model {
protected:
/** extension point for time axis zoom management. */
virtual int translateTimeToPixels (Time) const =0;
private:
/** adapted but also turned unaccessible; use #hook(WID&,int,int) */
void hook (WID& w) override { hook (w,0,0); }
};
/**
* A widget attached onto a display canvas or similar central presentation context.
* This decorator is an extension to the ViewHooked decorator, and thus inherits from
* the widget to be attached, i.e. the widget itself becomes embedded; moreover, the attachment
* is immediately performed at construction time and managed automatically thereafter. When the
* `CanvasHooked` element goes out of scope, it is automatically detached from presentation.
* This decorator is a variation of the ViewHooked decorator, and likewise embodies
* the widget to be attached; moreover, the attachment is immediately performed at
* construction time and managed automatically thereafter. When the `CanvasHooked`
* element goes out of scope, it is automatically detached from presentation.
* With the help of the CanvasHooked API, a widget (or similar entity) may control the coordinates
* of its placement onto some kind of _canvas_ (-> `Gtk::Layout`), while remaining agnostic
* regarding any further implementation details of the canvas and its placement thereon.
@ -150,28 +148,33 @@ namespace model {
*/
template<class WID, class BASE =WID>
class CanvasHooked
: public ViewHooked<WID,BASE>
: public WID
, util::NonCopyable
{
using Hooked = ViewHooked<WID,BASE>;
using Canvas = CanvasHook<BASE>;
Canvas* view_;
protected:
Canvas&
getCanvas() const
{
REQUIRE (INSTANCEOF(ViewHook<BASE>, &Hooked::getView() ));
return static_cast<Canvas&> (Hooked::getView());
}
Canvas& getCanvas() const { return *view_; }
public:
template<typename...ARGS>
CanvasHooked (typename Canvas::Pos attachmentPos, ARGS&& ...args)
: ViewHooked<WID,BASE>{attachmentPos.view
,std::forward<ARGS>(args)...}
: WID{std::forward<ARGS>(args)...}
, view_{attachmentPos.view}
{
getCanvas().hook (*this, attachmentPos.x, attachmentPos.y);
}
~CanvasHooked() noexcept
{
try {
if (view_)
view_->remove (*this);
}
ERROR_LOG_AND_IGNORE (progress, "Detaching of CanvasHooked widgets from the presentation")
}
void
moveTo (int xPos, int yPos)
{

View file

@ -78,7 +78,7 @@ namespace model {
* - a _canvas widget,_ (e.g. `Gtk::Layout`), allowing to attach
* child widgets at specific positions, together with custom drawing.
* @warning please ensure the ViewHook outlives any attached ViewHooked.
* @see CanvasHook extended interface to support positioning by coordinates
* @see CanvasHook similar interface to support positioning by coordinates
*/
template<class WID>
class ViewHook
@ -127,10 +127,6 @@ namespace model {
using View = ViewHook<BASE>;
View* view_;
protected:
View&
getView() const { return *view_; }
public:
template<typename...ARGS>
ViewHooked (View& view, ARGS&& ...args)

View file

@ -561,12 +561,6 @@ namespace timeline {
getCanvas(0).remove (widget);
}
void
BodyCanvasWidget::rehook (Gtk::Widget&) noexcept
{
/* NOOP */
}
void
BodyCanvasWidget::move (Gtk::Widget& widget, int xPos, int yPos)
{

View file

@ -175,7 +175,6 @@ namespace timeline {
void hook (Gtk::Widget&, int xPos=0, int yPos=0) override;
void move (Gtk::Widget&, int xPos, int yPos) override;
void remove (Gtk::Widget&) override;
void rehook (Gtk::Widget&) noexcept override;
int translateTimeToPixels (Time) const override;

View file

@ -74,6 +74,7 @@
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "stage/model/view-hook.hpp"
#include "stage/model/canvas-hook.hpp"
#include "lib/util.hpp"
@ -122,12 +123,6 @@ namespace timeline {
refHook_.remove (widget);
}
void
rehook (WID& hookedWidget) noexcept override
{
refHook_.rehook (hookedWidget);
}
/** allow to build a derived ViewRefHook with different offset */
model::CanvasHook<WID>&
getAnchorHook() noexcept override

View file

@ -88,6 +88,7 @@
#include "stage/timeline/header-pane-widget.hpp"
#include "stage/timeline/body-canvas-widget.hpp"
#include "stage/model/canvas-hook.hpp"
#include "stage/model/view-hook.hpp"
//#include "lib/util.hpp"

View file

@ -150,17 +150,6 @@ namespace test {
widgets_.remove_if ([&](Attachment const& a) { return a.widget == elm; });
}
void
rehook (DummyWidget& existingHook) noexcept override
{
auto pos = findEntry (existingHook);
REQUIRE (pos != widgets_.end(), "the given iterator must yield previously hooked-up elements");
Attachment existing{*pos};
this->remove (existing.widget);
this->hook (existing.widget, existing.posX,existing.posY);
}
protected:
int
translateTimeToPixels (Time) const override

View file

@ -19213,7 +19213,7 @@
<node CREATED="1575581015219" ID="ID_1015965713" MODIFIED="1575581026974" TEXT="f&#xfc;r den Widget -&gt; Canvas - Fall"/>
</node>
</node>
<node COLOR="#338800" CREATED="1576757730509" FOLDED="true" ID="ID_1949130658" MODIFIED="1584932567087" TEXT="&#xbb;Widget-Position&#xab; heraus-abstrahieren">
<node COLOR="#338800" CREATED="1576757730509" FOLDED="true" ID="ID_1949130658" MODIFIED="1612002019554" TEXT="&#xbb;Widget-Position&#xab; heraus-abstrahieren">
<linktarget COLOR="#47cdcd" DESTINATION="ID_1949130658" ENDARROW="Default" ENDINCLINATION="-1150;77;" ID="Arrow_ID_662295339" SOURCE="ID_730955223" STARTARROW="None" STARTINCLINATION="877;53;"/>
<linktarget COLOR="#605bb8" DESTINATION="ID_1949130658" ENDARROW="Default" ENDINCLINATION="204;611;" ID="Arrow_ID_993970158" SOURCE="ID_1816490333" STARTARROW="None" STARTINCLINATION="299;12;"/>
<icon BUILTIN="button_ok"/>
@ -19280,6 +19280,32 @@
<font ITALIC="true" NAME="SansSerif" SIZE="12"/>
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1612002421081" ID="ID_704646941" MODIFIED="1612003549568">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
aber dieses Design ist <i>schief</i>
</p>
</body>
</html>
</richcontent>
<linktarget COLOR="#ab3058" DESTINATION="ID_704646941" ENDARROW="Default" ENDINCLINATION="434;181;" ID="Arrow_ID_932322384" SOURCE="ID_781800644" STARTARROW="None" STARTINCLINATION="240;20;"/>
<icon BUILTIN="broken-line"/>
<node CREATED="1612002459090" ID="ID_336676484" MODIFIED="1612002485623" TEXT="und zwar die Code-reuse-Inheritance">
<icon BUILTIN="yes"/>
</node>
<node CREATED="1612002487402" ID="ID_922768406" MODIFIED="1612002504506" TEXT="dadurch sind beide Belange &#xfc;bereinander geschichtet"/>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1612002505382" ID="ID_1693567978" MODIFIED="1612009171119" TEXT="Bug: deshalb wird ein Widget zweimal an den Canvas geheftet">
<linktarget COLOR="#c16b96" DESTINATION="ID_1693567978" ENDARROW="Default" ENDINCLINATION="223;491;" ID="Arrow_ID_624678696" SOURCE="ID_1950742827" STARTARROW="None" STARTINCLINATION="1328;54;"/>
<icon BUILTIN="broken-line"/>
</node>
</node>
<node COLOR="#338800" CREATED="1612008761963" ID="ID_1362647591" MODIFIED="1612008774354" TEXT="die beiden Varianten komplett voneinander getrennt">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#338800" CREATED="1575057172259" ID="ID_1193961919" MODIFIED="1575670553256" TEXT="Umordnen">
<linktarget COLOR="#507b9b" DESTINATION="ID_1193961919" ENDARROW="Default" ENDINCLINATION="-877;88;" ID="Arrow_ID_1469804818" SOURCE="ID_876124745" STARTARROW="None" STARTINCLINATION="576;33;"/>
@ -19708,6 +19734,37 @@
<node CREATED="1582833303639" ID="ID_1136204632" MODIFIED="1582833315633" TEXT="und konvertiert die Referenzen automatisch hoch"/>
</node>
</node>
<node COLOR="#338800" CREATED="1612003404589" ID="ID_1629772875" MODIFIED="1612008746849" TEXT="Problem Code-reuse-Inheritance">
<icon BUILTIN="button_ok"/>
<node CREATED="1612003423875" ID="ID_781800644" MODIFIED="1612003549567">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
Ursache ist ein <i>schiefes Design</i>
</p>
</body>
</html>
</richcontent>
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...und das ist wohl entstanden, weil ich urspr&#252;nglich einen generischen Visitor im Blick hatte; es hat sich aber dann gezeigt, da&#223; eine solche universelle &quot;Quer-Beweglichkeit&quot; weder notwendig noch w&#252;nschenswert ist
</p>
</body>
</html>
</richcontent>
<arrowlink COLOR="#ab3058" DESTINATION="ID_704646941" ENDARROW="Default" ENDINCLINATION="434;181;" ID="Arrow_ID_932322384" STARTARROW="None" STARTINCLINATION="240;20;"/>
</node>
<node COLOR="#338800" CREATED="1612003579934" ID="ID_852410527" MODIFIED="1612008743069" TEXT="Refactoring: ViewHook und CanvasHook komplett trennen">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#338800" CREATED="1573242259267" ID="ID_1903570032" MODIFIED="1576166466194" TEXT="Test">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1573242271641" ID="ID_226481127" MODIFIED="1573242355581" TEXT="Basisfall">
@ -28945,8 +29002,7 @@
diese Alternative w&#252;rde dann attraktiv, wenn es h&#228;ufig vorkommt, da&#223; zwischen einem Clip-Widget und einer anderen Repr&#228;sentation des ClipDelegate dynamisch hin- und hergeschaltet werden mu&#223;. Weil man dann den relativ schwergewichtigen Datencontainer einfach umh&#228;ngen k&#246;nnte
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
</node>
</node>