changeset 751:712ff6ff3c19

- undo redo now works fine for both measure tool creation commands - added LayerHolder to streamline layer index management - added overloads for ORTHANC_ASSERT with no string message (some heavy preprocessor wizardry in there) - fixing wasm BasicScene is *not* finished.
author Benjamin Golinvaux <bgo@osimis.io>
date Wed, 22 May 2019 11:55:52 +0200
parents 284f37dc1c66
children 6030db24eaea
files .hgignore Framework/Scene2DViewport/AngleMeasureTool.cpp Framework/Scene2DViewport/AngleMeasureTool.h Framework/Scene2DViewport/LayerHolder.cpp Framework/Scene2DViewport/LayerHolder.h Framework/Scene2DViewport/LineMeasureTool.cpp Framework/Scene2DViewport/LineMeasureTool.h Framework/Scene2DViewport/MeasureCommands.cpp Framework/Scene2DViewport/MeasureTools.h Framework/Scene2DViewport/MeasureToolsToolbox.cpp Framework/Scene2DViewport/MeasureToolsToolbox.h Framework/Scene2DViewport/PointerTypes.h Framework/Scene2DViewport/ViewportController.cpp Framework/StoneException.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 15 files changed, 421 insertions(+), 282 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue May 21 13:18:35 2019 +0200
+++ b/.hgignore	Wed May 22 11:55:52 2019 +0200
@@ -34,3 +34,5 @@
 Samples/Sdl/ThirdPartyDownloads/
 Samples/Sdl/CMakeLists.txt.orig
 
+Samples/WebAssembly/build/
+Samples/WebAssembly/ThirdPartyDownloads/
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Wed May 22 11:55:52 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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.h	Wed May 22 11:55:52 2019 +0200
@@ -22,10 +22,12 @@
 
 #include "MeasureTools.h"
 
-#include <Framework/Scene2D/Scene2D.h>
-#include <Framework/Scene2D/ScenePoint2D.h>
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/TextSceneLayer.h>
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2D/ScenePoint2D.h"
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "../Scene2D/TextSceneLayer.h"
+
+#include "../Scene2DViewport/LayerHolder.h"
 
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
@@ -38,14 +40,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();
 
@@ -54,23 +49,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 11:55:52 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 11:55:52 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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Wed May 22 11:55:52 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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.h	Wed May 22 11:55:52 2019 +0200
@@ -38,14 +38,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();
 
@@ -54,17 +47,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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureCommands.cpp	Wed May 22 11:55:52 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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTools.h	Wed May 22 11:55:52 2019 +0200
@@ -93,6 +93,5 @@
   };
 }
 
-
 extern void TrackerSample_SetInfoDisplayMessage(
   std::string key, std::string value);
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.cpp	Wed May 22 11:55:52 2019 +0200
@@ -19,8 +19,11 @@
  **/
 
 #include "MeasureToolsToolbox.h"
+#include "PointerTypes.h"
+#include "LayerHolder.h"
 
-#include <Framework/Scene2D/TextSceneLayer.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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Wed May 22 11:55:52 2019 +0200
@@ -18,8 +18,8 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  **/
 
-#include <Framework/Scene2D/PolylineSceneLayer.h>
-#include <Framework/Scene2D/Scene2D.h>
+#include "../Scene2D/PolylineSceneLayer.h"
+#include "PointerTypes.h"
 
 namespace OrthancStone
 {
@@ -30,10 +30,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 +48,6 @@
   */
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         p1
     , const ScenePoint2D&         c
     , const ScenePoint2D&         p2
@@ -64,7 +62,6 @@
   */
   void AddShortestArc(
       PolylineSceneLayer::Chain&  chain
-    , const Scene2D&              scene
     , const ScenePoint2D&         centerS
     , const double&               radiusS
     , const double                startAngleRad
@@ -182,6 +179,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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/PointerTypes.h	Wed May 22 11:55:52 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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Wed May 22 11:55:52 2019 +0200
@@ -130,8 +130,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	Tue May 21 13:18:35 2019 +0200
+++ b/Framework/StoneException.h	Wed May 22 11:55:52 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 OrthancException(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	Tue May 21 13:18:35 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed May 22 11:55:52 2019 +0200
@@ -328,7 +328,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
@@ -347,6 +346,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