changeset 703:d4d6c5b502b5

Merging refactor-viewport-controller
author Benjamin Golinvaux <bgo@osimis.io>
date Sun, 19 May 2019 16:31:56 +0200
parents 75deb0acd632 (current diff) c0a5eb9a4290 (diff)
children f8242d4b1f8d
files Samples/Common/AngleMeasureTool.cpp Samples/Common/AngleMeasureTool.h Samples/Common/CreateAngleMeasureTracker.cpp Samples/Common/CreateAngleMeasureTracker.h Samples/Common/CreateCircleMeasureTracker.cpp Samples/Common/CreateCircleMeasureTracker.h Samples/Common/CreateLineMeasureTracker.cpp Samples/Common/CreateLineMeasureTracker.h Samples/Common/CreateMeasureTracker.cpp Samples/Common/CreateMeasureTracker.h Samples/Common/CreateSimpleTrackerAdapter.cpp Samples/Common/EditAngleMeasureTracker.cpp Samples/Common/EditAngleMeasureTracker.h Samples/Common/EditCircleMeasureTracker.cpp Samples/Common/EditCircleMeasureTracker.h Samples/Common/EditLineMeasureTracker.cpp Samples/Common/EditLineMeasureTracker.h Samples/Common/IFlexiblePointerTracker.h Samples/Common/LineMeasureTool.cpp Samples/Common/LineMeasureTool.h Samples/Common/MeasureCommands.cpp Samples/Common/MeasureCommands.h Samples/Common/MeasureTools.cpp Samples/Common/MeasureTools.h Samples/Common/MeasureToolsToolbox.cpp Samples/Common/MeasureToolsToolbox.h Samples/Common/MeasureTrackers.cpp Samples/Common/MeasureTrackers.h
diffstat 80 files changed, 3021 insertions(+), 2532 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/OpenGL/OpenGLShader.cpp	Fri May 17 18:04:26 2019 +0200
+++ b/Framework/OpenGL/OpenGLShader.cpp	Sun May 19 16:31:56 2019 +0200
@@ -35,7 +35,7 @@
       GLint sourceStringLengths[1];
 
       sourceString[0] = source.c_str();
-      sourceStringLengths[0] = source.length();
+      sourceStringLengths[0] = static_cast<GLint>(source.length());
       GLuint shader = glCreateShader(type);
 
       if (shader == 0)
--- a/Framework/Scene2D/IPointerTracker.h	Fri May 17 18:04:26 2019 +0200
+++ b/Framework/Scene2D/IPointerTracker.h	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/Internals/FixedPointAligner.cpp	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/Internals/FixedPointAligner.h	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/PanSceneTracker.cpp	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/PanSceneTracker.h	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/RotateSceneTracker.cpp	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/RotateSceneTracker.h	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/Scene2D.cpp	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/Scene2D.h	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/ZoomSceneTracker.cpp	Sun May 19 16:31:56 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 18:04:26 2019 +0200
+++ b/Framework/Scene2D/ZoomSceneTracker.h	Sun May 19 16:31:56 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()
-    {
-    }
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,283 @@
+/**
+ * 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/>.
+ **/
+
+#include "AngleMeasureTool.h"
+#include "MeasureToolsToolbox.h"
+
+#include <Core/Logging.h>
+
+#include <boost/math/constants/constants.hpp>
+
+extern void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value);
+
+namespace OrthancStone
+{
+  AngleMeasureTool::~AngleMeasureTool()
+  {
+    // this measuring tool is a RABI for the corresponding visual layers
+    // stored in the 2D scene
+    Disable();
+    RemoveFromScene();
+  }
+
+  void AngleMeasureTool::RemoveFromScene()
+  {
+    if (layersCreated)
+    {
+      assert(GetScene()->HasLayer(polylineZIndex_));
+      assert(GetScene()->HasLayer(textBaseZIndex_));
+      GetScene()->DeleteLayer(polylineZIndex_);
+      GetScene()->DeleteLayer(textBaseZIndex_);
+    }
+  }
+
+  void AngleMeasureTool::SetSide1End(ScenePoint2D pt)
+  {
+    side1End_ = pt;
+    RefreshScene();
+  }
+
+  void AngleMeasureTool::SetSide2End(ScenePoint2D pt)
+  {
+    side2End_ = pt;
+    RefreshScene();
+  }
+
+  void AngleMeasureTool::SetCenter(ScenePoint2D pt)
+  {
+    center_ = pt;
+    RefreshScene();
+  }
+  
+  PolylineSceneLayer* AngleMeasureTool::GetPolylineLayer()
+  {
+    assert(GetScene()->HasLayer(polylineZIndex_));
+    ISceneLayer* layer = &(GetScene()->GetLayer(polylineZIndex_));
+    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
+    assert(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  void AngleMeasureTool::RefreshScene()
+  {
+    if (IsEnabled())
+    {
+      // get the scaling factor 
+      const double pixelToScene =
+        GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+
+      if (!layersCreated)
+      {
+        // Create the layers if need be
+
+        assert(textBaseZIndex_ == -1);
+        {
+          polylineZIndex_ = GetScene()->GetMaxDepth() + 100;
+          //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
+          std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
+          GetScene()->SetLayer(polylineZIndex_, layer.release());
+
+        }
+        {
+          textBaseZIndex_ = GetScene()->GetMaxDepth() + 100;
+          // create the four text background layers
+          {
+            std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+            GetScene()->SetLayer(textBaseZIndex_, layer.release());
+          }
+          {
+            std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+            GetScene()->SetLayer(textBaseZIndex_+1, layer.release());
+          }
+          {
+            std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+            GetScene()->SetLayer(textBaseZIndex_+2, layer.release());
+          }
+          {
+            std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+            GetScene()->SetLayer(textBaseZIndex_+3, layer.release());
+          }
+          
+          // and the text layer itself
+          {
+            std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+            GetScene()->SetLayer(textBaseZIndex_+4, layer.release());
+          }
+          
+        }
+        layersCreated = true;
+      }
+      else
+      {
+        assert(GetScene()->HasLayer(polylineZIndex_));
+        assert(GetScene()->HasLayer(textBaseZIndex_));
+      }
+      {
+        // Fill the polyline layer with the measurement line
+
+        PolylineSceneLayer* polylineLayer = GetPolylineLayer();
+        polylineLayer->ClearAllChains();
+        polylineLayer->SetColor(0, 183, 17);
+
+        // sides
+        {
+          {
+            PolylineSceneLayer::Chain chain;
+            chain.push_back(side1End_);
+            chain.push_back(center_);
+            polylineLayer->AddChain(chain, false);
+          }
+          {
+            PolylineSceneLayer::Chain chain;
+            chain.push_back(side2End_);
+            chain.push_back(center_);
+            polylineLayer->AddChain(chain, false);
+          }
+        }
+
+        // handles
+        {
+          //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, *GetScene(), side1End_, 10.0* pixelToScene); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, *GetScene(), side2End_, 10.0* pixelToScene); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+        }
+
+        // arc
+        {
+          PolylineSceneLayer::Chain chain;
+
+          const double ARC_RADIUS_CANVAS_COORD = 30.0;
+          AddShortestArc(chain, *GetScene(), side1End_, center_, side2End_, 
+            ARC_RADIUS_CANVAS_COORD*pixelToScene);
+          polylineLayer->AddChain(chain, false);
+        }
+      }
+      {
+        // Set the text layer
+
+        double p1cAngle = atan2(
+          side1End_.GetY() - center_.GetY(),
+          side1End_.GetX() - center_.GetX());
+
+
+        double p2cAngle = atan2(
+          side2End_.GetY() - center_.GetY(),
+          side2End_.GetX() - center_.GetX());
+
+        double delta = NormalizeAngle(p2cAngle - p1cAngle);
+
+
+        double theta = p1cAngle + delta/2;
+
+
+        const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90;
+
+        double offsetX = TEXT_CENTER_DISTANCE_CANVAS_COORD * cos(theta);
+
+        double offsetY = TEXT_CENTER_DISTANCE_CANVAS_COORD * sin(theta);
+
+        double pointX = center_.GetX() + offsetX * pixelToScene;
+        double pointY = center_.GetY() + offsetY * pixelToScene;
+
+        char buf[64];
+        double angleDeg = RadiansToDegrees(delta);
+
+        // http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00B0&mode=hex
+        sprintf(buf, "%0.02f\xc2\xb0", angleDeg);
+
+        SetTextLayerOutlineProperties(
+          *GetScene(), textBaseZIndex_, buf, ScenePoint2D(pointX, pointY));
+
+        // TODO:make it togglable
+        bool enableInfoDisplay = false;
+        if (enableInfoDisplay)
+        {
+          TrackerSample_SetInfoDisplayMessage("center_.GetX()",
+            boost::lexical_cast<std::string>(center_.GetX()));
+
+          TrackerSample_SetInfoDisplayMessage("center_.GetY()",
+            boost::lexical_cast<std::string>(center_.GetY()));
+
+          TrackerSample_SetInfoDisplayMessage("side1End_.GetX()",
+            boost::lexical_cast<std::string>(side1End_.GetX()));
+
+          TrackerSample_SetInfoDisplayMessage("side1End_.GetY()",
+            boost::lexical_cast<std::string>(side1End_.GetY()));
+
+          TrackerSample_SetInfoDisplayMessage("side2End_.GetX()",
+            boost::lexical_cast<std::string>(side2End_.GetX()));
+
+          TrackerSample_SetInfoDisplayMessage("side2End_.GetY()",
+            boost::lexical_cast<std::string>(side2End_.GetY()));
+
+          TrackerSample_SetInfoDisplayMessage("p1cAngle (deg)",
+            boost::lexical_cast<std::string>(RadiansToDegrees(p1cAngle)));
+
+          TrackerSample_SetInfoDisplayMessage("delta (deg)",
+            boost::lexical_cast<std::string>(RadiansToDegrees(delta)));
+
+          TrackerSample_SetInfoDisplayMessage("theta (deg)",
+            boost::lexical_cast<std::string>(RadiansToDegrees(theta)));
+
+          TrackerSample_SetInfoDisplayMessage("p2cAngle (deg)",
+            boost::lexical_cast<std::string>(RadiansToDegrees(p2cAngle)));
+
+          TrackerSample_SetInfoDisplayMessage("offsetX (pix)",
+            boost::lexical_cast<std::string>(offsetX));
+
+          TrackerSample_SetInfoDisplayMessage("offsetY (pix)",
+            boost::lexical_cast<std::string>(offsetY));
+
+          TrackerSample_SetInfoDisplayMessage("pointX",
+            boost::lexical_cast<std::string>(pointX));
+
+          TrackerSample_SetInfoDisplayMessage("pointY",
+            boost::lexical_cast<std::string>(pointY));
+
+          TrackerSample_SetInfoDisplayMessage("angleDeg",
+            boost::lexical_cast<std::string>(angleDeg));
+        }
+
+
+
+      }
+    }
+    else
+    {
+      if (layersCreated)
+      {
+        RemoveFromScene();
+        layersCreated = false;
+      }
+    }
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,76 @@
+/**
+ * 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 "MeasureTools.h"
+
+#include <Framework/Scene2D/Scene2D.h>
+#include <Framework/Scene2D/ScenePoint2D.h>
+#include <Framework/Scene2D/PolylineSceneLayer.h>
+#include <Framework/Scene2D/TextSceneLayer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <vector>
+#include <cmath>
+
+namespace OrthancStone
+{
+  class AngleMeasureTool : public MeasureTool
+  {
+  public:
+    AngleMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
+      : MeasureTool(broker, controllerW)
+      , layersCreated(false)
+      , polylineZIndex_(-1)
+      , textBaseZIndex_(-1)
+    {
+
+    }
+
+    ~AngleMeasureTool();
+
+    void SetSide1End(ScenePoint2D start);
+    void SetCenter(ScenePoint2D start);
+    void SetSide2End(ScenePoint2D start);
+
+  private:
+    PolylineSceneLayer* GetPolylineLayer();
+    
+    // 0 --> 3 are for the text background (outline)
+    // 4 is for the actual text
+    TextSceneLayer*     GetTextLayer(int index);
+    virtual void        RefreshScene() ORTHANC_OVERRIDE;
+    void                RemoveFromScene();
+
+  private:
+    ScenePoint2D side1End_;
+    ScenePoint2D side2End_;
+    ScenePoint2D center_;
+    bool         layersCreated;
+    int          polylineZIndex_;
+    int          textBaseZIndex_;
+  };
+
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,130 @@
+/**
+ * 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/>.
+ **/
+
+#include "CreateAngleMeasureTracker.h"
+#include <Core/OrthancException.h>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+  CreateAngleMeasureTracker::CreateAngleMeasureTracker(
+    MessageBroker&                  broker,
+    ViewportControllerWPtr          controllerW,
+    std::vector<TrackerCommandPtr>& undoStack,
+    MeasureToolList&                measureTools,
+    const PointerEvent&             e)
+    : CreateMeasureTracker(controllerW, undoStack, measureTools)
+    , state_(CreatingSide1)
+  {
+    command_.reset(
+      new CreateAngleMeasureCommand(
+        broker,
+        controllerW,
+        measureTools,
+        e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform())));
+  }
+
+  CreateAngleMeasureTracker::~CreateAngleMeasureTracker()
+  {
+  }
+
+  void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
+  {
+    assert(GetScene());
+
+    if (!alive_)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+        "Internal error: wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: active_ == false");
+    }
+
+    ScenePoint2D scenePos = event.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
+
+    switch (state_)
+    {
+    case CreatingSide1:
+      GetCommand()->SetCenter(scenePos);
+      break;
+    case CreatingSide2:
+      GetCommand()->SetSide2End(scenePos);
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
+    //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
+    //  "scenePos.GetY() = " << scenePos.GetY();
+  }
+
+  void CreateAngleMeasureTracker::PointerUp(const PointerEvent& e)
+  {
+    // TODO: the current app does not prevent multiple PointerDown AND
+    // PointerUp to be sent to the tracker.
+    // 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.
+
+    switch (state_)
+    {
+    case CreatingSide1:
+      state_ = CreatingSide2;
+      break;
+    case CreatingSide2:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerUp: state_ == CreatingSide2 ; this should not happen");
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
+  }
+
+  void CreateAngleMeasureTracker::PointerDown(const PointerEvent& e)
+  {
+    switch (state_)
+    {
+    case CreatingSide1:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerDown: state_ == CreatingSide1 ; this should not happen");
+      break;
+    case CreatingSide2:
+      // we are done
+      alive_ = false;
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
+  }
+
+  CreateAngleMeasureCommandPtr CreateAngleMeasureTracker::GetCommand()
+  {
+    return boost::dynamic_pointer_cast<CreateAngleMeasureCommand>(command_);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * 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 "MeasureTrackers.h"
+#include "MeasureCommands.h"
+
+#include <vector>
+
+namespace OrthancStone
+{
+  class CreateAngleMeasureTracker : public CreateMeasureTracker
+  {
+  public:
+    /**
+    When you create this tracker, you need to supply it with the undo stack
+    where it will store the commands that perform the actual measure tool
+    creation and modification.
+    In turn, a container for these commands to store the actual measuring
+    must be supplied, too
+    */
+    CreateAngleMeasureTracker(
+      MessageBroker&                  broker,
+      ViewportControllerWPtr          controllerW,
+      std::vector<TrackerCommandPtr>& undoStack,
+      MeasureToolList&                measureTools,
+      const PointerEvent&             e);
+
+    ~CreateAngleMeasureTracker();
+
+    virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
+
+  private:
+    CreateAngleMeasureCommandPtr GetCommand();
+
+    enum State
+    {
+      CreatingSide1,
+      CreatingSide2,
+      Finished // just for debug
+    };
+    State state_;
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateCircleMeasureTracker.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,23 @@
+/**
+ * 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/>.
+ **/
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateCircleMeasureTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,25 @@
+/**
+ * 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
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,93 @@
+/**
+ * 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/>.
+ **/
+
+#include "CreateLineMeasureTracker.h"
+#include <Core/OrthancException.h>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+  CreateLineMeasureTracker::CreateLineMeasureTracker(
+    MessageBroker&                  broker,
+    ViewportControllerWPtr          controllerW,
+    std::vector<TrackerCommandPtr>& undoStack,
+    MeasureToolList&                measureTools,
+    const PointerEvent&             e)
+    : CreateMeasureTracker(controllerW, undoStack, measureTools)
+  {
+    command_.reset(
+      new CreateLineMeasureCommand(
+        broker,
+        controllerW,
+        measureTools,
+        e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform())));
+  }
+
+  CreateLineMeasureTracker::~CreateLineMeasureTracker()
+  {
+
+  }
+
+  void CreateLineMeasureTracker::PointerMove(const PointerEvent& event)
+  {
+    assert(GetScene());
+    
+    if (!alive_)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+        "Internal error: wrong state in CreateLineMeasureTracker::"
+        "PointerMove: active_ == false");
+    }
+
+    ScenePoint2D scenePos = event.GetMainPosition().Apply(
+      GetScene()->GetCanvasToSceneTransform());
+
+    //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
+    //  "scenePos.GetY() = " << scenePos.GetY();
+
+    CreateLineMeasureTracker* concreteThis =
+      dynamic_cast<CreateLineMeasureTracker*>(this);
+    assert(concreteThis != NULL);
+    GetCommand()->SetEnd(scenePos);
+  }
+
+  void CreateLineMeasureTracker::PointerUp(const PointerEvent& e)
+  {
+    // TODO: the current app does not prevent multiple PointerDown AND
+    // PointerUp to be sent to the tracker.
+    // 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.
+    alive_ = false;
+  }
+
+  void CreateLineMeasureTracker::PointerDown(const PointerEvent& e)
+  {
+    LOG(WARNING) << "Additional touches (fingers, pen, mouse buttons...) "
+      "are ignored when the line measure creation tracker is active";
+  }
+
+  CreateLineMeasureCommandPtr CreateLineMeasureTracker::GetCommand()
+  {
+    return boost::dynamic_pointer_cast<CreateLineMeasureCommand>(command_);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,53 @@
+/**
+ * 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 "MeasureTrackers.h"
+
+namespace OrthancStone
+{
+  class CreateLineMeasureTracker : public CreateMeasureTracker
+  {
+  public:
+    /**
+    When you create this tracker, you need to supply it with the undo stack
+    where it will store the commands that perform the actual measure tool
+    creation and modification.
+    In turn, a container for these commands to store the actual measuring
+    must be supplied, too
+    */
+    CreateLineMeasureTracker(
+      MessageBroker&                  broker,
+      ViewportControllerWPtr          controllerW,
+      std::vector<TrackerCommandPtr>& undoStack,
+      MeasureToolList&                measureTools,
+      const PointerEvent&             e);
+
+    ~CreateLineMeasureTracker();
+
+    virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
+    virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
+
+  private:
+    CreateLineMeasureCommandPtr GetCommand();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateMeasureTracker.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,20 @@
+/**
+ * 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/>.
+ **/
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateMeasureTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,22 @@
+/**
+ * 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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,79 @@
+/**
+ * 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/>.
+ **/
+
+#include "IFlexiblePointerTracker.h"
+#include <Framework/Scene2D/IPointerTracker.h>
+
+
+namespace OrthancStone
+{
+#if 0
+  namespace
+  {
+    class SimpleTrackerAdapter : public IFlexiblePointerTracker
+    {
+    public:
+      SimpleTrackerAdapter(PointerTrackerPtr wrappedTracker)
+        : wrappedTracker_(wrappedTracker)
+        , active_(true)
+      {
+      }
+
+      virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
+      {
+        if(active_)
+          wrappedTracker_->Update(event);
+      };
+      virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
+      {
+        if (wrappedTracker_)
+        {
+          wrappedTracker_->Release();
+          wrappedTracker_ = NULL;
+        }
+        active_ = false;
+      }
+      virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
+      {
+        // nothing to do atm
+      }
+      virtual bool IsActive() const ORTHANC_OVERRIDE
+      {
+        return active_;
+      }
+
+      virtual void Cancel() ORTHANC_OVERRIDE
+      {
+        wrappedTracker_ = NULL;
+        active_ = false;
+      }
+
+    private:
+      PointerTrackerPtr wrappedTracker_;
+      bool active_;
+    };
+  }
+
+  FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr t)
+  {
+    return FlexiblePointerTrackerPtr(new SimpleTrackerAdapter(t));
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,23 @@
+/**
+ * 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/>.
+ **/
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,25 @@
+/**
+ * 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
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditCircleMeasureTracker.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,23 @@
+/**
+ * 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/>.
+ **/
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditCircleMeasureTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,25 @@
+/**
+ * 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
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,23 @@
+/**
+ * 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/>.
+ **/
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,25 @@
+/**
+ * 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
+
+namespace OrthancStone
+{
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/IFlexiblePointerTracker.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,81 @@
+/**
+ * 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 "PointerTypes.h"
+
+#include <Framework/Scene2D/PointerEvent.h>
+
+namespace OrthancStone
+{
+  /**
+  This interface represents a flexible mouse tracker that can respond to 
+  several events and is not automatically deleted upon mouse up or when touch
+  interaction is suspended : for instance, a stateful tracker with a two-step 
+  interaction like: click & drag --> mouse up --> drag --> mouse click 
+  (for instance, for an angle measuring tracker or an ellipse tracker)
+  */
+  class IFlexiblePointerTracker : public boost::noncopyable
+  {
+  public:
+    virtual ~IFlexiblePointerTracker() {}
+
+    /**
+    This method will be repeatedly called during user interaction
+    */
+    virtual void PointerMove(const PointerEvent& event) = 0;
+
+    /**
+    This method will be called when a touch/pointer is removed (mouse up, 
+    pen lift, finger removed...)
+    */
+    virtual void PointerUp(const PointerEvent& event) = 0;
+
+    /**
+    This method will be called when a touch/pointer is added (mouse down, 
+    pen or finger press)
+    */
+    virtual void PointerDown(const PointerEvent& event) = 0;
+
+    /**
+    This method will be repeatedly called by the tracker owner (for instance,
+    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 IsAlive() const = 0;
+
+    /**
+    This will be called if the tracker needs to be dismissed without committing
+    its changes to the underlying model. If the model has been modified during
+    tracker lifetime, it must be restored to its initial value
+    */
+    virtual void Cancel() = 0;
+  };
+
+
+  /**
+  This factory adopts the supplied simple tracker and creates a flexible 
+  tracker wrapper around it.
+  */
+  FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,200 @@
+/**
+ * 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/>.
+ **/
+
+#include "LineMeasureTool.h"
+#include "MeasureToolsToolbox.h"
+
+#include <Core/Logging.h>
+
+
+namespace OrthancStone
+{
+  LineMeasureTool::~LineMeasureTool()
+  {
+    // this measuring tool is a RABI for the corresponding visual layers
+    // stored in the 2D scene
+    Disable();
+    RemoveFromScene();
+  }
+
+  void LineMeasureTool::RemoveFromScene()
+  {
+    if (layersCreated)
+    {
+      assert(GetScene()->HasLayer(polylineZIndex_));
+      assert(GetScene()->HasLayer(textZIndex_));
+      GetScene()->DeleteLayer(polylineZIndex_);
+      GetScene()->DeleteLayer(textZIndex_);
+    }
+  }
+
+
+  void LineMeasureTool::SetStart(ScenePoint2D start)
+  {
+    start_ = start;
+    RefreshScene();
+  }
+
+  void LineMeasureTool::SetEnd(ScenePoint2D end)
+  {
+    end_ = end;
+    RefreshScene();
+  }
+
+  void LineMeasureTool::Set(ScenePoint2D start, ScenePoint2D end)
+  {
+    start_ = start;
+    end_ = end;
+    RefreshScene();
+  }
+
+  PolylineSceneLayer* LineMeasureTool::GetPolylineLayer()
+  {
+    assert(GetScene()->HasLayer(polylineZIndex_));
+    ISceneLayer* layer = &(GetScene()->GetLayer(polylineZIndex_));
+    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
+    assert(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  TextSceneLayer* LineMeasureTool::GetTextLayer()
+  {
+    assert(GetScene()->HasLayer(textZIndex_));
+    ISceneLayer* layer = &(GetScene()->GetLayer(textZIndex_));
+    TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
+    assert(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  void LineMeasureTool::RefreshScene()
+  {
+    if (IsEnabled())
+    {
+      if (!layersCreated)
+      {
+        // Create the layers if need be
+
+        assert(textZIndex_ == -1);
+        {
+          polylineZIndex_ = GetScene()->GetMaxDepth() + 100;
+          //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
+          std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
+          GetScene()->SetLayer(polylineZIndex_, layer.release());
+        }
+        {
+          textZIndex_ = GetScene()->GetMaxDepth() + 100;
+          //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
+          std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+          GetScene()->SetLayer(textZIndex_, layer.release());
+        }
+        layersCreated = true;
+      }
+      else
+      {
+        assert(GetScene()->HasLayer(polylineZIndex_));
+        assert(GetScene()->HasLayer(textZIndex_));
+      }
+      {
+        // Fill the polyline layer with the measurement line
+
+        PolylineSceneLayer* polylineLayer = GetPolylineLayer();
+        polylineLayer->ClearAllChains();
+        polylineLayer->SetColor(0, 223, 21);
+
+        {
+          PolylineSceneLayer::Chain chain;
+          chain.push_back(start_);
+          chain.push_back(end_);
+          polylineLayer->AddChain(chain, false);
+        }
+
+        // handles
+        {
+          //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, *GetScene(), start_, 10.0); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, *GetScene(), end_, 10.0); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+
+          //ScenePoint2D startC = start_.Apply(GetScene()->GetSceneToCanvasTransform());
+          //double squareSize = 10.0; 
+          //double startHandleLX = startC.GetX() - squareSize/2;
+          //double startHandleTY = startC.GetY() - squareSize / 2;
+          //double startHandleRX = startC.GetX() + squareSize / 2;
+          //double startHandleBY = startC.GetY() + squareSize / 2;
+          //ScenePoint2D startLTC(startHandleLX, startHandleTY);
+          //ScenePoint2D startRTC(startHandleRX, startHandleTY);
+          //ScenePoint2D startRBC(startHandleRX, startHandleBY);
+          //ScenePoint2D startLBC(startHandleLX, startHandleBY);
+
+          //ScenePoint2D startLT = startLTC.Apply(GetScene()->GetCanvasToSceneTransform());
+          //ScenePoint2D startRT = startRTC.Apply(GetScene()->GetCanvasToSceneTransform());
+          //ScenePoint2D startRB = startRBC.Apply(GetScene()->GetCanvasToSceneTransform());
+          //ScenePoint2D startLB = startLBC.Apply(GetScene()->GetCanvasToSceneTransform());
+
+          //PolylineSceneLayer::Chain chain;
+          //chain.push_back(startLT);
+          //chain.push_back(startRT);
+          //chain.push_back(startRB);
+          //chain.push_back(startLB);
+          //polylineLayer->AddChain(chain, true);
+        }
+
+      }
+      {
+        // Set the text layer proporeties
+
+        TextSceneLayer* textLayer = GetTextLayer();
+        double deltaX = end_.GetX() - start_.GetX();
+        double deltaY = end_.GetY() - start_.GetY();
+        double squareDist = deltaX * deltaX + deltaY * deltaY;
+        double dist = sqrt(squareDist);
+        char buf[64];
+        sprintf(buf, "%0.02f units", dist);
+        textLayer->SetText(buf);
+        textLayer->SetColor(0, 223, 21);
+
+        // TODO: for now we simply position the text overlay at the middle
+        // of the measuring segment
+        double midX = 0.5*(end_.GetX() + start_.GetX());
+        double midY = 0.5*(end_.GetY() + start_.GetY());
+        textLayer->SetPosition(midX, midY);
+      }
+    }
+    else
+    {
+      if (layersCreated)
+      {
+        RemoveFromScene();
+        layersCreated = false;
+      }
+    }
+  }
+
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,71 @@
+/**
+ * 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 "MeasureTools.h"
+
+#include <Framework/Scene2D/Scene2D.h>
+#include <Framework/Scene2D/ScenePoint2D.h>
+#include <Framework/Scene2D/PolylineSceneLayer.h>
+#include <Framework/Scene2D/TextSceneLayer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <vector>
+#include <cmath>
+
+namespace OrthancStone
+{
+  class LineMeasureTool : public MeasureTool
+  {
+  public:
+    LineMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
+      : MeasureTool(broker, controllerW)
+      , layersCreated(false)
+      , polylineZIndex_(-1)
+      , textZIndex_(-1)
+    {
+
+    }
+
+    ~LineMeasureTool();
+
+    void SetStart(ScenePoint2D start);
+    void SetEnd(ScenePoint2D end);
+    void Set(ScenePoint2D start, ScenePoint2D end);
+
+  private:
+    PolylineSceneLayer* GetPolylineLayer();
+    TextSceneLayer*     GetTextLayer();
+    virtual void        RefreshScene() ORTHANC_OVERRIDE;
+    void                RemoveFromScene();
+
+  private:
+    ScenePoint2D start_;
+    ScenePoint2D end_;
+    bool         layersCreated;
+    int          polylineZIndex_;
+    int          textZIndex_;
+  };
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureCommands.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,93 @@
+/**
+ * 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/>.
+ **/
+
+#include "MeasureCommands.h"
+
+namespace OrthancStone
+{
+  void CreateMeasureCommand::Undo()
+  {
+    // simply disable the measure tool upon undo
+    GetMeasureTool()->Disable();
+  }
+
+  void CreateMeasureCommand::Redo()
+  {
+    GetMeasureTool()->Enable();
+  }
+
+  CreateMeasureCommand::CreateMeasureCommand(
+    ViewportControllerWPtr controllerW, MeasureToolList& measureTools)
+    : TrackerCommand(controllerW)
+    , measureTools_(measureTools)
+  {
+
+  }
+
+  CreateMeasureCommand::~CreateMeasureCommand()
+  {
+    // deleting the command should not change the model state
+    // we thus leave it as is
+  }
+
+  CreateLineMeasureCommand::CreateLineMeasureCommand(
+    MessageBroker&         broker, 
+    ViewportControllerWPtr controllerW,
+    MeasureToolList&       measureTools, 
+    ScenePoint2D           point)
+    : CreateMeasureCommand(controllerW, measureTools)
+    , measureTool_(new LineMeasureTool(broker, controllerW))
+  {
+    measureTools_.push_back(measureTool_);
+    measureTool_->Set(point, point);
+  }
+
+  void CreateLineMeasureCommand::SetEnd(ScenePoint2D scenePos)
+  {
+    measureTool_->SetEnd(scenePos);
+  }
+
+  CreateAngleMeasureCommand::CreateAngleMeasureCommand(
+    MessageBroker&         broker, 
+    ViewportControllerWPtr controllerW,
+    MeasureToolList&       measureTools, 
+    ScenePoint2D           point)
+    : CreateMeasureCommand(controllerW, measureTools)
+    , measureTool_(new AngleMeasureTool(broker, controllerW))
+  {
+    measureTools_.push_back(measureTool_);
+    measureTool_->SetSide1End(point);
+    measureTool_->SetCenter(point);
+    measureTool_->SetSide2End(point);
+  }
+
+  /** This method sets center*/
+  void CreateAngleMeasureCommand::SetCenter(ScenePoint2D scenePos)
+  {
+    measureTool_->SetCenter(scenePos);
+  }
+
+  /** This method sets end of side 2*/
+  void CreateAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos)
+  {
+    measureTool_->SetSide2End(scenePos);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureCommands.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,110 @@
+/**
+ * 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 <Framework/Scene2D/Scene2D.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+// to be moved into Stone
+#include "PointerTypes.h"
+#include "MeasureTools.h"
+#include "LineMeasureTool.h"
+#include "AngleMeasureTool.h"
+
+namespace OrthancStone
+{
+  class TrackerCommand : public boost::noncopyable
+  {
+  public:
+    TrackerCommand(ViewportControllerWPtr controllerW) 
+      : controllerW_(controllerW)
+    {
+
+    }
+    virtual void Undo() = 0;
+    virtual void Redo() = 0;
+
+  protected:
+    ViewportControllerWPtr controllerW_;
+  };
+
+  class CreateMeasureCommand : public TrackerCommand
+  {
+  public:
+    CreateMeasureCommand(
+      ViewportControllerWPtr controllerW, MeasureToolList& measureTools);
+    ~CreateMeasureCommand();
+    virtual void Undo() ORTHANC_OVERRIDE;
+    virtual void Redo() ORTHANC_OVERRIDE;
+  protected:
+    MeasureToolList& measureTools_;
+  private:
+    /** Must be implemented by the subclasses that create the actual tool */
+    virtual MeasureToolPtr GetMeasureTool() = 0;
+  };
+  
+  class CreateLineMeasureCommand : public CreateMeasureCommand
+  {
+  public:
+    CreateLineMeasureCommand(
+      MessageBroker&         broker, 
+      ViewportControllerWPtr controllerW,
+      MeasureToolList&       measureTools, 
+      ScenePoint2D           point);
+    
+    // the starting position is set in the ctor
+    void SetEnd(ScenePoint2D scenePos);
+
+  private:
+    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
+    {
+      return measureTool_;
+    }
+    LineMeasureToolPtr measureTool_;
+  };
+
+
+  class CreateAngleMeasureCommand : public CreateMeasureCommand
+  {
+  public:
+    /** Ctor sets end of side 1*/
+    CreateAngleMeasureCommand(
+      MessageBroker&         broker, 
+      ViewportControllerWPtr controllerW,
+      MeasureToolList&       measureTools, 
+      ScenePoint2D           point);
+
+    /** This method sets center*/
+    void SetCenter(ScenePoint2D scenePos);
+
+    /** This method sets end of side 2*/
+    void SetSide2End(ScenePoint2D scenePos);
+
+  private:
+    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
+    {
+      return measureTool_;
+    }
+    AngleMeasureToolPtr measureTool_;
+  };
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTools.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,90 @@
+/**
+ * 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/>.
+ **/
+
+#include "MeasureTools.h"
+
+#include <Core/Logging.h>
+#include <Core/Enumerations.h>
+#include <Core/OrthancException.h>
+
+#include <boost/math/constants/constants.hpp>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+
+  MeasureTool::~MeasureTool()
+  {
+
+  }
+
+  void MeasureTool::Enable()
+  {
+    enabled_ = true;
+    RefreshScene();
+  }
+
+  void MeasureTool::Disable()
+  {
+    enabled_ = false;
+    RefreshScene();
+  }
+
+  bool MeasureTool::IsEnabled() const
+  {
+    return enabled_;
+  }
+
+
+  ViewportControllerPtr MeasureTool::GetController()
+  {
+    ViewportControllerPtr controller = controllerW_.lock();
+    if (!controller)
+      throw OrthancException(ErrorCode_InternalError, 
+        "Using dead ViewportController object!");
+    return controller;
+  }
+
+  OrthancStone::Scene2DPtr MeasureTool::GetScene()
+  {
+    return GetController()->GetScene();
+  }
+
+  MeasureTool::MeasureTool(MessageBroker& broker,
+    ViewportControllerWPtr controllerW)
+    : IObserver(broker)
+    , controllerW_(controllerW)
+    , enabled_(true)
+  {
+    GetController()->RegisterObserverCallback(
+      new Callable<MeasureTool, ViewportController::SceneTransformChanged>
+      (*this, &MeasureTool::OnSceneTransformChanged));
+  }
+
+  void MeasureTool::OnSceneTransformChanged(
+    const ViewportController::SceneTransformChanged& message)
+  {
+    RefreshScene();
+  }
+
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTools.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,91 @@
+/**
+ * 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 <Framework/Scene2DViewport/PointerTypes.h>
+#include <Framework/Scene2DViewport/ViewportController.h>
+
+#include <Framework/Scene2D/Scene2D.h>
+#include <Framework/Scene2D/ScenePoint2D.h>
+#include <Framework/Scene2D/PolylineSceneLayer.h>
+#include <Framework/Scene2D/TextSceneLayer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <vector>
+#include <cmath>
+
+namespace OrthancStone
+{
+  class MeasureTool : public IObserver
+  {
+  public:
+    virtual ~MeasureTool();
+
+    /**
+    Enabled tools are rendered in the scene.
+    */
+    void Enable();
+
+    /**
+    Disabled tools are not rendered in the scene. This is useful to be able
+    to use them as their own memento in command stacks (when a measure tool
+    creation command has been undone, the measure remains alive in the
+    command object but is disabled so that it can be redone later on easily)
+    */
+    void Disable();
+
+    /**
+    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 ViewportController::SceneTransformChanged& message);
+
+  protected:
+    MeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW);
+    
+    /**
+    This is the meat of the tool: this method must [create (if needed) and]
+    update the layers and their data according to the measure tool kind and
+    current state. This is repeatedly called during user interaction
+    */
+    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
+    */
+    bool IsEnabled() const;
+
+  private:
+    ViewportControllerWPtr controllerW_;
+    bool     enabled_;
+  };
+}
+
+
+extern void TrackerSample_SetInfoDisplayMessage(
+  std::string key, std::string value);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,336 @@
+/**
+ * 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/>.
+ **/
+
+#include "MeasureToolsToolbox.h"
+
+#include <Framework/Scene2D/TextSceneLayer.h>
+
+#include <boost/math/constants/constants.hpp>
+
+namespace
+{
+  double g_pi = boost::math::constants::pi<double>();
+}
+
+namespace OrthancStone
+{
+  double RadiansToDegrees(double angleRad)
+  {
+    static const double factor = 180.0 / g_pi;
+    return angleRad * factor;
+  }
+
+  void AddSquare(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       sideLength)
+  {
+    chain.clear();
+    chain.reserve(4);
+    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    //TODO: take DPI into account 
+    double handleLX = centerC.GetX() - sideLength / 2;
+    double handleTY = centerC.GetY() - sideLength / 2;
+    double handleRX = centerC.GetX() + sideLength / 2;
+    double handleBY = centerC.GetY() + sideLength / 2;
+    ScenePoint2D LTC(handleLX, handleTY);
+    ScenePoint2D RTC(handleRX, handleTY);
+    ScenePoint2D RBC(handleRX, handleBY);
+    ScenePoint2D LBC(handleLX, handleBY);
+
+    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
+
+    chain.push_back(startLT);
+    chain.push_back(startRT);
+    chain.push_back(startRB);
+    chain.push_back(startLB);
+  }
+#if 0
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&      scene
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double&       radiusS
+    , const bool          clockwise
+    , const int           subdivisionsCount)
+  {
+    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+    AddArc(
+      chain, scene, c, radiusS, p1cAngle, p2cAngle, 
+      clockwise, subdivisionsCount);
+  }
+#endif
+
+  void AddShortestArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&             scene
+    , const ScenePoint2D&        p1
+    , const ScenePoint2D&        c
+    , const ScenePoint2D&        p2
+    , const double&              radiusS
+    , const int                  subdivisionsCount)
+  {
+    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+    AddShortestArc(
+      chain, scene, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount);
+  }
+
+  void GetPositionOnBisectingLine(
+    ScenePoint2D&       result
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double d)
+  {
+    // TODO: fix correct half-plane
+    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+    double angle = 0.5*(p1cAngle + p2cAngle);
+    double unitVectorX = cos(angle);
+    double unitVectorY = sin(angle);
+    double posX = c.GetX() + d * unitVectorX;
+    double posY = c.GetX() + d * unitVectorY;
+    result = ScenePoint2D(posX, posY);
+  }
+   
+  void AddShortestArc(
+      PolylineSceneLayer::Chain&  chain
+    , const Scene2D&              scene
+    , const ScenePoint2D&         centerS
+    , const double&               radiusS
+    , const double                startAngleRad
+    , const double                endAngleRad
+    , const int                   subdivisionsCount)
+  {
+    // this gives a signed difference between angle which
+    // is the smallest difference (in magnitude) between 
+    // the angles
+    double delta = NormalizeAngle(endAngleRad-startAngleRad);
+
+    chain.clear();
+    chain.reserve(subdivisionsCount + 1);
+
+    double angleIncr = delta/static_cast<double>(subdivisionsCount);
+
+    double theta = startAngleRad;
+    for (int i = 0; i < subdivisionsCount + 1; ++i)
+    {
+      double offsetX = radiusS * cos(theta);
+      double offsetY = radiusS * sin(theta);
+      double pointX = centerS.GetX() + offsetX;
+      double pointY = centerS.GetY() + offsetY;
+      chain.push_back(ScenePoint2D(pointX, pointY));
+      theta += angleIncr;
+    }
+  }
+
+#if 0
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&      scene
+    , const ScenePoint2D& centerS
+    , const double&       radiusS
+    , const double        startAngleRad
+    , const double        endAngleRad
+    , const bool          clockwise
+    , const int           subdivisionsCount)
+  {
+    double startAngleRadN = NormalizeAngle(startAngleRad);
+    double endAngleRadN = NormalizeAngle(endAngleRad);
+
+    double angle1Rad = std::min(startAngleRadN, endAngleRadN);
+    double angle2Rad = std::max(startAngleRadN, endAngleRadN);
+
+    // now we are sure angle1Rad < angle2Rad
+    // this means that if we draw from 1 to 2, it will be clockwise (
+    // increasing angles).
+    // let's fix this:
+    if (!clockwise)
+    {
+      angle2Rad -= 2 * g_pi;
+      // now we are sure angle2Rad < angle1Rad (since they were normalized) 
+      // and, thus, going from 1 to 2 means the angle values will DECREASE,
+      // which is the definition of anticlockwise
+    }
+
+    chain.clear();
+    chain.reserve(subdivisionsCount + 1);
+
+    double angleIncr = (angle2Rad - angle1Rad)
+      / static_cast<double>(subdivisionsCount);
+
+    double theta = angle1Rad;
+    for (int i = 0; i < subdivisionsCount + 1; ++i)
+    {
+      double offsetX = radiusS * cos(theta);
+      double offsetY = radiusS * sin(theta);
+      double pointX = centerS.GetX() + offsetX;
+      double pointY = centerS.GetY() + offsetY;
+      chain.push_back(ScenePoint2D(pointX, pointY));
+      theta += angleIncr;
+    }
+  }
+#endif
+
+  void AddCircle(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       radiusS,
+    const int           numSubdivisions)
+  {
+    //ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    //TODO: take DPI into account
+
+    // TODO: automatically compute the number for segments for smooth 
+    // display based on the radius in pixels.
+
+    chain.clear();
+    chain.reserve(numSubdivisions);
+
+    double angleIncr = (2.0 * g_pi)
+      / static_cast<double>(numSubdivisions);
+
+    double theta = 0;
+    for (int i = 0; i < numSubdivisions; ++i)
+    {
+      double offsetX = radiusS * cos(theta);
+      double offsetY = radiusS * sin(theta);
+      double pointX = centerS.GetX() + offsetX;
+      double pointY = centerS.GetY() + offsetY;
+      chain.push_back(ScenePoint2D(pointX, pointY));
+      theta += angleIncr;
+    }
+  }
+
+  double NormalizeAngle(double angle)
+  {
+    double retAngle = angle;
+    while (retAngle < -1.0*g_pi)
+      retAngle += 2 * g_pi;
+    while (retAngle >= g_pi)
+      retAngle -= 2 * g_pi;
+    return retAngle;
+  }
+
+  double MeasureAngle(const ScenePoint2D& p1, const ScenePoint2D& c, const ScenePoint2D& p2)
+  {
+    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
+    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
+    double delta = p2cAngle - p1cAngle;
+    return NormalizeAngle(delta);
+  }
+
+
+#if 0
+  void AddEllipse(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       halfHAxis,
+    const double&       halfVAxis)
+  {
+    chain.clear();
+    chain.reserve(4);
+    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    //TODO: take DPI into account
+    double handleLX = centerC.GetX() - sideLength / 2;
+    double handleTY = centerC.GetY() - sideLength / 2;
+    double handleRX = centerC.GetX() + sideLength / 2;
+    double handleBY = centerC.GetY() + sideLength / 2;
+    ScenePoint2D LTC(handleLX, handleTY);
+    ScenePoint2D RTC(handleRX, handleTY);
+    ScenePoint2D RBC(handleRX, handleBY);
+    ScenePoint2D LBC(handleLX, handleBY);
+
+    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
+    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
+
+    chain.push_back(startLT);
+    chain.push_back(startRT);
+    chain.push_back(startRB);
+    chain.push_back(startLB);
+}
+#endif
+
+
+  namespace
+  {
+    /**
+    Helper function for outlined text rendering
+    */
+    TextSceneLayer* GetOutlineTextLayer(
+      Scene2D& scene, int baseLayerIndex, int index)
+    {
+      assert(scene.HasLayer(baseLayerIndex));
+      assert(index >= 0);
+      assert(index < 5);
+
+      ISceneLayer * layer = &(scene.GetLayer(baseLayerIndex + index));
+      TextSceneLayer * concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
+      assert(concreteLayer != NULL);
+      return concreteLayer;
+    }
+  }
+   
+  void SetTextLayerOutlineProperties(
+    Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p)
+  {
+    double xoffsets[5] = { 2, 0, -2, 0, 0 };
+    double yoffsets[5] = { 0, -2, 0, 2, 0 };
+
+    // get the scaling factor 
+    const double pixelToScene =
+      scene.GetCanvasToSceneTransform().ComputeZoom();
+
+    for (int i = 0; i < 5; ++i)
+    {
+      TextSceneLayer* textLayer = GetOutlineTextLayer(scene, baseLayerIndex, i);
+      textLayer->SetText(text);
+
+      if (i == 4)
+        textLayer->SetColor(0, 223, 81);
+      else
+        textLayer->SetColor(0, 56, 21);
+
+      ScenePoint2D textAnchor;
+      //GetPositionOnBisectingLine(
+      //  textAnchor, side1End_, center_, side2End_, 40.0*pixelToScene);
+      textLayer->SetPosition(
+        p.GetX() + xoffsets[i] * pixelToScene,
+        p.GetY() + yoffsets[i] * pixelToScene);
+    }
+  }
+
+
+
+
+
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,187 @@
+/**
+ * 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/>.
+ **/
+
+#include <Framework/Scene2D/PolylineSceneLayer.h>
+#include <Framework/Scene2D/Scene2D.h>
+
+namespace OrthancStone
+{
+
+  /**
+  This function will create a square around the center point supplied in
+  scene coordinates, with a side length given in canvas coordinates. The
+  square sides are parallel to the canvas boundaries.
+  */
+  void AddSquare(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       sideLength);
+
+
+  /**
+    Creates an arc centered on c that goes
+    - from a point r1:
+      - so that r1 belongs to the p1,c line
+      - so that the distance from c to r1 equals radius
+    - to a point r2:
+      - so that r2 belongs to the p2,c line
+      - so that the distance from c to r2 equals radius
+    - that follows the shortest among the two possible paths
+
+    Warning: the existing chain content will be wiped out.
+  */
+  void AddShortestArc(
+      PolylineSceneLayer::Chain&  chain
+    , const Scene2D&              scene
+    , const ScenePoint2D&         p1
+    , const ScenePoint2D&         c
+    , const ScenePoint2D&         p2
+    , const double&               radiusS
+    , const int                   subdivisionsCount = 63);
+
+  /**
+    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
+    start angle to end angle, by following the shortest arc.
+
+    Warning: the existing chain content will be wiped out.
+  */
+  void AddShortestArc(
+      PolylineSceneLayer::Chain&  chain
+    , const Scene2D&              scene
+    , const ScenePoint2D&         centerS
+    , const double&               radiusS
+    , const double                startAngleRad
+    , const double                endAngleRad
+    , const int                   subdivisionsCount = 63);
+
+#if 0
+  /**
+    Creates an arc centered on c that goes
+    - from a point r1:
+      - so that r1 belongs to the p1,c line
+      - so that the distance from c to r1 equals radius
+    - to a point r2:
+      - so that r2 belongs to the p2,c line
+      - so that the distance from c to r2 equals radius
+
+    if clockwise is true, the arc is drawn from r1 to r2 with increasing 
+    angle values. Otherwise, the angle values decrease.
+
+    Warning: the existing chain content will be wiped out.
+  */
+
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&             scene
+    , const ScenePoint2D&        p1
+    , const ScenePoint2D&        c
+    , const ScenePoint2D&        p2
+    , const double&              radiusS
+    , const bool                 clockwise
+    , const int                  subdivisionsCount = 63);
+ 
+  /**
+    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
+    start angle to end angle with the supplied radius.
+
+    if clockwise is true, the arc is drawn from start to end by increasing the
+    angle values.
+
+    Otherwise, the angle value decreases from start to end.
+
+    Warning: the existing chain content will be wiped out.
+  */
+  void AddArc(
+      PolylineSceneLayer::Chain& chain
+    , const Scene2D&      scene
+    , const ScenePoint2D& centerS
+    , const double&       radiusS
+    , const double        startAngleRad
+    , const double        endAngleRad
+    , const bool          clockwise
+    , const int           subdivisionsCount = 63);
+#endif
+  /**
+    Creates a circle (closed curve) with "numSubdivisions"
+    (N points)
+
+    Warning: the existing chain content will be wiped out.
+  */
+  void AddCircle(PolylineSceneLayer::Chain& chain,
+    const Scene2D&      scene,
+    const ScenePoint2D& centerS,
+    const double&       radiusS,
+    const int           numSubdivisions = 63);
+
+  /**
+    Adds or subtracts 2*pi as many times as need to shift the specified
+    angle to a value such as: -pi <= value < pi
+   */
+  double NormalizeAngle(double angle);
+
+  /**
+    Returns the angle magnitude between the p1,c and p2,c lines. 
+    The returned angle is between 0 and 2*pi
+
+    If the angle is between 0 and pi, this means that the shortest arc 
+    from p1 to p2 is clockwise.
+
+    If the angle is between pi and 2*pi, this means that the shortest arc
+    from p1 to p2 is COUNTERclockwise.
+
+  */
+  double MeasureAngle(
+      const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2);
+
+  /**
+  RadiansToDegrees
+  */
+  double RadiansToDegrees(double angleRad);
+
+  /**
+  This function will return the coordinates of a point that:
+  - belongs to the two bisecting lines of the p1 c p2 angle.
+  - is a distance d from c.
+  Among the four possible points, the one returned will be the one belonging
+  to the *smallest* half-plane defined by the [c,p1[ and [c,p2[ half-lines.
+  */
+  void GetPositionOnBisectingLine(
+      ScenePoint2D&       result
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double d);
+
+
+  /**
+  This helper is used when drawing text with an outline.
+  It set the properties for several text layers at once : first the 
+  four outline layers, with a position shift and then the actual main text 
+  layer.
+
+  The five text layers are supposed to already exist in the scene, starting
+  from layerIndex, up to (and not including) layerIndex+5. 
+  */
+  void SetTextLayerOutlineProperties(
+    Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTrackers.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,72 @@
+/**
+ * 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/>.
+ **/
+
+#include "MeasureTrackers.h"
+#include <Core/OrthancException.h>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+
+  CreateMeasureTracker::CreateMeasureTracker(
+    ViewportControllerWPtr          controllerW,
+    std::vector<TrackerCommandPtr>& undoStack,
+    std::vector<MeasureToolPtr>&    measureTools)
+    : controllerW_(controllerW)
+    , alive_(true)
+    , undoStack_(undoStack)
+    , measureTools_(measureTools)
+    , commitResult_(true)
+  {
+  }
+
+  void CreateMeasureTracker::Cancel()
+  {
+    commitResult_ = false;
+    alive_ = false;
+  }
+
+  bool CreateMeasureTracker::IsAlive() const
+  {
+    return alive_;
+  }
+
+  CreateMeasureTracker::~CreateMeasureTracker()
+  {
+    // if the tracker completes successfully, we add the command
+    // to the undo stack
+
+    // otherwise, we simply undo it
+    if (commitResult_)
+      undoStack_.push_back(command_);
+    else
+      command_->Undo();
+  }
+
+
+  OrthancStone::Scene2DPtr CreateMeasureTracker::GetScene()
+  {
+    return controllerW_.lock()->GetScene();
+  }
+
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTrackers.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,59 @@
+/**
+ * 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"
+#include "../../Framework/Scene2D/Scene2D.h"
+#include "../../Framework/Scene2D/PointerEvent.h"
+
+#include "MeasureTools.h"
+#include "MeasureCommands.h"
+
+#include <vector>
+
+namespace OrthancStone
+{
+  class CreateMeasureTracker : public IFlexiblePointerTracker
+  {
+  public:
+    virtual void Cancel() ORTHANC_OVERRIDE;
+    virtual bool IsAlive() const ORTHANC_OVERRIDE;
+  protected:
+    CreateMeasureTracker(
+      ViewportControllerWPtr          controllerW,
+      std::vector<TrackerCommandPtr>& undoStack,
+      std::vector<MeasureToolPtr>&    measureTools);
+
+    ~CreateMeasureTracker();
+  
+  protected:
+    CreateMeasureCommandPtr         command_;
+    ViewportControllerWPtr          controllerW_;
+    bool                            alive_;
+    Scene2DPtr                      GetScene();
+
+  private:
+    std::vector<TrackerCommandPtr>& undoStack_;
+    std::vector<MeasureToolPtr>&    measureTools_;
+    bool                            commitResult_;
+  };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/OneGesturePointerTracker.cpp	Sun May 19 16:31:56 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:31:56 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_;
+  };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/PointerTypes.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,83 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 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 <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <vector>
+
+namespace OrthancStone
+{
+  class Scene2D;
+  typedef boost::shared_ptr<Scene2D> Scene2DPtr;
+
+  typedef boost::weak_ptr<Scene2D> Scene2DWPtr;
+
+  class MeasureTool;
+  typedef boost::shared_ptr<MeasureTool>
+    MeasureToolPtr;
+  typedef boost::weak_ptr<MeasureTool>
+    MeasureToolWPtr;
+  typedef std::vector<MeasureToolPtr>
+    MeasureToolList;
+
+  class LineMeasureTool;
+  typedef boost::shared_ptr<LineMeasureTool>
+    LineMeasureToolPtr;
+
+  class AngleMeasureTool;
+  typedef boost::shared_ptr<AngleMeasureTool>
+    AngleMeasureToolPtr;
+
+  class IPointerTracker;
+  typedef boost::shared_ptr<IPointerTracker>
+    PointerTrackerPtr;
+
+  class IFlexiblePointerTracker;
+  typedef boost::shared_ptr<IFlexiblePointerTracker>
+    FlexiblePointerTrackerPtr;
+
+  typedef boost::shared_ptr<LineMeasureTool>
+    LineMeasureToolPtr;
+
+  class CreateMeasureCommand;
+  typedef boost::shared_ptr<CreateMeasureCommand>
+    CreateMeasureCommandPtr;
+
+  class CreateLineMeasureCommand;
+  typedef boost::shared_ptr<CreateLineMeasureCommand>
+    CreateLineMeasureCommandPtr;
+
+  class CreateAngleMeasureCommand;
+  typedef boost::shared_ptr<CreateAngleMeasureCommand>
+    CreateAngleMeasureCommandPtr;
+
+
+  typedef boost::shared_ptr<Scene2D> Scene2DPtr;
+
+  class TrackerCommand;
+  typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr;
+
+  class ViewportController;
+  typedef boost::shared_ptr<ViewportController> ViewportControllerPtr;
+  typedef boost::weak_ptr<ViewportController> ViewportControllerWPtr;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * 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/>.
+ **/
+
+#include "ViewportController.h"
+
+#include <Framework/StoneException.h>
+
+#include <boost/make_shared.hpp>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+  ViewportController::ViewportController(MessageBroker& broker)
+    : IObservable(broker)
+  {
+    scene_ = boost::make_shared<Scene2D>();
+  }
+
+  Scene2DPtr ViewportController::GetScene()
+  {
+    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));
+  }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/ViewportController.h	Sun May 19 16:31:56 2019 +0200
@@ -0,0 +1,86 @@
+/**
+ * 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 "PointerTypes.h"
+
+#include <Framework/Scene2D/Scene2D.h>
+#include <Framework/Scene2D/PointerEvent.h>
+#include <Framework/Scene2DViewport/IFlexiblePointerTracker.h>
+
+namespace OrthancStone
+{
+  /**
+  This object is responsible for hosting a scene, responding to messages from
+  the model and updating the scene accordingly.
+
+  It contains the list of active measuring tools as well as the stack
+  where measuring tool commands are stored.
+
+  The active tracker is also stored in the viewport controller.
+
+  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 IObservable
+  {
+  public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \
+      SceneTransformChanged, ViewportController);
+
+    ViewportController(MessageBroker& broker);
+
+    Scene2DPtr GetScene();
+
+    /** 
+    This method is called by the GUI system and should update/delete the
+    current tracker
+    */
+    bool HandlePointerEvent(PointerEvent e);
+
+    /**
+    This method returns the list of measure tools containing the supplied point
+    (in scene coords). A tracker can then be requested from the chosen 
+    measure tool, if needed
+    */
+    std::vector<MeasureToolPtr> HitTestMeasureTools(ScenePoint2D p);
+
+    /**
+    With this method, the object takes ownership of the supplied tracker and
+    updates it according to user interaction
+    */
+    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:
+    Scene2DPtr                scene_;
+    FlexiblePointerTrackerPtr tracker_;
+  };
+}
--- a/Framework/Toolbox/ShearWarpProjectiveTransform.cpp	Fri May 17 18:04:26 2019 +0200
+++ b/Framework/Toolbox/ShearWarpProjectiveTransform.cpp	Sun May 19 16:31:56 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/README.md	Fri May 17 18:04:26 2019 +0200
+++ b/README.md	Sun May 19 16:31:56 2019 +0200
@@ -251,7 +251,8 @@
 ```
 
 TODO trackers:
-- text overlay 50% --> ColorTextureLayer 50%
-- angle tracker: draw arcs
+- CANCELLED (using outlined text now) text overlay 50% --> ColorTextureLayer 50%
+- DONE angle tracker: draw arcs
+- Handles on arc
+- Select measure tool with hit test --> Delete command
 
-
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri May 17 18:04:26 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Sun May 19 16:31:56 2019 +0200
@@ -329,6 +329,40 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp
 
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateCircleMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateLineMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditAngleMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditCircleMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditCircleMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/IFlexiblePointerTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTools.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureTools.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureToolsToolbox.cpp
+  ${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
+  
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/FontRenderer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/Glyph.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Fonts/GlyphAlphabet.cpp
--- a/Samples/Common/AngleMeasureTool.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,261 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "AngleMeasureTool.h"
-#include "MeasureToolsToolbox.h"
-
-#include <Core/Logging.h>
-
-#include <boost/math/constants/constants.hpp>
-
-extern void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value);
-
-namespace OrthancStone
-{
-  AngleMeasureTool::~AngleMeasureTool()
-  {
-    // this measuring tool is a RABI for the corresponding visual layers
-    // stored in the 2D scene
-    Disable();
-    RemoveFromScene();
-  }
-
-  void AngleMeasureTool::RemoveFromScene()
-  {
-    if (layersCreated)
-    {
-      assert(GetScene().HasLayer(polylineZIndex_));
-      assert(GetScene().HasLayer(textZIndex_));
-      GetScene().DeleteLayer(polylineZIndex_);
-      GetScene().DeleteLayer(textZIndex_);
-    }
-  }
-
-  void AngleMeasureTool::SetSide1End(ScenePoint2D pt)
-  {
-    side1End_ = pt;
-    RefreshScene();
-  }
-
-  void AngleMeasureTool::SetSide2End(ScenePoint2D pt)
-  {
-    side2End_ = pt;
-    RefreshScene();
-  }
-
-  void AngleMeasureTool::SetCenter(ScenePoint2D pt)
-  {
-    center_ = pt;
-    RefreshScene();
-  }
-  
-  PolylineSceneLayer* AngleMeasureTool::GetPolylineLayer()
-  {
-    assert(GetScene().HasLayer(polylineZIndex_));
-    ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_));
-    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
-  TextSceneLayer* AngleMeasureTool::GetTextLayer()
-  {
-    assert(GetScene().HasLayer(textZIndex_));
-    ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_));
-    TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
-
-  void AngleMeasureTool::RefreshScene()
-  {
-    if (IsEnabled())
-    {
-      // get the scaling factor 
-      const double pixelToScene =
-        GetScene().GetCanvasToSceneTransform().ComputeZoom();
-
-      if (!layersCreated)
-      {
-        // Create the layers if need be
-
-        assert(textZIndex_ == -1);
-        {
-          polylineZIndex_ = GetScene().GetMaxDepth() + 100;
-          //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
-          std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
-          GetScene().SetLayer(polylineZIndex_, layer.release());
-        }
-        {
-          textZIndex_ = GetScene().GetMaxDepth() + 100;
-          //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
-          std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-          GetScene().SetLayer(textZIndex_, layer.release());
-        }
-        layersCreated = true;
-      }
-      else
-      {
-        assert(GetScene().HasLayer(polylineZIndex_));
-        assert(GetScene().HasLayer(textZIndex_));
-      }
-      {
-        // Fill the polyline layer with the measurement line
-
-        PolylineSceneLayer* polylineLayer = GetPolylineLayer();
-        polylineLayer->ClearAllChains();
-        polylineLayer->SetColor(0, 223, 21);
-
-        // sides
-        {
-          {
-            PolylineSceneLayer::Chain chain;
-            chain.push_back(side1End_);
-            chain.push_back(center_);
-            polylineLayer->AddChain(chain, false);
-          }
-          {
-            PolylineSceneLayer::Chain chain;
-            chain.push_back(side2End_);
-            chain.push_back(center_);
-            polylineLayer->AddChain(chain, false);
-          }
-        }
-
-        // handles
-        {
-          //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
-
-          {
-            PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), side1End_, 10.0* pixelToScene); //TODO: take DPI into account
-            polylineLayer->AddChain(chain, true);
-          }
-
-          {
-            PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), side2End_, 10.0* pixelToScene); //TODO: take DPI into account
-            polylineLayer->AddChain(chain, true);
-          }
-        }
-
-        // arc
-        {
-          PolylineSceneLayer::Chain chain;
-
-          const double ARC_RADIUS_CANVAS_COORD = 30.0;
-          AddShortestArc(chain, GetScene(), side1End_, center_, side2End_, 
-            ARC_RADIUS_CANVAS_COORD*pixelToScene);
-          polylineLayer->AddChain(chain, false);
-        }
-      }
-      {
-        // Set the text layer
-
-        double p1cAngle = atan2(
-          side1End_.GetY() - center_.GetY(),
-          side1End_.GetX() - center_.GetX());
-
-        TrackerSample_SetInfoDisplayMessage("center_.GetX()",
-          boost::lexical_cast<std::string>(center_.GetX()));
-
-        TrackerSample_SetInfoDisplayMessage("center_.GetY()",
-          boost::lexical_cast<std::string>(center_.GetY()));
-
-        TrackerSample_SetInfoDisplayMessage("side1End_.GetX()",
-          boost::lexical_cast<std::string>(side1End_.GetX()));
-
-        TrackerSample_SetInfoDisplayMessage("side1End_.GetY()",
-          boost::lexical_cast<std::string>(side1End_.GetY()));
-
-        TrackerSample_SetInfoDisplayMessage("side2End_.GetX()",
-          boost::lexical_cast<std::string>(side2End_.GetX()));
-
-        TrackerSample_SetInfoDisplayMessage("side2End_.GetY()",
-          boost::lexical_cast<std::string>(side2End_.GetY()));
-
-        TrackerSample_SetInfoDisplayMessage("p1cAngle (deg)",
-          boost::lexical_cast<std::string>(RadiansToDegrees(p1cAngle)));
-
-        double p2cAngle = atan2(
-          side2End_.GetY() - center_.GetY(),
-          side2End_.GetX() - center_.GetX());
-
-        double delta = NormalizeAngle(p2cAngle - p1cAngle);
-        TrackerSample_SetInfoDisplayMessage("delta (deg)",
-          boost::lexical_cast<std::string>(RadiansToDegrees(delta)));
-
-        double theta = p1cAngle + delta/2;
-
-        TrackerSample_SetInfoDisplayMessage("theta (deg)",
-          boost::lexical_cast<std::string>(RadiansToDegrees(theta)));
-
-        TrackerSample_SetInfoDisplayMessage("p2cAngle (deg)",
-          boost::lexical_cast<std::string>(RadiansToDegrees(p2cAngle)));
-
-        const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90;
-
-        double offsetX = TEXT_CENTER_DISTANCE_CANVAS_COORD * cos(theta);
-        TrackerSample_SetInfoDisplayMessage("offsetX (pix)",
-          boost::lexical_cast<std::string>(offsetX));
-
-        double offsetY = TEXT_CENTER_DISTANCE_CANVAS_COORD * sin(theta);
-        TrackerSample_SetInfoDisplayMessage("offsetY (pix)",
-          boost::lexical_cast<std::string>(offsetY));
-
-        double pointX = center_.GetX() + offsetX * pixelToScene;
-        double pointY = center_.GetY() + offsetY * pixelToScene;
-        TrackerSample_SetInfoDisplayMessage("pointX",
-          boost::lexical_cast<std::string>(pointX));
-
-        TrackerSample_SetInfoDisplayMessage("pointY",
-          boost::lexical_cast<std::string>(pointY));
-
-        TextSceneLayer* textLayer = GetTextLayer();
-
-        char buf[64];
-        double angleDeg = RadiansToDegrees(delta);
-
-        TrackerSample_SetInfoDisplayMessage("angleDeg",
-          boost::lexical_cast<std::string>(angleDeg));
-
-        sprintf(buf, "%0.02f deg", angleDeg);
-        textLayer->SetText(buf);
-        textLayer->SetColor(0, 223, 21);
-
-        ScenePoint2D textAnchor;
-        //GetPositionOnBisectingLine(
-        //  textAnchor, side1End_, center_, side2End_, 40.0*pixelToScene);
-        textLayer->SetPosition(pointX, pointY);
-      }
-    }
-    else
-    {
-      if (layersCreated)
-      {
-        RemoveFromScene();
-        layersCreated = false;
-      }
-    }
-  }
-
-
-}
--- a/Samples/Common/AngleMeasureTool.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-/**
- * 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 "MeasureTools.h"
-
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/TextSceneLayer.h>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-
-#include <vector>
-#include <cmath>
-
-namespace OrthancStone
-{
-  class AngleMeasureTool : public MeasureTool
-  {
-  public:
-    AngleMeasureTool(MessageBroker& broker, Scene2D& scene)
-      : MeasureTool(broker, scene)
-      , layersCreated(false)
-      , polylineZIndex_(-1)
-      , textZIndex_(-1)
-    {
-
-    }
-
-    ~AngleMeasureTool();
-
-    void SetSide1End(ScenePoint2D start);
-    void SetCenter(ScenePoint2D start);
-    void SetSide2End(ScenePoint2D start);
-
-  private:
-    PolylineSceneLayer* GetPolylineLayer();
-    TextSceneLayer*     GetTextLayer();
-    virtual void        RefreshScene() ORTHANC_OVERRIDE;
-    void                RemoveFromScene();
-
-  private:
-    ScenePoint2D side1End_;
-    ScenePoint2D side2End_;
-    ScenePoint2D center_;
-    bool         layersCreated;
-    int          polylineZIndex_;
-    int          textZIndex_;
-  };
-
-  typedef boost::shared_ptr<AngleMeasureTool> AngleMeasureToolPtr;
-}
-
-
--- a/Samples/Common/CreateAngleMeasureTracker.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "CreateAngleMeasureTracker.h"
-#include <Core/OrthancException.h>
-
-using namespace Orthanc;
-
-namespace OrthancStone
-{
-  CreateAngleMeasureTracker::CreateAngleMeasureTracker(
-    MessageBroker&                  broker,
-    Scene2D&                        scene,
-    std::vector<TrackerCommandPtr>& undoStack,
-    std::vector<MeasureToolPtr>&    measureTools,
-    const PointerEvent&             e)
-    : CreateMeasureTracker(scene, undoStack, measureTools)
-    , state_(CreatingSide1)
-  {
-    command_.reset(
-      new CreateAngleMeasureCommand(
-        broker,
-        scene,
-        measureTools,
-        e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform())));
-  }
-
-  CreateAngleMeasureTracker::~CreateAngleMeasureTracker()
-  {
-  }
-
-  void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
-  {
-    if (!active_)
-    {
-      throw OrthancException(ErrorCode_InternalError,
-        "Internal error: wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: active_ == false");
-    }
-
-    ScenePoint2D scenePos = event.GetMainPosition().Apply(
-      scene_.GetCanvasToSceneTransform());
-
-    switch (state_)
-    {
-    case CreatingSide1:
-      GetCommand()->SetCenter(scenePos);
-      break;
-    case CreatingSide2:
-      GetCommand()->SetSide2End(scenePos);
-      break;
-    default:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: state_ invalid");
-    }
-    //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
-    //  "scenePos.GetY() = " << scenePos.GetY();
-  }
-
-  void CreateAngleMeasureTracker::PointerUp(const PointerEvent& e)
-  {
-    // TODO: the current app does not prevent multiple PointerDown AND
-    // PointerUp to be sent to the tracker.
-    // 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.
-
-    switch (state_)
-    {
-    case CreatingSide1:
-      state_ = CreatingSide2;
-      break;
-    case CreatingSide2:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerUp: state_ == CreatingSide2 ; this should not happen");
-      break;
-    default:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: state_ invalid");
-    }
-  }
-
-  void CreateAngleMeasureTracker::PointerDown(const PointerEvent& e)
-  {
-    switch (state_)
-    {
-    case CreatingSide1:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerDown: state_ == CreatingSide1 ; this should not happen");
-      break;
-    case CreatingSide2:
-      // we are done
-      active_ = false;
-      break;
-    default:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: state_ invalid");
-    }
-  }
-
-  CreateAngleMeasureCommandPtr CreateAngleMeasureTracker::GetCommand()
-  {
-    return boost::dynamic_pointer_cast<CreateAngleMeasureCommand>(command_);
-  }
-
-}
--- a/Samples/Common/CreateAngleMeasureTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * 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 "MeasureTrackers.h"
-#include "MeasureCommands.h"
-
-#include <vector>
-
-namespace OrthancStone
-{
-  class CreateAngleMeasureTracker : public CreateMeasureTracker
-  {
-  public:
-    /**
-    When you create this tracker, you need to supply it with the undo stack
-    where it will store the commands that perform the actual measure tool
-    creation and modification.
-    In turn, a container for these commands to store the actual measuring
-    must be supplied, too
-    */
-    CreateAngleMeasureTracker(
-      MessageBroker&                  broker,
-      Scene2D&                        scene,
-      std::vector<TrackerCommandPtr>& undoStack,
-      std::vector<MeasureToolPtr>&    measureTools,
-      const PointerEvent&             e);
-
-    ~CreateAngleMeasureTracker();
-
-    virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
-    virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
-    virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
-
-  private:
-    CreateAngleMeasureCommandPtr GetCommand();
-
-    enum State
-    {
-      CreatingSide1,
-      CreatingSide2,
-      Finished // just for debug
-    };
-    State state_;
-
-  };
-}
--- a/Samples/Common/CreateCircleMeasureTracker.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/**
- * 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/>.
- **/
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/CreateCircleMeasureTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/**
- * 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
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/CreateLineMeasureTracker.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "CreateLineMeasureTracker.h"
-#include <Core/OrthancException.h>
-
-using namespace Orthanc;
-
-namespace OrthancStone
-{
-  CreateLineMeasureTracker::CreateLineMeasureTracker(
-    MessageBroker&                  broker,
-    Scene2D&                        scene,
-    std::vector<TrackerCommandPtr>& undoStack,
-    std::vector<MeasureToolPtr>&    measureTools,
-    const PointerEvent&             e)
-    : CreateMeasureTracker(scene, undoStack, measureTools)
-  {
-    command_.reset(
-      new CreateLineMeasureCommand(
-        broker,
-        scene,
-        measureTools,
-        e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform())));
-  }
-
-  CreateLineMeasureTracker::~CreateLineMeasureTracker()
-  {
-
-  }
-
-  void CreateLineMeasureTracker::PointerMove(const PointerEvent& event)
-  {
-    if (!active_)
-    {
-      throw OrthancException(ErrorCode_InternalError,
-        "Internal error: wrong state in CreateLineMeasureTracker::"
-        "PointerMove: active_ == false");
-    }
-
-    ScenePoint2D scenePos = event.GetMainPosition().Apply(
-      scene_.GetCanvasToSceneTransform());
-
-    //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
-    //  "scenePos.GetY() = " << scenePos.GetY();
-
-    CreateLineMeasureTracker* concreteThis =
-      dynamic_cast<CreateLineMeasureTracker*>(this);
-    assert(concreteThis != NULL);
-    GetCommand()->SetEnd(scenePos);
-  }
-
-  void CreateLineMeasureTracker::PointerUp(const PointerEvent& e)
-  {
-    // TODO: the current app does not prevent multiple PointerDown AND
-    // PointerUp to be sent to the tracker.
-    // 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;
-  }
-
-  void CreateLineMeasureTracker::PointerDown(const PointerEvent& e)
-  {
-    LOG(WARNING) << "Additional touches (fingers, pen, mouse buttons...) "
-      "are ignored when the line measure creation tracker is active";
-  }
-
-  CreateLineMeasureCommandPtr CreateLineMeasureTracker::GetCommand()
-  {
-    return boost::dynamic_pointer_cast<CreateLineMeasureCommand>(command_);
-  }
-
-}
--- a/Samples/Common/CreateLineMeasureTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/**
- * 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 "MeasureTrackers.h"
-
-namespace OrthancStone
-{
-  class CreateLineMeasureTracker : public CreateMeasureTracker
-  {
-  public:
-    /**
-    When you create this tracker, you need to supply it with the undo stack
-    where it will store the commands that perform the actual measure tool
-    creation and modification.
-    In turn, a container for these commands to store the actual measuring
-    must be supplied, too
-    */
-    CreateLineMeasureTracker(
-      MessageBroker&                  broker,
-      Scene2D&                        scene,
-      std::vector<TrackerCommandPtr>& undoStack,
-      std::vector<MeasureToolPtr>&    measureTools,
-      const PointerEvent&             e);
-
-    ~CreateLineMeasureTracker();
-
-    virtual void PointerMove(const PointerEvent& e) ORTHANC_OVERRIDE;
-    virtual void PointerUp(const PointerEvent& e) ORTHANC_OVERRIDE;
-    virtual void PointerDown(const PointerEvent& e) ORTHANC_OVERRIDE;
-
-  private:
-    CreateLineMeasureCommandPtr GetCommand();
-  };
-}
--- a/Samples/Common/CreateMeasureTracker.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-/**
- * 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/>.
- **/
-
--- a/Samples/Common/CreateMeasureTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-/**
- * 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
-
--- a/Samples/Common/CreateSimpleTrackerAdapter.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "IFlexiblePointerTracker.h"
-#include <Framework/Scene2D/IPointerTracker.h>
-
-
-namespace OrthancStone
-{
-  namespace 
-  {
-    class SimpleTrackerAdapter : public IFlexiblePointerTracker
-    {
-    public:
-      SimpleTrackerAdapter(PointerTrackerPtr wrappedTracker)
-        : wrappedTracker_(wrappedTracker)
-        , active_(true)
-      {
-      }
-
-      virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        if(active_)
-          wrappedTracker_->Update(event);
-      };
-      virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        if (wrappedTracker_)
-        {
-          wrappedTracker_->Release();
-          wrappedTracker_ = NULL;
-        }
-        active_ = false;
-      }
-      virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
-      {
-        // nothing to do atm
-      }
-      virtual bool IsActive() const ORTHANC_OVERRIDE
-      {
-        return active_;
-      }
-
-      virtual void Cancel() ORTHANC_OVERRIDE
-      {
-        wrappedTracker_ = NULL;
-        active_ = false;
-      }
-
-    private:
-      PointerTrackerPtr wrappedTracker_;
-      bool active_;
-    };
-  }
-
-  FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr t)
-  {
-    return FlexiblePointerTrackerPtr(new SimpleTrackerAdapter(t));
-  }
-}
--- a/Samples/Common/EditAngleMeasureTracker.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/**
- * 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/>.
- **/
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/EditAngleMeasureTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/**
- * 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
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/EditCircleMeasureTracker.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/**
- * 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/>.
- **/
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/EditCircleMeasureTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/**
- * 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
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/EditLineMeasureTracker.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-/**
- * 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/>.
- **/
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/EditLineMeasureTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-/**
- * 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
-
-namespace OrthancStone
-{
-}
--- a/Samples/Common/IFlexiblePointerTracker.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/**
- * 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 <Framework/Scene2D/PointerEvent.h>
-#include <boost/shared_ptr.hpp>
-
-namespace OrthancStone
-{
-  class IPointerTracker;
-  typedef boost::shared_ptr<IPointerTracker> PointerTrackerPtr;
-
-  /**
-  This interface represents a flexible mouse tracker that can respond to 
-  several events and is not automatically deleted upon mouse up or when touch
-  interaction is suspended : for instance, a stateful tracker with a two-step 
-  interaction like: click & drag --> mouse up --> drag --> mouse click 
-  (for instance, for an angle measuring tracker or an ellipse tracker)
-  */
-  class IFlexiblePointerTracker : public boost::noncopyable
-  {
-  public:
-    virtual ~IFlexiblePointerTracker() {}
-
-    /**
-    This method will be repeatedly called during user interaction
-    */
-    virtual void PointerMove(const PointerEvent& event) = 0;
-
-    /**
-    This method will be called when a touch/pointer is removed (mouse up, 
-    pen lift, finger removed...)
-    */
-    virtual void PointerUp(const PointerEvent& event) = 0;
-
-    /**
-    This method will be called when a touch/pointer is added (mouse down, 
-    pen or finger press)
-    */
-    virtual void PointerDown(const PointerEvent& event) = 0;
-
-    /**
-    This method will be repeatedly called by the tracker owner (for instance,
-    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;
-
-    /**
-    This will be called if the tracker needs to be dismissed without committing
-    its changes to the underlying model. If the model has been modified during
-    tracker lifetime, it must be restored to its initial value
-    */
-    virtual void Cancel() = 0;
-  };
-
-  typedef boost::shared_ptr<IFlexiblePointerTracker> FlexiblePointerTrackerPtr;
-
-  /**
-  This factory adopts the supplied simple tracker and creates a flexible 
-  tracker wrapper around it.
-  */
-  FlexiblePointerTrackerPtr CreateSimpleTrackerAdapter(PointerTrackerPtr);
-}
-
--- a/Samples/Common/LineMeasureTool.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "LineMeasureTool.h"
-#include "MeasureToolsToolbox.h"
-
-#include <Core/Logging.h>
-
-
-namespace OrthancStone
-{
-  LineMeasureTool::~LineMeasureTool()
-  {
-    // this measuring tool is a RABI for the corresponding visual layers
-    // stored in the 2D scene
-    Disable();
-    RemoveFromScene();
-  }
-
-  void LineMeasureTool::RemoveFromScene()
-  {
-    if (layersCreated)
-    {
-      assert(GetScene().HasLayer(polylineZIndex_));
-      assert(GetScene().HasLayer(textZIndex_));
-      GetScene().DeleteLayer(polylineZIndex_);
-      GetScene().DeleteLayer(textZIndex_);
-    }
-  }
-
-
-  void LineMeasureTool::SetStart(ScenePoint2D start)
-  {
-    start_ = start;
-    RefreshScene();
-  }
-
-  void LineMeasureTool::SetEnd(ScenePoint2D end)
-  {
-    end_ = end;
-    RefreshScene();
-  }
-
-  void LineMeasureTool::Set(ScenePoint2D start, ScenePoint2D end)
-  {
-    start_ = start;
-    end_ = end;
-    RefreshScene();
-  }
-
-  PolylineSceneLayer* LineMeasureTool::GetPolylineLayer()
-  {
-    assert(GetScene().HasLayer(polylineZIndex_));
-    ISceneLayer* layer = &(GetScene().GetLayer(polylineZIndex_));
-    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
-  TextSceneLayer* LineMeasureTool::GetTextLayer()
-  {
-    assert(GetScene().HasLayer(textZIndex_));
-    ISceneLayer* layer = &(GetScene().GetLayer(textZIndex_));
-    TextSceneLayer* concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
-  void LineMeasureTool::RefreshScene()
-  {
-    if (IsEnabled())
-    {
-      if (!layersCreated)
-      {
-        // Create the layers if need be
-
-        assert(textZIndex_ == -1);
-        {
-          polylineZIndex_ = GetScene().GetMaxDepth() + 100;
-          //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
-          std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
-          GetScene().SetLayer(polylineZIndex_, layer.release());
-        }
-        {
-          textZIndex_ = GetScene().GetMaxDepth() + 100;
-          //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
-          std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-          GetScene().SetLayer(textZIndex_, layer.release());
-        }
-        layersCreated = true;
-      }
-      else
-      {
-        assert(GetScene().HasLayer(polylineZIndex_));
-        assert(GetScene().HasLayer(textZIndex_));
-      }
-      {
-        // Fill the polyline layer with the measurement line
-
-        PolylineSceneLayer* polylineLayer = GetPolylineLayer();
-        polylineLayer->ClearAllChains();
-        polylineLayer->SetColor(0, 223, 21);
-
-        {
-          PolylineSceneLayer::Chain chain;
-          chain.push_back(start_);
-          chain.push_back(end_);
-          polylineLayer->AddChain(chain, false);
-        }
-
-        // handles
-        {
-          //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
-
-          {
-            PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), start_, 10.0); //TODO: take DPI into account
-            polylineLayer->AddChain(chain, true);
-          }
-
-          {
-            PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), end_, 10.0); //TODO: take DPI into account
-            polylineLayer->AddChain(chain, true);
-          }
-
-          //ScenePoint2D startC = start_.Apply(GetScene().GetSceneToCanvasTransform());
-          //double squareSize = 10.0; 
-          //double startHandleLX = startC.GetX() - squareSize/2;
-          //double startHandleTY = startC.GetY() - squareSize / 2;
-          //double startHandleRX = startC.GetX() + squareSize / 2;
-          //double startHandleBY = startC.GetY() + squareSize / 2;
-          //ScenePoint2D startLTC(startHandleLX, startHandleTY);
-          //ScenePoint2D startRTC(startHandleRX, startHandleTY);
-          //ScenePoint2D startRBC(startHandleRX, startHandleBY);
-          //ScenePoint2D startLBC(startHandleLX, startHandleBY);
-
-          //ScenePoint2D startLT = startLTC.Apply(GetScene().GetCanvasToSceneTransform());
-          //ScenePoint2D startRT = startRTC.Apply(GetScene().GetCanvasToSceneTransform());
-          //ScenePoint2D startRB = startRBC.Apply(GetScene().GetCanvasToSceneTransform());
-          //ScenePoint2D startLB = startLBC.Apply(GetScene().GetCanvasToSceneTransform());
-
-          //PolylineSceneLayer::Chain chain;
-          //chain.push_back(startLT);
-          //chain.push_back(startRT);
-          //chain.push_back(startRB);
-          //chain.push_back(startLB);
-          //polylineLayer->AddChain(chain, true);
-        }
-
-      }
-      {
-        // Set the text layer proporeties
-
-        TextSceneLayer* textLayer = GetTextLayer();
-        double deltaX = end_.GetX() - start_.GetX();
-        double deltaY = end_.GetY() - start_.GetY();
-        double squareDist = deltaX * deltaX + deltaY * deltaY;
-        double dist = sqrt(squareDist);
-        char buf[64];
-        sprintf(buf, "%0.02f units", dist);
-        textLayer->SetText(buf);
-        textLayer->SetColor(0, 223, 21);
-
-        // TODO: for now we simply position the text overlay at the middle
-        // of the measuring segment
-        double midX = 0.5*(end_.GetX() + start_.GetX());
-        double midY = 0.5*(end_.GetY() + start_.GetY());
-        textLayer->SetPosition(midX, midY);
-      }
-    }
-    else
-    {
-      if (layersCreated)
-      {
-        RemoveFromScene();
-        layersCreated = false;
-      }
-    }
-  }
-
-
-}
\ No newline at end of file
--- a/Samples/Common/LineMeasureTool.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/**
- * 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 "MeasureTools.h"
-
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/TextSceneLayer.h>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-
-#include <vector>
-#include <cmath>
-
-namespace OrthancStone
-{
-  class LineMeasureTool : public MeasureTool
-  {
-  public:
-    LineMeasureTool(MessageBroker& broker, Scene2D& scene)
-      : MeasureTool(broker, scene)
-      , layersCreated(false)
-      , polylineZIndex_(-1)
-      , textZIndex_(-1)
-    {
-
-    }
-
-    ~LineMeasureTool();
-
-    void SetStart(ScenePoint2D start);
-    void SetEnd(ScenePoint2D end);
-    void Set(ScenePoint2D start, ScenePoint2D end);
-
-  private:
-    PolylineSceneLayer* GetPolylineLayer();
-    TextSceneLayer*     GetTextLayer();
-    virtual void        RefreshScene() ORTHANC_OVERRIDE;
-    void                RemoveFromScene();
-
-  private:
-    ScenePoint2D start_;
-    ScenePoint2D end_;
-    bool         layersCreated;
-    int          polylineZIndex_;
-    int          textZIndex_;
-  };
-
-  typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr;
-}
-
--- a/Samples/Common/MeasureCommands.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,93 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "MeasureCommands.h"
-
-namespace OrthancStone
-{
-  void CreateMeasureCommand::Undo()
-  {
-    // simply disable the measure tool upon undo
-    GetMeasureTool()->Disable();
-  }
-
-  void CreateMeasureCommand::Redo()
-  {
-    GetMeasureTool()->Enable();
-  }
-
-  CreateMeasureCommand::CreateMeasureCommand(
-    Scene2D& scene, MeasureToolList& measureTools)
-    : TrackerCommand(scene)
-    , measureTools_(measureTools)
-  {
-
-  }
-
-  CreateMeasureCommand::~CreateMeasureCommand()
-  {
-    // deleting the command should not change the model state
-    // we thus leave it as is
-  }
-
-  CreateLineMeasureCommand::CreateLineMeasureCommand(
-    MessageBroker&    broker, 
-    Scene2D&          scene, 
-    MeasureToolList&  measureTools, 
-    ScenePoint2D      point)
-    : CreateMeasureCommand(scene, measureTools)
-    , measureTool_(new LineMeasureTool(broker,scene))
-  {
-    measureTools_.push_back(measureTool_);
-    measureTool_->Set(point, point);
-  }
-
-  void CreateLineMeasureCommand::SetEnd(ScenePoint2D scenePos)
-  {
-    measureTool_->SetEnd(scenePos);
-  }
-
-  CreateAngleMeasureCommand::CreateAngleMeasureCommand(
-    MessageBroker&    broker, 
-    Scene2D&          scene, 
-    MeasureToolList&  measureTools, 
-    ScenePoint2D      point)
-    : CreateMeasureCommand(scene, measureTools)
-    , measureTool_(new AngleMeasureTool(broker,scene))
-  {
-    measureTools_.push_back(measureTool_);
-    measureTool_->SetSide1End(point);
-    measureTool_->SetCenter(point);
-    measureTool_->SetSide2End(point);
-  }
-
-  /** This method sets center*/
-  void CreateAngleMeasureCommand::SetCenter(ScenePoint2D scenePos)
-  {
-    measureTool_->SetCenter(scenePos);
-  }
-
-  /** This method sets end of side 2*/
-  void CreateAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos)
-  {
-    measureTool_->SetSide2End(scenePos);
-  }
-
-}
--- a/Samples/Common/MeasureCommands.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-/**
- * 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 <Framework/Scene2D/Scene2D.h>
-#include <boost/shared_ptr.hpp>
-
-// to be moved into Stone
-#include "MeasureTools.h"
-#include "LineMeasureTool.h"
-#include "AngleMeasureTool.h"
-
-namespace OrthancStone
-{
-  //class LineMeasureTool;
-  //typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr;
-  //class AngleMeasureTool;
-  //typedef boost::shared_ptr<AngleMeasureTool> AngleMeasureToolPtr;
-   
-  class TrackerCommand
-  {
-  public:
-    TrackerCommand(Scene2D& scene) : scene_(scene)
-    {
-
-    }
-    virtual void Undo() = 0;
-    virtual void Redo() = 0;
-    Scene2D& GetScene()
-    {
-      return scene_;
-    }
-
-  protected:
-    Scene2D& scene_;
-  private:
-    TrackerCommand(const TrackerCommand&);
-    TrackerCommand& operator=(const TrackerCommand&);
-  };
-
-  typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr;
-  
-  class CreateMeasureCommand : public TrackerCommand
-  {
-  public:
-    CreateMeasureCommand(Scene2D& scene, MeasureToolList& measureTools);
-    ~CreateMeasureCommand();
-    virtual void Undo() ORTHANC_OVERRIDE;
-    virtual void Redo() ORTHANC_OVERRIDE;
-  protected:
-    MeasureToolList& measureTools_;
-  private:
-    /** Must be implemented by the subclasses that create the actual tool */
-    virtual MeasureToolPtr GetMeasureTool() = 0;
-  };
-
-  typedef boost::shared_ptr<CreateMeasureCommand> CreateMeasureCommandPtr;
-
-  class CreateLineMeasureCommand : public CreateMeasureCommand
-  {
-  public:
-    CreateLineMeasureCommand(
-      MessageBroker& broker, Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
-    
-    // the starting position is set in the ctor
-    void SetEnd(ScenePoint2D scenePos);
-
-  private:
-    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
-    {
-      return measureTool_;
-    }
-    LineMeasureToolPtr measureTool_;
-  };
-
-  typedef boost::shared_ptr<CreateLineMeasureCommand> CreateLineMeasureCommandPtr;
-
-  class CreateAngleMeasureCommand : public CreateMeasureCommand
-  {
-  public:
-    /** Ctor sets end of side 1*/
-    CreateAngleMeasureCommand(
-      MessageBroker& broker, Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
-
-    /** This method sets center*/
-    void SetCenter(ScenePoint2D scenePos);
-
-    /** This method sets end of side 2*/
-    void SetSide2End(ScenePoint2D scenePos);
-
-  private:
-    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
-    {
-      return measureTool_;
-    }
-    AngleMeasureToolPtr measureTool_;
-  };
-
-  typedef boost::shared_ptr<CreateAngleMeasureCommand> CreateAngleMeasureCommandPtr;
-}
-
--- a/Samples/Common/MeasureTools.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "MeasureTools.h"
-
-#include <Core/Logging.h>
-
-#include <boost/math/constants/constants.hpp>
-
-namespace OrthancStone
-{
-
-  MeasureTool::~MeasureTool()
-  {
-
-  }
-
-  void MeasureTool::Enable()
-  {
-    enabled_ = true;
-    RefreshScene();
-  }
-
-  void MeasureTool::Disable()
-  {
-    enabled_ = false;
-    RefreshScene();
-  }
-
-  bool MeasureTool::IsEnabled() const
-  {
-    return enabled_;
-  }
-
-  OrthancStone::Scene2D& MeasureTool::GetScene()
-  {
-    return scene_;
-  }
-
-  MeasureTool::MeasureTool(MessageBroker& broker, Scene2D& scene)
-    : IObserver(broker)
-    , scene_(scene)
-    , enabled_(true)
-  {
-    scene_.RegisterObserverCallback(
-      new Callable<MeasureTool, Scene2D::SceneTransformChanged>
-      (*this, &MeasureTool::OnSceneTransformChanged));
-  }
-
-  void MeasureTool::OnSceneTransformChanged(
-    const Scene2D::SceneTransformChanged& message)
-  {
-    RefreshScene();
-  }
-
-
-}
-
--- a/Samples/Common/MeasureTools.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-/**
- * 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 <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/TextSceneLayer.h>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-
-#include <vector>
-#include <cmath>
-
-namespace OrthancStone
-{
-  class MeasureTool : public IObserver
-  {
-  public:
-    virtual ~MeasureTool();
-
-    /**
-    Enabled tools are rendered in the scene.
-    */
-    void Enable();
-
-    /**
-    Disabled tools are not rendered in the scene. This is useful to be able
-    to use them as their own memento in command stacks (when a measure tool
-    creation command has been undone, the measure remains alive in the
-    command object but is disabled so that it can be redone later on easily)
-    */
-    void Disable();
-
-    /**
-    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);
-
-  protected:
-    MeasureTool(MessageBroker& broker, Scene2D& scene);
-    
-    /**
-    This is the meat of the tool: this method must [create (if needed) and]
-    update the layers and their data according to the measure tool kind and
-    current state. This is repeatedly called during user interaction
-    */
-    virtual void RefreshScene() = 0;
-
-    Scene2D& GetScene();
-
-    /**
-    enabled_ is not accessible by subclasses because there is a state machine
-    that we do not wanna mess with
-    */
-    bool IsEnabled() const;
-
-  private:
-    Scene2D& scene_;
-    bool     enabled_;
-  };
-
-  typedef boost::shared_ptr<MeasureTool> MeasureToolPtr;
-  typedef std::vector<MeasureToolPtr> MeasureToolList;
-}
-
-
-extern void TrackerSample_SetInfoDisplayMessage(
-  std::string key, std::string value);
--- a/Samples/Common/MeasureToolsToolbox.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,278 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "MeasureToolsToolbox.h"
-
-#include <boost/math/constants/constants.hpp>
-
-namespace
-{
-  double g_pi = boost::math::constants::pi<double>();
-}
-
-namespace OrthancStone
-{
-  double RadiansToDegrees(double angleRad)
-  {
-    static const double factor = 180.0 / g_pi;
-    return angleRad * factor;
-  }
-
-  void AddSquare(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
-    const ScenePoint2D& centerS,
-    const double&       sideLength)
-  {
-    chain.clear();
-    chain.reserve(4);
-    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
-    //TODO: take DPI into account 
-    double handleLX = centerC.GetX() - sideLength / 2;
-    double handleTY = centerC.GetY() - sideLength / 2;
-    double handleRX = centerC.GetX() + sideLength / 2;
-    double handleBY = centerC.GetY() + sideLength / 2;
-    ScenePoint2D LTC(handleLX, handleTY);
-    ScenePoint2D RTC(handleRX, handleTY);
-    ScenePoint2D RBC(handleRX, handleBY);
-    ScenePoint2D LBC(handleLX, handleBY);
-
-    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
-
-    chain.push_back(startLT);
-    chain.push_back(startRT);
-    chain.push_back(startRB);
-    chain.push_back(startLB);
-  }
-#if 0
-  void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&      scene
-    , const ScenePoint2D& p1
-    , const ScenePoint2D& c
-    , const ScenePoint2D& p2
-    , const double&       radiusS
-    , const bool          clockwise
-    , const int           subdivisionsCount)
-  {
-    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
-    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
-    AddArc(
-      chain, scene, c, radiusS, p1cAngle, p2cAngle, 
-      clockwise, subdivisionsCount);
-  }
-#endif
-
-  void AddShortestArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&             scene
-    , const ScenePoint2D&        p1
-    , const ScenePoint2D&        c
-    , const ScenePoint2D&        p2
-    , const double&              radiusS
-    , const int                  subdivisionsCount)
-  {
-    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
-    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
-    AddShortestArc(
-      chain, scene, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount);
-  }
-
-  void GetPositionOnBisectingLine(
-    ScenePoint2D&       result
-    , const ScenePoint2D& p1
-    , const ScenePoint2D& c
-    , const ScenePoint2D& p2
-    , const double d)
-  {
-    // TODO: fix correct half-plane
-    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
-    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
-    double angle = 0.5*(p1cAngle + p2cAngle);
-    double unitVectorX = cos(angle);
-    double unitVectorY = sin(angle);
-    double posX = c.GetX() + d * unitVectorX;
-    double posY = c.GetX() + d * unitVectorY;
-    result = ScenePoint2D(posX, posY);
-  }
-   
-
-  void AddShortestArc(
-      PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
-    , const ScenePoint2D&         centerS
-    , const double&               radiusS
-    , const double                startAngleRad
-    , const double                endAngleRad
-    , const int                   subdivisionsCount)
-  {
-    // this gives a signed difference between angle which
-    // is the smallest difference (in magnitude) between 
-    // the angles
-    double delta = NormalizeAngle(endAngleRad-startAngleRad);
-
-    chain.clear();
-    chain.reserve(subdivisionsCount + 1);
-
-    double angleIncr = delta/static_cast<double>(subdivisionsCount);
-
-    double theta = startAngleRad;
-    for (int i = 0; i < subdivisionsCount + 1; ++i)
-    {
-      double offsetX = radiusS * cos(theta);
-      double offsetY = radiusS * sin(theta);
-      double pointX = centerS.GetX() + offsetX;
-      double pointY = centerS.GetY() + offsetY;
-      chain.push_back(ScenePoint2D(pointX, pointY));
-      theta += angleIncr;
-    }
-  }
-
-#if 0
-  void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&      scene
-    , const ScenePoint2D& centerS
-    , const double&       radiusS
-    , const double        startAngleRad
-    , const double        endAngleRad
-    , const bool          clockwise
-    , const int           subdivisionsCount)
-  {
-    double startAngleRadN = NormalizeAngle(startAngleRad);
-    double endAngleRadN = NormalizeAngle(endAngleRad);
-
-    double angle1Rad = std::min(startAngleRadN, endAngleRadN);
-    double angle2Rad = std::max(startAngleRadN, endAngleRadN);
-
-    // now we are sure angle1Rad < angle2Rad
-    // this means that if we draw from 1 to 2, it will be clockwise (
-    // increasing angles).
-    // let's fix this:
-    if (!clockwise)
-    {
-      angle2Rad -= 2 * g_pi;
-      // now we are sure angle2Rad < angle1Rad (since they were normalized) 
-      // and, thus, going from 1 to 2 means the angle values will DECREASE,
-      // which is the definition of anticlockwise
-    }
-
-    chain.clear();
-    chain.reserve(subdivisionsCount + 1);
-
-    double angleIncr = (angle2Rad - angle1Rad)
-      / static_cast<double>(subdivisionsCount);
-
-    double theta = angle1Rad;
-    for (int i = 0; i < subdivisionsCount + 1; ++i)
-    {
-      double offsetX = radiusS * cos(theta);
-      double offsetY = radiusS * sin(theta);
-      double pointX = centerS.GetX() + offsetX;
-      double pointY = centerS.GetY() + offsetY;
-      chain.push_back(ScenePoint2D(pointX, pointY));
-      theta += angleIncr;
-    }
-  }
-#endif
-
-  void AddCircle(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
-    const ScenePoint2D& centerS,
-    const double&       radiusS,
-    const int           numSubdivisions)
-  {
-    //ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
-    //TODO: take DPI into account
-
-    // TODO: automatically compute the number for segments for smooth 
-    // display based on the radius in pixels.
-
-    chain.clear();
-    chain.reserve(numSubdivisions);
-
-    double angleIncr = (2.0 * g_pi)
-      / static_cast<double>(numSubdivisions);
-
-    double theta = 0;
-    for (int i = 0; i < numSubdivisions; ++i)
-    {
-      double offsetX = radiusS * cos(theta);
-      double offsetY = radiusS * sin(theta);
-      double pointX = centerS.GetX() + offsetX;
-      double pointY = centerS.GetY() + offsetY;
-      chain.push_back(ScenePoint2D(pointX, pointY));
-      theta += angleIncr;
-    }
-  }
-
-  double NormalizeAngle(double angle)
-  {
-    double retAngle = angle;
-    while (retAngle < -1.0*g_pi)
-      retAngle += 2 * g_pi;
-    while (retAngle >= g_pi)
-      retAngle -= 2 * g_pi;
-    return retAngle;
-  }
-
-  double MeasureAngle(const ScenePoint2D& p1, const ScenePoint2D& c, const ScenePoint2D& p2)
-  {
-    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
-    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
-    double delta = p2cAngle - p1cAngle;
-    return NormalizeAngle(delta);
-  }
-
-
-#if 0
-  void AddEllipse(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
-    const ScenePoint2D& centerS,
-    const double&       halfHAxis,
-    const double&       halfVAxis)
-  {
-    chain.clear();
-    chain.reserve(4);
-    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
-    //TODO: take DPI into account
-    double handleLX = centerC.GetX() - sideLength / 2;
-    double handleTY = centerC.GetY() - sideLength / 2;
-    double handleRX = centerC.GetX() + sideLength / 2;
-    double handleBY = centerC.GetY() + sideLength / 2;
-    ScenePoint2D LTC(handleLX, handleTY);
-    ScenePoint2D RTC(handleRX, handleTY);
-    ScenePoint2D RBC(handleRX, handleBY);
-    ScenePoint2D LBC(handleLX, handleBY);
-
-    ScenePoint2D startLT = LTC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startRT = RTC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startRB = RBC.Apply(scene.GetCanvasToSceneTransform());
-    ScenePoint2D startLB = LBC.Apply(scene.GetCanvasToSceneTransform());
-
-    chain.push_back(startLT);
-    chain.push_back(startRT);
-    chain.push_back(startRB);
-    chain.push_back(startLB);
-}
-#endif
-}
--- a/Samples/Common/MeasureToolsToolbox.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/Scene2D.h>
-
-namespace OrthancStone
-{
-
-  /**
-  This function will create a square around the center point supplied in
-  scene coordinates, with a side length given in canvas coordinates. The
-  square sides are parallel to the canvas boundaries.
-  */
-  void AddSquare(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
-    const ScenePoint2D& centerS,
-    const double&       sideLength);
-
-
-  /**
-    Creates an arc centered on c that goes
-    - from a point r1:
-      - so that r1 belongs to the p1,c line
-      - so that the distance from c to r1 equals radius
-    - to a point r2:
-      - so that r2 belongs to the p2,c line
-      - so that the distance from c to r2 equals radius
-    - that follows the shortest among the two possible paths
-
-    Warning: the existing chain content will be wiped out.
-  */
-  void AddShortestArc(
-      PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
-    , const ScenePoint2D&         p1
-    , const ScenePoint2D&         c
-    , const ScenePoint2D&         p2
-    , const double&               radiusS
-    , const int                   subdivisionsCount = 63);
-
-  /**
-    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
-    start angle to end angle, by following the shortest arc.
-
-    Warning: the existing chain content will be wiped out.
-  */
-  void AddShortestArc(
-      PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
-    , const ScenePoint2D&         centerS
-    , const double&               radiusS
-    , const double                startAngleRad
-    , const double                endAngleRad
-    , const int                   subdivisionsCount = 63);
-
-#if 0
-  /**
-    Creates an arc centered on c that goes
-    - from a point r1:
-      - so that r1 belongs to the p1,c line
-      - so that the distance from c to r1 equals radius
-    - to a point r2:
-      - so that r2 belongs to the p2,c line
-      - so that the distance from c to r2 equals radius
-
-    if clockwise is true, the arc is drawn from r1 to r2 with increasing 
-    angle values. Otherwise, the angle values decrease.
-
-    Warning: the existing chain content will be wiped out.
-  */
-
-  void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&             scene
-    , const ScenePoint2D&        p1
-    , const ScenePoint2D&        c
-    , const ScenePoint2D&        p2
-    , const double&              radiusS
-    , const bool                 clockwise
-    , const int                  subdivisionsCount = 63);
- 
-  /**
-    Creates an arc (open curve) with "numSubdivisions" (N + 1 points) from 
-    start angle to end angle with the supplied radius.
-
-    if clockwise is true, the arc is drawn from start to end by increasing the
-    angle values.
-
-    Otherwise, the angle value decreases from start to end.
-
-    Warning: the existing chain content will be wiped out.
-  */
-  void AddArc(
-      PolylineSceneLayer::Chain& chain
-    , const Scene2D&      scene
-    , const ScenePoint2D& centerS
-    , const double&       radiusS
-    , const double        startAngleRad
-    , const double        endAngleRad
-    , const bool          clockwise
-    , const int           subdivisionsCount = 63);
-#endif
-  /**
-    Creates a circle (closed curve) with "numSubdivisions"
-    (N points)
-
-    Warning: the existing chain content will be wiped out.
-  */
-  void AddCircle(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
-    const ScenePoint2D& centerS,
-    const double&       radiusS,
-    const int           numSubdivisions = 63);
-
-  /**
-    Adds or subtracts 2*pi as many times as need to shift the specified
-    angle to a value such as: -pi <= value < pi
-   */
-  double NormalizeAngle(double angle);
-
-  /**
-    Returns the angle magnitude between the p1,c and p2,c lines. 
-    The returned angle is between 0 and 2*pi
-
-    If the angle is between 0 and pi, this means that the shortest arc 
-    from p1 to p2 is clockwise.
-
-    If the angle is between pi and 2*pi, this means that the shortest arc
-    from p1 to p2 is COUNTERclockwise.
-
-  */
-  double MeasureAngle(
-      const ScenePoint2D& p1
-    , const ScenePoint2D& c
-    , const ScenePoint2D& p2);
-
-  /**
-  RadiansToDegrees
-  */
-  double RadiansToDegrees(double angleRad);
-
-  /**
-  This function will return the coordinates of a point that:
-  - belongs to the two bisecting lines of the p1 c p2 angle.
-  - is a distance d from c.
-  Among the four possible points, the one returned will be the one belonging
-  to the *smallest* half-plane defined by the [c,p1[ and [c,p2[ half-lines.
-  */
-  void GetPositionOnBisectingLine(
-      ScenePoint2D&       result
-    , const ScenePoint2D& p1
-    , const ScenePoint2D& c
-    , const ScenePoint2D& p2
-    , const double d);
-}
--- a/Samples/Common/MeasureTrackers.cpp	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/**
- * 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/>.
- **/
-
-#include "MeasureTrackers.h"
-#include <Core/OrthancException.h>
-
-using namespace Orthanc;
-
-namespace OrthancStone
-{
-
-  CreateMeasureTracker::CreateMeasureTracker(
-    Scene2D&                        scene,
-    std::vector<TrackerCommandPtr>& undoStack,
-    std::vector<MeasureToolPtr>&    measureTools)
-    : scene_(scene)
-    , active_(true)
-    , undoStack_(undoStack)
-    , measureTools_(measureTools)
-    , commitResult_(true)
-  {
-  }
-
-  void CreateMeasureTracker::Cancel()
-  {
-    commitResult_ = false;
-    active_ = false;
-  }
-
-  bool CreateMeasureTracker::IsActive() const
-  {
-    return active_;
-  }
-
-  CreateMeasureTracker::~CreateMeasureTracker()
-  {
-    // if the tracker completes successfully, we add the command
-    // to the undo stack
-
-    // otherwise, we simply undo it
-    if (commitResult_)
-      undoStack_.push_back(command_);
-    else
-      command_->Undo();
-  }
-}
-
-
--- a/Samples/Common/MeasureTrackers.h	Fri May 17 18:04:26 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-/**
- * 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"
-#include "../../Framework/Scene2D/Scene2D.h"
-#include "../../Framework/Scene2D/PointerEvent.h"
-
-#include "MeasureTools.h"
-#include "MeasureCommands.h"
-
-#include <vector>
-
-namespace OrthancStone
-{
-  class CreateMeasureTracker : public IFlexiblePointerTracker
-  {
-  public:
-    virtual void Cancel() ORTHANC_OVERRIDE;
-    virtual bool IsActive() const ORTHANC_OVERRIDE;
-  protected:
-    CreateMeasureTracker(
-      Scene2D&                        scene,
-      std::vector<TrackerCommandPtr>& undoStack,
-      std::vector<MeasureToolPtr>&    measureTools);
-
-    ~CreateMeasureTracker();
-  
-  protected:
-    CreateMeasureCommandPtr         command_;
-    Scene2D&                        scene_;
-    bool                            active_;
-  private:
-    std::vector<TrackerCommandPtr>& undoStack_;
-    std::vector<MeasureToolPtr>&    measureTools_;
-    bool                            commitResult_;
-  };
-}
-
--- a/Samples/Sdl/BasicScene.cpp	Fri May 17 18:04:26 2019 +0200
+++ b/Samples/Sdl/BasicScene.cpp	Sun May 19 16:31:56 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/CMakeLists.txt	Fri May 17 18:04:26 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Sun May 19 16:31:56 2019 +0200
@@ -69,44 +69,6 @@
   LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h")
 endif()
 
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/AngleMeasureTool.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/AngleMeasureTool.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateAngleMeasureTracker.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateAngleMeasureTracker.h")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateCircleMeasureTracker.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateCircleMeasureTracker.h")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateLineMeasureTracker.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateLineMeasureTracker.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateMeasureTracker.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateMeasureTracker.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/CreateSimpleTrackerAdapter.cpp")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditAngleMeasureTracker.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditAngleMeasureTracker.h")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditCircleMeasureTracker.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditCircleMeasureTracker.h")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditLineMeasureTracker.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/EditLineMeasureTracker.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/IFlexiblePointerTracker.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/LineMeasureTool.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/LineMeasureTool.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureCommands.h")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTools.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureToolsToolbox.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureToolsToolbox.h")
-
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.cpp")
-LIST(APPEND TRACKERSAMPLE_SOURCE "../Common/MeasureTrackers.h")
-
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSample.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.cpp")
 LIST(APPEND TRACKERSAMPLE_SOURCE "TrackerSampleApp.h")
--- a/Samples/Sdl/TrackerSampleApp.cpp	Fri May 17 18:04:26 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Sun May 19 16:31:56 2019 +0200
@@ -20,21 +20,22 @@
 
 #include "TrackerSampleApp.h"
 
-#include "../Common/CreateLineMeasureTracker.h"
-#include "../Common/CreateAngleMeasureTracker.h"
-
-#include "../../Applications/Sdl/SdlOpenGLWindow.h"
+#include <Framework/Scene2DViewport/CreateLineMeasureTracker.h>
+#include <Framework/Scene2DViewport/CreateAngleMeasureTracker.h>
 
-#include "../../Framework/Scene2D/PanSceneTracker.h"
-#include "../../Framework/Scene2D/RotateSceneTracker.h"
-#include "../../Framework/Scene2D/Scene2D.h"
-#include "../../Framework/Scene2D/ZoomSceneTracker.h"
-#include "../../Framework/Scene2D/CairoCompositor.h"
-#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
-#include "../../Framework/Scene2D/OpenGLCompositor.h"
-#include "../../Framework/StoneInitialization.h"
+#include <Framework/Scene2D/PanSceneTracker.h>
+#include <Framework/Scene2D/RotateSceneTracker.h>
+#include <Framework/Scene2D/Scene2D.h>
+#include <Framework/Scene2D/ZoomSceneTracker.h>
+#include <Framework/Scene2D/CairoCompositor.h>
+#include <Framework/Scene2D/ColorTextureSceneLayer.h>
+#include <Framework/Scene2D/OpenGLCompositor.h>
 
- // From Orthanc framework
+#include <Framework/StoneInitialization.h>
+
+#include <Applications/Sdl/SdlOpenGLWindow.h>
+
+// From Orthanc framework
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 #include <Core/Images/Image.h>
@@ -67,9 +68,9 @@
     return descs[i];
   }
 
-  Scene2D& TrackerSampleApp::GetScene()
+  Scene2DPtr TrackerSampleApp::GetScene()
   {
-    return scene_;
+    return controller_->GetScene();
   }
 
   void TrackerSampleApp::SelectNextTool()
@@ -94,10 +95,10 @@
     auto msgS = msg.str();
 
     TextSceneLayer* layerP = NULL;
-    if (scene_.HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
+    if (GetScene()->HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
     {
       TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>(
-        scene_.GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
+        GetScene()->GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
       layerP = &layer;
     }
     else
@@ -109,29 +110,29 @@
       layer->SetBorder(20);
       layer->SetAnchor(BitmapAnchor_TopLeft);
       //layer->SetPosition(0,0);
-      scene_.SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
+      GetScene()->SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
     }
     // position the fixed info text in the upper right corner
     layerP->SetText(msgS.c_str());
     double cX = compositor_->GetCanvasWidth() * (-0.5);
     double cY = compositor_->GetCanvasHeight() * (-0.5);
-    scene_.GetCanvasToSceneTransform().Apply(cX,cY);
+    GetScene()->GetCanvasToSceneTransform().Apply(cX,cY);
     layerP->SetPosition(cX, cY);
   }
 
   void TrackerSampleApp::DisplayFloatingCtrlInfoText(const PointerEvent& e)
   {
-    ScenePoint2D p = e.GetMainPosition().Apply(scene_.GetCanvasToSceneTransform());
+    ScenePoint2D p = e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform());
 
     char buf[128];
     sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", 
       p.GetX(), p.GetY(), 
       e.GetMainPosition().GetX(), e.GetMainPosition().GetY());
 
-    if (scene_.HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
+    if (GetScene()->HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
     {
       TextSceneLayer& layer =
-        dynamic_cast<TextSceneLayer&>(scene_.GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
+        dynamic_cast<TextSceneLayer&>(GetScene()->GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
       layer.SetText(buf);
       layer.SetPosition(p.GetX(), p.GetY());
     }
@@ -143,13 +144,13 @@
       layer->SetBorder(20);
       layer->SetAnchor(BitmapAnchor_BottomCenter);
       layer->SetPosition(p.GetX(), p.GetY());
-      scene_.SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
+      GetScene()->SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
     }
   }
 
   void TrackerSampleApp::HideInfoText()
   {
-    scene_.DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
+    GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
   }
 
   void TrackerSampleApp::HandleApplicationEvent(
@@ -191,7 +192,7 @@
           //  e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
           
           activeTracker_->PointerMove(e);
-          if (!activeTracker_->IsActive())
+          if (!activeTracker_->IsAlive())
             activeTracker_ = NULL;
         }
       }
@@ -203,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;
       }
     }
@@ -215,7 +216,7 @@
       if (activeTracker_)
       {
         activeTracker_->PointerDown(e);
-        if (!activeTracker_->IsActive())
+        if (!activeTracker_->IsAlive())
           activeTracker_ = NULL;
       }
       else
@@ -233,7 +234,7 @@
         if (activeTracker_)
         {
           activeTracker_->Cancel();
-          if (!activeTracker_->IsActive())
+          if (!activeTracker_->IsAlive())
             activeTracker_ = NULL;
         }
         break;
@@ -249,7 +250,7 @@
         break;
 
       case SDLK_s:
-        scene_.FitContent(compositor_->GetCanvasWidth(),
+        GetScene()->FitContent(compositor_->GetCanvasWidth(),
           compositor_->GetCanvasHeight());
         break;
 
@@ -267,7 +268,8 @@
   }
 
 
-  void TrackerSampleApp::OnSceneTransformChanged(const Scene2D::SceneTransformChanged& message)
+  void TrackerSampleApp::OnSceneTransformChanged(
+    const ViewportController::SceneTransformChanged& message)
   {
     DisplayInfoText();
   }
@@ -279,12 +281,12 @@
     switch (event.button.button)
     {
     case SDL_BUTTON_MIDDLE:
-      return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-        new PanSceneTracker(scene_, e)));
+      return FlexiblePointerTrackerPtr(new PanSceneTracker
+        (controller_, e));
 
     case SDL_BUTTON_RIGHT:
-      return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-        new ZoomSceneTracker(scene_, e, compositor_->GetCanvasHeight())));
+      return FlexiblePointerTrackerPtr(new ZoomSceneTracker
+        (controller_, e, compositor_->GetCanvasHeight()));
 
     case SDL_BUTTON_LEFT:
     {
@@ -309,26 +311,26 @@
         {
         case GuiTool_Rotate:
           //LOG(TRACE) << "Creating RotateSceneTracker";
-          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new RotateSceneTracker(scene_, e)));
+          return FlexiblePointerTrackerPtr(new RotateSceneTracker(
+            controller_, e));
         case GuiTool_Pan:
-          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new PanSceneTracker(scene_, e)));
+          return FlexiblePointerTrackerPtr(new PanSceneTracker(
+            controller_, e));
         case GuiTool_Zoom:
-          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new ZoomSceneTracker(scene_, e, compositor_->GetCanvasHeight())));
+          return FlexiblePointerTrackerPtr(new ZoomSceneTracker(
+            controller_, e, compositor_->GetCanvasHeight()));
         //case GuiTool_AngleMeasure:
-        //  return new AngleMeasureTracker(scene_, measureTools_, undoStack_, e);
+        //  return new AngleMeasureTracker(GetScene(), measureTools_, undoStack_, e);
         //case GuiTool_CircleMeasure:
-        //  return new CircleMeasureTracker(scene_, measureTools_, undoStack_, e);
+        //  return new CircleMeasureTracker(GetScene(), measureTools_, undoStack_, e);
         //case GuiTool_EllipseMeasure:
-        //  return new EllipseMeasureTracker(scene_, measureTools_, undoStack_, e);
+        //  return new EllipseMeasureTracker(GetScene(), measureTools_, undoStack_, e);
         case GuiTool_LineMeasure:
           return FlexiblePointerTrackerPtr(new CreateLineMeasureTracker(
-            IObserver::GetBroker(), scene_, undoStack_, measureTools_, e));
+            IObserver::GetBroker(), controller_, undoStack_, measureTools_, e));
         case GuiTool_AngleMeasure:
           return FlexiblePointerTrackerPtr(new CreateAngleMeasureTracker(
-            IObserver::GetBroker(), scene_, undoStack_, measureTools_, e));
+            IObserver::GetBroker(), controller_, undoStack_, measureTools_, e));
           return NULL;
         case GuiTool_CircleMeasure:
           LOG(ERROR) << "Not implemented yet!";
@@ -348,11 +350,12 @@
 
 
   TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker)
-    , scene_(broker)
     , currentTool_(GuiTool_Rotate)
   {
-    scene_.RegisterObserverCallback(
-      new Callable<TrackerSampleApp, Scene2D::SceneTransformChanged>
+    controller_ = ViewportControllerPtr(new ViewportController(broker));
+
+    controller_->RegisterObserverCallback(
+      new Callable<TrackerSampleApp, ViewportController::SceneTransformChanged>
       (*this, &TrackerSampleApp::OnSceneTransformChanged));
 
     TEXTURE_2x2_1_ZINDEX = 1;
@@ -362,8 +365,6 @@
     LINESET_2_ZINDEX = 5;
     FLOATING_INFOTEXT_LAYER_ZINDEX = 6;
     FIXED_INFOTEXT_LAYER_ZINDEX = 7;
-
-
   }
 
   void TrackerSampleApp::PrepareScene()
@@ -390,13 +391,13 @@
       p[4] = 0;
       p[5] = 0;
 
-      scene_.SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i));
+      GetScene()->SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i));
 
       std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
       l->SetOrigin(-3, 2);
       l->SetPixelSpacing(1.5, 1);
       l->SetAngle(20.0 / 180.0 * M_PI);
-      scene_.SetLayer(TEXTURE_2x2_2_ZINDEX, l.release());
+      GetScene()->SetLayer(TEXTURE_2x2_2_ZINDEX, l.release());
     }
 
     // Texture of 1x1 size
@@ -411,7 +412,7 @@
       std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
       l->SetOrigin(-2, 1);
       l->SetAngle(20.0 / 180.0 * M_PI);
-      scene_.SetLayer(TEXTURE_1x1_ZINDEX, l.release());
+      GetScene()->SetLayer(TEXTURE_1x1_ZINDEX, l.release());
     }
 
     // Some lines
@@ -443,14 +444,14 @@
       layer->AddChain(chain, false);
 
       layer->SetColor(0, 255, 255);
-      scene_.SetLayer(LINESET_1_ZINDEX, layer.release());
+      GetScene()->SetLayer(LINESET_1_ZINDEX, layer.release());
     }
 
     // Some text
     {
       std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
       layer->SetText("Hello");
-      scene_.SetLayer(LINESET_2_ZINDEX, layer.release());
+      GetScene()->SetLayer(LINESET_2_ZINDEX, layer.release());
     }
   }
 
@@ -468,7 +469,7 @@
     unsigned int canvasWidth,
     unsigned int canvasHeight)
   {
-    CairoCompositor compositor(scene_, canvasWidth, canvasHeight);
+    CairoCompositor compositor(*GetScene(), canvasWidth, canvasHeight);
     compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE_0, Orthanc::Encoding_Latin1);
     compositor.Refresh();
 
@@ -514,12 +515,12 @@
     // that needs to be scaled
     SdlOpenGLWindow window("Hello", 1024, 1024, false);
 
-    GetScene().FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
+    GetScene()->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
 
     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);
@@ -564,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 18:04:26 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.h	Sun May 19 16:31:56 2019 +0200
@@ -18,11 +18,15 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-#include <Framework/Scene2D/OpenGLCompositor.h>
+#include <Framework/Scene2DViewport/PointerTypes.h>
+
 #include <Framework/Messages/IObserver.h>
 
-#include "../Common/IFlexiblePointerTracker.h"
-#include "../Common/MeasureTools.h"
+#include <Framework/Scene2D/OpenGLCompositor.h>
+
+#include <Framework/Scene2DViewport/ViewportController.h>
+#include <Framework/Scene2DViewport/IFlexiblePointerTracker.h>
+#include <Framework/Scene2DViewport/MeasureTools.h>
 
 #include <SDL.h>
 
@@ -32,9 +36,6 @@
 
 namespace OrthancStone
 {
-  class TrackerCommand;
-  typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr;
-
   enum GuiTool
   {
     GuiTool_Rotate = 0,
@@ -65,7 +66,7 @@
     void SetInfoDisplayMessage(std::string key, std::string value);
     void DisableTracker();
 
-    Scene2D& GetScene();
+    Scene2DPtr GetScene();
 
     void HandleApplicationEvent(const SDL_Event& event);
 
@@ -73,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();
@@ -108,7 +110,7 @@
     WARNING: the measuring tools do store a reference to the scene, and it 
     paramount that the scene gets destroyed AFTER the measurement tools.
     */
-    Scene2D scene_;
+    ViewportControllerPtr controller_;
 
     std::map<std::string, std::string> infoTextMap_;
     FlexiblePointerTrackerPtr activeTracker_;