changeset 700:059e1fd05fd6 refactor-viewport-controller

Introduced the ViewportController that sits between the application and the Scene2D to handle the trackers and measuring tools. This is a work in progress. The Scene2D is no longer an observable. Message sending is managed by the ViewportController. Move some refs to shared and weak to prevent lifetime issues.
author Benjamin Golinvaux <bgo@osimis.io>
date Sun, 19 May 2019 16:26:17 +0200
parents 5c551f078c18
children 58e1faeafb1b
files Framework/Scene2D/IPointerTracker.h Framework/Scene2D/Internals/FixedPointAligner.cpp Framework/Scene2D/Internals/FixedPointAligner.h Framework/Scene2D/PanSceneTracker.cpp Framework/Scene2D/PanSceneTracker.h Framework/Scene2D/RotateSceneTracker.cpp Framework/Scene2D/RotateSceneTracker.h Framework/Scene2D/Scene2D.cpp Framework/Scene2D/Scene2D.h Framework/Scene2D/ZoomSceneTracker.cpp Framework/Scene2D/ZoomSceneTracker.h Framework/Scene2DViewport/AngleMeasureTool.h Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Framework/Scene2DViewport/CreateAngleMeasureTracker.h Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Framework/Scene2DViewport/CreateLineMeasureTracker.h Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp Framework/Scene2DViewport/IFlexiblePointerTracker.h Framework/Scene2DViewport/LineMeasureTool.h Framework/Scene2DViewport/MeasureCommands.cpp Framework/Scene2DViewport/MeasureCommands.h Framework/Scene2DViewport/MeasureTools.cpp Framework/Scene2DViewport/MeasureTools.h Framework/Scene2DViewport/MeasureTrackers.cpp Framework/Scene2DViewport/MeasureTrackers.h Framework/Scene2DViewport/OneGesturePointerTracker.cpp Framework/Scene2DViewport/OneGesturePointerTracker.h Framework/Scene2DViewport/PointerTypes.h Framework/Scene2DViewport/ViewportController.cpp Framework/Scene2DViewport/ViewportController.h Framework/Toolbox/ShearWarpProjectiveTransform.cpp Resources/CMake/OrthancStoneConfiguration.cmake Samples/Sdl/BasicScene.cpp Samples/Sdl/TrackerSampleApp.cpp Samples/Sdl/TrackerSampleApp.h
diffstat 35 files changed, 439 insertions(+), 253 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Scene2D/IPointerTracker.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/IPointerTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -21,6 +21,8 @@
 
 #pragma once
 
+#if 0
+
 #include "PointerEvent.h"
 
 namespace OrthancStone
@@ -44,3 +46,5 @@
     virtual void Release() = 0;
   };
 }
+
+#endif
--- a/Framework/Scene2D/Internals/FixedPointAligner.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/Internals/FixedPointAligner.cpp	Sun May 19 16:26:17 2019 +0200
@@ -18,29 +18,31 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-
+#include <Framework/Scene2DViewport/ViewportController.h>
 #include "FixedPointAligner.h"
 
 namespace OrthancStone
 {
   namespace Internals
   {
-    FixedPointAligner::FixedPointAligner(Scene2D& scene,
-                                         const ScenePoint2D& p) :
-      scene_(scene),
-      canvas_(p)
+    FixedPointAligner::FixedPointAligner(ViewportControllerWPtr controllerW,
+                                         const ScenePoint2D& p) 
+      : controllerW_(controllerW)
+      , canvas_(p)
     {
-      pivot_ = canvas_.Apply(scene_.GetCanvasToSceneTransform());
+      ViewportControllerPtr controller = controllerW_.lock();
+      pivot_ = canvas_.Apply(controller->GetCanvasToSceneTransform());
     }
 
     
     void FixedPointAligner::Apply()
     {
-      ScenePoint2D p = canvas_.Apply(scene_.GetCanvasToSceneTransform());
+      ViewportControllerPtr controller = controllerW_.lock();
+      ScenePoint2D p = canvas_.Apply(controller->GetCanvasToSceneTransform());
 
-      scene_.SetSceneToCanvasTransform(
+      controller->SetSceneToCanvasTransform(
         AffineTransform2D::Combine(
-          scene_.GetSceneToCanvasTransform(),
+          controller->GetSceneToCanvasTransform(),
           AffineTransform2D::CreateOffset(p.GetX() - pivot_.GetX(),
                                           p.GetY() - pivot_.GetY())));
     }
--- a/Framework/Scene2D/Internals/FixedPointAligner.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/Internals/FixedPointAligner.h	Sun May 19 16:26:17 2019 +0200
@@ -18,11 +18,10 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-
 #pragma once
 
-#include "../Scene2D.h"
-#include "../ScenePoint2D.h"
+#include <Framework/Scene2DViewport/PointerTypes.h>
+#include <Framework/Scene2D/ScenePoint2D.h>
 
 namespace OrthancStone
 {
@@ -33,12 +32,12 @@
     class FixedPointAligner : public boost::noncopyable
     {
     private:
-      Scene2D&      scene_;
-      ScenePoint2D  pivot_;
-      ScenePoint2D  canvas_;
+      ViewportControllerWPtr controllerW_;
+      ScenePoint2D           pivot_;
+      ScenePoint2D           canvas_;
 
     public:
-      FixedPointAligner(Scene2D& scene,
+      FixedPointAligner(ViewportControllerWPtr controllerW,
                         const ScenePoint2D& p);
 
       void Apply();
--- a/Framework/Scene2D/PanSceneTracker.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/PanSceneTracker.cpp	Sun May 19 16:26:17 2019 +0200
@@ -20,27 +20,34 @@
 
 
 #include "PanSceneTracker.h"
+#include <Framework/Scene2DViewport/ViewportController.h>
 
 namespace OrthancStone
 {
-  PanSceneTracker::PanSceneTracker(Scene2D& scene,
-                                   const PointerEvent& event) :
-    scene_(scene),
-    originalSceneToCanvas_(scene_.GetSceneToCanvasTransform()),
-    originalCanvasToScene_(scene_.GetCanvasToSceneTransform())
+  PanSceneTracker::PanSceneTracker(ViewportControllerWPtr controllerW,
+                                   const PointerEvent& event)
+    : OneGesturePointerTracker(controllerW)
+    , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform())
+    , originalCanvasToScene_(GetController()->GetCanvasToSceneTransform())
   {
     pivot_ = event.GetMainPosition().Apply(originalCanvasToScene_);
   }
 
 
-  void PanSceneTracker::Update(const PointerEvent& event)
+  void PanSceneTracker::PointerMove(const PointerEvent& event)
   {
     ScenePoint2D p = event.GetMainPosition().Apply(originalCanvasToScene_);
       
-    scene_.SetSceneToCanvasTransform(
+    GetController()->SetSceneToCanvasTransform(
       AffineTransform2D::Combine(
         originalSceneToCanvas_,
         AffineTransform2D::CreateOffset(p.GetX() - pivot_.GetX(),
                                         p.GetY() - pivot_.GetY())));
   }
+
+  void PanSceneTracker::Cancel()
+  {
+    GetController()->SetSceneToCanvasTransform(originalSceneToCanvas_);
+  }
+
 }
--- a/Framework/Scene2D/PanSceneTracker.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/PanSceneTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -21,27 +21,25 @@
 
 #pragma once
 
-#include "IPointerTracker.h"
-#include "Scene2D.h"
+#include "../Scene2DViewport/OneGesturePointerTracker.h"
 
 namespace OrthancStone
 {
-  class PanSceneTracker : public IPointerTracker
+  class ViewportController;
+
+  class PanSceneTracker : public OneGesturePointerTracker
   {
-  private:
-    Scene2D&           scene_;
-    ScenePoint2D       pivot_;
-    AffineTransform2D  originalSceneToCanvas_;
-    AffineTransform2D  originalCanvasToScene_;
-
   public:
-    PanSceneTracker(Scene2D& scene,
+    PanSceneTracker(ViewportControllerWPtr controllerW,
                     const PointerEvent& event);
 
-    virtual void Update(const PointerEvent& event);
+    virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE;
+    virtual void Cancel() ORTHANC_OVERRIDE;
 
-    virtual void Release()
-    {
-    }
+  private:
+    ViewportControllerWPtr controllerW_;
+    ScenePoint2D           pivot_;
+    AffineTransform2D      originalSceneToCanvas_;
+    AffineTransform2D      originalCanvasToScene_;
   };
 }
--- a/Framework/Scene2D/RotateSceneTracker.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/RotateSceneTracker.cpp	Sun May 19 16:26:17 2019 +0200
@@ -18,23 +18,22 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-
 #include "RotateSceneTracker.h"
+#include <Framework/Scene2DViewport/ViewportController.h>
 
 namespace OrthancStone
 {
-  RotateSceneTracker::RotateSceneTracker(Scene2D& scene,
-                                         const PointerEvent& event) :
-    scene_(scene),
-    click_(event.GetMainPosition()),
-    aligner_(scene, click_),
-    isFirst_(true),
-    originalSceneToCanvas_(scene.GetSceneToCanvasTransform())
+  RotateSceneTracker::RotateSceneTracker(ViewportControllerWPtr controllerW,
+                                         const PointerEvent& event)
+    : OneGesturePointerTracker(controllerW)
+    , click_(event.GetMainPosition())
+    , aligner_(controllerW, click_)
+    , isFirst_(true)
+    , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform())
   {
   }
-
-
-  void RotateSceneTracker::Update(const PointerEvent& event)
+  
+  void RotateSceneTracker::PointerMove(const PointerEvent& event)
   {
     ScenePoint2D p = event.GetMainPosition();
     double dx = p.GetX() - click_.GetX();
@@ -51,7 +50,7 @@
         isFirst_ = false;
       }
 
-      scene_.SetSceneToCanvasTransform(
+      GetController()->SetSceneToCanvasTransform(
         AffineTransform2D::Combine(
           AffineTransform2D::CreateRotation(a - referenceAngle_),
           originalSceneToCanvas_));
@@ -59,4 +58,10 @@
       aligner_.Apply();
     }
   }
+
+  void RotateSceneTracker::Cancel()
+  {
+    GetController()->SetSceneToCanvasTransform(originalSceneToCanvas_);
+  }
+
 }
--- a/Framework/Scene2D/RotateSceneTracker.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/RotateSceneTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -21,29 +21,27 @@
 
 #pragma once
 
-#include "IPointerTracker.h"
+#include "../Scene2DViewport/OneGesturePointerTracker.h"
 #include "Internals/FixedPointAligner.h"
 
 namespace OrthancStone
 {
-  class RotateSceneTracker : public IPointerTracker
+  class ViewportController;
+
+  class RotateSceneTracker : public OneGesturePointerTracker
   {
-  private:
-    Scene2D&                      scene_;
-    ScenePoint2D                  click_;
-    Internals::FixedPointAligner  aligner_;
-    double                        referenceAngle_;
-    bool                          isFirst_;
-    AffineTransform2D             originalSceneToCanvas_;
-
   public:
-    RotateSceneTracker(Scene2D& scene,
+    RotateSceneTracker(ViewportControllerWPtr controllerW,
                        const PointerEvent& event);
 
-    virtual void Update(const PointerEvent& event);
+    virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE;
+    virtual void Cancel() ORTHANC_OVERRIDE;
 
-    virtual void Release()
-    {
-    }
+  private:
+    ScenePoint2D                 click_;
+    Internals::FixedPointAligner aligner_;
+    double                       referenceAngle_;
+    bool                         isFirst_;
+    AffineTransform2D            originalSceneToCanvas_;
   };
 }
--- a/Framework/Scene2D/Scene2D.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/Scene2D.cpp	Sun May 19 16:26:17 2019 +0200
@@ -76,8 +76,7 @@
   
   
   Scene2D::Scene2D(const Scene2D& other) 
-    : IObservable(other.GetBroker())
-    , sceneToCanvas_(other.sceneToCanvas_)
+    : sceneToCanvas_(other.sceneToCanvas_)
     , canvasToScene_(other.canvasToScene_)
     , layerCounter_(0)
   {
@@ -221,7 +220,6 @@
 
     sceneToCanvas_ = transform;
     canvasToScene_ = inverse;
-    BroadcastMessage(SceneTransformChanged(*this));
   }
 
   void Scene2D::FitContent(unsigned int canvasWidth,
--- a/Framework/Scene2D/Scene2D.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/Scene2D.h	Sun May 19 16:26:17 2019 +0200
@@ -30,11 +30,9 @@
 
 namespace OrthancStone
 {
-  class Scene2D : public IObservable
+  class Scene2D : public boost::noncopyable
   {
   public:
-    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SceneTransformChanged, Scene2D);
-    
     class IVisitor : public boost::noncopyable
     {
     public:
@@ -60,9 +58,7 @@
     Scene2D(const Scene2D& other);
     
   public:
-    Scene2D(MessageBroker& broker) 
-      : IObservable(broker)
-      , layerCounter_(0)
+    Scene2D() : layerCounter_(0)
     {
     }
     
--- a/Framework/Scene2D/ZoomSceneTracker.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/ZoomSceneTracker.cpp	Sun May 19 16:26:17 2019 +0200
@@ -20,16 +20,17 @@
 
 
 #include "ZoomSceneTracker.h"
+#include <Framework/Scene2DViewport/ViewportController.h>
 
 namespace OrthancStone
 {
-  ZoomSceneTracker::ZoomSceneTracker(Scene2D& scene,
+  ZoomSceneTracker::ZoomSceneTracker(ViewportControllerWPtr controllerW,
                                      const PointerEvent& event,
-                                     unsigned int canvasHeight) :
-    scene_(scene),
-    clickY_(event.GetMainPosition().GetY()),
-    aligner_(scene, event.GetMainPosition()),
-    originalSceneToCanvas_(scene.GetSceneToCanvasTransform())
+                                     unsigned int canvasHeight)
+    : OneGesturePointerTracker(controllerW)
+    , clickY_(event.GetMainPosition().GetY())
+    , aligner_(controllerW, event.GetMainPosition())
+    , originalSceneToCanvas_(GetController()->GetSceneToCanvasTransform())
   {
     if (canvasHeight <= 3)
     {
@@ -42,8 +43,7 @@
     }
   }
   
-
-  void ZoomSceneTracker::Update(const PointerEvent& event)
+  void ZoomSceneTracker::PointerMove(const PointerEvent& event)
   {
     static const double MIN_ZOOM = -4;
     static const double MAX_ZOOM = 4;
@@ -51,7 +51,10 @@
     if (active_)
     {
       double y = event.GetMainPosition().GetY();
-      double dy = static_cast<double>(y - clickY_) * normalization_;  // In the range [-1,1]
+      
+      // In the range [-1,1]
+      double dy = static_cast<double>(y - clickY_) * normalization_;  
+      
       double z;
 
       // Linear interpolation from [-1, 1] to [MIN_ZOOM, MAX_ZOOM]
@@ -70,7 +73,7 @@
 
       double zoom = pow(2.0, z);
 
-      scene_.SetSceneToCanvasTransform(
+      GetController()->SetSceneToCanvasTransform(
         AffineTransform2D::Combine(
           AffineTransform2D::CreateScaling(zoom, zoom),
           originalSceneToCanvas_));
@@ -78,4 +81,9 @@
       aligner_.Apply();
     }
   }
+
+  void ZoomSceneTracker::Cancel()
+  {
+    GetController()->SetSceneToCanvasTransform(originalSceneToCanvas_);
+  }
 }
--- a/Framework/Scene2D/ZoomSceneTracker.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2D/ZoomSceneTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -22,32 +22,29 @@
 #pragma once
 
 
-#include "IPointerTracker.h"
+#include "../Scene2DViewport/OneGesturePointerTracker.h"
 #include "Internals/FixedPointAligner.h"
 
 namespace OrthancStone
 {
   class Scene2D;
 
-  class ZoomSceneTracker : public IPointerTracker
+  class ZoomSceneTracker : public OneGesturePointerTracker
   {
+  public:
+    ZoomSceneTracker(ViewportControllerWPtr controllerW,
+                     const PointerEvent& event,
+                     unsigned int canvasHeight);
+
+    virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE;
+    virtual void Cancel() ORTHANC_OVERRIDE;
+  
   private:
-    Scene2D&                      scene_;
     double                        clickY_;
     bool                          active_;
     double                        normalization_;
     Internals::FixedPointAligner  aligner_;
     AffineTransform2D             originalSceneToCanvas_;
 
-  public:
-    ZoomSceneTracker(Scene2D& scene,
-                     const PointerEvent& event,
-                     unsigned int canvasHeight);
-
-    virtual void Update(const PointerEvent& event);
-
-    virtual void Release()
-    {
-    }
   };
 }
--- a/Framework/Scene2DViewport/AngleMeasureTool.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Sun May 19 16:26:17 2019 +0200
@@ -38,8 +38,8 @@
   class AngleMeasureTool : public MeasureTool
   {
   public:
-    AngleMeasureTool(MessageBroker& broker, Scene2DWPtr sceneW)
-      : MeasureTool(broker, sceneW)
+    AngleMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
+      : MeasureTool(broker, controllerW)
       , layersCreated(false)
       , polylineZIndex_(-1)
       , textBaseZIndex_(-1)
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Sun May 19 16:26:17 2019 +0200
@@ -27,20 +27,19 @@
 {
   CreateAngleMeasureTracker::CreateAngleMeasureTracker(
     MessageBroker&                  broker,
-    Scene2DWPtr                     sceneW,
+    ViewportControllerWPtr          controllerW,
     std::vector<TrackerCommandPtr>& undoStack,
     MeasureToolList&                measureTools,
     const PointerEvent&             e)
-    : CreateMeasureTracker(sceneW, undoStack, measureTools)
+    : CreateMeasureTracker(controllerW, undoStack, measureTools)
     , state_(CreatingSide1)
   {
-    Scene2DPtr scene = sceneW.lock();
     command_.reset(
       new CreateAngleMeasureCommand(
         broker,
-        sceneW,
+        controllerW,
         measureTools,
-        e.GetMainPosition().Apply(scene->GetCanvasToSceneTransform())));
+        e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform())));
   }
 
   CreateAngleMeasureTracker::~CreateAngleMeasureTracker()
@@ -49,8 +48,9 @@
 
   void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
   {
-    Scene2DPtr scene = scene_.lock();
-    if (!active_)
+    assert(GetScene());
+
+    if (!alive_)
     {
       throw OrthancException(ErrorCode_InternalError,
         "Internal error: wrong state in CreateAngleMeasureTracker::"
@@ -58,7 +58,7 @@
     }
 
     ScenePoint2D scenePos = event.GetMainPosition().Apply(
-      scene->GetCanvasToSceneTransform());
+      GetScene()->GetCanvasToSceneTransform());
 
     switch (state_)
     {
@@ -113,7 +113,7 @@
       break;
     case CreatingSide2:
       // we are done
-      active_ = false;
+      alive_ = false;
       break;
     default:
       throw OrthancException(ErrorCode_InternalError,
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -39,7 +39,7 @@
     */
     CreateAngleMeasureTracker(
       MessageBroker&                  broker,
-      Scene2DWPtr                     scene,
+      ViewportControllerWPtr          controllerW,
       std::vector<TrackerCommandPtr>& undoStack,
       MeasureToolList&                measureTools,
       const PointerEvent&             e);
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Sun May 19 16:26:17 2019 +0200
@@ -27,19 +27,18 @@
 {
   CreateLineMeasureTracker::CreateLineMeasureTracker(
     MessageBroker&                  broker,
-    Scene2DWPtr                     sceneW,
+    ViewportControllerWPtr          controllerW,
     std::vector<TrackerCommandPtr>& undoStack,
     MeasureToolList&                measureTools,
     const PointerEvent&             e)
-    : CreateMeasureTracker(sceneW, undoStack, measureTools)
+    : CreateMeasureTracker(controllerW, undoStack, measureTools)
   {
-    Scene2DPtr scene = sceneW.lock();
     command_.reset(
       new CreateLineMeasureCommand(
         broker,
-        sceneW,
+        controllerW,
         measureTools,
-        e.GetMainPosition().Apply(scene->GetCanvasToSceneTransform())));
+        e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform())));
   }
 
   CreateLineMeasureTracker::~CreateLineMeasureTracker()
@@ -49,10 +48,9 @@
 
   void CreateLineMeasureTracker::PointerMove(const PointerEvent& event)
   {
-    Scene2DPtr scene = scene_.lock();
-    assert(scene);
+    assert(GetScene());
     
-    if (!active_)
+    if (!alive_)
     {
       throw OrthancException(ErrorCode_InternalError,
         "Internal error: wrong state in CreateLineMeasureTracker::"
@@ -60,7 +58,7 @@
     }
 
     ScenePoint2D scenePos = event.GetMainPosition().Apply(
-      scene->GetCanvasToSceneTransform());
+      GetScene()->GetCanvasToSceneTransform());
 
     //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
     //  "scenePos.GetY() = " << scenePos.GetY();
@@ -78,7 +76,7 @@
     // Unless we augment the PointerEvent structure with the button index, 
     // we cannot really tell if this pointer up event matches the initial
     // pointer down event. Let's make it simple for now.
-    active_ = false;
+    alive_ = false;
   }
 
   void CreateLineMeasureTracker::PointerDown(const PointerEvent& e)
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -36,7 +36,7 @@
     */
     CreateLineMeasureTracker(
       MessageBroker&                  broker,
-      Scene2DWPtr                     scene,
+      ViewportControllerWPtr          controllerW,
       std::vector<TrackerCommandPtr>& undoStack,
       MeasureToolList&                measureTools,
       const PointerEvent&             e);
--- a/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp	Sun May 19 16:26:17 2019 +0200
@@ -24,7 +24,8 @@
 
 namespace OrthancStone
 {
-  namespace 
+#if 0
+  namespace
   {
     class SimpleTrackerAdapter : public IFlexiblePointerTracker
     {
@@ -74,4 +75,5 @@
   {
     return FlexiblePointerTrackerPtr(new SimpleTrackerAdapter(t));
   }
+#endif
 }
--- a/Framework/Scene2DViewport/IFlexiblePointerTracker.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/IFlexiblePointerTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -61,7 +61,7 @@
     the application) to check whether the tracker must keep on receiving 
     interaction or if its job is done and it should be deleted.
     */
-    virtual bool IsActive() const = 0;
+    virtual bool IsAlive() const = 0;
 
     /**
     This will be called if the tracker needs to be dismissed without committing
--- a/Framework/Scene2DViewport/LineMeasureTool.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Sun May 19 16:26:17 2019 +0200
@@ -38,8 +38,8 @@
   class LineMeasureTool : public MeasureTool
   {
   public:
-    LineMeasureTool(MessageBroker& broker, Scene2DWPtr sceneW)
-      : MeasureTool(broker, sceneW)
+    LineMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
+      : MeasureTool(broker, controllerW)
       , layersCreated(false)
       , polylineZIndex_(-1)
       , textZIndex_(-1)
--- a/Framework/Scene2DViewport/MeasureCommands.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.cpp	Sun May 19 16:26:17 2019 +0200
@@ -34,8 +34,8 @@
   }
 
   CreateMeasureCommand::CreateMeasureCommand(
-    Scene2DWPtr sceneW, MeasureToolList& measureTools)
-    : TrackerCommand(sceneW)
+    ViewportControllerWPtr controllerW, MeasureToolList& measureTools)
+    : TrackerCommand(controllerW)
     , measureTools_(measureTools)
   {
 
@@ -48,12 +48,12 @@
   }
 
   CreateLineMeasureCommand::CreateLineMeasureCommand(
-    MessageBroker&    broker, 
-    Scene2DWPtr       sceneW, 
-    MeasureToolList&  measureTools, 
-    ScenePoint2D      point)
-    : CreateMeasureCommand(sceneW, measureTools)
-    , measureTool_(new LineMeasureTool(broker,sceneW))
+    MessageBroker&         broker, 
+    ViewportControllerWPtr controllerW,
+    MeasureToolList&       measureTools, 
+    ScenePoint2D           point)
+    : CreateMeasureCommand(controllerW, measureTools)
+    , measureTool_(new LineMeasureTool(broker, controllerW))
   {
     measureTools_.push_back(measureTool_);
     measureTool_->Set(point, point);
@@ -65,12 +65,12 @@
   }
 
   CreateAngleMeasureCommand::CreateAngleMeasureCommand(
-    MessageBroker&    broker, 
-    Scene2DWPtr       sceneW,
-    MeasureToolList&  measureTools, 
-    ScenePoint2D      point)
-    : CreateMeasureCommand(sceneW, measureTools)
-    , measureTool_(new AngleMeasureTool(broker,sceneW))
+    MessageBroker&         broker, 
+    ViewportControllerWPtr controllerW,
+    MeasureToolList&       measureTools, 
+    ScenePoint2D           point)
+    : CreateMeasureCommand(controllerW, measureTools)
+    , measureTool_(new AngleMeasureTool(broker, controllerW))
   {
     measureTools_.push_back(measureTool_);
     measureTool_->SetSide1End(point);
--- a/Framework/Scene2DViewport/MeasureCommands.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.h	Sun May 19 16:26:17 2019 +0200
@@ -34,25 +34,23 @@
   class TrackerCommand : public boost::noncopyable
   {
   public:
-    TrackerCommand(Scene2DWPtr sceneW) : scene_(sceneW)
+    TrackerCommand(ViewportControllerWPtr controllerW) 
+      : controllerW_(controllerW)
     {
 
     }
     virtual void Undo() = 0;
     virtual void Redo() = 0;
-    Scene2DPtr GetScene()
-    {
-      return scene_.lock();
-    }
 
   protected:
-    Scene2DWPtr scene_;
+    ViewportControllerWPtr controllerW_;
   };
 
   class CreateMeasureCommand : public TrackerCommand
   {
   public:
-    CreateMeasureCommand(Scene2DWPtr sceneW, MeasureToolList& measureTools);
+    CreateMeasureCommand(
+      ViewportControllerWPtr controllerW, MeasureToolList& measureTools);
     ~CreateMeasureCommand();
     virtual void Undo() ORTHANC_OVERRIDE;
     virtual void Redo() ORTHANC_OVERRIDE;
@@ -62,16 +60,15 @@
     /** Must be implemented by the subclasses that create the actual tool */
     virtual MeasureToolPtr GetMeasureTool() = 0;
   };
-
-
+  
   class CreateLineMeasureCommand : public CreateMeasureCommand
   {
   public:
     CreateLineMeasureCommand(
-      MessageBroker&   broker, 
-      Scene2DWPtr      scene, 
-      MeasureToolList& measureTools, 
-      ScenePoint2D     point);
+      MessageBroker&         broker, 
+      ViewportControllerWPtr controllerW,
+      MeasureToolList&       measureTools, 
+      ScenePoint2D           point);
     
     // the starting position is set in the ctor
     void SetEnd(ScenePoint2D scenePos);
@@ -90,10 +87,10 @@
   public:
     /** Ctor sets end of side 1*/
     CreateAngleMeasureCommand(
-      MessageBroker&   broker, 
-      Scene2DWPtr      scene, 
-      MeasureToolList& measureTools, 
-      ScenePoint2D     point);
+      MessageBroker&         broker, 
+      ViewportControllerWPtr controllerW,
+      MeasureToolList&       measureTools, 
+      ScenePoint2D           point);
 
     /** This method sets center*/
     void SetCenter(ScenePoint2D scenePos);
--- a/Framework/Scene2DViewport/MeasureTools.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTools.cpp	Sun May 19 16:26:17 2019 +0200
@@ -22,7 +22,7 @@
 
 #include <Core/Logging.h>
 #include <Core/Enumerations.h>
-#include <Core>
+#include <Core/OrthancException.h>
 
 #include <boost/math/constants/constants.hpp>
 
@@ -53,26 +53,34 @@
     return enabled_;
   }
 
-  OrthancStone::Scene2DPtr MeasureTool::GetScene()
+
+  ViewportControllerPtr MeasureTool::GetController()
   {
-    Scene2DPtr scene = scene_.lock();
-    if (!scene)
-      throw OrthancException(ErrorCode_InternalError, "Using dead object!");
-    return scene;
+    ViewportControllerPtr controller = controllerW_.lock();
+    if (!controller)
+      throw OrthancException(ErrorCode_InternalError, 
+        "Using dead ViewportController object!");
+    return controller;
   }
 
-  MeasureTool::MeasureTool(MessageBroker& broker, Scene2DWPtr sceneW)
+  OrthancStone::Scene2DPtr MeasureTool::GetScene()
+  {
+    return GetController()->GetScene();
+  }
+
+  MeasureTool::MeasureTool(MessageBroker& broker,
+    ViewportControllerWPtr controllerW)
     : IObserver(broker)
-    , scene_(sceneW)
+    , controllerW_(controllerW)
     , enabled_(true)
   {
-    GetScene()->RegisterObserverCallback(
-      new Callable<MeasureTool, Scene2D::SceneTransformChanged>
+    GetController()->RegisterObserverCallback(
+      new Callable<MeasureTool, ViewportController::SceneTransformChanged>
       (*this, &MeasureTool::OnSceneTransformChanged));
   }
 
   void MeasureTool::OnSceneTransformChanged(
-    const Scene2D::SceneTransformChanged& message)
+    const ViewportController::SceneTransformChanged& message)
   {
     RefreshScene();
   }
--- a/Framework/Scene2DViewport/MeasureTools.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTools.h	Sun May 19 16:26:17 2019 +0200
@@ -21,6 +21,7 @@
 #pragma once
 
 #include <Framework/Scene2DViewport/PointerTypes.h>
+#include <Framework/Scene2DViewport/ViewportController.h>
 
 #include <Framework/Scene2D/Scene2D.h>
 #include <Framework/Scene2D/ScenePoint2D.h>
@@ -57,10 +58,11 @@
     This method is called when the scene transform changes. It allows to 
     recompute the visual elements whose content depend upon the scene transform
     */
-    void OnSceneTransformChanged(const Scene2D::SceneTransformChanged& message);
+    void OnSceneTransformChanged(
+      const ViewportController::SceneTransformChanged& message);
 
   protected:
-    MeasureTool(MessageBroker& broker, Scene2DWPtr sceneW);
+    MeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW);
     
     /**
     This is the meat of the tool: this method must [create (if needed) and]
@@ -69,8 +71,9 @@
     */
     virtual void RefreshScene() = 0;
 
+    ViewportControllerPtr GetController();
     Scene2DPtr GetScene();
-
+    
     /**
     enabled_ is not accessible by subclasses because there is a state machine
     that we do not wanna mess with
@@ -78,7 +81,7 @@
     bool IsEnabled() const;
 
   private:
-    Scene2DWPtr scene_;
+    ViewportControllerWPtr controllerW_;
     bool     enabled_;
   };
 }
--- a/Framework/Scene2DViewport/MeasureTrackers.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.cpp	Sun May 19 16:26:17 2019 +0200
@@ -27,11 +27,11 @@
 {
 
   CreateMeasureTracker::CreateMeasureTracker(
-    Scene2DWPtr                     sceneW,
+    ViewportControllerWPtr          controllerW,
     std::vector<TrackerCommandPtr>& undoStack,
     std::vector<MeasureToolPtr>&    measureTools)
-    : scene_(sceneW)
-    , active_(true)
+    : controllerW_(controllerW)
+    , alive_(true)
     , undoStack_(undoStack)
     , measureTools_(measureTools)
     , commitResult_(true)
@@ -41,12 +41,12 @@
   void CreateMeasureTracker::Cancel()
   {
     commitResult_ = false;
-    active_ = false;
+    alive_ = false;
   }
 
-  bool CreateMeasureTracker::IsActive() const
+  bool CreateMeasureTracker::IsAlive() const
   {
-    return active_;
+    return alive_;
   }
 
   CreateMeasureTracker::~CreateMeasureTracker()
@@ -60,6 +60,13 @@
     else
       command_->Undo();
   }
+
+
+  OrthancStone::Scene2DPtr CreateMeasureTracker::GetScene()
+  {
+    return controllerW_.lock()->GetScene();
+  }
+
 }
 
 
--- a/Framework/Scene2DViewport/MeasureTrackers.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.h	Sun May 19 16:26:17 2019 +0200
@@ -35,10 +35,10 @@
   {
   public:
     virtual void Cancel() ORTHANC_OVERRIDE;
-    virtual bool IsActive() const ORTHANC_OVERRIDE;
+    virtual bool IsAlive() const ORTHANC_OVERRIDE;
   protected:
     CreateMeasureTracker(
-      Scene2DWPtr                     scene,
+      ViewportControllerWPtr          controllerW,
       std::vector<TrackerCommandPtr>& undoStack,
       std::vector<MeasureToolPtr>&    measureTools);
 
@@ -46,8 +46,10 @@
   
   protected:
     CreateMeasureCommandPtr         command_;
-    Scene2DWPtr                     scene_;
-    bool                            active_;
+    ViewportControllerWPtr          controllerW_;
+    bool                            alive_;
+    Scene2DPtr                      GetScene();
+
   private:
     std::vector<TrackerCommandPtr>& undoStack_;
     std::vector<MeasureToolPtr>&    measureTools_;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/OneGesturePointerTracker.cpp	Sun May 19 16:26:17 2019 +0200
@@ -0,0 +1,56 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "OneGesturePointerTracker.h"
+#include <Core/OrthancException.h>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+  OneGesturePointerTracker::OneGesturePointerTracker(
+    ViewportControllerWPtr controllerW)
+    : controllerW_(controllerW)
+    , alive_(true)
+  {
+  }
+
+  void OneGesturePointerTracker::PointerUp(const PointerEvent& event)
+  {
+    alive_ = false;
+  }
+
+  void OneGesturePointerTracker::PointerDown(const PointerEvent& event)
+  {
+    throw OrthancException(ErrorCode_InternalError, "Wrong state in tracker");
+  }
+
+  bool OneGesturePointerTracker::IsAlive() const
+  {
+    return alive_;
+  }
+
+  ViewportControllerPtr OneGesturePointerTracker::GetController()
+  {
+    return controllerW_.lock();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/OneGesturePointerTracker.h	Sun May 19 16:26:17 2019 +0200
@@ -0,0 +1,54 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "IFlexiblePointerTracker.h"
+
+namespace OrthancStone
+{
+  /**
+  This base is class allows to write simple trackers that deal with single 
+  drag gestures. It is *not* suitables for multi-state trackers where various
+  mouse operations need to be handled.
+
+  In order to write such a tracker:
+  - subclass this class
+  - you may store the initial click/touch position in the constructor
+  - implement PointerMove to react to pointer/touch events
+  - implement Cancel to restore the state at initial tracker creation time
+  */
+  class OneGesturePointerTracker : public IFlexiblePointerTracker
+  {
+  public:
+    OneGesturePointerTracker(ViewportControllerWPtr controllerW);
+    virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE;
+    virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE;
+    virtual bool IsAlive() const ORTHANC_OVERRIDE;
+  
+  protected:
+    ViewportControllerPtr  GetController();
+
+  private:
+    ViewportControllerWPtr controllerW_;
+    bool                   alive_;
+  };
+}
+
--- a/Framework/Scene2DViewport/PointerTypes.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/PointerTypes.h	Sun May 19 16:26:17 2019 +0200
@@ -79,5 +79,5 @@
 
   class ViewportController;
   typedef boost::shared_ptr<ViewportController> ViewportControllerPtr;
-
+  typedef boost::weak_ptr<ViewportController> ViewportControllerWPtr;
 }
--- a/Framework/Scene2DViewport/ViewportController.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Sun May 19 16:26:17 2019 +0200
@@ -22,28 +22,44 @@
 
 #include <Framework/StoneException.h>
 
+#include <boost/make_shared.hpp>
+
 using namespace Orthanc;
 
 namespace OrthancStone
 {
-
   ViewportController::ViewportController(MessageBroker& broker)
-    : broker_(broker)
+    : IObservable(broker)
   {
-
+    scene_ = boost::make_shared<Scene2D>();
   }
 
   Scene2DPtr ViewportController::GetScene()
   {
-    Scene2DPtr scene = scene_.lock();
-    if (!scene)
-      throw OrthancException(ErrorCode_InternalError, "Using dead object!");
-    return scene;
+    return scene_;
   }
 
   bool ViewportController::HandlePointerEvent(PointerEvent e)
   {
     throw StoneException(ErrorCode_NotImplemented);
   }
+
+  const OrthancStone::AffineTransform2D& ViewportController::GetCanvasToSceneTransform() const
+  {
+    return scene_->GetCanvasToSceneTransform();
+  }
+
+  const OrthancStone::AffineTransform2D& ViewportController::GetSceneToCanvasTransform() const
+  {
+    return scene_->GetSceneToCanvasTransform();
+  }
+
+  void ViewportController::SetSceneToCanvasTransform(
+    const AffineTransform2D& transform)
+  {
+    scene_->SetSceneToCanvasTransform(transform);
+    BroadcastMessage(SceneTransformChanged(*this));
+  }
+
 }
 
--- a/Framework/Scene2DViewport/ViewportController.h	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.h	Sun May 19 16:26:17 2019 +0200
@@ -24,7 +24,6 @@
 
 #include <Framework/Scene2D/Scene2D.h>
 #include <Framework/Scene2D/PointerEvent.h>
-#include <Framework/Scene2DViewport/MeasureTools.h>
 #include <Framework/Scene2DViewport/IFlexiblePointerTracker.h>
 
 namespace OrthancStone
@@ -41,9 +40,12 @@
   Each canvas or other GUI area where we want to display a 2D image, either 
   directly or through slicing must be assigned a ViewportController.
   */
-  class ViewportController : public boost::noncopyable
+  class ViewportController : public IObservable
   {
   public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \
+      SceneTransformChanged, ViewportController);
+
     ViewportController(MessageBroker& broker);
 
     Scene2DPtr GetScene();
@@ -67,9 +69,18 @@
     */
     void SetActiveTracker(FlexiblePointerTrackerPtr tracker);
 
+    /** Forwarded to the underlying scene */
+    const AffineTransform2D& GetCanvasToSceneTransform() const;
+
+    /** Forwarded to the underlying scene */
+    const AffineTransform2D& GetSceneToCanvasTransform() const;
+
+    /** Forwarded to the underlying scene, and broadcasted to the observers */
+    void SetSceneToCanvasTransform(const AffineTransform2D& transform);
+
+
   private:
-    MessageBroker&            broker_;
-    Scene2DWPtr               scene_;
+    Scene2DPtr                scene_;
     FlexiblePointerTrackerPtr tracker_;
   };
 }
--- a/Framework/Toolbox/ShearWarpProjectiveTransform.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Framework/Toolbox/ShearWarpProjectiveTransform.cpp	Sun May 19 16:26:17 2019 +0200
@@ -543,7 +543,8 @@
         }
         else
         {
-          *p = *qacc / static_cast<float>(*qcount);
+          *p = static_cast<TargetTraits::PixelType>
+            (*qacc / static_cast<float>(*qcount));
 
           if (*p > maxValue)
           {
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri May 17 09:20:46 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Sun May 19 16:26:17 2019 +0200
@@ -357,6 +357,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTrackers.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/OneGesturePointerTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/PointerTypes.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/ViewportController.h
--- a/Samples/Sdl/BasicScene.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Samples/Sdl/BasicScene.cpp	Sun May 19 16:26:17 2019 +0200
@@ -28,6 +28,8 @@
 #include "../../Framework/Scene2D/RotateSceneTracker.h"
 #include "../../Framework/Scene2D/Scene2D.h"
 #include "../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../Framework/Scene2DViewport/ViewportController.h"
+
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/Messages/MessageBroker.h"
 
@@ -44,11 +46,11 @@
 static const unsigned int FONT_SIZE = 32;
 static const int LAYER_POSITION = 150;
 
+using namespace OrthancStone;
 
-void PrepareScene(OrthancStone::Scene2D& scene)
+void PrepareScene(ViewportControllerPtr controller)
 {
-  using namespace OrthancStone;
-
+  Scene2D& scene(*controller->GetScene());
   // Texture of 2x2 size
   {
     Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
@@ -137,12 +139,12 @@
 
 
 void TakeScreenshot(const std::string& target,
-                    const OrthancStone::Scene2D& scene,
+                    const Scene2D& scene,
                     unsigned int canvasWidth,
                     unsigned int canvasHeight)
 {
   // Take a screenshot, then save it as PNG file
-  OrthancStone::CairoCompositor compositor(scene, canvasWidth, canvasHeight);
+  CairoCompositor compositor(scene, canvasWidth, canvasHeight);
   compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1);
   compositor.Refresh();
 
@@ -157,11 +159,12 @@
 }
 
 
-void HandleApplicationEvent(OrthancStone::Scene2D& scene,
-                            const OrthancStone::OpenGLCompositor& compositor,
+void HandleApplicationEvent(ViewportControllerPtr controller,
+                            const OpenGLCompositor& compositor,
                             const SDL_Event& event,
-                            std::auto_ptr<OrthancStone::IPointerTracker>& activeTracker)
+                            FlexiblePointerTrackerPtr& activeTracker)
 {
+  Scene2D& scene(*controller->GetScene());
   if (event.type == SDL_MOUSEMOTION)
   {
     int scancodeCount = 0;
@@ -173,28 +176,29 @@
     {
       // The "left-ctrl" key is down, while no tracker is present
 
-      OrthancStone::PointerEvent e;
+      PointerEvent e;
       e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
 
-      OrthancStone::ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform());
+      ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform());
 
       char buf[64];
       sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY());
 
       if (scene.HasLayer(LAYER_POSITION))
       {
-        OrthancStone::TextSceneLayer& layer =
-          dynamic_cast<OrthancStone::TextSceneLayer&>(scene.GetLayer(LAYER_POSITION));
+        TextSceneLayer& layer =
+          dynamic_cast<TextSceneLayer&>(scene.GetLayer(LAYER_POSITION));
         layer.SetText(buf);
         layer.SetPosition(p.GetX(), p.GetY());
       }
       else
       {
-        std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer);
+        std::auto_ptr<TextSceneLayer> 
+          layer(new TextSceneLayer);
         layer->SetColor(0, 255, 0);
         layer->SetText(buf);
         layer->SetBorder(20);
-        layer->SetAnchor(OrthancStone::BitmapAnchor_BottomCenter);
+        layer->SetAnchor(BitmapAnchor_BottomCenter);
         layer->SetPosition(p.GetX(), p.GetY());
         scene.SetLayer(LAYER_POSITION, layer.release());
       }
@@ -206,22 +210,24 @@
   }
   else if (event.type == SDL_MOUSEBUTTONDOWN)
   {
-    OrthancStone::PointerEvent e;
+    PointerEvent e;
     e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
 
     switch (event.button.button)
     {
       case SDL_BUTTON_MIDDLE:
-        activeTracker.reset(new OrthancStone::PanSceneTracker(scene, e));
+        activeTracker.reset(new PanSceneTracker(
+          controller, e));
         break;
 
       case SDL_BUTTON_RIGHT:
-        activeTracker.reset(new OrthancStone::ZoomSceneTracker(scene, e, 
-                                                               compositor.GetCanvasHeight()));
+        activeTracker.reset(new ZoomSceneTracker(
+          controller, e, compositor.GetCanvasHeight()));
         break;
 
       case SDL_BUTTON_LEFT:
-        activeTracker.reset(new OrthancStone::RotateSceneTracker(scene, e));
+        activeTracker.reset(new RotateSceneTracker(
+          controller, e));
         break;
 
       default:
@@ -269,20 +275,21 @@
 }
 
 
-void Run(OrthancStone::Scene2D& scene)
+void Run(ViewportControllerPtr controller)
 {
-  OrthancStone::SdlOpenGLWindow window("Hello", 1024, 768);
+  SdlOpenGLWindow window("Hello", 1024, 768);
 
-  scene.FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
+  controller->GetScene()->FitContent(
+    window.GetCanvasWidth(), window.GetCanvasHeight());
   
   glEnable(GL_DEBUG_OUTPUT);
   glDebugMessageCallback(OpenGLMessageCallback, 0);
 
-  OrthancStone::OpenGLCompositor compositor(window, scene);
+  OpenGLCompositor compositor(window, *controller->GetScene());
   compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
                      FONT_SIZE, Orthanc::Encoding_Latin1);
 
-  std::auto_ptr<OrthancStone::IPointerTracker>  tracker;
+  FlexiblePointerTrackerPtr tracker;
 
   bool stop = false;
   while (!stop)
@@ -300,25 +307,30 @@
       }
       else if (event.type == SDL_MOUSEMOTION)
       {
-        if (tracker.get() != NULL)
+        if (tracker)
         {
-          OrthancStone::PointerEvent e;
-          e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-          tracker->Update(e);
+          PointerEvent e;
+          e.AddPosition(compositor.GetPixelCenterCoordinates(
+            event.button.x, event.button.y));
+          tracker->PointerMove(e);
         }
       }
       else if (event.type == SDL_MOUSEBUTTONUP)
       {
-        if (tracker.get() != NULL)
+        if (tracker)
         {
-          tracker->Release();
-          tracker.reset(NULL);
+          PointerEvent e;
+          e.AddPosition(compositor.GetPixelCenterCoordinates(
+            event.button.x, event.button.y));
+          tracker->PointerUp(e);
+          if(!tracker->IsAlive())
+            tracker = NULL;
         }
       }
       else if (event.type == SDL_WINDOWEVENT &&
                event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
       {
-        tracker.reset(NULL);
+        tracker = NULL;
         compositor.UpdateSize();
       }
       else if (event.type == SDL_KEYDOWN &&
@@ -339,7 +351,7 @@
         }
       }
       
-      HandleApplicationEvent(scene, compositor, event, tracker);
+      HandleApplicationEvent(controller, compositor, event, tracker);
     }
 
     SDL_Delay(1);
@@ -356,22 +368,23 @@
  **/
 int main(int argc, char* argv[])
 {
-  OrthancStone::StoneInitialize();
+  StoneInitialize();
   Orthanc::Logging::EnableInfoLevel(true);
 
   try
   {
-    OrthancStone::MessageBroker broker;
-    OrthancStone::Scene2D scene(broker);
-    PrepareScene(scene);
-    Run(scene);
+    MessageBroker broker;
+    ViewportControllerPtr controller(
+      new ViewportController(broker));
+    PrepareScene(controller);
+    Run(controller);
   }
   catch (Orthanc::OrthancException& e)
   {
     LOG(ERROR) << "EXCEPTION: " << e.What();
   }
 
-  OrthancStone::StoneFinalize();
+  StoneFinalize();
 
   return 0;
 }
--- a/Samples/Sdl/TrackerSampleApp.cpp	Fri May 17 09:20:46 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Sun May 19 16:26:17 2019 +0200
@@ -70,7 +70,7 @@
 
   Scene2DPtr TrackerSampleApp::GetScene()
   {
-    return controller_.GetScene();
+    return controller_->GetScene();
   }
 
   void TrackerSampleApp::SelectNextTool()
@@ -192,7 +192,7 @@
           //  e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
           
           activeTracker_->PointerMove(e);
-          if (!activeTracker_->IsActive())
+          if (!activeTracker_->IsAlive())
             activeTracker_ = NULL;
         }
       }
@@ -204,7 +204,7 @@
         PointerEvent e;
         e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y));
         activeTracker_->PointerUp(e);
-        if (!activeTracker_->IsActive())
+        if (!activeTracker_->IsAlive())
           activeTracker_ = NULL;
       }
     }
@@ -216,7 +216,7 @@
       if (activeTracker_)
       {
         activeTracker_->PointerDown(e);
-        if (!activeTracker_->IsActive())
+        if (!activeTracker_->IsAlive())
           activeTracker_ = NULL;
       }
       else
@@ -234,7 +234,7 @@
         if (activeTracker_)
         {
           activeTracker_->Cancel();
-          if (!activeTracker_->IsActive())
+          if (!activeTracker_->IsAlive())
             activeTracker_ = NULL;
         }
         break;
@@ -268,7 +268,8 @@
   }
 
 
-  void TrackerSampleApp::OnSceneTransformChanged(const Scene2D::SceneTransformChanged& message)
+  void TrackerSampleApp::OnSceneTransformChanged(
+    const ViewportController::SceneTransformChanged& message)
   {
     DisplayInfoText();
   }
@@ -280,12 +281,12 @@
     switch (event.button.button)
     {
     case SDL_BUTTON_MIDDLE:
-      return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-        new PanSceneTracker(*GetScene(), e)));
+      return FlexiblePointerTrackerPtr(new PanSceneTracker
+        (controller_, e));
 
     case SDL_BUTTON_RIGHT:
-      return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-        new ZoomSceneTracker(*GetScene(), e, compositor_->GetCanvasHeight())));
+      return FlexiblePointerTrackerPtr(new ZoomSceneTracker
+        (controller_, e, compositor_->GetCanvasHeight()));
 
     case SDL_BUTTON_LEFT:
     {
@@ -310,14 +311,14 @@
         {
         case GuiTool_Rotate:
           //LOG(TRACE) << "Creating RotateSceneTracker";
-          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new RotateSceneTracker(*GetScene(), e)));
+          return FlexiblePointerTrackerPtr(new RotateSceneTracker(
+            controller_, e));
         case GuiTool_Pan:
-          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new PanSceneTracker(*GetScene(), e)));
+          return FlexiblePointerTrackerPtr(new PanSceneTracker(
+            controller_, e));
         case GuiTool_Zoom:
-          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new ZoomSceneTracker(*GetScene(), e, compositor_->GetCanvasHeight())));
+          return FlexiblePointerTrackerPtr(new ZoomSceneTracker(
+            controller_, e, compositor_->GetCanvasHeight()));
         //case GuiTool_AngleMeasure:
         //  return new AngleMeasureTracker(GetScene(), measureTools_, undoStack_, e);
         //case GuiTool_CircleMeasure:
@@ -326,10 +327,10 @@
         //  return new EllipseMeasureTracker(GetScene(), measureTools_, undoStack_, e);
         case GuiTool_LineMeasure:
           return FlexiblePointerTrackerPtr(new CreateLineMeasureTracker(
-            IObserver::GetBroker(), GetScene(), undoStack_, measureTools_, e));
+            IObserver::GetBroker(), controller_, undoStack_, measureTools_, e));
         case GuiTool_AngleMeasure:
           return FlexiblePointerTrackerPtr(new CreateAngleMeasureTracker(
-            IObserver::GetBroker(), GetScene(), undoStack_, measureTools_, e));
+            IObserver::GetBroker(), controller_, undoStack_, measureTools_, e));
           return NULL;
         case GuiTool_CircleMeasure:
           LOG(ERROR) << "Not implemented yet!";
@@ -349,7 +350,6 @@
 
 
   TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker)
-    , scene_(broker)
     , currentTool_(GuiTool_Rotate)
   {
     controller_ = ViewportControllerPtr(new ViewportController(broker));
@@ -469,7 +469,7 @@
     unsigned int canvasWidth,
     unsigned int canvasHeight)
   {
-    CairoCompositor compositor(GetScene(), canvasWidth, canvasHeight);
+    CairoCompositor compositor(*GetScene(), canvasWidth, canvasHeight);
     compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1);
     compositor.Refresh();
 
@@ -520,7 +520,7 @@
     glEnable(GL_DEBUG_OUTPUT);
     glDebugMessageCallback(OpenGLMessageCallback, 0);
 
-    compositor_.reset(new OpenGLCompositor(window, GetScene()));
+    compositor_.reset(new OpenGLCompositor(window, *GetScene()));
 
     compositor_->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT,
       FONT_SIZE_0, Orthanc::Encoding_Latin1);
@@ -565,6 +565,9 @@
       }
       SDL_Delay(1);
     }
+
+    // the following is paramount because the compositor holds a reference
+    // to the scene and we do not want this reference to become dangling
     compositor_.reset(NULL);
   }
 
--- a/Samples/Sdl/TrackerSampleApp.h	Fri May 17 09:20:46 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.h	Sun May 19 16:26:17 2019 +0200
@@ -74,7 +74,8 @@
     This method is called when the scene transform changes. It allows to
     recompute the visual elements whose content depend upon the scene transform
     */
-    void OnSceneTransformChanged(const Scene2D::SceneTransformChanged& message);
+    void OnSceneTransformChanged(
+      const ViewportController::SceneTransformChanged& message);
 
   private:
     void SelectNextTool();