changeset 654:462a5074f914

Turned the scene into an observable to be able to dynamically react to scene to canvas transform changes --> now the handles and angle measure adornments are immune to zoom changes
author Benjamin Golinvaux <bgo@osimis.io>
date Tue, 14 May 2019 13:51:00 +0200
parents 4eccf698e52f
children 1e26bb5f2a02
files Framework/Scene2D/Scene2D.cpp Framework/Scene2D/Scene2D.h Samples/Common/AngleMeasureTool.cpp Samples/Common/AngleMeasureTool.h Samples/Common/CreateAngleMeasureTracker.cpp Samples/Common/CreateAngleMeasureTracker.h Samples/Common/CreateLineMeasureTracker.cpp Samples/Common/CreateLineMeasureTracker.h Samples/Common/LineMeasureTool.h Samples/Common/MeasureCommands.cpp Samples/Common/MeasureCommands.h Samples/Common/MeasureTools.cpp Samples/Common/MeasureTools.h Samples/Common/MeasureToolsToolbox.cpp Samples/Sdl/BasicScene.cpp Samples/Sdl/TrackerSample.cpp Samples/Sdl/TrackerSampleApp.cpp Samples/Sdl/TrackerSampleApp.h Samples/Sdl/cpp.hint
diffstat 19 files changed, 186 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/Framework/Scene2D/Scene2D.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Framework/Scene2D/Scene2D.cpp	Tue May 14 13:51:00 2019 +0200
@@ -75,10 +75,11 @@
   };
   
   
-  Scene2D::Scene2D(const Scene2D& other) :
-    sceneToCanvas_(other.sceneToCanvas_),
-    canvasToScene_(other.canvasToScene_),
-    layerCounter_(0)
+  Scene2D::Scene2D(const Scene2D& other) 
+    : IObservable(other.GetBroker())
+    , sceneToCanvas_(other.sceneToCanvas_)
+    , canvasToScene_(other.canvasToScene_)
+    , layerCounter_(0)
   {
     for (Content::const_iterator it = other.content_.begin();
          it != other.content_.end(); ++it)
@@ -220,9 +221,9 @@
 
     sceneToCanvas_ = transform;
     canvasToScene_ = inverse;
+    BroadcastMessage(SceneTransformChanged(*this));
   }
 
-
   void Scene2D::FitContent(unsigned int canvasWidth,
                            unsigned int canvasHeight)
   {
--- a/Framework/Scene2D/Scene2D.h	Tue May 14 13:49:12 2019 +0200
+++ b/Framework/Scene2D/Scene2D.h	Tue May 14 13:51:00 2019 +0200
@@ -23,13 +23,18 @@
 
 #include "ISceneLayer.h"
 #include "../Toolbox/AffineTransform2D.h"
+#include <Framework/Messages/IObservable.h>
+#include <Framework/Messages/IMessage.h>
+
 #include <map>
 
 namespace OrthancStone
 {
-  class Scene2D : public boost::noncopyable
+  class Scene2D : public IObservable
   {
   public:
+    ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, SceneTransformChanged, Scene2D);
+    
     class IVisitor : public boost::noncopyable
     {
     public:
@@ -55,8 +60,9 @@
     Scene2D(const Scene2D& other);
     
   public:
-    Scene2D() :
-      layerCounter_(0)
+    Scene2D(MessageBroker& broker) 
+      : IObservable(broker)
+      , layerCounter_(0)
     {
     }
     
--- a/Samples/Common/AngleMeasureTool.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/AngleMeasureTool.cpp	Tue May 14 13:51:00 2019 +0200
@@ -145,13 +145,13 @@
 
           {
             PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), side1End_, 10.0); //TODO: take DPI into account
+            AddSquare(chain, GetScene(), side1End_, 10.0* pixelToScene); //TODO: take DPI into account
             polylineLayer->AddChain(chain, true);
           }
 
           {
             PolylineSceneLayer::Chain chain;
-            AddSquare(chain, GetScene(), side2End_, 10.0); //TODO: take DPI into account
+            AddSquare(chain, GetScene(), side2End_, 10.0* pixelToScene); //TODO: take DPI into account
             polylineLayer->AddChain(chain, true);
           }
         }
--- a/Samples/Common/AngleMeasureTool.h	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/AngleMeasureTool.h	Tue May 14 13:51:00 2019 +0200
@@ -38,8 +38,8 @@
   class AngleMeasureTool : public MeasureTool
   {
   public:
-    AngleMeasureTool(Scene2D& scene)
-      : MeasureTool(scene)
+    AngleMeasureTool(MessageBroker& broker, Scene2D& scene)
+      : MeasureTool(broker, scene)
       , layersCreated(false)
       , polylineZIndex_(-1)
       , textZIndex_(-1)
@@ -69,6 +69,6 @@
   };
 
   typedef boost::shared_ptr<AngleMeasureTool> AngleMeasureToolPtr;
-}
-
-
+}
+
+
--- a/Samples/Common/CreateAngleMeasureTracker.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/CreateAngleMeasureTracker.cpp	Tue May 14 13:51:00 2019 +0200
@@ -26,15 +26,17 @@
 namespace OrthancStone
 {
   CreateAngleMeasureTracker::CreateAngleMeasureTracker(
+    MessageBroker&                  broker,
     Scene2D&                        scene,
     std::vector<TrackerCommandPtr>& undoStack,
     std::vector<MeasureToolPtr>&    measureTools,
     const PointerEvent&             e)
     : CreateMeasureTracker(scene, undoStack, measureTools)
     , state_(CreatingSide1)
-  {
+  {
     command_.reset(
       new CreateAngleMeasureCommand(
+        broker,
         scene,
         measureTools,
         e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform())));
@@ -44,35 +46,35 @@
   {
   }
 
-  void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
-  {
-    if (!active_)
-    {
-      throw OrthancException(ErrorCode_InternalError,
-        "Internal error: wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: active_ == false");
-    }
-
-    ScenePoint2D scenePos = event.GetMainPosition().Apply(
-      scene_.GetCanvasToSceneTransform());
-
-    switch (state_)
-    {
-    case CreatingSide1:
-      GetCommand()->SetCenter(scenePos);
-      break;
-    case CreatingSide2:
-      GetCommand()->SetSide2End(scenePos);
-      break;
-    default:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: state_ invalid");
-    }
+  void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
+  {
+    if (!active_)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+        "Internal error: wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: active_ == false");
+    }
+
+    ScenePoint2D scenePos = event.GetMainPosition().Apply(
+      scene_.GetCanvasToSceneTransform());
+
+    switch (state_)
+    {
+    case CreatingSide1:
+      GetCommand()->SetCenter(scenePos);
+      break;
+    case CreatingSide2:
+      GetCommand()->SetSide2End(scenePos);
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
     //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
     //  "scenePos.GetY() = " << scenePos.GetY();
-  }
-
+  }
+
   void CreateAngleMeasureTracker::PointerUp(const PointerEvent& e)
   {
     // TODO: the current app does not prevent multiple PointerDown AND
@@ -80,42 +82,42 @@
     // Unless we augment the PointerEvent structure with the button index, 
     // we cannot really tell if this pointer up event matches the initial
     // pointer down event. Let's make it simple for now.
-
-    switch (state_)
-    {
-    case CreatingSide1:
-      state_ = CreatingSide2;
-      break;
-    case CreatingSide2:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerUp: state_ == CreatingSide2 ; this should not happen");
-      break;
-    default:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: state_ invalid");
-    }
+
+    switch (state_)
+    {
+    case CreatingSide1:
+      state_ = CreatingSide2;
+      break;
+    case CreatingSide2:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerUp: state_ == CreatingSide2 ; this should not happen");
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
   }
 
   void CreateAngleMeasureTracker::PointerDown(const PointerEvent& e)
   {
-    switch (state_)
-    {
-    case CreatingSide1:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerDown: state_ == CreatingSide1 ; this should not happen");
-      break;
-    case CreatingSide2:
-      // we are done
-      active_ = false;
-      break;
-    default:
-      throw OrthancException(ErrorCode_InternalError,
-        "Wrong state in CreateAngleMeasureTracker::"
-        "PointerMove: state_ invalid");
-    }
+    switch (state_)
+    {
+    case CreatingSide1:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerDown: state_ == CreatingSide1 ; this should not happen");
+      break;
+    case CreatingSide2:
+      // we are done
+      active_ = false;
+      break;
+    default:
+      throw OrthancException(ErrorCode_InternalError,
+        "Wrong state in CreateAngleMeasureTracker::"
+        "PointerMove: state_ invalid");
+    }
   }
 
   CreateAngleMeasureCommandPtr CreateAngleMeasureTracker::GetCommand()
--- a/Samples/Common/CreateAngleMeasureTracker.h	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/CreateAngleMeasureTracker.h	Tue May 14 13:51:00 2019 +0200
@@ -38,6 +38,7 @@
     must be supplied, too
     */
     CreateAngleMeasureTracker(
+      MessageBroker&                  broker,
       Scene2D&                        scene,
       std::vector<TrackerCommandPtr>& undoStack,
       std::vector<MeasureToolPtr>&    measureTools,
--- a/Samples/Common/CreateLineMeasureTracker.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/CreateLineMeasureTracker.cpp	Tue May 14 13:51:00 2019 +0200
@@ -26,45 +26,47 @@
 namespace OrthancStone
 {
   CreateLineMeasureTracker::CreateLineMeasureTracker(
+    MessageBroker&                  broker,
     Scene2D&                        scene,
     std::vector<TrackerCommandPtr>& undoStack,
     std::vector<MeasureToolPtr>&    measureTools,
     const PointerEvent&             e)
     : CreateMeasureTracker(scene, undoStack, measureTools)
-  {
+  {
     command_.reset(
       new CreateLineMeasureCommand(
+        broker,
         scene,
         measureTools,
-        e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform())));
-  }
-
+        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());
-
+  void CreateLineMeasureTracker::PointerMove(const PointerEvent& event)
+  {
+    if (!active_)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+        "Internal error: wrong state in CreateLineMeasureTracker::"
+        "PointerMove: active_ == false");
+    }
+
+    ScenePoint2D scenePos = event.GetMainPosition().Apply(
+      scene_.GetCanvasToSceneTransform());
+
     //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
     //  "scenePos.GetY() = " << scenePos.GetY();
-
+
     CreateLineMeasureTracker* concreteThis =
       dynamic_cast<CreateLineMeasureTracker*>(this);
     assert(concreteThis != NULL);
-    GetCommand()->SetEnd(scenePos);
-  }
-
+    GetCommand()->SetEnd(scenePos);
+  }
+
   void CreateLineMeasureTracker::PointerUp(const PointerEvent& e)
   {
     // TODO: the current app does not prevent multiple PointerDown AND
@@ -72,7 +74,7 @@
     // 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;
+    active_ = false;
   }
 
   void CreateLineMeasureTracker::PointerDown(const PointerEvent& e)
--- a/Samples/Common/CreateLineMeasureTracker.h	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/CreateLineMeasureTracker.h	Tue May 14 13:51:00 2019 +0200
@@ -35,6 +35,7 @@
     must be supplied, too
     */
     CreateLineMeasureTracker(
+      MessageBroker&                  broker,
       Scene2D&                        scene,
       std::vector<TrackerCommandPtr>& undoStack,
       std::vector<MeasureToolPtr>&    measureTools,
--- a/Samples/Common/LineMeasureTool.h	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/LineMeasureTool.h	Tue May 14 13:51:00 2019 +0200
@@ -38,8 +38,8 @@
   class LineMeasureTool : public MeasureTool
   {
   public:
-    LineMeasureTool(Scene2D& scene)
-      : MeasureTool(scene)
+    LineMeasureTool(MessageBroker& broker, Scene2D& scene)
+      : MeasureTool(broker, scene)
       , layersCreated(false)
       , polylineZIndex_(-1)
       , textZIndex_(-1)
@@ -68,5 +68,5 @@
   };
 
   typedef boost::shared_ptr<LineMeasureTool> LineMeasureToolPtr;
-}
-
+}
+
--- a/Samples/Common/MeasureCommands.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/MeasureCommands.cpp	Tue May 14 13:51:00 2019 +0200
@@ -48,9 +48,12 @@
   }
 
   CreateLineMeasureCommand::CreateLineMeasureCommand(
-    Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point)
+    MessageBroker&    broker, 
+    Scene2D&          scene, 
+    MeasureToolList&  measureTools, 
+    ScenePoint2D      point)
     : CreateMeasureCommand(scene, measureTools)
-    , measureTool_(new LineMeasureTool(scene))
+    , measureTool_(new LineMeasureTool(broker,scene))
   {
     measureTools_.push_back(measureTool_);
     measureTool_->Set(point, point);
@@ -62,9 +65,12 @@
   }
 
   CreateAngleMeasureCommand::CreateAngleMeasureCommand(
-    Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point)
+    MessageBroker&    broker, 
+    Scene2D&          scene, 
+    MeasureToolList&  measureTools, 
+    ScenePoint2D      point)
     : CreateMeasureCommand(scene, measureTools)
-    , measureTool_(new AngleMeasureTool(scene))
+    , measureTool_(new AngleMeasureTool(broker,scene))
   {
     measureTools_.push_back(measureTool_);
     measureTool_->SetSide1End(point);
--- a/Samples/Common/MeasureCommands.h	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/MeasureCommands.h	Tue May 14 13:51:00 2019 +0200
@@ -77,7 +77,7 @@
   {
   public:
     CreateLineMeasureCommand(
-      Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
+      MessageBroker& broker, Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
     
     // the starting position is set in the ctor
     void SetEnd(ScenePoint2D scenePos);
@@ -97,7 +97,7 @@
   public:
     /** Ctor sets end of side 1*/
     CreateAngleMeasureCommand(
-      Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
+      MessageBroker& broker, Scene2D& scene, MeasureToolList& measureTools, ScenePoint2D point);
 
     /** This method sets center*/
     void SetCenter(ScenePoint2D scenePos);
--- a/Samples/Common/MeasureTools.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/MeasureTools.cpp	Tue May 14 13:51:00 2019 +0200
@@ -26,6 +26,12 @@
 
 namespace OrthancStone
 {
+
+  MeasureTool::~MeasureTool()
+  {
+
+  }
+
   void MeasureTool::Enable()
   {
     enabled_ = true;
@@ -37,5 +43,33 @@
     enabled_ = false;
     RefreshScene();
   }
+
+  bool MeasureTool::IsEnabled() const
+  {
+    return enabled_;
+  }
+
+  OrthancStone::Scene2D& MeasureTool::GetScene()
+  {
+    return scene_;
+  }
+
+  MeasureTool::MeasureTool(MessageBroker& broker, Scene2D& scene)
+    : IObserver(broker)
+    , scene_(scene)
+    , enabled_(true)
+  {
+    scene_.RegisterObserverCallback(
+      new Callable<MeasureTool, Scene2D::SceneTransformChanged>
+      (*this, &MeasureTool::OnSceneTransformChanged));
+  }
+
+  void MeasureTool::OnSceneTransformChanged(
+    const Scene2D::SceneTransformChanged& message)
+  {
+    RefreshScene();
+  }
+
+
 }
 
--- a/Samples/Common/MeasureTools.h	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/MeasureTools.h	Tue May 14 13:51:00 2019 +0200
@@ -33,10 +33,10 @@
 
 namespace OrthancStone
 {
-  class MeasureTool
+  class MeasureTool : public IObserver
   {
   public:
-    virtual ~MeasureTool() {}
+    virtual ~MeasureTool();
 
     /**
     Enabled tools are rendered in the scene.
@@ -51,15 +51,15 @@
     */
     void Disable();
 
+    /**
+    This method is called when the scene transform changes. It allows to 
+    recompute the visual elements whose content depend upon the scene transform
+    */
+    void OnSceneTransformChanged(const Scene2D::SceneTransformChanged& message);
+
   protected:
-    MeasureTool(Scene2D& scene)
-      : scene_(scene)
-      , enabled_(true)
-    {
-    }
-  
-
-
+    MeasureTool(MessageBroker& broker, Scene2D& scene);
+    
     /**
     This is the meat of the tool: this method must [create (if needed) and]
     update the layers and their data according to the measure tool kind and
@@ -67,19 +67,13 @@
     */
     virtual void RefreshScene() = 0;
 
-    Scene2D& GetScene()
-    {
-      return scene_;
-    }
+    Scene2D& GetScene();
 
     /**
     enabled_ is not accessible by subclasses because there is a state machine
     that we do not wanna mess with
     */
-    bool IsEnabled() const
-    {
-      return enabled_;
-    }
+    bool IsEnabled() const;
 
   private:
     Scene2D& scene_;
--- a/Samples/Common/MeasureToolsToolbox.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Common/MeasureToolsToolbox.cpp	Tue May 14 13:51:00 2019 +0200
@@ -43,7 +43,7 @@
     chain.clear();
     chain.reserve(4);
     ScenePoint2D centerC = centerS.Apply(scene.GetSceneToCanvasTransform());
-    //TODO: take DPI into account
+    //TODO: take DPI into account 
     double handleLX = centerC.GetX() - sideLength / 2;
     double handleTY = centerC.GetY() - sideLength / 2;
     double handleRX = centerC.GetX() + sideLength / 2;
--- a/Samples/Sdl/BasicScene.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Sdl/BasicScene.cpp	Tue May 14 13:51:00 2019 +0200
@@ -29,6 +29,7 @@
 #include "../../Framework/Scene2D/Scene2D.h"
 #include "../../Framework/Scene2D/ZoomSceneTracker.h"
 #include "../../Framework/StoneInitialization.h"
+#include "../../Framework/Messages/MessageBroker.h"
 
 // From Orthanc framework
 #include <Core/Logging.h>
@@ -360,7 +361,8 @@
 
   try
   {
-    OrthancStone::Scene2D scene;
+    OrthancStone::MessageBroker broker;
+    OrthancStone::Scene2D scene(broker);
     PrepareScene(scene);
     Run(scene);
   }
--- a/Samples/Sdl/TrackerSample.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Sdl/TrackerSample.cpp	Tue May 14 13:51:00 2019 +0200
@@ -140,7 +140,8 @@
 
   try
   {
-    TrackerSampleApp app;
+    MessageBroker broker;
+    TrackerSampleApp app(broker);
     app.PrepareScene();
     Run(&app);
   }
--- a/Samples/Sdl/TrackerSampleApp.cpp	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Tue May 14 13:51:00 2019 +0200
@@ -273,10 +273,10 @@
         //  return new EllipseMeasureTracker(scene_, measureTools_, undoStack_, e);
         case GuiTool_LineMeasure:
           return FlexiblePointerTrackerPtr(new CreateLineMeasureTracker(
-            scene_, undoStack_, measureTools_, e));
+            IObserver::GetBroker(), scene_, undoStack_, measureTools_, e));
         case GuiTool_AngleMeasure:
           return FlexiblePointerTrackerPtr(new CreateAngleMeasureTracker(
-            scene_, undoStack_, measureTools_, e));
+            IObserver::GetBroker(), scene_, undoStack_, measureTools_, e));
           return NULL;
         case GuiTool_CircleMeasure:
           LOG(ERROR) << "Not implemented yet!";
--- a/Samples/Sdl/TrackerSampleApp.h	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.h	Tue May 14 13:51:00 2019 +0200
@@ -19,6 +19,7 @@
  **/
 
 #include <Framework/Scene2D/OpenGLCompositor.h>
+#include <Framework/Messages/IObserver.h>
 
 #include "../Common/IFlexiblePointerTracker.h"
 #include "../Common/MeasureTools.h"
@@ -51,11 +52,14 @@
 
   class Scene2D;
 
-  class TrackerSampleApp
+  class TrackerSampleApp : public IObserver
   {
   public:
     // 12 because.
-    TrackerSampleApp() : currentTool_(GuiTool_Rotate)
+    TrackerSampleApp(MessageBroker& broker)
+      : IObserver(broker)
+      , currentTool_(GuiTool_Rotate)
+      , scene_(broker)
     {
       TEXTURE_2x2_1_ZINDEX = 1;
       TEXTURE_1x1_ZINDEX = 2;
--- a/Samples/Sdl/cpp.hint	Tue May 14 13:49:12 2019 +0200
+++ b/Samples/Sdl/cpp.hint	Tue May 14 13:51:00 2019 +0200
@@ -1,1 +1,2 @@
 #define ORTHANC_OVERRIDE
+#define ORTHANC_STONE_DEFINE_EMPTY_MESSAGE(FILE, LINE, NAME) class NAME : public ::OrthancStone::IMessage {};