changeset 646:b4fe9642e83b

Merge from default
author Benjamin Golinvaux <bgo@osimis.io>
date Mon, 13 May 2019 15:22:08 +0200
parents 1e9ed656318e (diff) f0008c55e5f7 (current diff)
children 6af3099ed8da f1bfe3d1759f
files Framework/Scene2D/Scene2D.cpp Samples/Sdl/Loader.cpp
diffstat 36 files changed, 2343 insertions(+), 683 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri May 10 17:34:48 2019 +0200
+++ b/.hgignore	Mon May 13 15:22:08 2019 +0200
@@ -30,3 +30,5 @@
 Resources/CommandTool/protoc-tests/generated_ts/
 Resources/CommandTool/protoc-tests/node_modules/
 Samples/Sdl/ThirdPartyDownloads/
+Samples/Sdl/CMakeLists.txt.orig
+
--- a/Framework/Scene2D/IPointerTracker.h	Fri May 10 17:34:48 2019 +0200
+++ b/Framework/Scene2D/IPointerTracker.h	Mon May 13 15:22:08 2019 +0200
@@ -38,8 +38,8 @@
     virtual void Update(const PointerEvent& event) = 0;
 
     /**
-    This method will be called if the tracker is to be abandoned without
-    committing its result
+    This method will be called when the tracker should commit its result
+    before being destroyed.
     */
     virtual void Release() = 0;
   };
--- a/README.md	Fri May 10 17:34:48 2019 +0200
+++ b/README.md	Mon May 13 15:22:08 2019 +0200
@@ -244,3 +244,14 @@
 ```
 cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DENABLE_OPENGL=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="$($pwd)\..\orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl
 ```
+
+And under Ubuntu (note the /mnt/c/osi/dev/orthanc folder):
+```
+cmake -G "Ninja" -DENABLE_OPENGL=ON -DSTATIC_BUILD=OFF -DOPENSSL_NO_CAPIENG=ON -DORTHANC_FRAMEWORK_SOURCE=path -DORTHANC_FRAMEWORK_ROOT="/mnt/c/osi/dev/orthanc" -DALLOW_DOWNLOADS=ON -DENABLE_SDL=ON ../orthanc-stone/Samples/Sdl
+```
+
+TODO trackers:
+- text overlay 50% --> ColorTextureLayer 50%
+- angle tracker: draw arcs
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/AngleMeasureTool.cpp	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,199 @@
+/**
+ * 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>
+
+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); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+
+          {
+            PolylineSceneLayer::Chain chain;
+            AddSquare(chain, GetScene(), side2End_, 10.0); //TODO: take DPI into account
+            polylineLayer->AddChain(chain, true);
+          }
+        }
+
+        // arc
+        {
+          PolylineSceneLayer::Chain chain;
+
+          AddArc(chain, GetScene(), side1End_, center_, side2End_, 
+            20.0*pixelToScene, true); //TODO: true means always clockwise
+          polylineLayer->AddChain(chain, true);
+        }
+      }
+      {
+        // Set the text layer proporeties
+
+        // the angle is measured in a clockwise way between the points
+        double angleRad = MeasureAngle(side1End_, center_, side2End_);
+        double angleDeg = RadiansToDegrees(angleRad);
+               
+        TextSceneLayer* textLayer = GetTextLayer();
+
+        char buf[64];
+        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(textAnchor.GetX(), textAnchor.GetY());
+      }
+    }
+    else
+    {
+      if (layersCreated)
+      {
+        RemoveFromScene();
+        layersCreated = false;
+      }
+    }
+  }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/AngleMeasureTool.h	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,74 @@
+/**
+ * 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(Scene2D& scene)
+      : MeasureTool(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;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/CreateAngleMeasureTracker.cpp	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,126 @@
+/**
+ * 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(
+    Scene2D&                        scene,
+    std::vector<TrackerCommandPtr>& undoStack,
+    std::vector<MeasureToolPtr>&    measureTools,
+    const PointerEvent&             e)
+    : CreateMeasureTracker(scene, undoStack, measureTools)
+    , state_(CreatingSide1)
+  {
+    command_.reset(
+      new CreateAngleMeasureCommand(
+        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_);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/CreateAngleMeasureTracker.h	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,64 @@
+/**
+ * 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(
+      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_;
+
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/CreateCircleMeasureTracker.cpp	Mon May 13 15:22:08 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/Samples/Common/CreateCircleMeasureTracker.h	Mon May 13 15:22:08 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/Samples/Common/CreateLineMeasureTracker.cpp	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,89 @@
+/**
+ * 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(
+    Scene2D&                        scene,
+    std::vector<TrackerCommandPtr>& undoStack,
+    std::vector<MeasureToolPtr>&    measureTools,
+    const PointerEvent&             e)
+    : CreateMeasureTracker(scene, undoStack, measureTools)
+  {
+    command_.reset(
+      new CreateLineMeasureCommand(
+        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_);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/CreateLineMeasureTracker.h	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,52 @@
+/**
+ * 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(
+      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();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/CreateMeasureTracker.cpp	Mon May 13 15:22:08 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/Samples/Common/CreateMeasureTracker.h	Mon May 13 15:22:08 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/Samples/Common/CreateSimpleTrackerAdapter.cpp	Mon May 13 15:22:08 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/Samples/Common/EditAngleMeasureTracker.cpp	Mon May 13 15:22:08 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/Samples/Common/EditAngleMeasureTracker.h	Mon May 13 15:22:08 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/Samples/Common/EditCircleMeasureTracker.cpp	Mon May 13 15:22:08 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/Samples/Common/EditCircleMeasureTracker.h	Mon May 13 15:22:08 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/Samples/Common/EditLineMeasureTracker.cpp	Mon May 13 15:22:08 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/Samples/Common/EditLineMeasureTracker.h	Mon May 13 15:22:08 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/Samples/Common/IFlexiblePointerTracker.h	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,84 @@
+/**
+ * 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);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/LineMeasureTool.cpp	Mon May 13 15:22:08 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/Samples/Common/LineMeasureTool.h	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,72 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#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(Scene2D& scene)
+      : MeasureTool(scene)
+      , layersCreated(false)
+      , polylineZIndex_(-1)
+      , textZIndex_(-1)
+    {
+
+    }
+
+    ~LineMeasureTool();
+
+    void SetStart(ScenePoint2D start);
+    void SetEnd(ScenePoint2D end);
+    void Set(ScenePoint2D start, ScenePoint2D end);
+
+  private:
+    PolylineSceneLayer* GetPolylineLayer();
+    TextSceneLayer*     GetTextLayer();
+    virtual void        RefreshScene() ORTHANC_OVERRIDE;
+    void                RemoveFromScene();
+
+  private:
+    ScenePoint2D start_;
+    ScenePoint2D end_;
+    bool         layersCreated;
+    int          polylineZIndex_;
+    int          textZIndex_;
+  };
+
+  typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr;
+}
+
--- a/Samples/Common/MeasureCommands.cpp	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Common/MeasureCommands.cpp	Mon May 13 15:22:08 2019 +0200
@@ -52,14 +52,36 @@
     : CreateMeasureCommand(scene, measureTools)
     , measureTool_(new LineMeasureTool(scene))
   {
-    measureTool_ = LineMeasureToolPtr(new LineMeasureTool(scene));
     measureTools_.push_back(measureTool_);
     measureTool_->Set(point, point);
   }
 
-  void CreateLineMeasureCommand::Update(ScenePoint2D scenePos)
+  void CreateLineMeasureCommand::SetEnd(ScenePoint2D scenePos)
   {
     measureTool_->SetEnd(scenePos);
   }
 
+  CreateAngleMeasureCommand::CreateAngleMeasureCommand(
+    Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point)
+    : CreateMeasureCommand(scene, measureTools)
+    , measureTool_(new AngleMeasureTool(scene))
+  {
+    measureTools_.push_back(measureTool_);
+    measureTool_->SetSide1End(point);
+    measureTool_->SetCenter(point);
+    measureTool_->SetSide2End(point);
+  }
+
+  /** This method sets center*/
+  void CreateAngleMeasureCommand::SetCenter(ScenePoint2D scenePos)
+  {
+    measureTool_->SetCenter(scenePos);
+  }
+
+  /** This method sets end of side 2*/
+  void CreateAngleMeasureCommand::SetSide2End(ScenePoint2D scenePos)
+  {
+    measureTool_->SetSide2End(scenePos);
+  }
+
 }
--- a/Samples/Common/MeasureCommands.h	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Common/MeasureCommands.h	Mon May 13 15:22:08 2019 +0200
@@ -24,9 +24,16 @@
 
 // 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:
@@ -36,8 +43,6 @@
     }
     virtual void Undo() = 0;
     virtual void Redo() = 0;
-    virtual void Update(ScenePoint2D scenePos) = 0;
-
     Scene2D& GetScene()
     {
       return scene_;
@@ -71,11 +76,11 @@
   class CreateLineMeasureCommand : public CreateMeasureCommand
   {
   public:
-    CreateLineMeasureCommand(Scene2D& scene, 
-                             MeasureToolList& measureTools, 
-                             ScenePoint2D point);
+    CreateLineMeasureCommand(
+      Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
     
-    void Update(ScenePoint2D scenePos) ORTHANC_OVERRIDE;
+    // the starting position is set in the ctor
+    void SetEnd(ScenePoint2D scenePos);
 
   private:
     virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
@@ -83,10 +88,31 @@
       return measureTool_;
     }
     LineMeasureToolPtr measureTool_;
-    ScenePoint2D       start_;
-    ScenePoint2D       end_;
   };
 
   typedef boost::shared_ptr<CreateLineMeasureCommand> CreateLineMeasureCommandPtr;
+
+  class CreateAngleMeasureCommand : public CreateMeasureCommand
+  {
+  public:
+    /** Ctor sets end of side 1*/
+    CreateAngleMeasureCommand(
+      Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
+
+    /** This method sets center*/
+    void SetCenter(ScenePoint2D scenePos);
+
+    /** This method sets end of side 2*/
+    void SetSide2End(ScenePoint2D scenePos);
+
+  private:
+    virtual MeasureToolPtr GetMeasureTool() ORTHANC_OVERRIDE
+    {
+      return measureTool_;
+    }
+    AngleMeasureToolPtr measureTool_;
+  };
+
+  typedef boost::shared_ptr<CreateAngleMeasureCommand> CreateAngleMeasureCommandPtr;
 }
 
--- a/Samples/Common/MeasureTools.cpp	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Common/MeasureTools.cpp	Mon May 13 15:22:08 2019 +0200
@@ -18,8 +18,11 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#include "MeasureTools.h"
+
 #include <Core/Logging.h>
-#include "MeasureTools.h"
+
+#include <boost/math/constants/constants.hpp>
 
 namespace OrthancStone
 {
@@ -34,163 +37,5 @@
     enabled_ = false;
     RefreshScene();
   }
-
-  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
-        {
-          ScenePoint2D startC = start_.Apply(GetScene().GetSceneToCanvasTransform());
-          double squareSize = 10.0; //TODO: take DPI into account
-          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;
-      }
-    }
-  }
 }
 
--- a/Samples/Common/MeasureTools.h	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Common/MeasureTools.h	Mon May 13 15:22:08 2019 +0200
@@ -17,6 +17,7 @@
  * 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>
@@ -25,6 +26,7 @@
 #include <Framework/Scene2D/TextSceneLayer.h>
 
 #include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
 
 #include <vector>
 #include <cmath>
@@ -50,7 +52,7 @@
     void Disable();
 
   protected:
-    MeasureTool(Scene2D& scene) 
+    MeasureTool(Scene2D& scene)
       : scene_(scene)
       , enabled_(true)
     {
@@ -65,7 +67,6 @@
     */
     virtual void RefreshScene() = 0;
 
-
     Scene2D& GetScene()
     {
       return scene_;
@@ -86,40 +87,6 @@
   };
 
   typedef boost::shared_ptr<MeasureTool> MeasureToolPtr;
-
-  class LineMeasureTool : public MeasureTool
-  {
-  public:
-    LineMeasureTool(Scene2D& scene)
-      : MeasureTool(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;
   typedef std::vector<MeasureToolPtr> MeasureToolList;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/MeasureToolsToolbox.cpp	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,229 @@
+/**
+ * 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
+{
+
+  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);
+  }
+
+  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);
+  }
+
+  double RadiansToDegrees(double angleRad)
+  {
+    static const double factor = 180.0 / g_pi;
+    return angleRad * factor;
+  }
+
+  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 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;
+    }
+  }
+
+  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 < 0)
+      retAngle += 2 * g_pi;
+    while (retAngle >= 2 * 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
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/MeasureToolsToolbox.h	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,135 @@
+/**
+ * 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 pm 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.
+
+    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);
+
+  /**
+    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:   0 <= value < 2*pi
+   */
+  double NormalizeAngle(double angle);
+
+  /**
+    Returns the angle magnitude between the p1,c and p2,c lines. 
+    The returned angle is between 0 and 2*pi
+
+    If the angle is between 0 and pi, this means that the shortest arc 
+    from p1 to p2 is clockwise.
+
+    If the angle is between pi and 2*pi, this means that the shortest arc
+    from p1 to p2 is COUNTERclockwise.
+
+  */
+  double MeasureAngle(
+      const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2);
+
+  /**
+  RadiansToDegrees
+  */
+  double RadiansToDegrees(double angleRad);
+
+  /**
+  This function will return the coordinates of a point that:
+  - belongs to the two bisecting lines of the p1 c p2 angle.
+  - is a distance d from c.
+  Among the four possible points, the one returned will be the one belonging
+  to the *smallest* half-plane defined by the [c,p1[ and [c,p2[ half-lines.
+  */
+  void GetPositionOnBisectingLine(
+      ScenePoint2D&       result
+    , const ScenePoint2D& p1
+    , const ScenePoint2D& c
+    , const ScenePoint2D& p2
+    , const double d);
+}
--- a/Samples/Common/MeasureTrackers.cpp	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Common/MeasureTrackers.cpp	Mon May 13 15:22:08 2019 +0200
@@ -31,12 +31,24 @@
     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
@@ -48,45 +60,6 @@
     else
       command_->Undo();
   }
-  
-  CreateLineMeasureTracker::CreateLineMeasureTracker(
-    Scene2D&                        scene,
-    std::vector<TrackerCommandPtr>& undoStack,
-    std::vector<MeasureToolPtr>&    measureTools,
-    const PointerEvent&             e) 
-    : CreateMeasureTracker(scene, undoStack, measureTools)
-  {
-    command_.reset(
-      new CreateLineMeasureCommand(
-        scene,
-        measureTools,
-        e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform())));
-  }
-
-  CreateLineMeasureTracker::~CreateLineMeasureTracker()
-  {
-
-  }
-
-  void CreateMeasureTracker::Update(const PointerEvent& event)
-  {
-    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);
-    command_->Update(scenePos);
-  }
-
-  void CreateMeasureTracker::Release()
-  {
-    commitResult_ = false;
-  }
-
 }
 
 
--- a/Samples/Common/MeasureTrackers.h	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Common/MeasureTrackers.h	Mon May 13 15:22:08 2019 +0200
@@ -20,8 +20,9 @@
 
 #pragma once
 
-#include "../../Framework/Scene2D/IPointerTracker.h"
+#include "IFlexiblePointerTracker.h"
 #include "../../Framework/Scene2D/Scene2D.h"
+#include "../../Framework/Scene2D/PointerEvent.h"
 
 #include "MeasureTools.h"
 #include "MeasureCommands.h"
@@ -30,11 +31,11 @@
 
 namespace OrthancStone
 {
-  class CreateMeasureTracker : public IPointerTracker
+  class CreateMeasureTracker : public IFlexiblePointerTracker
   {
   public:
-    virtual void Update(const PointerEvent& e) ORTHANC_OVERRIDE;
-    virtual void Release() ORTHANC_OVERRIDE;
+    virtual void Cancel() ORTHANC_OVERRIDE;
+    virtual bool IsActive() const ORTHANC_OVERRIDE;
   protected:
     CreateMeasureTracker(
       Scene2D&                        scene,
@@ -43,32 +44,14 @@
 
     ~CreateMeasureTracker();
   
+  protected:
+    CreateMeasureCommandPtr         command_;
+    Scene2D&                        scene_;
+    bool                            active_;
   private:
-    Scene2D&                        scene_;
     std::vector<TrackerCommandPtr>& undoStack_;
     std::vector<MeasureToolPtr>&    measureTools_;
     bool                            commitResult_;
-
-  protected:
-    CreateMeasureCommandPtr         command_;
-  };
-
-  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(
-      Scene2D&                        scene,
-      std::vector<TrackerCommandPtr>& undoStack,
-      std::vector<MeasureToolPtr>&    measureTools,
-      const PointerEvent&             e);
-
-    ~CreateLineMeasureTracker();
   };
 }
+
--- a/Samples/Sdl/CMakeLists.txt	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Mon May 13 15:22:08 2019 +0200
@@ -69,13 +69,51 @@
   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")
+
+if (MSVC AND MSVC_VERSION GREATER 1700)
+  LIST(APPEND TRACKERSAMPLE_SOURCE "cpp.hint")
+endif()
 
 add_executable(TrackerSample
   ${TRACKERSAMPLE_SOURCE}
--- a/Samples/Sdl/TrackerSample.cpp	Fri May 10 17:34:48 2019 +0200
+++ b/Samples/Sdl/TrackerSample.cpp	Mon May 13 15:22:08 2019 +0200
@@ -18,24 +18,18 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#include "TrackerSampleApp.h"
 
  // From Stone
 #include "../../Applications/Sdl/SdlOpenGLWindow.h"
 #include "../../Framework/Scene2D/CairoCompositor.h"
 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
 #include "../../Framework/Scene2D/OpenGLCompositor.h"
-#include "../../Framework/Scene2D/PanSceneTracker.h"
-#include "../../Framework/Scene2D/RotateSceneTracker.h"
-#include "../../Framework/Scene2D/Scene2D.h"
-#include "../../Framework/Scene2D/ZoomSceneTracker.h"
 #include "../../Framework/StoneInitialization.h"
 
-// From Orthanc framework
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
-#include <Core/Images/Image.h>
-#include <Core/Images/ImageProcessing.h>
-#include <Core/Images/PngWriter.h>
+
 
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
@@ -43,11 +37,6 @@
 #include <SDL.h>
 #include <stdio.h>
 
-
-// to be moved into Stone
-#include "../Common/MeasureTrackers.h"
-#include "../Common/MeasureCommands.h"
-
 /*
 TODO:
 
@@ -63,369 +52,6 @@
 using namespace Orthanc;
 using namespace OrthancStone;
 
-namespace OrthancStone
-{
-  enum GuiTool
-  {
-    GuiTool_Rotate = 0,
-    GuiTool_Pan,
-    GuiTool_Zoom,
-    GuiTool_LineMeasure,
-    GuiTool_CircleMeasure,
-    GuiTool_AngleMeasure,
-    GuiTool_EllipseMeasure,
-    GuiTool_LAST
-  };
-
-  const char* MeasureToolToString(size_t i)
-  {
-    static const char* descs[] = {
-      "GuiTool_Rotate",
-      "GuiTool_Pan",
-      "GuiTool_Zoom",
-      "GuiTool_LineMeasure",
-      "GuiTool_CircleMeasure",
-      "GuiTool_AngleMeasure",
-      "GuiTool_EllipseMeasure",
-      "GuiTool_LAST"
-    };
-    if (i >= GuiTool_LAST)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index");
-    }
-    return descs[i];
-  }
-}
-
-class TrackerSampleApp
-{
-public:
-  // 12 because.
-  TrackerSampleApp() : currentTool_(GuiTool_Rotate)
-  {
-    TEXTURE_2x2_1_ZINDEX  = 1;
-    TEXTURE_1x1_ZINDEX    = 2;
-    TEXTURE_2x2_2_ZINDEX  = 3;
-    LINESET_1_ZINDEX      = 4;
-    LINESET_2_ZINDEX      = 5;
-    INFOTEXT_LAYER_ZINDEX = 6;
-  }
-  void PrepareScene();
-  void Run();
-
-private:
-  Scene2D& GetScene()
-  {
-    return scene_;
-  }
-
-  void SelectNextTool()
-  {
-    currentTool_ = static_cast<GuiTool>(currentTool_ + 1);
-    if (currentTool_ == GuiTool_LAST)
-      currentTool_ = static_cast<GuiTool>(0);;
-    printf("Current tool is now: %s\n", MeasureToolToString(currentTool_));
-  }
-
-  void HandleApplicationEvent(
-    const OpenGLCompositor& compositor,
-    const SDL_Event& event,
-    std::auto_ptr<IPointerTracker>& activeTracker);
-
-  IPointerTracker* TrackerHitTest(const PointerEvent& e);
-
-  IPointerTracker* CreateSuitableTracker(
-    const SDL_Event& event,
-    const PointerEvent& e,
-    const OpenGLCompositor& compositor);
-  
-  void TakeScreenshot(
-    const std::string& target,
-    unsigned int canvasWidth,
-    unsigned int canvasHeight);
-
-  /**
-    This adds the command at the top of the undo stack  
-  */
-  void Commit(TrackerCommandPtr cmd);
-  void Undo();
-  void Redo();
-  
-private:
-  static const unsigned int FONT_SIZE = 32;
-
-  std::vector<TrackerCommandPtr> undoStack_;
-  
-  // we store the measure tools here so that they don't get deleted
-  std::vector<MeasureToolPtr> measureTools_;
-
-  //static const int LAYER_POSITION = 150;
-#if 0
-  int TEXTURE_2x2_1_ZINDEX = 12;
-  int TEXTURE_1x1_ZINDEX = 13;
-  int TEXTURE_2x2_2_ZINDEX = 14;
-  int LINESET_1_ZINDEX = 50;
-  int LINESET_2_ZINDEX = 100;
-  int INFOTEXT_LAYER_ZINDEX = 150;
-#else
-  int TEXTURE_2x2_1_ZINDEX;
-  int TEXTURE_1x1_ZINDEX;
-  int TEXTURE_2x2_2_ZINDEX;
-  int LINESET_1_ZINDEX;
-  int LINESET_2_ZINDEX;
-  int INFOTEXT_LAYER_ZINDEX;
-#endif
-  Scene2D scene_;
-  GuiTool currentTool_;
-};
-
-
-void TrackerSampleApp::PrepareScene()
-{
-  // Texture of 2x2 size
-  {
-    Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
-
-    uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0));
-    p[0] = 255;
-    p[1] = 0;
-    p[2] = 0;
-
-    p[3] = 0;
-    p[4] = 255;
-    p[5] = 0;
-
-    p = reinterpret_cast<uint8_t*>(i.GetRow(1));
-    p[0] = 0;
-    p[1] = 0;
-    p[2] = 255;
-
-    p[3] = 255;
-    p[4] = 0;
-    p[5] = 0;
-
-    scene_.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());
-  }
-
-  // Texture of 1x1 size
-  {
-    Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false);
-
-    uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0));
-    p[0] = 255;
-    p[1] = 0;
-    p[2] = 0;
-
-    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());
-  }
-
-  // Some lines
-  {
-    std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
-
-    layer->SetThickness(1);
-
-    PolylineSceneLayer::Chain chain;
-    chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5));
-    chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5));
-    chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5));
-    chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5));
-    layer->AddChain(chain, true);
-
-    chain.clear();
-    chain.push_back(ScenePoint2D(-5, -5));
-    chain.push_back(ScenePoint2D(5, -5));
-    chain.push_back(ScenePoint2D(5, 5));
-    chain.push_back(ScenePoint2D(-5, 5));
-    layer->AddChain(chain, true);
-
-    double dy = 1.01;
-    chain.clear();
-    chain.push_back(ScenePoint2D(-4, -4));
-    chain.push_back(ScenePoint2D(4, -4 + dy));
-    chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy));
-    chain.push_back(ScenePoint2D(4, 2));
-    layer->AddChain(chain, false);
-
-    layer->SetColor(0, 255, 255);
-    scene_.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());
-  }
-}
-
-
-void TrackerSampleApp::TakeScreenshot(const std::string& target,
-  unsigned int canvasWidth,
-  unsigned int canvasHeight)
-{
-  // Take a screenshot, then save it as PNG file
-  CairoCompositor compositor(scene_, canvasWidth, canvasHeight);
-  compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1);
-  compositor.Refresh();
-
-  Orthanc::ImageAccessor canvas;
-  compositor.GetCanvas().GetReadOnlyAccessor(canvas);
-
-  Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
-  Orthanc::ImageProcessing::Convert(png, canvas);
-
-  Orthanc::PngWriter writer;
-  writer.WriteToFile(target, png);
-}
-
-
-IPointerTracker* TrackerSampleApp::TrackerHitTest(const PointerEvent& e)
-{
-  // std::vector<MeasureToolPtr> measureTools_;
-  return NULL;
-}
-
-IPointerTracker* TrackerSampleApp::CreateSuitableTracker(
-  const SDL_Event& event, 
-  const PointerEvent& e, 
-  const OpenGLCompositor& compositor)
-{
-  switch (event.button.button)
-  {
-  case SDL_BUTTON_MIDDLE:
-    return new PanSceneTracker(scene_, e);
-
-  case SDL_BUTTON_RIGHT:
-    return new ZoomSceneTracker(
-      scene_, e, compositor.GetCanvasHeight());
-
-  case SDL_BUTTON_LEFT:
-  {
-    // TODO: we need to iterate on the set of measuring tool and perform
-    // a hit test to check if a tracker needs to be created for edition.
-    // Otherwise, depending upon the active tool, we might want to create
-    // a "measuring tool creation" tracker
-
-    // TODO: if there are conflicts, we should prefer a tracker that 
-    // pertains to the type of measuring tool currently selected (TBD?)
-    IPointerTracker* hitTestTracker = TrackerHitTest(e);
-    
-    if (hitTestTracker != NULL)
-    {
-      return hitTestTracker;
-    }
-    else
-    { 
-      switch (currentTool_)
-      {
-      case GuiTool_Rotate:
-        return new RotateSceneTracker(scene_, e);
-      case GuiTool_LineMeasure:
-        return new CreateLineMeasureTracker(
-          scene_, undoStack_, measureTools_, e);
-        //case GuiTool_AngleMeasure:
-        //  return new AngleMeasureTracker(scene_, measureTools_, undoStack_, e);
-        //case GuiTool_CircleMeasure:
-        //  return new CircleMeasureTracker(scene_, measureTools_, undoStack_, e);
-        //case GuiTool_EllipseMeasure:
-        //  return new EllipseMeasureTracker(scene_, measureTools_, undoStack_, e);
-      default:
-        throw OrthancException(ErrorCode_InternalError, "Wrong tool!");
-      }
-    }
-  }
-  default:
-    return NULL;
-  }
-}
-
-void TrackerSampleApp::HandleApplicationEvent(
-  const OpenGLCompositor& compositor,
-  const SDL_Event& event,
-  std::auto_ptr<IPointerTracker>& activeTracker)
-{
-  if (event.type == SDL_MOUSEMOTION)
-  {
-    int scancodeCount = 0;
-    const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
-
-    if (activeTracker.get() == NULL &&
-      SDL_SCANCODE_LCTRL < scancodeCount &&
-      keyboardState[SDL_SCANCODE_LCTRL])
-    {
-      // The "left-ctrl" key is down, while no tracker is present
-
-      PointerEvent e;
-      e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-
-      ScenePoint2D p = e.GetMainPosition().Apply(scene_.GetCanvasToSceneTransform());
-
-      char buf[64];
-      sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY());
-
-      if (scene_.HasLayer(INFOTEXT_LAYER_ZINDEX))
-      {
-        TextSceneLayer& layer =
-          dynamic_cast<TextSceneLayer&>(scene_.GetLayer(INFOTEXT_LAYER_ZINDEX));
-        layer.SetText(buf);
-        layer.SetPosition(p.GetX(), p.GetY());
-      }
-      else
-      {
-        std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
-        layer->SetColor(0, 255, 0);
-        layer->SetText(buf);
-        layer->SetBorder(20);
-        layer->SetAnchor(BitmapAnchor_BottomCenter);
-        layer->SetPosition(p.GetX(), p.GetY());
-        scene_.SetLayer(INFOTEXT_LAYER_ZINDEX, layer.release());
-      }
-    }
-    else
-    {
-      scene_.DeleteLayer(INFOTEXT_LAYER_ZINDEX);
-    }
-  }
-  else if (event.type == SDL_MOUSEBUTTONDOWN)
-  {
-    PointerEvent e;
-    e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-
-    activeTracker.reset(CreateSuitableTracker(event, e, compositor));
-  }
-  else if (event.type == SDL_KEYDOWN &&
-    event.key.repeat == 0 /* Ignore key bounce */)
-  {
-    switch (event.key.keysym.sym)
-    {
-    case SDLK_s:
-      scene_.FitContent(compositor.GetCanvasWidth(),
-        compositor.GetCanvasHeight());
-      break;
-
-    case SDLK_c:
-      TakeScreenshot(
-        "screenshot.png",
-        compositor.GetCanvasWidth(),
-        compositor.GetCanvasHeight());
-      break;
-
-    default:
-      break;
-    }
-  }
-}
-
 
 static void GLAPIENTRY
 OpenGLMessageCallback(GLenum source,
@@ -446,23 +72,19 @@
 
 bool g_stopApplication = false;
 
-void TrackerSampleApp::Run()
+void Run(TrackerSampleApp* app)
 {
   SdlOpenGLWindow window("Hello", 1024, 768);
 
-  scene_.FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
+  app->GetScene().FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
 
   glEnable(GL_DEBUG_OUTPUT);
   glDebugMessageCallback(OpenGLMessageCallback, 0);
 
-  OpenGLCompositor compositor(window, scene_);
+  OpenGLCompositor compositor(window, app->GetScene());
   compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT,
     FONT_SIZE, Orthanc::Encoding_Latin1);
 
-  // this will either be empty or contain the current tracker, if any
-  std::auto_ptr<IPointerTracker>  tracker;
-
- 
   while (!g_stopApplication)
   {
     compositor.Refresh();
@@ -475,29 +97,10 @@
         g_stopApplication = true;
         break;
       }
-      else if (event.type == SDL_MOUSEMOTION)
-      {
-        if (tracker.get() != NULL)
-        {
-          PointerEvent e;
-          e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-          LOG(TRACE) << "event.button.x = " << event.button.x << "     " <<
-            "event.button.y = " << event.button.y;
-          tracker->Update(e);
-        }
-      }
-      else if (event.type == SDL_MOUSEBUTTONUP)
-      {
-        if (tracker.get() != NULL)
-        {
-          tracker->Release();
-          tracker.reset(NULL);
-        }
-      }
       else if (event.type == SDL_WINDOWEVENT &&
         event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
       {
-        tracker.reset(NULL);
+        app->DisableTracker(); // was: tracker.reset(NULL);
         compositor.UpdateSize();
       }
       else if (event.type == SDL_KEYDOWN &&
@@ -512,16 +115,11 @@
         case SDLK_q:
           g_stopApplication = true;
           break;
-
-        case SDLK_t:
-          SelectNextTool();
-          break;
-
         default:
           break;
         }
       }
-      HandleApplicationEvent(compositor, event, tracker);
+      app->HandleApplicationEvent(compositor, event);
     }
     SDL_Delay(1);
   }
@@ -537,12 +135,14 @@
 {
   StoneInitialize();
   Orthanc::Logging::EnableInfoLevel(true);
+  Orthanc::Logging::EnableTraceLevel(true);
+
 
   try
   {
     TrackerSampleApp app;
     app.PrepareScene();
-    app.Run();
+    Run(&app);
   }
   catch (Orthanc::OrthancException& e)
   {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,421 @@
+/**
+ * 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 "TrackerSampleApp.h"
+
+#include "../Common/CreateLineMeasureTracker.h"
+
+#include "../../Applications/Sdl/SdlOpenGLWindow.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"
+
+ // From Orthanc framework
+#include <Core/Logging.h>
+#include <Core/OrthancException.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/PngWriter.h>
+
+#include <SDL.h>
+#include <stdio.h>
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+  const char* MeasureToolToString(size_t i)
+  {
+    static const char* descs[] = {
+      "GuiTool_Rotate",
+      "GuiTool_Pan",
+      "GuiTool_Zoom",
+      "GuiTool_LineMeasure",
+      "GuiTool_CircleMeasure",
+      "GuiTool_AngleMeasure",
+      "GuiTool_EllipseMeasure",
+      "GuiTool_LAST"
+    };
+    if (i >= GuiTool_LAST)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, "Wrong tool index");
+    }
+    return descs[i];
+  }
+
+  Scene2D& TrackerSampleApp::GetScene()
+  {
+    return scene_;
+  }
+
+  void TrackerSampleApp::SelectNextTool()
+  {
+    currentTool_ = static_cast<GuiTool>(currentTool_ + 1);
+    if (currentTool_ == GuiTool_LAST)
+      currentTool_ = static_cast<GuiTool>(0);;
+    printf("Current tool is now: %s\n", MeasureToolToString(currentTool_));
+  }
+
+  void TrackerSampleApp::DisplayInfoText(const PointerEvent& e)
+  {
+    ScenePoint2D p = e.GetMainPosition().Apply(scene_.GetCanvasToSceneTransform());
+
+    char buf[64];
+    sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY());
+
+    if (scene_.HasLayer(INFOTEXT_LAYER_ZINDEX))
+    {
+      TextSceneLayer& layer =
+        dynamic_cast<TextSceneLayer&>(scene_.GetLayer(INFOTEXT_LAYER_ZINDEX));
+      layer.SetText(buf);
+      layer.SetPosition(p.GetX(), p.GetY());
+    }
+    else
+    {
+      std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
+      layer->SetColor(0, 255, 0);
+      layer->SetText(buf);
+      layer->SetBorder(20);
+      layer->SetAnchor(BitmapAnchor_BottomCenter);
+      layer->SetPosition(p.GetX(), p.GetY());
+      scene_.SetLayer(INFOTEXT_LAYER_ZINDEX, layer.release());
+    }
+  }
+
+  void TrackerSampleApp::HideInfoText()
+  {
+    scene_.DeleteLayer(INFOTEXT_LAYER_ZINDEX);
+  }
+
+  void TrackerSampleApp::HandleApplicationEvent(
+    const OpenGLCompositor & compositor,
+    const SDL_Event & event)
+  {
+    if (event.type == SDL_MOUSEMOTION)
+    {
+      int scancodeCount = 0;
+      const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
+
+      if (activeTracker_.get() == NULL &&
+        SDL_SCANCODE_LCTRL < scancodeCount &&
+        keyboardState[SDL_SCANCODE_LCTRL])
+      {
+        // The "left-ctrl" key is down, while no tracker is present
+        // Let's display the info text
+        PointerEvent e;
+        e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+        DisplayInfoText(e);
+      }
+      else
+      {
+        HideInfoText();
+        //LOG(TRACE) << "(event.type == SDL_MOUSEMOTION)";
+        if (activeTracker_.get() != NULL)
+        {
+          //LOG(TRACE) << "(activeTracker_.get() != NULL)";
+          PointerEvent e;
+          e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+          //LOG(TRACE) << "event.button.x = " << event.button.x << "     " <<
+          //  "event.button.y = " << event.button.y;
+          //LOG(TRACE) << "activeTracker_->PointerMove(e); " <<
+          //  e.GetMainPosition().GetX() << " " << e.GetMainPosition().GetY();
+          activeTracker_->PointerMove(e);
+          if (!activeTracker_->IsActive())
+            activeTracker_ = NULL;
+        }
+      }
+    }
+    else if (event.type == SDL_MOUSEBUTTONUP)
+    {
+      if (activeTracker_)
+      {
+        PointerEvent e;
+        e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+        activeTracker_->PointerUp(e);
+        if (!activeTracker_->IsActive())
+          activeTracker_ = NULL;
+      }
+    }
+    else if (event.type == SDL_MOUSEBUTTONDOWN)
+    {
+      PointerEvent e;
+      e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+      if (activeTracker_)
+      {
+        activeTracker_->PointerDown(e);
+        if (!activeTracker_->IsActive())
+          activeTracker_ = NULL;
+      }
+      else
+      {
+        // we ATTEMPT to create a tracker if need be
+        activeTracker_ = CreateSuitableTracker(event, e, compositor);
+      }
+    }
+    else if (event.type == SDL_KEYDOWN &&
+      event.key.repeat == 0 /* Ignore key bounce */)
+    {
+      switch (event.key.keysym.sym)
+      {
+      case SDLK_ESCAPE:
+        if (activeTracker_)
+        {
+          activeTracker_->Cancel();
+          if (!activeTracker_->IsActive())
+            activeTracker_ = NULL;
+        }
+        break;
+
+      case SDLK_t:
+        if (!activeTracker_)
+          SelectNextTool();
+        else
+        {
+          LOG(WARNING) << "You cannot change the active tool when an interaction"
+            " is taking place";
+        }
+        break;
+
+      case SDLK_s:
+        scene_.FitContent(compositor.GetCanvasWidth(),
+          compositor.GetCanvasHeight());
+        break;
+
+      case SDLK_c:
+        TakeScreenshot(
+          "screenshot.png",
+          compositor.GetCanvasWidth(),
+          compositor.GetCanvasHeight());
+        break;
+
+      default:
+        break;
+      }
+    }
+  }
+
+  FlexiblePointerTrackerPtr TrackerSampleApp::CreateSuitableTracker(
+    const SDL_Event & event,
+    const PointerEvent & e,
+    const OpenGLCompositor & compositor)
+  {
+    switch (event.button.button)
+    {
+    case SDL_BUTTON_MIDDLE:
+      return CreateSimpleTrackerAdapter(PointerTrackerPtr(
+        new PanSceneTracker(scene_, e)));
+
+    case SDL_BUTTON_RIGHT:
+      return CreateSimpleTrackerAdapter(PointerTrackerPtr(
+        new ZoomSceneTracker(scene_, e, compositor.GetCanvasHeight())));
+
+    case SDL_BUTTON_LEFT:
+    {
+      //LOG(TRACE) << "CreateSuitableTracker: case SDL_BUTTON_LEFT:";
+      // TODO: we need to iterate on the set of measuring tool and perform
+      // a hit test to check if a tracker needs to be created for edition.
+      // Otherwise, depending upon the active tool, we might want to create
+      // a "measuring tool creation" tracker
+
+      // TODO: if there are conflicts, we should prefer a tracker that 
+      // pertains to the type of measuring tool currently selected (TBD?)
+      FlexiblePointerTrackerPtr hitTestTracker = TrackerHitTest(e);
+
+      if (hitTestTracker != NULL)
+      {
+        //LOG(TRACE) << "hitTestTracker != NULL";
+        return hitTestTracker;
+      }
+      else
+      {
+        switch (currentTool_)
+        {
+        case GuiTool_Rotate:
+          //LOG(TRACE) << "Creating RotateSceneTracker";
+          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
+            new RotateSceneTracker(scene_, e)));
+        case GuiTool_LineMeasure:
+          return FlexiblePointerTrackerPtr(new CreateLineMeasureTracker(
+            scene_, undoStack_, measureTools_, e));
+        case GuiTool_Pan:
+          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
+            new PanSceneTracker(scene_, e)));
+        case GuiTool_Zoom:
+          return CreateSimpleTrackerAdapter(PointerTrackerPtr(
+            new ZoomSceneTracker(scene_, e, compositor.GetCanvasHeight())));
+        //case GuiTool_AngleMeasure:
+        //  return new AngleMeasureTracker(scene_, measureTools_, undoStack_, e);
+        //case GuiTool_CircleMeasure:
+        //  return new CircleMeasureTracker(scene_, measureTools_, undoStack_, e);
+        //case GuiTool_EllipseMeasure:
+        //  return new EllipseMeasureTracker(scene_, measureTools_, undoStack_, e);
+        case GuiTool_AngleMeasure:
+          LOG(ERROR) << "Not implemented yet!";
+          return NULL;
+        case GuiTool_CircleMeasure:
+          LOG(ERROR) << "Not implemented yet!";
+          return NULL;
+        case GuiTool_EllipseMeasure:
+          LOG(ERROR) << "Not implemented yet!";
+          return NULL;
+        default:
+          throw OrthancException(ErrorCode_InternalError, "Wrong tool!");
+        }
+      }
+    }
+    default:
+      return NULL;
+    }
+  }
+
+
+  void TrackerSampleApp::PrepareScene()
+  {
+    // Texture of 2x2 size
+    {
+      Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
+
+      uint8_t* p = reinterpret_cast<uint8_t*>(i.GetRow(0));
+      p[0] = 255;
+      p[1] = 0;
+      p[2] = 0;
+
+      p[3] = 0;
+      p[4] = 255;
+      p[5] = 0;
+
+      p = reinterpret_cast<uint8_t*>(i.GetRow(1));
+      p[0] = 0;
+      p[1] = 0;
+      p[2] = 255;
+
+      p[3] = 255;
+      p[4] = 0;
+      p[5] = 0;
+
+      scene_.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());
+    }
+
+    // Texture of 1x1 size
+    {
+      Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false);
+
+      uint8_t* p = reinterpret_cast<uint8_t*>(i.GetRow(0));
+      p[0] = 255;
+      p[1] = 0;
+      p[2] = 0;
+
+      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());
+    }
+
+    // Some lines
+    {
+      std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
+
+      layer->SetThickness(1);
+
+      PolylineSceneLayer::Chain chain;
+      chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5));
+      chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5));
+      chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5));
+      chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5));
+      layer->AddChain(chain, true);
+
+      chain.clear();
+      chain.push_back(ScenePoint2D(-5, -5));
+      chain.push_back(ScenePoint2D(5, -5));
+      chain.push_back(ScenePoint2D(5, 5));
+      chain.push_back(ScenePoint2D(-5, 5));
+      layer->AddChain(chain, true);
+
+      double dy = 1.01;
+      chain.clear();
+      chain.push_back(ScenePoint2D(-4, -4));
+      chain.push_back(ScenePoint2D(4, -4 + dy));
+      chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy));
+      chain.push_back(ScenePoint2D(4, 2));
+      layer->AddChain(chain, false);
+
+      layer->SetColor(0, 255, 255);
+      scene_.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());
+    }
+  }
+
+
+  void TrackerSampleApp::DisableTracker()
+  {
+    if (activeTracker_)
+    {
+      activeTracker_->Cancel();
+      activeTracker_ = NULL;
+    }
+  }
+
+  void TrackerSampleApp::TakeScreenshot(const std::string& target,
+    unsigned int canvasWidth,
+    unsigned int canvasHeight)
+  {
+    // Take a screenshot, then save it as PNG file
+    CairoCompositor compositor(scene_, canvasWidth, canvasHeight);
+    compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1);
+    compositor.Refresh();
+
+    Orthanc::ImageAccessor canvas;
+    compositor.GetCanvas().GetReadOnlyAccessor(canvas);
+
+    Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
+    Orthanc::ImageProcessing::Convert(png, canvas);
+
+    Orthanc::PngWriter writer;
+    writer.WriteToFile(target, png);
+  }
+
+
+  FlexiblePointerTrackerPtr TrackerSampleApp::TrackerHitTest(const PointerEvent & e)
+  {
+    // std::vector<MeasureToolPtr> measureTools_;
+    return nullptr;
+  }
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Sdl/TrackerSampleApp.h	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,136 @@
+/**
+ * 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/OpenGLCompositor.h>
+
+#include "../Common/IFlexiblePointerTracker.h"
+#include "../Common/MeasureTools.h"
+
+#include <SDL.h>
+
+#include <boost/make_shared.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  class TrackerCommand;
+  typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr;
+
+  enum GuiTool
+  {
+    GuiTool_Rotate = 0,
+    GuiTool_Pan,
+    GuiTool_Zoom,
+    GuiTool_LineMeasure,
+    GuiTool_CircleMeasure,
+    GuiTool_AngleMeasure,
+    GuiTool_EllipseMeasure,
+    GuiTool_LAST
+  };
+
+  const char* MeasureToolToString(size_t i);
+
+  static const unsigned int FONT_SIZE = 32;
+
+  class Scene2D;
+
+  class TrackerSampleApp
+  {
+  public:
+    // 12 because.
+    TrackerSampleApp() : currentTool_(GuiTool_Rotate)
+    {
+      TEXTURE_2x2_1_ZINDEX = 1;
+      TEXTURE_1x1_ZINDEX = 2;
+      TEXTURE_2x2_2_ZINDEX = 3;
+      LINESET_1_ZINDEX = 4;
+      LINESET_2_ZINDEX = 5;
+      INFOTEXT_LAYER_ZINDEX = 6;
+    }
+    void PrepareScene();
+
+    void DisableTracker();
+
+    Scene2D& GetScene();
+
+    void HandleApplicationEvent(
+      const OpenGLCompositor& compositor,
+      const SDL_Event& event);
+
+  private:
+    void SelectNextTool();
+
+
+    FlexiblePointerTrackerPtr TrackerHitTest(const PointerEvent& e);
+
+    FlexiblePointerTrackerPtr CreateSuitableTracker(
+      const SDL_Event& event,
+      const PointerEvent& e,
+      const OpenGLCompositor& compositor);
+
+    void TakeScreenshot(
+      const std::string& target,
+      unsigned int canvasWidth,
+      unsigned int canvasHeight);
+
+    /**
+      This adds the command at the top of the undo stack
+    */
+    void Commit(TrackerCommandPtr cmd);
+    void Undo();
+    void Redo();
+
+  private:
+    void DisplayInfoText(const PointerEvent& e);
+    void HideInfoText();
+
+  private:
+    /**
+    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_;
+
+    FlexiblePointerTrackerPtr activeTracker_;
+    std::vector<TrackerCommandPtr> undoStack_;
+
+    // we store the measure tools here so that they don't get deleted
+    std::vector<MeasureToolPtr> measureTools_;
+
+    //static const int LAYER_POSITION = 150;
+#if 0
+    int TEXTURE_2x2_1_ZINDEX = 12;
+    int TEXTURE_1x1_ZINDEX = 13;
+    int TEXTURE_2x2_2_ZINDEX = 14;
+    int LINESET_1_ZINDEX = 50;
+    int LINESET_2_ZINDEX = 100;
+    int INFOTEXT_LAYER_ZINDEX = 150;
+#else
+    int TEXTURE_2x2_1_ZINDEX;
+    int TEXTURE_1x1_ZINDEX;
+    int TEXTURE_2x2_2_ZINDEX;
+    int LINESET_1_ZINDEX;
+    int LINESET_2_ZINDEX;
+    int INFOTEXT_LAYER_ZINDEX;
+#endif
+    GuiTool currentTool_;
+  };
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Sdl/cpp.hint	Mon May 13 15:22:08 2019 +0200
@@ -0,0 +1,1 @@
+#define ORTHANC_OVERRIDE