changeset 698:8b6adfb62a2f refactor-viewport-controller

Code is broken -- stashing ongoing work in a branch
author Benjamin Golinvaux <bgo@osimis.io>
date Wed, 15 May 2019 16:56:17 +0200
parents cb3b76d16234
children 5c551f078c18
files Framework/Scene2D/Internals/OpenGLLinesProgram.cpp Framework/Scene2D/Internals/OpenGLTextProgram.cpp Framework/Scene2D/Scene2D.h Framework/Scene2D/ScenePoint2D.h Framework/Scene2D/ZoomSceneTracker.h Framework/Scene2DViewport/AngleMeasureTool.cpp Framework/Scene2DViewport/AngleMeasureTool.h Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Framework/Scene2DViewport/CreateAngleMeasureTracker.h Framework/Scene2DViewport/CreateCircleMeasureTracker.cpp Framework/Scene2DViewport/CreateCircleMeasureTracker.h Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Framework/Scene2DViewport/CreateLineMeasureTracker.h Framework/Scene2DViewport/CreateMeasureTracker.cpp Framework/Scene2DViewport/CreateMeasureTracker.h Framework/Scene2DViewport/CreateSimpleTrackerAdapter.cpp Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Framework/Scene2DViewport/EditAngleMeasureTracker.h Framework/Scene2DViewport/EditCircleMeasureTracker.cpp Framework/Scene2DViewport/EditCircleMeasureTracker.h Framework/Scene2DViewport/EditLineMeasureTracker.cpp Framework/Scene2DViewport/EditLineMeasureTracker.h Framework/Scene2DViewport/IFlexiblePointerTracker.h Framework/Scene2DViewport/LineMeasureTool.cpp Framework/Scene2DViewport/LineMeasureTool.h Framework/Scene2DViewport/MeasureCommands.cpp Framework/Scene2DViewport/MeasureCommands.h Framework/Scene2DViewport/MeasureTools.cpp Framework/Scene2DViewport/MeasureTools.h Framework/Scene2DViewport/MeasureToolsToolbox.cpp Framework/Scene2DViewport/MeasureToolsToolbox.h Framework/Scene2DViewport/MeasureTrackers.cpp Framework/Scene2DViewport/MeasureTrackers.h Framework/Scene2DViewport/PointerTypes.h Framework/Scene2DViewport/ViewportController.cpp Framework/Scene2DViewport/ViewportController.h README.md Resources/CMake/OrthancStoneConfiguration.cmake 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 Samples/Sdl/CMakeLists.txt Samples/Sdl/TrackerSampleApp.cpp Samples/Sdl/TrackerSampleApp.h
diffstat 69 files changed, 2740 insertions(+), 2426 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp	Tue May 14 19:38:51 2019 +0200
+++ b/Framework/Scene2D/Internals/OpenGLLinesProgram.cpp	Wed May 15 16:56:17 2019 +0200
@@ -205,40 +205,40 @@
         }
 
         // First triangle
-        coords.push_back(x1_);
-        coords.push_back(y1_);
-        coords.push_back(1);
-        coords.push_back(x2_);
-        coords.push_back(y2_);
-        coords.push_back(-1);
-        coords.push_back(x2_);
-        coords.push_back(y2_);
-        coords.push_back(1);
+        coords.push_back(static_cast<float>(x1_));
+        coords.push_back(static_cast<float>(y1_));
+        coords.push_back(static_cast<float>(1));
+        coords.push_back(static_cast<float>(x2_));
+        coords.push_back(static_cast<float>(y2_));
+        coords.push_back(static_cast<float>(-1));
+        coords.push_back(static_cast<float>(x2_));
+        coords.push_back(static_cast<float>(y2_));
+        coords.push_back(static_cast<float>(1));
 
-        miterDirections.push_back(miterX1_);
-        miterDirections.push_back(miterY1_);
-        miterDirections.push_back(miterX2_);
-        miterDirections.push_back(miterY2_);
-        miterDirections.push_back(miterX2_);
-        miterDirections.push_back(miterY2_);
+        miterDirections.push_back(static_cast<float>(miterX1_));
+        miterDirections.push_back(static_cast<float>(miterY1_));
+        miterDirections.push_back(static_cast<float>(miterX2_));
+        miterDirections.push_back(static_cast<float>(miterY2_));
+        miterDirections.push_back(static_cast<float>(miterX2_));
+        miterDirections.push_back(static_cast<float>(miterY2_));
         
         // Second triangle
-        coords.push_back(x1_);
-        coords.push_back(y1_);
-        coords.push_back(1);
-        coords.push_back(x1_);
-        coords.push_back(y1_);
-        coords.push_back(-1);
-        coords.push_back(x2_);
-        coords.push_back(y2_);
-        coords.push_back(-1);
+        coords.push_back(static_cast<float>(x1_));
+        coords.push_back(static_cast<float>(y1_));
+        coords.push_back(static_cast<float>(1));
+        coords.push_back(static_cast<float>(x1_));
+        coords.push_back(static_cast<float>(y1_));
+        coords.push_back(static_cast<float>(-1));
+        coords.push_back(static_cast<float>(x2_));
+        coords.push_back(static_cast<float>(y2_));
+        coords.push_back(static_cast<float>(-1));
 
-        miterDirections.push_back(miterX1_);
-        miterDirections.push_back(miterY1_);
-        miterDirections.push_back(miterX1_);
-        miterDirections.push_back(miterY1_);
-        miterDirections.push_back(miterX2_);
-        miterDirections.push_back(miterY2_);
+        miterDirections.push_back(static_cast<float>(miterX1_));
+        miterDirections.push_back(static_cast<float>(miterY1_));
+        miterDirections.push_back(static_cast<float>(miterX1_));
+        miterDirections.push_back(static_cast<float>(miterY1_));
+        miterDirections.push_back(static_cast<float>(miterX2_));
+        miterDirections.push_back(static_cast<float>(miterY2_));
       }        
     };
 
@@ -247,7 +247,7 @@
                                    const PolylineSceneLayer& layer) :
       context_(context),
       verticesCount_(0),
-      thickness_(layer.GetThickness()),
+      thickness_(static_cast<float>(layer.GetThickness())),
       red_(layer.GetRedAsFloat()),
       green_(layer.GetGreenAsFloat()),
       blue_(layer.GetBlueAsFloat())
@@ -418,12 +418,15 @@
             double t1 = std::max(thickness, aliasingBorder);
             double t0 = std::max(0.0, thickness - aliasingBorder);
             
-            glUniform1f(program_->GetUniformLocation("u_thickness"), t1 / zoom);
-            glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), t0 / t1);
+            glUniform1f(program_->GetUniformLocation("u_thickness"), 
+              static_cast<GLfloat>(t1 / zoom));
+            glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), 
+              static_cast<GLfloat>(t0 / t1));
           }
           else
           {
-            glUniform1f(program_->GetUniformLocation("u_thickness"), thickness / zoom);
+            glUniform1f(program_->GetUniformLocation("u_thickness"), 
+              static_cast<GLfloat>(thickness / zoom));
           }
         }
         else
@@ -433,12 +436,15 @@
             double t1 = std::max(thickness, aliasingBorder / zoom);
             double t0 = std::max(0.0, thickness - aliasingBorder / zoom);
 
-            glUniform1f(program_->GetUniformLocation("u_thickness"), t1);
-            glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), t0 / t1);
+            glUniform1f(program_->GetUniformLocation("u_thickness"), 
+              static_cast<GLfloat>(t1));
+            glUniform1f(program_->GetUniformLocation("u_antialiasing_start"), 
+              static_cast<GLfloat>(t0 / t1));
           }
           else
           {
-            glUniform1f(program_->GetUniformLocation("u_thickness"), thickness);
+            glUniform1f(program_->GetUniformLocation("u_thickness"), 
+              static_cast<GLfloat>(thickness));
           }
         }
 
@@ -446,12 +452,14 @@
         {
           glEnable(GL_BLEND);
           glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-          glDrawArrays(GL_TRIANGLES, 0, data.GetVerticesCount());
+          glDrawArrays(GL_TRIANGLES, 0, 
+            static_cast<GLsizei>(data.GetVerticesCount()));
           glDisable(GL_BLEND);
         }
         else
         {
-          glDrawArrays(GL_TRIANGLES, 0, data.GetVerticesCount());
+          glDrawArrays(GL_TRIANGLES, 0, 
+            static_cast<GLsizei>(data.GetVerticesCount()));
         }
 
         glDisableVertexAttribArray(locationPosition);
--- a/Framework/Scene2D/Internals/OpenGLTextProgram.cpp	Tue May 14 19:38:51 2019 +0200
+++ b/Framework/Scene2D/Internals/OpenGLTextProgram.cpp	Wed May 15 16:56:17 2019 +0200
@@ -155,8 +155,8 @@
         program_->Use();
 
         double dx, dy;  // In pixels
-        ComputeAnchorTranslation(dx, dy, data.GetAnchor(), 
-                                 data.GetTextWidth(), data.GetTextHeight(), data.GetBorder());
+        ComputeAnchorTranslation(dx, dy, data.GetAnchor(), data.GetTextWidth(), 
+          data.GetTextHeight(), static_cast<unsigned int>(data.GetBorder()));
       
         double x = data.GetX();
         double y = data.GetY();
@@ -182,7 +182,8 @@
 
         glEnable(GL_BLEND);
         glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-        glDrawArrays(GL_TRIANGLES, 0, data.GetCoordinatesCount() / COMPONENTS);
+        glDrawArrays(GL_TRIANGLES, 0, 
+          static_cast<GLsizei>(data.GetCoordinatesCount() / COMPONENTS));
         glDisable(GL_BLEND);
 
         glDisableVertexAttribArray(positionLocation_);
--- a/Framework/Scene2D/Scene2D.h	Tue May 14 19:38:51 2019 +0200
+++ b/Framework/Scene2D/Scene2D.h	Wed May 15 16:56:17 2019 +0200
@@ -119,3 +119,4 @@
                     unsigned int canvasHeight);
   };
 }
+
--- a/Framework/Scene2D/ScenePoint2D.h	Tue May 14 19:38:51 2019 +0200
+++ b/Framework/Scene2D/ScenePoint2D.h	Wed May 15 16:56:17 2019 +0200
@@ -23,7 +23,6 @@
 
 #include "../Toolbox/AffineTransform2D.h"
 
-
 namespace OrthancStone
 {
   class ScenePoint2D
--- a/Framework/Scene2D/ZoomSceneTracker.h	Tue May 14 19:38:51 2019 +0200
+++ b/Framework/Scene2D/ZoomSceneTracker.h	Wed May 15 16:56:17 2019 +0200
@@ -21,11 +21,14 @@
 
 #pragma once
 
+
 #include "IPointerTracker.h"
 #include "Internals/FixedPointAligner.h"
 
 namespace OrthancStone
 {
+  class Scene2D;
+
   class ZoomSceneTracker : public IPointerTracker
   {
   private:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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, Scene2DWPtr sceneW)
+      : MeasureTool(broker, sceneW)
+      , 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	Wed May 15 16:56:17 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,
+    Scene2DWPtr                     sceneW,
+    std::vector<TrackerCommandPtr>& undoStack,
+    MeasureToolList&                measureTools,
+    const PointerEvent&             e)
+    : CreateMeasureTracker(sceneW, undoStack, measureTools)
+    , state_(CreatingSide1)
+  {
+    Scene2DPtr scene = sceneW.lock();
+    command_.reset(
+      new CreateAngleMeasureCommand(
+        broker,
+        sceneW,
+        measureTools,
+        e.GetMainPosition().Apply(scene->GetCanvasToSceneTransform())));
+  }
+
+  CreateAngleMeasureTracker::~CreateAngleMeasureTracker()
+  {
+  }
+
+  void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
+  {
+    Scene2DPtr scene = scene_.lock();
+    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_);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.h	Wed May 15 16:56:17 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,
+      Scene2DWPtr                     scene,
+      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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,95 @@
+/**
+ * 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,
+    Scene2DWPtr                     sceneW,
+    std::vector<TrackerCommandPtr>& undoStack,
+    MeasureToolList&                measureTools,
+    const PointerEvent&             e)
+    : CreateMeasureTracker(sceneW, undoStack, measureTools)
+  {
+    Scene2DPtr scene = sceneW.lock();
+    command_.reset(
+      new CreateLineMeasureCommand(
+        broker,
+        sceneW,
+        measureTools,
+        e.GetMainPosition().Apply(scene->GetCanvasToSceneTransform())));
+  }
+
+  CreateLineMeasureTracker::~CreateLineMeasureTracker()
+  {
+
+  }
+
+  void CreateLineMeasureTracker::PointerMove(const PointerEvent& event)
+  {
+    Scene2DPtr scene = scene_.lock();
+    assert(scene);
+    
+    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_);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.h	Wed May 15 16:56:17 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,
+      Scene2DWPtr                     scene,
+      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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,77 @@
+/**
+ * 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));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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 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;
+  };
+
+
+  /**
+  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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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, Scene2DWPtr sceneW)
+      : MeasureTool(broker, sceneW)
+      , 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	Wed May 15 16:56:17 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(
+    Scene2DWPtr sceneW, MeasureToolList& measureTools)
+    : TrackerCommand(sceneW)
+    , measureTools_(measureTools)
+  {
+
+  }
+
+  CreateMeasureCommand::~CreateMeasureCommand()
+  {
+    // deleting the command should not change the model state
+    // we thus leave it as is
+  }
+
+  CreateLineMeasureCommand::CreateLineMeasureCommand(
+    MessageBroker&    broker, 
+    Scene2DWPtr       sceneW, 
+    MeasureToolList&  measureTools, 
+    ScenePoint2D      point)
+    : CreateMeasureCommand(sceneW, measureTools)
+    , measureTool_(new LineMeasureTool(broker,sceneW))
+  {
+    measureTools_.push_back(measureTool_);
+    measureTool_->Set(point, point);
+  }
+
+  void CreateLineMeasureCommand::SetEnd(ScenePoint2D scenePos)
+  {
+    measureTool_->SetEnd(scenePos);
+  }
+
+  CreateAngleMeasureCommand::CreateAngleMeasureCommand(
+    MessageBroker&    broker, 
+    Scene2DWPtr       sceneW,
+    MeasureToolList&  measureTools, 
+    ScenePoint2D      point)
+    : CreateMeasureCommand(sceneW, measureTools)
+    , measureTool_(new AngleMeasureTool(broker,sceneW))
+  {
+    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	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,113 @@
+/**
+ * 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(Scene2DWPtr sceneW) : scene_(sceneW)
+    {
+
+    }
+    virtual void Undo() = 0;
+    virtual void Redo() = 0;
+    Scene2DPtr GetScene()
+    {
+      return scene_.lock();
+    }
+
+  protected:
+    Scene2DWPtr scene_;
+  };
+
+  class CreateMeasureCommand : public TrackerCommand
+  {
+  public:
+    CreateMeasureCommand(Scene2DWPtr sceneW, 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, 
+      Scene2DWPtr      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_;
+  };
+
+
+  class CreateAngleMeasureCommand : public CreateMeasureCommand
+  {
+  public:
+    /** Ctor sets end of side 1*/
+    CreateAngleMeasureCommand(
+      MessageBroker&   broker, 
+      Scene2DWPtr      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_;
+  };
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTools.cpp	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,82 @@
+/**
+ * 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>
+
+#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_;
+  }
+
+  OrthancStone::Scene2DPtr MeasureTool::GetScene()
+  {
+    Scene2DPtr scene = scene_.lock();
+    if (!scene)
+      throw OrthancException(ErrorCode_InternalError, "Using dead object!");
+    return scene;
+  }
+
+  MeasureTool::MeasureTool(MessageBroker& broker, Scene2DWPtr sceneW)
+    : IObserver(broker)
+    , scene_(sceneW)
+    , enabled_(true)
+  {
+    GetScene()->RegisterObserverCallback(
+      new Callable<MeasureTool, Scene2D::SceneTransformChanged>
+      (*this, &MeasureTool::OnSceneTransformChanged));
+  }
+
+  void MeasureTool::OnSceneTransformChanged(
+    const Scene2D::SceneTransformChanged& message)
+  {
+    RefreshScene();
+  }
+
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTools.h	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,88 @@
+/**
+ * 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/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, Scene2DWPtr sceneW);
+    
+    /**
+    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;
+
+    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:
+    Scene2DWPtr scene_;
+    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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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	Wed May 15 16:56:17 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 "MeasureTrackers.h"
+#include <Core/OrthancException.h>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+
+  CreateMeasureTracker::CreateMeasureTracker(
+    Scene2DWPtr                     sceneW,
+    std::vector<TrackerCommandPtr>& undoStack,
+    std::vector<MeasureToolPtr>&    measureTools)
+    : scene_(sceneW)
+    , 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();
+  }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/MeasureTrackers.h	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,57 @@
+/**
+ * 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(
+      Scene2DWPtr                     scene,
+      std::vector<TrackerCommandPtr>& undoStack,
+      std::vector<MeasureToolPtr>&    measureTools);
+
+    ~CreateMeasureTracker();
+  
+  protected:
+    CreateMeasureCommandPtr         command_;
+    Scene2DWPtr                     scene_;
+    bool                            active_;
+  private:
+    std::vector<TrackerCommandPtr>& undoStack_;
+    std::vector<MeasureToolPtr>&    measureTools_;
+    bool                            commitResult_;
+  };
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/PointerTypes.h	Wed May 15 16:56:17 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;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,49 @@
+/**
+ * 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>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+
+  ViewportController::ViewportController(MessageBroker& broker)
+    : broker_(broker)
+  {
+
+  }
+
+  Scene2DPtr ViewportController::GetScene()
+  {
+    Scene2DPtr scene = scene_.lock();
+    if (!scene)
+      throw OrthancException(ErrorCode_InternalError, "Using dead object!");
+    return scene;
+  }
+
+  bool ViewportController::HandlePointerEvent(PointerEvent e)
+  {
+    throw StoneException(ErrorCode_NotImplemented);
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/ViewportController.h	Wed May 15 16:56:17 2019 +0200
@@ -0,0 +1,75 @@
+/**
+ * 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/MeasureTools.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 boost::noncopyable
+  {
+  public:
+    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);
+
+  private:
+    MessageBroker&            broker_;
+    Scene2DWPtr               scene_;
+    FlexiblePointerTrackerPtr tracker_;
+  };
+}
--- a/README.md	Tue May 14 19:38:51 2019 +0200
+++ b/README.md	Wed May 15 16:56:17 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	Tue May 14 19:38:51 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 15 16:56:17 2019 +0200
@@ -323,6 +323,38 @@
   ${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/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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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	Tue May 14 19:38:51 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/CMakeLists.txt	Tue May 14 19:38:51 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Wed May 15 16:56:17 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	Tue May 14 19:38:51 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Wed May 15 16:56:17 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(
@@ -249,7 +250,7 @@
         break;
 
       case SDLK_s:
-        scene_.FitContent(compositor_->GetCanvasWidth(),
+        GetScene()->FitContent(compositor_->GetCanvasWidth(),
           compositor_->GetCanvasHeight());
         break;
 
@@ -280,11 +281,11 @@
     {
     case SDL_BUTTON_MIDDLE:
       return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-        new PanSceneTracker(scene_, e)));
+        new PanSceneTracker(*GetScene(), e)));
 
     case SDL_BUTTON_RIGHT:
       return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-        new ZoomSceneTracker(scene_, e, compositor_->GetCanvasHeight())));
+        new ZoomSceneTracker(*GetScene(), e, compositor_->GetCanvasHeight())));
 
     case SDL_BUTTON_LEFT:
     {
@@ -310,25 +311,25 @@
         case GuiTool_Rotate:
           //LOG(TRACE) << "Creating RotateSceneTracker";
           return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new RotateSceneTracker(scene_, e)));
+            new RotateSceneTracker(*GetScene(), e)));
         case GuiTool_Pan:
           return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new PanSceneTracker(scene_, e)));
+            new PanSceneTracker(*GetScene(), e)));
         case GuiTool_Zoom:
           return CreateSimpleTrackerAdapter(PointerTrackerPtr(
-            new ZoomSceneTracker(scene_, e, compositor_->GetCanvasHeight())));
+            new ZoomSceneTracker(*GetScene(), 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(), GetScene(), undoStack_, measureTools_, e));
         case GuiTool_AngleMeasure:
           return FlexiblePointerTrackerPtr(new CreateAngleMeasureTracker(
-            IObserver::GetBroker(), scene_, undoStack_, measureTools_, e));
+            IObserver::GetBroker(), GetScene(), undoStack_, measureTools_, e));
           return NULL;
         case GuiTool_CircleMeasure:
           LOG(ERROR) << "Not implemented yet!";
@@ -349,10 +350,11 @@
 
   TrackerSampleApp::TrackerSampleApp(MessageBroker& broker) : IObserver(broker)
     , currentTool_(GuiTool_Rotate)
-    , scene_(broker)
   {
-    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 +364,6 @@
     LINESET_2_ZINDEX = 5;
     FLOATING_INFOTEXT_LAYER_ZINDEX = 6;
     FIXED_INFOTEXT_LAYER_ZINDEX = 7;
-
-
   }
 
   void TrackerSampleApp::PrepareScene()
@@ -390,13 +390,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 +411,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 +443,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 +468,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,7 +514,7 @@
     // 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);
--- a/Samples/Sdl/TrackerSampleApp.h	Tue May 14 19:38:51 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.h	Wed May 15 16:56:17 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);
 
@@ -108,7 +109,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_;