changeset 758:8c97c381f242

merge
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 May 2019 17:05:38 +0200
parents f7c236894c1a (current diff) 46403ab629f6 (diff)
children 774681b2c77c
files
diffstat 18 files changed, 558 insertions(+), 343 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed May 22 17:05:14 2019 +0200
+++ b/.hgignore	Wed May 22 17:05:38 2019 +0200
@@ -34,3 +34,6 @@
 Samples/Sdl/ThirdPartyDownloads/
 Samples/Sdl/CMakeLists.txt.orig
 
+Samples/WebAssembly/build/
+Samples/WebAssembly/ThirdPartyDownloads/
+Samples/WebAssembly/installDir/
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Wed May 22 17:05:38 2019 +0200
@@ -20,15 +20,33 @@
 
 #include "AngleMeasureTool.h"
 #include "MeasureToolsToolbox.h"
+#include "LayerHolder.h"
 
 #include <Core/Logging.h>
 
 #include <boost/math/constants/constants.hpp>
+#include <boost/make_shared.hpp>
 
-extern void TrackerSample_SetInfoDisplayMessage(std::string key, std::string value);
+// <HACK>
+// REMOVE THIS
+#ifndef NDEBUG
+extern void 
+TrackerSample_SetInfoDisplayMessage(std::string key, std::string value);
+#endif
+// </HACK>
 
 namespace OrthancStone
 {
+  // the params in the LayerHolder ctor specify the number of polyline and text
+  // layers
+  AngleMeasureTool::AngleMeasureTool(
+    MessageBroker& broker, ViewportControllerWPtr controllerW)
+    : MeasureTool(broker, controllerW)
+    , layerHolder_(boost::make_shared<LayerHolder>(controllerW,1,5))
+  {
+
+  }
+
   AngleMeasureTool::~AngleMeasureTool()
   {
     // this measuring tool is a RABI for the corresponding visual layers
@@ -39,12 +57,9 @@
 
   void AngleMeasureTool::RemoveFromScene()
   {
-    if (layersCreated)
+    if (layerHolder_->AreLayersCreated() && IsSceneAlive())
     {
-      assert(GetScene()->HasLayer(polylineZIndex_));
-      assert(GetScene()->HasLayer(textBaseZIndex_));
-      GetScene()->DeleteLayer(polylineZIndex_);
-      GetScene()->DeleteLayer(textBaseZIndex_);
+      layerHolder_->DeleteLayers();
     }
   }
 
@@ -66,79 +81,25 @@
     RefreshScene();
   }
   
-  PolylineSceneLayer* AngleMeasureTool::GetPolylineLayer()
-  {
-    assert(GetScene()->HasLayer(polylineZIndex_));
-    ISceneLayer* layer = &(GetScene()->GetLayer(polylineZIndex_));
-    PolylineSceneLayer* concreteLayer = dynamic_cast<PolylineSceneLayer*>(layer);
-    assert(concreteLayer != NULL);
-    return concreteLayer;
-  }
-
   void AngleMeasureTool::RefreshScene()
   {
     if (IsSceneAlive())
     {
-
       if (IsEnabled())
       {
         // get the scaling factor 
         const double pixelToScene =
           GetScene()->GetCanvasToSceneTransform().ComputeZoom();
 
-        if (!layersCreated)
-        {
-          // Create the layers if need be
-
-          assert(textBaseZIndex_ == -1);
-          {
-            polylineZIndex_ = GetScene()->GetMaxDepth() + 100;
-            //LOG(INFO) << "set polylineZIndex_ to: " << polylineZIndex_;
-            std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
-            GetScene()->SetLayer(polylineZIndex_, layer.release());
+        layerHolder_->CreateLayersIfNeeded();
 
-          }
-          {
-            textBaseZIndex_ = GetScene()->GetMaxDepth() + 100;
-            // create the four text background layers
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_, layer.release());
-            }
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 1, layer.release());
-            }
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 2, layer.release());
-            }
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 3, layer.release());
-            }
-
-            // and the text layer itself
-            {
-              std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-              GetScene()->SetLayer(textBaseZIndex_ + 4, layer.release());
-            }
-
-          }
-          layersCreated = true;
-        }
-        else
         {
-          assert(GetScene()->HasLayer(polylineZIndex_));
-          assert(GetScene()->HasLayer(textBaseZIndex_));
-        }
-        {
-          // Fill the polyline layer with the measurement line
-
-          PolylineSceneLayer* polylineLayer = GetPolylineLayer();
+          // Fill the polyline layer with the measurement lines
+          PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0);
           polylineLayer->ClearAllChains();
           polylineLayer->SetColor(0, 183, 17);
 
+
           // sides
           {
             {
@@ -155,30 +116,29 @@
             }
           }
 
-          // handles
+          // Create the handles
           {
-            //void AddSquare(PolylineSceneLayer::Chain& chain,const Scene2D& scene,const ScenePoint2D& centerS,const double& sideLength)
-
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), side1End_, 10.0 * pixelToScene); //TODO: take DPI into account
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), side1End_, 10.0 * pixelToScene);
               polylineLayer->AddChain(chain, true);
             }
-
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), side2End_, 10.0 * pixelToScene); //TODO: take DPI into account
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), side2End_, 10.0 * pixelToScene); 
               polylineLayer->AddChain(chain, true);
             }
           }
 
-          // arc
+          // Create the arc
           {
             PolylineSceneLayer::Chain chain;
 
             const double ARC_RADIUS_CANVAS_COORD = 30.0;
-            AddShortestArc(chain, *GetScene(), side1End_, center_, side2End_,
-              ARC_RADIUS_CANVAS_COORD * pixelToScene);
+            AddShortestArc(chain, side1End_, center_, side2End_,
+                           ARC_RADIUS_CANVAS_COORD * pixelToScene);
             polylineLayer->AddChain(chain, false);
           }
         }
@@ -189,21 +149,16 @@
             side1End_.GetY() - center_.GetY(),
             side1End_.GetX() - center_.GetX());
 
-
           double p2cAngle = atan2(
             side2End_.GetY() - center_.GetY(),
             side2End_.GetX() - center_.GetX());
 
           double delta = NormalizeAngle(p2cAngle - p1cAngle);
-
-
           double theta = p1cAngle + delta / 2;
 
-
           const double TEXT_CENTER_DISTANCE_CANVAS_COORD = 90;
 
           double offsetX = TEXT_CENTER_DISTANCE_CANVAS_COORD * cos(theta);
-
           double offsetY = TEXT_CENTER_DISTANCE_CANVAS_COORD * sin(theta);
 
           double pointX = center_.GetX() + offsetX * pixelToScene;
@@ -216,7 +171,7 @@
           sprintf(buf, "%0.02f\xc2\xb0", angleDeg);
 
           SetTextLayerOutlineProperties(
-            *GetScene(), textBaseZIndex_, buf, ScenePoint2D(pointX, pointY));
+            GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY));
 
           // TODO:make it togglable
           bool enableInfoDisplay = false;
@@ -267,18 +222,11 @@
             TrackerSample_SetInfoDisplayMessage("angleDeg",
               boost::lexical_cast<std::string>(angleDeg));
           }
-
-
-
         }
       }
       else
       {
-        if (layersCreated)
-        {
-          RemoveFromScene();
-          layersCreated = false;
-        }
+        RemoveFromScene();
       }
     }
   }
--- a/Framework/Scene2DViewport/AngleMeasureTool.h	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Wed May 22 17:05:38 2019 +0200
@@ -21,6 +21,8 @@
 #pragma once
 
 #include "MeasureTools.h"
+
+#include "../Scene2DViewport/LayerHolder.h"
 #include "../Scene2D/Scene2D.h"
 #include "../Scene2D/ScenePoint2D.h"
 #include "../Scene2D/PolylineSceneLayer.h"
@@ -37,14 +39,7 @@
   class AngleMeasureTool : public MeasureTool
   {
   public:
-    AngleMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
-      : MeasureTool(broker, controllerW)
-      , layersCreated(false)
-      , polylineZIndex_(-1)
-      , textBaseZIndex_(-1)
-    {
-
-    }
+    AngleMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW);
 
     ~AngleMeasureTool();
 
@@ -53,23 +48,15 @@
     void SetSide2End(ScenePoint2D start);
 
   private:
-    PolylineSceneLayer* GetPolylineLayer();
-    
-    // 0 --> 3 are for the text background (outline)
-    // 4 is for the actual text
-    TextSceneLayer*     GetTextLayer(int index);
     virtual void        RefreshScene() ORTHANC_OVERRIDE;
     void                RemoveFromScene();
 
   private:
-    ScenePoint2D side1End_;
-    ScenePoint2D side2End_;
-    ScenePoint2D center_;
-    bool         layersCreated;
-    int          polylineZIndex_;
-    int          textBaseZIndex_;
+    ScenePoint2D    side1End_;
+    ScenePoint2D    side2End_;
+    ScenePoint2D    center_;
+    LayerHolderPtr  layerHolder_;
   };
-
 }
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/LayerHolder.cpp	Wed May 22 17:05:38 2019 +0200
@@ -0,0 +1,137 @@
+/**
+ * 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 "LayerHolder.h"
+#include "../Scene2D/TextSceneLayer.h"
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2DViewport/ViewportController.h"
+#include "../StoneException.h"
+
+using namespace Orthanc;
+
+namespace OrthancStone
+{
+  LayerHolder::LayerHolder(
+    ViewportControllerWPtr controllerW,
+    int                    polylineLayerCount,
+    int                    textLayerCount)
+    : textLayerCount_(textLayerCount)
+    , polylineLayerCount_(polylineLayerCount)
+    , controllerW_(controllerW)
+    , baseLayerIndex_(-1)
+  {
+
+  }
+
+  void LayerHolder::CreateLayers()
+  {
+    assert(baseLayerIndex_ == -1);
+
+    baseLayerIndex_ = GetScene()->GetMaxDepth() + 100;
+
+    for (int i = 0; i < polylineLayerCount_; ++i)
+    {
+      std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
+      GetScene()->SetLayer(baseLayerIndex_ + i, layer.release());
+    }
+
+    for (int i = 0; i < textLayerCount_; ++i)
+    {
+      std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
+      GetScene()->SetLayer(
+        baseLayerIndex_ + polylineLayerCount_ + i,
+        layer.release());
+    }
+
+  }
+
+  void LayerHolder::CreateLayersIfNeeded()
+  {
+    if (baseLayerIndex_ == -1)
+      CreateLayers();
+  }
+
+  bool LayerHolder::AreLayersCreated() const
+  {
+    return (baseLayerIndex_ != -1);
+  }
+
+  OrthancStone::Scene2DPtr LayerHolder::GetScene()
+  {
+    ViewportControllerPtr controller = controllerW_.lock();
+    ORTHANC_ASSERT(controller.get() != 0, "Zombie attack!");
+    return controller->GetScene();
+  }
+
+  void LayerHolder::DeleteLayers()
+  {
+    for (int i = 0; i < textLayerCount_ + polylineLayerCount_; ++i)
+    {
+      ORTHANC_ASSERT(GetScene()->HasLayer(baseLayerIndex_ + i), "No layer");
+      GetScene()->DeleteLayer(baseLayerIndex_ + i);
+    }
+    baseLayerIndex_ = -1;
+  }
+
+  PolylineSceneLayer* LayerHolder::GetPolylineLayer(int index /*= 0*/)
+  {
+    ORTHANC_ASSERT(baseLayerIndex_ != -1);
+    ORTHANC_ASSERT(GetScene()->HasLayer(GetPolylineLayerIndex(index)));
+    ISceneLayer* layer =
+      &(GetScene()->GetLayer(GetPolylineLayerIndex(index)));
+
+    PolylineSceneLayer* concreteLayer =
+      dynamic_cast<PolylineSceneLayer*>(layer);
+
+    ORTHANC_ASSERT(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  TextSceneLayer* LayerHolder::GetTextLayer(int index /*= 0*/)
+  {
+    ORTHANC_ASSERT(baseLayerIndex_ != -1);
+    ORTHANC_ASSERT(GetScene()->HasLayer(GetTextLayerIndex(index)));
+    ISceneLayer* layer =
+      &(GetScene()->GetLayer(GetTextLayerIndex(index)));
+
+    TextSceneLayer* concreteLayer =
+      dynamic_cast<TextSceneLayer*>(layer);
+
+    ORTHANC_ASSERT(concreteLayer != NULL);
+    return concreteLayer;
+  }
+
+  int LayerHolder::GetPolylineLayerIndex(int index /*= 0*/)
+  {
+    ORTHANC_ASSERT(index < polylineLayerCount_);
+    return baseLayerIndex_ + index;
+  }
+
+
+  int LayerHolder::GetTextLayerIndex(int index /*= 0*/)
+  {
+    ORTHANC_ASSERT(index < textLayerCount_);
+
+    // the text layers are placed right after the polyline layers
+    // this means they are drawn ON TOP
+    return baseLayerIndex_ + polylineLayerCount_ + index;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Scene2DViewport/LayerHolder.h	Wed May 22 17:05:38 2019 +0200
@@ -0,0 +1,99 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "PointerTypes.h"
+#include "boost/noncopyable.hpp"
+
+namespace OrthancStone
+{
+  class PolylineSceneLayer;
+  class TextSceneLayer;
+
+  /**
+  This class holds the indices of a set a layer and supplies
+  getters to the concrete layer objects. Sounds very ad hoc, and it is.
+  */
+  class LayerHolder : public boost::noncopyable
+  {
+  public:
+    /**
+    This ctor merely stores the scene and layer counts. No layer creation
+    performed at this time
+    */
+    LayerHolder(
+      ViewportControllerWPtr controllerW,
+      int polylineLayerCount, int textLayerCount);
+
+    /**
+    This actually creates the layers
+    */
+    void CreateLayers();
+
+    /**
+    This creates the layers if they are not created yet. Can be useful in 
+    some scenarios
+    */
+    void CreateLayersIfNeeded();
+
+    /**
+    Whether the various text and polylines layers have all been created or 
+    none at all
+    */
+    bool AreLayersCreated() const;
+
+    /**
+    This removes the layers from the scene
+    */
+    void DeleteLayers();
+
+    /**
+    Please note that the returned pointer belongs to the scene.Don't you dare
+    storing or deleting it, you fool!
+
+    This throws if the index is not valid or if the layers are not created or
+    have been deleted
+    */
+    PolylineSceneLayer* GetPolylineLayer(int index = 0);
+
+    /**
+    Please note that the returned pointer belongs to the scene. Don't you dare
+    storing or deleting it, you fool!
+
+    This throws if the index is not valid or if the layers are not created or
+    have been deleted
+    */
+    TextSceneLayer* GetTextLayer(int index = 0);
+
+  private:
+    int GetPolylineLayerIndex(int index = 0);
+    int GetTextLayerIndex(int index = 0);
+    Scene2DPtr GetScene();
+
+    int textLayerCount_;
+    int polylineLayerCount_;
+    ViewportControllerWPtr controllerW_;
+    int baseLayerIndex_;
+  };
+
+  typedef boost::shared_ptr<LayerHolder> LayerHolderPtr;
+}
+
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Wed May 22 17:05:38 2019 +0200
@@ -20,12 +20,23 @@
 
 #include "LineMeasureTool.h"
 #include "MeasureToolsToolbox.h"
+#include "LayerHolder.h"
 
 #include <Core/Logging.h>
 
+#include <boost/make_shared.hpp>
 
 namespace OrthancStone
 {
+
+  LineMeasureTool::LineMeasureTool(
+    MessageBroker& broker, ViewportControllerWPtr controllerW)
+    : MeasureTool(broker, controllerW)
+    , layerHolder_(boost::make_shared<LayerHolder>(controllerW, 1, 5))
+  {
+
+  }
+
   LineMeasureTool::~LineMeasureTool()
   {
     // this measuring tool is a RABI for the corresponding visual layers
@@ -36,16 +47,12 @@
 
   void LineMeasureTool::RemoveFromScene()
   {
-    if (layersCreated)
+    if (layerHolder_->AreLayersCreated() && IsSceneAlive())
     {
-      assert(GetScene()->HasLayer(polylineZIndex_));
-      assert(GetScene()->HasLayer(textZIndex_));
-      GetScene()->DeleteLayer(polylineZIndex_);
-      GetScene()->DeleteLayer(textZIndex_);
+      layerHolder_->DeleteLayers();
     }
   }
-
-
+  
   void LineMeasureTool::SetStart(ScenePoint2D start)
   {
     start_ = start;
@@ -65,58 +72,22 @@
     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 (IsSceneAlive())
     {
       if (IsEnabled())
       {
-        if (!layersCreated)
-        {
-          // Create the layers if need be
+        // get the scaling factor 
+        const double pixelToScene =
+          GetScene()->GetCanvasToSceneTransform().ComputeZoom();
 
-          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_));
-        }
+        layerHolder_->CreateLayersIfNeeded();
+
         {
           // Fill the polyline layer with the measurement line
 
-          PolylineSceneLayer* polylineLayer = GetPolylineLayer();
+          PolylineSceneLayer* polylineLayer = layerHolder_->GetPolylineLayer(0);
           polylineLayer->ClearAllChains();
           polylineLayer->SetColor(0, 223, 21);
 
@@ -133,68 +104,46 @@
 
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), start_, 10.0); //TODO: take DPI into account
+              
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), start_, 10.0 * pixelToScene);
+              
               polylineLayer->AddChain(chain, true);
             }
 
             {
               PolylineSceneLayer::Chain chain;
-              AddSquare(chain, *GetScene(), end_, 10.0); //TODO: take DPI into account
+              
+              //TODO: take DPI into account
+              AddSquare(chain, GetScene(), end_, 10.0 * pixelToScene);
+              
               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);
+
+          SetTextLayerOutlineProperties(
+            GetScene(), layerHolder_, buf, ScenePoint2D(midX, midY));
         }
       }
       else
       {
-        if (layersCreated)
-        {
-          RemoveFromScene();
-          layersCreated = false;
-        }
+        RemoveFromScene();
       }
     }
   }
--- a/Framework/Scene2DViewport/LineMeasureTool.h	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Wed May 22 17:05:38 2019 +0200
@@ -37,14 +37,7 @@
   class LineMeasureTool : public MeasureTool
   {
   public:
-    LineMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW)
-      : MeasureTool(broker, controllerW)
-      , layersCreated(false)
-      , polylineZIndex_(-1)
-      , textZIndex_(-1)
-    {
-
-    }
+    LineMeasureTool(MessageBroker& broker, ViewportControllerWPtr controllerW);
 
     ~LineMeasureTool();
 
@@ -53,17 +46,14 @@
     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_;
+    ScenePoint2D   start_;
+    ScenePoint2D   end_;
+    LayerHolderPtr layerHolder_;
+    int            baseLayerIndex_;
   };
 
 }
--- a/Framework/Scene2DViewport/MeasureCommands.cpp	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.cpp	Wed May 22 17:05:38 2019 +0200
@@ -28,11 +28,13 @@
   void CreateMeasureCommand::Undo()
   {
     // simply disable the measure tool upon undo
+    GetMeasureTool()->Disable();
     GetController()->RemoveMeasureTool(GetMeasureTool());
   }
 
   void CreateMeasureCommand::Redo()
   {
+    GetMeasureTool()->Enable();
     GetController()->AddMeasureTool(GetMeasureTool());
   }
 
--- a/Framework/Scene2DViewport/MeasureTools.h	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTools.h	Wed May 22 17:05:38 2019 +0200
@@ -92,6 +92,5 @@
   };
 }
 
-
 extern void TrackerSample_SetInfoDisplayMessage(
   std::string key, std::string value);
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Wed May 22 17:05:38 2019 +0200
@@ -19,8 +19,11 @@
  **/
 
 #include "MeasureToolsToolbox.h"
+#include "PointerTypes.h"
+#include "LayerHolder.h"
 
 #include "../Scene2D/TextSceneLayer.h"
+#include "../Scene2D/Scene2D.h"
 
 #include <boost/math/constants/constants.hpp>
 
@@ -31,6 +34,24 @@
 
 namespace OrthancStone
 {
+    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);
+    }
+
   double RadiansToDegrees(double angleRad)
   {
     static const double factor = 180.0 / g_pi;
@@ -38,27 +59,31 @@
   }
 
   void AddSquare(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
+    Scene2DConstPtr     scene,
     const ScenePoint2D& centerS,
-    const double&       sideLength)
+    const double&       sideLengthS)
   {
+    // get the scaling factor 
+    const double sceneToCanvas = 
+      scene->GetSceneToCanvasTransform().ComputeZoom();
+
     chain.clear();
     chain.reserve(4);
-    ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
+    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;
+    double handleLX = centerC.GetX() - sideLengthS * sceneToCanvas * 0.5;
+    double handleTY = centerC.GetY() - sideLengthS * sceneToCanvas * 0.5;
+    double handleRX = centerC.GetX() + sideLengthS * sceneToCanvas * 0.5;
+    double handleBY = centerC.GetY() + sideLengthS * sceneToCanvas * 0.5;
     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());
+    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);
@@ -86,7 +111,6 @@
 
   void AddShortestArc(
       PolylineSceneLayer::Chain& chain
-    , const Scene2D&             scene
     , const ScenePoint2D&        p1
     , const ScenePoint2D&        c
     , const ScenePoint2D&        p2
@@ -96,30 +120,11 @@
     double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
     double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
     AddShortestArc(
-      chain, scene, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount);
+      chain, c, radiusS, p1cAngle, p2cAngle, subdivisionsCount);
   }
 
-  void GetPositionOnBisectingLine(
-    ScenePoint2D&       result
-    , const ScenePoint2D& p1
-    , const ScenePoint2D& c
-    , const ScenePoint2D& p2
-    , const double d)
-  {
-    // TODO: fix correct half-plane
-    double p1cAngle = atan2(p1.GetY() - c.GetY(), p1.GetX() - c.GetX());
-    double p2cAngle = atan2(p2.GetY() - c.GetY(), p2.GetX() - c.GetX());
-    double angle = 0.5*(p1cAngle + p2cAngle);
-    double unitVectorX = cos(angle);
-    double unitVectorY = sin(angle);
-    double posX = c.GetX() + d * unitVectorX;
-    double posY = c.GetX() + d * unitVectorY;
-    result = ScenePoint2D(posX, posY);
-  }
-   
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         centerS
     , const double&               radiusS
     , const double                startAngleRad
@@ -197,7 +202,6 @@
 #endif
 
   void AddCircle(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
     const ScenePoint2D& centerS,
     const double&       radiusS,
     const int           numSubdivisions)
@@ -278,38 +282,25 @@
 #endif
 
 
-  namespace
-  {
-    /**
-    Helper function for outlined text rendering
-    */
-    TextSceneLayer* GetOutlineTextLayer(
-      Scene2D& scene, int baseLayerIndex, int index)
-    {
-      assert(scene.HasLayer(baseLayerIndex));
-      assert(index >= 0);
-      assert(index < 5);
-
-      ISceneLayer * layer = &(scene.GetLayer(baseLayerIndex + index));
-      TextSceneLayer * concreteLayer = dynamic_cast<TextSceneLayer*>(layer);
-      assert(concreteLayer != NULL);
-      return concreteLayer;
-    }
-  }
-   
+  /**
+  This utility function assumes that the layer holder contains 5 text layers
+  and will use the first four ones for the text background and the fifth one
+  for the actual text
+  */
   void SetTextLayerOutlineProperties(
-    Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p)
+    Scene2DPtr scene, LayerHolderPtr layerHolder, 
+    const char* text, ScenePoint2D p)
   {
     double xoffsets[5] = { 2, 0, -2, 0, 0 };
     double yoffsets[5] = { 0, -2, 0, 2, 0 };
 
     // get the scaling factor 
     const double pixelToScene =
-      scene.GetCanvasToSceneTransform().ComputeZoom();
+      scene->GetCanvasToSceneTransform().ComputeZoom();
 
     for (int i = 0; i < 5; ++i)
     {
-      TextSceneLayer* textLayer = GetOutlineTextLayer(scene, baseLayerIndex, i);
+      TextSceneLayer* textLayer = layerHolder->GetTextLayer(i);
       textLayer->SetText(text);
 
       if (i == 4)
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.h	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Wed May 22 17:05:38 2019 +0200
@@ -18,6 +18,7 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
+#include "PointerTypes.h"
 #include "../Scene2D/PolylineSceneLayer.h"
 #include "../Scene2D/Scene2D.h"
 
@@ -30,10 +31,9 @@
   square sides are parallel to the canvas boundaries.
   */
   void AddSquare(PolylineSceneLayer::Chain& chain,
-    const Scene2D&      scene,
+    Scene2DConstPtr     scene,
     const ScenePoint2D& centerS,
-    const double&       sideLength);
-
+    const double&       sideLengthS);
 
   /**
     Creates an arc centered on c that goes
@@ -49,7 +49,6 @@
   */
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         p1
     , const ScenePoint2D&         c
     , const ScenePoint2D&         p2
@@ -64,7 +63,6 @@
   */
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         centerS
     , const double&               radiusS
     , const double                startAngleRad
@@ -182,6 +180,6 @@
   from layerIndex, up to (and not including) layerIndex+5. 
   */
   void SetTextLayerOutlineProperties(
-    Scene2D& scene, int baseLayerIndex, const char* text, ScenePoint2D p);
-
+    Scene2DPtr scene, LayerHolderPtr layerHolder,
+    const char* text, ScenePoint2D p);
 }
--- a/Framework/Scene2DViewport/PointerTypes.h	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/PointerTypes.h	Wed May 22 17:05:38 2019 +0200
@@ -29,6 +29,7 @@
 {
   class Scene2D;
   typedef boost::shared_ptr<Scene2D> Scene2DPtr;
+  typedef boost::shared_ptr<const Scene2D> Scene2DConstPtr;
 
   typedef boost::weak_ptr<Scene2D> Scene2DWPtr;
 
@@ -69,13 +70,13 @@
   typedef boost::shared_ptr<CreateAngleMeasureCommand>
     CreateAngleMeasureCommandPtr;
 
-
-  typedef boost::shared_ptr<Scene2D> Scene2DPtr;
-
   class TrackerCommand;
   typedef boost::shared_ptr<TrackerCommand> TrackerCommandPtr;
 
   class ViewportController;
   typedef boost::shared_ptr<ViewportController> ViewportControllerPtr;
   typedef boost::weak_ptr<ViewportController> ViewportControllerWPtr;
+
+  class LayerHolder;
+  typedef boost::shared_ptr<LayerHolder> LayerHolderPtr;
 }
--- a/Framework/Scene2DViewport/ViewportController.cpp	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Wed May 22 17:05:38 2019 +0200
@@ -128,8 +128,9 @@
   {
     ORTHANC_ASSERT(std::find(measureTools_.begin(), measureTools_.end(), measureTool)
       != measureTools_.end(), "Measure tool not found");
-    measureTools_.push_back(measureTool);
+    measureTools_.erase(
+      std::remove(measureTools_.begin(), measureTools_.end(), measureTool), 
+      measureTools_.end());
   }
-
 }
 
--- a/Framework/StoneException.h	Wed May 22 17:05:14 2019 +0200
+++ b/Framework/StoneException.h	Wed May 22 17:05:38 2019 +0200
@@ -115,7 +115,7 @@
 
 // See https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-multi-stmts
 // (or google "Multiple lines macro C++ faq lite" if link is dead)
-#define ORTHANC_ASSERT(cond,streamChainMessage) \
+#define ORTHANC_ASSERT2(cond,streamChainMessage) \
     if (!(cond)) { \
       std::stringstream sst; \
       sst << "Assertion failed. Condition = \"" #cond "\" Message = \"" << streamChainMessage << "\""; \
@@ -123,3 +123,38 @@
       throw ::Orthanc::OrthancException(::Orthanc::ErrorCode_InternalError,sstr.c_str()); \
     } else (void)0
 
+#define ORTHANC_ASSERT1(cond) \
+    if (!(cond)) { \
+      std::stringstream sst; \
+      sst << "Assertion failed. Condition = \"" #cond "\""; \
+      std::string sstr = sst.str(); \
+      throw OrthancException(ErrorCode_InternalError,sstr.c_str()); \
+    } else (void)0
+
+
+
+# define ORTHANC_EXPAND( x ) x 
+# define GET_ORTHANC_ASSERT(_1,_2,NAME,...) NAME
+# define ORTHANC_ASSERT(...) ORTHANC_EXPAND(GET_ORTHANC_ASSERT(__VA_ARGS__, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(__VA_ARGS__))
+
+/*
+Explanation:
+
+ORTHANC_ASSERT(a)
+ORTHANC_EXPAND(GET_ORTHANC_ASSERT(a, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(a))
+ORTHANC_EXPAND(ORTHANC_ASSERT1(a))
+ORTHANC_ASSERT1(a)
+
+ORTHANC_ASSERT(a,b)
+ORTHANC_EXPAND(GET_ORTHANC_ASSERT(a, b, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(a,b))
+ORTHANC_EXPAND(ORTHANC_ASSERT2(a,b))
+ORTHANC_ASSERT2(a,b)
+
+Note: ORTHANC_EXPAND is required for some older compilers (MS v100 cl.exe )
+*/
+
+
+
+
+
+
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 22 17:05:14 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 22 17:05:38 2019 +0200
@@ -392,7 +392,6 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/TextureBaseSceneLayer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2D/ZoomSceneTracker.cpp
-
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/AngleMeasureTool.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp
@@ -411,6 +410,8 @@
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/EditLineMeasureTracker.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/IFlexiblePointerTracker.h
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LayerHolder.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/LineMeasureTool.h
   ${ORTHANC_STONE_ROOT}/Framework/Scene2DViewport/MeasureCommands.cpp
--- a/Samples/Sdl/TrackerSampleApp.cpp	Wed May 22 17:05:14 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Wed May 22 17:05:38 2019 +0200
@@ -158,14 +158,18 @@
   {
     DisplayInfoText();
 
+    // we seed the random number generator for random measure tool 
+    // generation
+    srand(42);
+
     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])
+        SDL_SCANCODE_LALT < scancodeCount &&
+        keyboardState[SDL_SCANCODE_LALT])
       {
         // The "left-ctrl" key is down, while no tracker is present
         // Let's display the info text
@@ -249,11 +253,48 @@
         }
         break;
 
+      case SDLK_m:
+        // let's create a random measuring tool
+        
+      
+
       case SDLK_s:
         controller_->FitContent(compositor_->GetCanvasWidth(),
           compositor_->GetCanvasHeight());
         break;
 
+      case SDLK_z:
+        LOG(INFO) << "SDLK_z has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller_->CanUndo())
+          {
+            LOG(INFO) << "Undoing...";
+            controller_->Undo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to undo!!!";
+          }
+        }
+        break;
+
+      case SDLK_y:
+        LOG(INFO) << "SDLK_y has been pressed. event.key.keysym.mod == " << event.key.keysym.mod;
+        if (event.key.keysym.mod & KMOD_CTRL)
+        {
+          if (controller_->CanRedo())
+          {
+            LOG(INFO) << "Redoing...";
+            controller_->Redo();
+          }
+          else
+          {
+            LOG(WARNING) << "Nothing to redo!!!";
+          }
+        }
+        break;
+
       case SDLK_c:
         TakeScreenshot(
           "screenshot.png",
--- a/Samples/WebAssembly/BasicScene.cpp	Wed May 22 17:05:14 2019 +0200
+++ b/Samples/WebAssembly/BasicScene.cpp	Wed May 22 17:05:38 2019 +0200
@@ -32,6 +32,7 @@
 #include "../../Framework/Scene2D/RotateSceneTracker.h"
 #include "../../Framework/Scene2D/Scene2D.h"
 #include "../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../Framework/Scene2DViewport/ViewportController.h"
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h"
 
@@ -40,12 +41,16 @@
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 
+#include <boost/make_shared.hpp>
+
 #include <stdio.h>
 
 static const unsigned int FONT_SIZE = 32;
 
+using boost::shared_ptr;
+using boost::make_shared;
 
-void PrepareScene(OrthancStone::Scene2D& scene)
+void PrepareScene(OrthancStone::Scene2DPtr scene)
 {
   using namespace OrthancStone;
 
@@ -72,13 +77,13 @@
     p[4] = 0;
     p[5] = 0;
 
-    scene.SetLayer(12, new ColorTextureSceneLayer(i));
+    scene->SetLayer(12, 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(14, l.release());
+    scene->SetLayer(14, l.release());
   }
 
   // Texture of 1x1 size
@@ -94,7 +99,7 @@
     std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
     l->SetOrigin(-2, 1);
     l->SetAngle(20.0 / 180.0 * M_PI);
-    scene.SetLayer(13, l.release());
+    scene->SetLayer(13, l.release());
   }
 
   // Some lines
@@ -126,7 +131,7 @@
     layer->AddChain(chain, false);
 
     layer->SetColor(0,255, 255);
-    scene.SetLayer(50, layer.release());
+    scene->SetLayer(50, layer.release());
   }
 
   // Some text
@@ -134,37 +139,43 @@
   {
     std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
     layer->SetText("Hello");
-    scene.SetLayer(100, layer.release());
+    scene->SetLayer(100, layer.release());
   }
 }
 
-
-
-
 namespace OrthancStone
 {
   class WebAssemblyViewport : public boost::noncopyable
   {
   private:
+    // the construction order is important because compositor_
+    // will hold a reference to the scene that belong to the 
+    // controller_ object
     OpenGL::WebAssemblyOpenGLContext  context_;
-    Scene2D                           scene_;
+    ViewportControllerPtr             controller_;
     OpenGLCompositor                  compositor_;
 
     void SetupEvents(const std::string& canvas);
 
   public:
-    WebAssemblyViewport(const std::string& canvas) :
+    WebAssemblyViewport(MessageBroker& broker, const std::string& canvas) :
       context_(canvas),
-      compositor_(context_, scene_)
+      controller_(make_shared<ViewportController>(broker)),
+      compositor_(context_, *controller_->GetScene())
     {
       compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
                           FONT_SIZE, Orthanc::Encoding_Latin1);
       SetupEvents(canvas);
     }
 
-    Scene2D& GetScene()
+    Scene2DPtr GetScene()
     {
-      return scene_;
+      return controller_->GetScene();
+    }
+
+    ViewportControllerPtr GetController()
+    {
+      return controller_;
     }
 
     void UpdateSize()
@@ -200,17 +211,15 @@
     }
   };
 
-
-
   class ActiveTracker : public boost::noncopyable
   {
   private:
-    std::auto_ptr<IPointerTracker>  tracker_;
-    std::string                     canvasIdentifier_;
-    bool                            insideCanvas_;
+    OrthancStone::FlexiblePointerTrackerPtr tracker_;
+    std::string                             canvasIdentifier_;
+    bool                                    insideCanvas_;
     
   public:
-    ActiveTracker(IPointerTracker* tracker,
+    ActiveTracker(FlexiblePointerTrackerPtr tracker,
                   const WebAssemblyViewport& viewport) :
       tracker_(tracker),
       canvasIdentifier_(viewport.GetCanvasIdentifier()),
@@ -222,26 +231,32 @@
       }
     }
 
-    void Update(const PointerEvent& event)
+    bool IsAlive() const
     {
-      tracker_->Update(event);
+      return tracker_->IsAlive();
     }
 
-    void Release()
+    void PointerMove(const PointerEvent& event)
     {
-      tracker_->Release();
+      tracker_->PointerMove(event);
+    }
+
+    void PointerUp(const PointerEvent& event)
+    {
+      tracker_->PointerUp(event);
     }
   };
 }
 
-
+static OrthancStone::PointerEvent* ConvertMouseEvent(
+  const EmscriptenMouseEvent&        source,
+  OrthancStone::WebAssemblyViewport& viewport)
+{
+  std::auto_ptr<OrthancStone::PointerEvent> target(
+    new OrthancStone::PointerEvent);
 
-static OrthancStone::PointerEvent* ConvertMouseEvent(const EmscriptenMouseEvent& source,
-                                                     OrthancStone::WebAssemblyViewport& viewport)
-{
-  std::auto_ptr<OrthancStone::PointerEvent> target(new OrthancStone::PointerEvent);
-
-  target->AddPosition(viewport.GetPixelCenterCoordinates(source.targetX, source.targetY));
+  target->AddPosition(viewport.GetPixelCenterCoordinates(
+    source.targetX, source.targetY));
   target->SetAltModifier(source.altKey);
   target->SetControlModifier(source.ctrlKey);
   target->SetShiftModifier(source.shiftKey);
@@ -249,9 +264,7 @@
   return target.release();
 }
 
-
-std::auto_ptr<OrthancStone::ActiveTracker>      tracker_;
-
+std::auto_ptr<OrthancStone::ActiveTracker> tracker_;
 
 EM_BOOL OnMouseEvent(int eventType, 
                      const EmscriptenMouseEvent *mouseEvent, 
@@ -273,31 +286,38 @@
 
         std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer);
         layer->SetText(buf);
-        viewport.GetScene().SetLayer(100, layer.release());
+        viewport.GetScene()->SetLayer(100, layer.release());
         viewport.Refresh();
         break;
       }
 
       case EMSCRIPTEN_EVENT_MOUSEDOWN:
       {
-        std::auto_ptr<OrthancStone::IPointerTracker> t;
+        OrthancStone::FlexiblePointerTrackerPtr t;
 
         {
-          std::auto_ptr<OrthancStone::PointerEvent> event(ConvertMouseEvent(*mouseEvent, viewport));
+          std::auto_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, viewport));
 
           switch (mouseEvent->button)
           {
             case 0:  // Left button
-              t.reset(new OrthancStone::RotateSceneTracker(viewport.GetScene(), *event));
+              emscripten_console_log("Creating RotateSceneTracker");
+              t.reset(new OrthancStone::RotateSceneTracker(
+                viewport.GetController(), *event));
               break;
 
             case 1:  // Middle button
-              t.reset(new OrthancStone::PanSceneTracker(viewport.GetScene(), *event));
+              emscripten_console_log("Creating PanSceneTracker");
+              LOG(INFO) << "Creating PanSceneTracker" ;
+              t.reset(new OrthancStone::PanSceneTracker(
+                viewport.GetController(), *event));
               break;
 
             case 2:  // Right button
-              t.reset(new OrthancStone::ZoomSceneTracker
-                      (viewport.GetScene(), *event, viewport.GetCanvasWidth()));
+              emscripten_console_log("Creating ZoomSceneTracker");
+              t.reset(new OrthancStone::ZoomSceneTracker(
+                viewport.GetController(), *event, viewport.GetCanvasWidth()));
               break;
 
             default:
@@ -307,7 +327,8 @@
 
         if (t.get() != NULL)
         {
-          tracker_.reset(new OrthancStone::ActiveTracker(t.release(), viewport));
+          tracker_.reset(
+            new OrthancStone::ActiveTracker(t, viewport));
           viewport.Refresh();
         }
 
@@ -317,8 +338,9 @@
       case EMSCRIPTEN_EVENT_MOUSEMOVE:
         if (tracker_.get() != NULL)
         {
-          std::auto_ptr<OrthancStone::PointerEvent> event(ConvertMouseEvent(*mouseEvent, viewport));
-          tracker_->Update(*event);
+          std::auto_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, viewport));
+          tracker_->PointerMove(*event);
           viewport.Refresh();
         }
         break;
@@ -326,9 +348,12 @@
       case EMSCRIPTEN_EVENT_MOUSEUP:
         if (tracker_.get() != NULL)
         {
-          tracker_->Release();
+          std::auto_ptr<OrthancStone::PointerEvent> event(
+            ConvertMouseEvent(*mouseEvent, viewport));
+          tracker_->PointerUp(*event);
           viewport.Refresh();
-          tracker_.reset();
+          if (!tracker_->IsAlive())
+            tracker_.reset();
         }
         break;
 
@@ -343,27 +368,22 @@
 
 void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas)
 {
-  if (0)
-  {
-    emscripten_set_click_callback(canvas.c_str(), this, false, OnMouseEvent);
-  }
-  else
-  {
-    emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent);
-    emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent);
-    emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent);
-  }
+#if 0
+  emscripten_set_click_callback(canvas.c_str(), this, false, OnMouseEvent);
+#else
+  emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent);
+  emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent);
+  emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent);
+#endif
 }
 
-
-
-
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport1_;
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport2_;
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport3_;
-
+OrthancStone::MessageBroker  broker_;
 
-EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
+EM_BOOL OnWindowResize(
+  int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
 {
   if (viewport1_.get() != NULL)
   {
@@ -383,28 +403,31 @@
   return true;
 }
 
-
-
 extern "C"
 {
   int main(int argc, char const *argv[]) 
   {
     OrthancStone::StoneInitialize();
+    // Orthanc::Logging::EnableInfoLevel(true);
+    // Orthanc::Logging::EnableTraceLevel(true);
     EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded")););
   }
 
   EMSCRIPTEN_KEEPALIVE
   void Initialize()
   {
-    viewport1_.reset(new OrthancStone::WebAssemblyViewport("mycanvas1"));
+    viewport1_.reset(
+      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas1"));
     PrepareScene(viewport1_->GetScene());
     viewport1_->UpdateSize();
 
-    viewport2_.reset(new OrthancStone::WebAssemblyViewport("mycanvas2"));
+    viewport2_.reset(
+      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas2"));
     PrepareScene(viewport2_->GetScene());
     viewport2_->UpdateSize();
 
-    viewport3_.reset(new OrthancStone::WebAssemblyViewport("mycanvas3"));
+    viewport3_.reset(
+      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas3"));
     PrepareScene(viewport3_->GetScene());
     viewport3_->UpdateSize();
 
--- a/Samples/WebAssembly/NOTES.txt	Wed May 22 17:05:14 2019 +0200
+++ b/Samples/WebAssembly/NOTES.txt	Wed May 22 17:05:38 2019 +0200
@@ -2,3 +2,13 @@
 $ cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/tmp/stone
 $ ninja install
 $ sudo docker run -p 4242:4242 -p 8042:8042 --rm -v /tmp/stone:/root/stone:ro jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose
+
+notes BGO :
+source ~/apps/emsdk/emsdk_env.sh
+cd /mnt/c/osi/dev/orthanc-stone/Samples/WebAssembly
+mkdir build
+cd build
+cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=Release -DALLOW_DOWNLOADS=ON .. -DCMAKE_INSTALL_PREFIX=/mnt/c/osi/dev/orthanc-stone/Samples/WebAssembly/installDir
+ninja install
+
+docker run -p 4242:4242 -p 8042:8042 --rm -v "C:/osi/dev/orthanc-stone/Samples/WebAssembly/installDir:/root/stone:ro" jodogne/orthanc-plugins:1.5.6 /root/stone/Configuration.json --verbose
\ No newline at end of file