changeset 644:f939f449482c

Ongoing tracker work.
author Benjamin Golinvaux <bgo@osimis.io>
date Fri, 10 May 2019 16:15:55 +0200
parents 6a144a45b2d8
children 1e9ed656318e
files Framework/Scene2D/IPointerTracker.h Framework/Scene2D/Scene2D.cpp README.md Samples/Common/CreateAngleMeasureTracker.cpp Samples/Common/CreateAngleMeasureTracker.h Samples/Common/CreateCircleMeasureTracker.cpp Samples/Common/CreateCircleMeasureTracker.h Samples/Common/CreateLineMeasureTracker.cpp Samples/Common/CreateLineMeasureTracker.h Samples/Common/CreateMeasureTracker.cpp Samples/Common/CreateMeasureTracker.h Samples/Common/CreateSimpleTrackerAdapter.cpp Samples/Common/EditAngleMeasureTracker.cpp Samples/Common/EditAngleMeasureTracker.h Samples/Common/EditCircleMeasureTracker.cpp Samples/Common/EditCircleMeasureTracker.h Samples/Common/EditLineMeasureTracker.cpp Samples/Common/EditLineMeasureTracker.h Samples/Common/IFlexiblePointerTracker.h Samples/Common/MeasureCommands.cpp Samples/Common/MeasureCommands.h Samples/Common/MeasureTools.cpp Samples/Common/MeasureTools.h Samples/Common/MeasureTrackers.cpp Samples/Common/MeasureTrackers.h Samples/Sdl/CMakeLists.txt Samples/Sdl/TrackerSample.cpp Samples/Sdl/TrackerSampleApp.cpp Samples/Sdl/TrackerSampleApp.h Samples/Sdl/cpp.hint
diffstat 30 files changed, 1351 insertions(+), 505 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Scene2D/IPointerTracker.h	Thu May 09 14:54:12 2019 +0200
+++ b/Framework/Scene2D/IPointerTracker.h	Fri May 10 16:15:55 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/Framework/Scene2D/Scene2D.cpp	Thu May 09 14:54:12 2019 +0200
+++ b/Framework/Scene2D/Scene2D.cpp	Fri May 10 16:15:55 2019 +0200
@@ -102,8 +102,8 @@
   void Scene2D::SetLayer(int depth,
                          ISceneLayer* layer)  // Takes ownership
   {
-    LOG(INFO) << "SetLayer(" << depth << ", " <<
-      reinterpret_cast<intptr_t>(layer) << ")";
+    //LOG(INFO) << "SetLayer(" << depth << ", " <<
+    //  reinterpret_cast<intptr_t>(layer) << ")";
     std::auto_ptr<Item> item(new Item(layer, layerCounter_++));
 
     if (layer == NULL)
--- a/README.md	Thu May 09 14:54:12 2019 +0200
+++ b/README.md	Fri May 10 16:15:55 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/CreateAngleMeasureTracker.cpp	Fri May 10 16:15:55 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/CreateAngleMeasureTracker.h	Fri May 10 16:15:55 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/CreateCircleMeasureTracker.cpp	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 2019 +0200
@@ -0,0 +1,83 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-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);
+    command_->Update(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";
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/CreateLineMeasureTracker.h	Fri May 10 16:15:55 2019 +0200
@@ -0,0 +1,49 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#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;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Samples/Common/CreateMeasureTracker.cpp	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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);
+}
+
--- a/Samples/Common/MeasureCommands.cpp	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Common/MeasureCommands.cpp	Fri May 10 16:15:55 2019 +0200
@@ -62,4 +62,5 @@
     measureTool_->SetEnd(scenePos);
   }
 
+
 }
--- a/Samples/Common/MeasureCommands.h	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Common/MeasureCommands.h	Fri May 10 16:15:55 2019 +0200
@@ -71,7 +71,7 @@
   class CreateLineMeasureCommand : public CreateMeasureCommand
   {
   public:
-    CreateLineMeasureCommand::CreateLineMeasureCommand(
+    CreateLineMeasureCommand(
       Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
     
     void Update(ScenePoint2D scenePos) ORTHANC_OVERRIDE;
--- a/Samples/Common/MeasureTools.cpp	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Common/MeasureTools.cpp	Fri May 10 16:15:55 2019 +0200
@@ -20,6 +20,7 @@
 
 #include <Core/Logging.h>
 #include "MeasureTools.h"
+#include <boost/math/constants/constants.hpp>
 
 namespace OrthancStone
 {
@@ -92,6 +93,107 @@
     return concreteLayer;
   }
 
+  namespace
+  {
+    /**
+    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)
+    {
+      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 AddCircle(PolylineSceneLayer::Chain& chain,
+      const Scene2D&      scene,
+      const ScenePoint2D& centerS,
+      const double&       radiusS)
+    {
+      chain.clear();
+      chain.reserve(4);
+      //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.
+      int lineCount = 50;
+      
+      double angleIncr = (2.0 * boost::math::constants::pi<double>())
+        / static_cast<double>(lineCount);
+
+      double theta = 0;
+      for (int i = 0; i < lineCount; ++i)
+      {
+        double offsetX = radiusS * cos(theta);
+        double offsetY = radiusS * sin(theta);
+        double pointX = centerS.GetX() + offsetX;
+        double pointY = centerS.GetY() + offsetY;
+        chain.push_back(ScenePoint2D(pointX, pointY));
+        theta += angleIncr;
+      }
+    }
+
+#if 0
+    void 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
+
+
+  }
+
+
   void LineMeasureTool::RefreshScene()
   {
     if (IsEnabled())
@@ -103,13 +205,13 @@
         assert(textZIndex_ == -1);
         {
           polylineZIndex_ = GetScene().GetMaxDepth() + 100;
-          LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
+          //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_;
+          //LOG(INFO) << "set textZIndex_ to: " << textZIndex_;
           std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
           GetScene().SetLayer(textZIndex_, layer.release());
         }
@@ -136,32 +238,44 @@
 
         // 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);
+          //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 startLT = startLTC.Apply(GetScene().GetCanvasToSceneTransform());
-          ScenePoint2D startRT = startRTC.Apply(GetScene().GetCanvasToSceneTransform());
-          ScenePoint2D startRB = startRBC.Apply(GetScene().GetCanvasToSceneTransform());
-          ScenePoint2D startLB = startLBC.Apply(GetScene().GetCanvasToSceneTransform());
+          //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);
 
-          PolylineSceneLayer::Chain chain;
-          chain.push_back(startLT);
-          chain.push_back(startRT);
-          chain.push_back(startRB);
-          chain.push_back(startLB);
-          polylineLayer->AddChain(chain, true);
+          //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
--- a/Samples/Common/MeasureTools.h	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Common/MeasureTools.h	Fri May 10 16:15:55 2019 +0200
@@ -25,6 +25,7 @@
 #include <Framework/Scene2D/TextSceneLayer.h>
 
 #include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
 
 #include <vector>
 #include <cmath>
@@ -50,7 +51,7 @@
     void Disable();
 
   protected:
-    MeasureTool(Scene2D& scene) 
+    MeasureTool(Scene2D& scene)
       : scene_(scene)
       , enabled_(true)
     {
@@ -65,7 +66,6 @@
     */
     virtual void RefreshScene() = 0;
 
-
     Scene2D& GetScene()
     {
       return scene_;
--- a/Samples/Common/MeasureTrackers.cpp	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Common/MeasureTrackers.cpp	Fri May 10 16:15:55 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
@@ -49,44 +61,7 @@
       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	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Common/MeasureTrackers.h	Fri May 10 16:15:55 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:
+    Scene2D&                        scene_;
+    CreateMeasureCommandPtr         command_;
+    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	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Fri May 10 16:15:55 2019 +0200
@@ -69,13 +69,42 @@
   LIST(APPEND TRACKERSAMPLE_SOURCE "../../../SDL-Console/SDL_Console.h")
 endif()
 
+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/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/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	Thu May 09 14:54:12 2019 +0200
+++ b/Samples/Sdl/TrackerSample.cpp	Fri May 10 16:15:55 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* TrackerSampleApp::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 nullptr;
-}
-
-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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 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	Fri May 10 16:15:55 2019 +0200
@@ -0,0 +1,1 @@
+#define ORTHANC_OVERRIDE