changeset 2354:2a9d30482584 refactoring

added IFlexiblePointerTracker::IObserver
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 06 Oct 2026 22:04:34 +0200
parents d8a6f6296d3b
children 0f8d09f1c245
files Applications/Samples/Common/SimpleViewerCore.cpp Applications/Samples/Common/SimpleViewerCore.h Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.cpp OrthancStone/Sources/Scene2D/GrayscaleWindowingSceneTracker.cpp OrthancStone/Sources/Scene2D/GrayscaleWindowingSceneTracker.h OrthancStone/Sources/Scene2D/PinchZoomTracker.cpp OrthancStone/Sources/Scene2D/PinchZoomTracker.h OrthancStone/Sources/Scene2D/RotateSceneTracker.cpp OrthancStone/Sources/Scene2D/ZoomSceneTracker.cpp OrthancStone/Sources/Scene2DViewport/BaseFlexiblePointerTracker.cpp OrthancStone/Sources/Scene2DViewport/BaseFlexiblePointerTracker.h OrthancStone/Sources/Scene2DViewport/IFlexiblePointerTracker.h OrthancStone/Sources/Scene2DViewport/MeasureTrackers.h OrthancStone/Sources/Scene2DViewport/OneGesturePointerTracker.h OrthancStone/Sources/Scene2DViewport/ViewportController.h OrthancStone/Sources/Viewport/DefaultViewportInteractor.cpp OrthancStone/Sources/Viewport/DefaultViewportInteractor.h
diffstat 19 files changed, 265 insertions(+), 86 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/Samples/Common/SimpleViewerCore.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/Applications/Samples/Common/SimpleViewerCore.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -109,7 +109,7 @@
   }
 
 
-  void SimpleViewerCore::Handle(const ViewportController::SceneTransformChanged& message)
+  void SimpleViewerCore::RenderAnnotations()
   {
     // This is necessary to update the size of handles in the annotations layer while zoom level changes
     std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
@@ -146,8 +146,6 @@
     {
       std::unique_ptr<IViewport::ILock> viewportLock(viewport->Lock());
       viewportLock->GetController().SetUndoStack(boost::make_shared<UndoStack>());
-
-      Register<OrthancStone::ViewportController::SceneTransformChanged>(viewportLock->GetController(), &SimpleViewerCore::Handle);
     }
   }
 
@@ -155,14 +153,62 @@
   class SimpleViewerCore::Interactor : public IViewportInteractor
   {
   private:
+    class Observer : public IFlexiblePointerTracker::IObserver
+    {
+    private:
+      boost::shared_ptr<SimpleViewerCore>  core_;
+
+    public:
+      Observer(const boost::shared_ptr<SimpleViewerCore>& core) :
+        core_(core)
+      {
+        assert(core_);
+      }
+
+      virtual void HandleGrayscaleWindowingChange(const OrthancStone::Windowing& windowing) ORTHANC_OVERRIDE
+      {
+      }
+
+      virtual void HandleSceneTransformChange(const OrthancStone::Scene2D& scene) ORTHANC_OVERRIDE
+      {
+        core_->RenderAnnotations();
+      }
+    };
+
+    class Factory : public DefaultViewportInteractor::IObserverFactory
+    {
+    private:
+      boost::shared_ptr<SimpleViewerCore>  core_;
+
+    public:
+      Factory(const boost::shared_ptr<SimpleViewerCore>& core) :
+        core_(core)
+      {
+        assert(core_);
+      }
+
+      virtual IFlexiblePointerTracker::IObserver* CreateObserver()
+      {
+        return new Observer(core_);
+      }
+    };
+
     DefaultViewportInteractor                 default_;
     boost::shared_ptr<AnnotationsSceneLayer>  annotations_;
 
   public:
-    Interactor(const boost::shared_ptr<AnnotationsSceneLayer>& annotations) :
+    Interactor(const boost::shared_ptr<SimpleViewerCore>& core,
+               const boost::shared_ptr<AnnotationsSceneLayer>& annotations) :
       annotations_(annotations)
     {
+      if (!core ||
+          !annotations)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+      }
+
       default_.SetWindowingLayer(0);
+      default_.SetObserverFactory(new Factory(core));
     }
 
     virtual IFlexiblePointerTracker* CreateTracker(Scene2D& scene,
@@ -203,7 +249,7 @@
     }
     else
     {
-      return new Interactor(annotations_);
+      return new Interactor(shared_from_this(), annotations_);
     }
   }
 
--- a/Applications/Samples/Common/SimpleViewerCore.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/Applications/Samples/Common/SimpleViewerCore.h	Tue Oct 06 22:04:34 2026 +0200
@@ -56,7 +56,7 @@
     virtual void Handle(const New::IObservable& observable,
                         const DicomResourcesLoadedMessage& message) ORTHANC_OVERRIDE;
 
-    void Handle(const ViewportController::SceneTransformChanged& message);
+    void RenderAnnotations();
 
   protected:
     virtual void CreateComponents(const boost::shared_ptr<StoneApplication::Context>& context,
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -3885,13 +3885,13 @@
   class Interactor : public OrthancStone::DefaultViewportInteractor
   {
   private:
-    class GrayscaleObserver : public OrthancStone::GrayscaleWindowingSceneTracker::IObserver
+    class Observer : public OrthancStone::IFlexiblePointerTracker::IObserver
     {
     private:
       ViewerViewport&  viewer_;
 
     public:
-      GrayscaleObserver(ViewerViewport& viewer) :
+      Observer(ViewerViewport& viewer) :
         viewer_(viewer)
       {
       }
@@ -3900,22 +3900,27 @@
       {
         viewer_.UpdateWindowing(WindowingState_User, windowing);
       }
+
+      virtual void HandleSceneTransformChange(const OrthancStone::Scene2D& scene) ORTHANC_OVERRIDE
+      {
+        viewer_.RefreshAnnotations(false /* don't save */);
+      }
     };
 
-    class GrayscaleFactory : public OrthancStone::GrayscaleWindowingSceneTracker::IObserverFactory
+    class Factory : public OrthancStone::DefaultViewportInteractor::IObserverFactory
     {
     private:
       ViewerViewport&  viewer_;
 
     public:
-      GrayscaleFactory(ViewerViewport& viewer) :
+      Factory(ViewerViewport& viewer) :
         viewer_(viewer)
       {
       }
 
-      virtual OrthancStone::GrayscaleWindowingSceneTracker::IObserver* Create() ORTHANC_OVERRIDE
+      virtual OrthancStone::IFlexiblePointerTracker::IObserver* Create() ORTHANC_OVERRIDE
       {
-        return new GrayscaleObserver(viewer_);
+        return new Observer(viewer_);
       }
     };
 
@@ -3956,7 +3961,7 @@
       SetLeftButtonAction(ConvertWebViewerAction(leftAction));
       SetMiddleButtonAction(ConvertWebViewerAction(middleAction));
       SetRightButtonAction(ConvertWebViewerAction(rightAction));
-      SetGrayscaleWindowingObserverFactory(new GrayscaleFactory(viewer));
+      SetObserverFactory(new Factory(viewer));
     }
 
     virtual OrthancStone::IFlexiblePointerTracker* CreateTracker(OrthancStone::Scene2D& scene,
--- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Oct 06 22:04:34 2026 +0200
@@ -320,6 +320,7 @@
   ${ORTHANC_STONE_ROOT}/Scene2D/Internals/MacroLayerRenderer.cpp
   
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/AngleMeasureTool.cpp
+  ${ORTHANC_STONE_ROOT}/Scene2DViewport/BaseFlexiblePointerTracker.cpp
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateAngleMeasureCommand.cpp
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateAngleMeasureTracker.cpp
   ${ORTHANC_STONE_ROOT}/Scene2DViewport/CreateCircleMeasureTracker.cpp
--- a/OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -23,6 +23,7 @@
 
 #include "AnnotationsSceneLayer.h"
 
+#include "../Scene2DViewport/BaseFlexiblePointerTracker.h"
 #include "MacroSceneLayer.h"
 #include "PolylineSceneLayer.h"
 #include "TextSceneLayer.h"
@@ -1028,7 +1029,7 @@
   };
 
     
-  class AnnotationsSceneLayer::EditPrimitiveTracker : public IFlexiblePointerTracker
+  class AnnotationsSceneLayer::EditPrimitiveTracker : public BaseFlexiblePointerTracker
   {
   private:
     AnnotationsSceneLayer&  that_;
@@ -2360,7 +2361,7 @@
   };
 
   
-  class AnnotationsSceneLayer::CreateTwoHandlesTracker : public IFlexiblePointerTracker
+  class AnnotationsSceneLayer::CreateTwoHandlesTracker : public BaseFlexiblePointerTracker
   {
   private:
     AnnotationsSceneLayer&  layer_;
@@ -2445,7 +2446,7 @@
   };
 
 
-  class AnnotationsSceneLayer::CreateAngleTracker : public IFlexiblePointerTracker
+  class AnnotationsSceneLayer::CreateAngleTracker : public BaseFlexiblePointerTracker
   {
   private:
     AnnotationsSceneLayer&  that_;
@@ -2545,7 +2546,7 @@
   };
 
 
-  class AnnotationsSceneLayer::CreatePixelProbeTracker : public IFlexiblePointerTracker
+  class AnnotationsSceneLayer::CreatePixelProbeTracker : public BaseFlexiblePointerTracker
   {
   public:
     CreatePixelProbeTracker(AnnotationsSceneLayer& that,
@@ -2618,7 +2619,7 @@
 
   // Dummy tracker that is only used for deletion, in order to warn
   // the caller that the mouse action was taken into consideration
-  class AnnotationsSceneLayer::RemoveTracker : public IFlexiblePointerTracker
+  class AnnotationsSceneLayer::RemoveTracker : public BaseFlexiblePointerTracker
   {
   public:
     RemoveTracker()
--- a/OrthancStone/Sources/Scene2D/GrayscaleWindowingSceneTracker.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2D/GrayscaleWindowingSceneTracker.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -83,9 +83,9 @@
       {
         accessor.GetLayer().SetCustomWindowing(center, width);
 
-        if (observer_.get() != NULL)
+        if (HasObserver())
         {
-          observer_->HandleGrayscaleWindowingChange(Windowing(center, width));
+          GetObserver().HandleGrayscaleWindowingChange(Windowing(center, width));
         }
 
         return true;
@@ -129,18 +129,6 @@
     }
   }
 
-  void GrayscaleWindowingSceneTracker::SetObserver(IObserver* observer)
-  {
-    if (observer == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
-    }
-    else
-    {
-      observer_.reset(observer);
-    }
-  }
-  
   bool GrayscaleWindowingSceneTracker::PointerMove(const PointerEvent& event,
                                                    Scene2D& scene)
   {
--- a/OrthancStone/Sources/Scene2D/GrayscaleWindowingSceneTracker.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2D/GrayscaleWindowingSceneTracker.h	Tue Oct 06 22:04:34 2026 +0200
@@ -26,33 +26,13 @@
 #include "../Scene2DViewport/OneGesturePointerTracker.h"
 #include "../Toolbox/Windowing.h"
 #include "../Viewport/IViewport.h"
+#include "../Viewport/IViewportInteractor.h"
 
 
 namespace OrthancStone
 {
   class GrayscaleWindowingSceneTracker : public OneGesturePointerTracker
   {
-  public:
-    class IObserver : public boost::noncopyable
-    {
-    public:
-      virtual ~IObserver()
-      {
-      }
-
-      virtual void HandleGrayscaleWindowingChange(const Windowing& windowing) = 0;
-    };
-
-    class IObserverFactory : public boost::noncopyable
-    {
-    public:
-      virtual ~IObserverFactory()
-      {
-      }
-
-      virtual IObserver* Create() = 0;
-    };
-
   private:
     bool    active_;
     int     layerIndex_;
@@ -62,8 +42,6 @@
     double  clickX_;
     double  clickY_;
 
-    std::unique_ptr<IObserver>  observer_;
-
     bool SetWindowing(Scene2D& scene,
                       float center,
                       float width);
@@ -75,8 +53,6 @@
                                    unsigned int canvasWidth,
                                    unsigned int canvasHeight);
 
-    void SetObserver(IObserver* observer /* takes ownership */);
-
     virtual bool PointerMove(const PointerEvent& event,
                              Scene2D& scene) ORTHANC_OVERRIDE;
     
--- a/OrthancStone/Sources/Scene2D/PinchZoomTracker.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2D/PinchZoomTracker.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -110,6 +110,11 @@
         AffineTransform2D::CreateScaling(zoom),
         AffineTransform2D::CreateOffset(-pivot_.GetX(), -pivot_.GetY())));
 
+    if (HasObserver())
+    {
+      GetObserver().HandleSceneTransformChange(scene);
+    }
+
     return true;
   }
 }
--- a/OrthancStone/Sources/Scene2D/PinchZoomTracker.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2D/PinchZoomTracker.h	Tue Oct 06 22:04:34 2026 +0200
@@ -23,14 +23,14 @@
 
 #pragma once
 
-#include "../Scene2DViewport/IFlexiblePointerTracker.h"
+#include "../Scene2DViewport/BaseFlexiblePointerTracker.h"
 #include "../Viewport/IViewport.h"
 #include "PointerEvent.h"
 
 
 namespace OrthancStone
 {
-  class PinchZoomTracker : public IFlexiblePointerTracker
+  class PinchZoomTracker : public BaseFlexiblePointerTracker
   {
   private:
     enum State
--- a/OrthancStone/Sources/Scene2D/RotateSceneTracker.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2D/RotateSceneTracker.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -60,6 +60,12 @@
                                           AffineTransform2D::CreateRotation(a - referenceAngle_),
                                           originalSceneToCanvas_));
         aligner_->Apply(scene);
+
+        if (HasObserver())
+        {
+          GetObserver().HandleSceneTransformChange(scene);
+        }
+
         return true;
       }
     }
@@ -71,5 +77,10 @@
   void RotateSceneTracker::Cancel(Scene2D& scene)
   {
     scene.SetSceneToCanvasTransform(originalSceneToCanvas_);
+
+    if (HasObserver())
+    {
+      GetObserver().HandleSceneTransformChange(scene);
+    }
   }
 }
--- a/OrthancStone/Sources/Scene2D/ZoomSceneTracker.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2D/ZoomSceneTracker.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -76,6 +76,12 @@
                                         AffineTransform2D::CreateScaling(zoom, zoom),
                                         originalSceneToCanvas_));
       aligner_->Apply(scene);
+
+      if (HasObserver())
+      {
+        GetObserver().HandleSceneTransformChange(scene);
+      }
+
       return true;
     }
 
@@ -86,5 +92,10 @@
   void ZoomSceneTracker::Cancel(Scene2D& scene)
   {
     scene.SetSceneToCanvasTransform(originalSceneToCanvas_);
+
+    if (HasObserver())
+    {
+      GetObserver().HandleSceneTransformChange(scene);
+    }
   }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Scene2DViewport/BaseFlexiblePointerTracker.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -0,0 +1,55 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "BaseFlexiblePointerTracker.h"
+
+#include <OrthancException.h>
+
+
+namespace OrthancStone
+{
+  IFlexiblePointerTracker::IObserver& BaseFlexiblePointerTracker::GetObserver() const
+  {
+    if (observer_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *observer_;
+    }
+  }
+
+
+  void BaseFlexiblePointerTracker::SetObserver(IObserver* observer)
+  {
+    if (observer == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+    else
+    {
+      observer_.reset(observer);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancStone/Sources/Scene2DViewport/BaseFlexiblePointerTracker.h	Tue Oct 06 22:04:34 2026 +0200
@@ -0,0 +1,49 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2023 Osimis S.A., Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IFlexiblePointerTracker.h"
+
+#include <Compatibility.h>
+
+
+namespace OrthancStone
+{
+  class BaseFlexiblePointerTracker : public IFlexiblePointerTracker
+  {
+  private:
+    std::unique_ptr<IObserver>  observer_;
+
+  protected:
+    bool HasObserver() const
+    {
+      return observer_.get() != NULL;
+    }
+
+    IObserver& GetObserver() const;
+
+  public:
+    virtual void SetObserver(IObserver* observer) ORTHANC_OVERRIDE;
+  };
+}
--- a/OrthancStone/Sources/Scene2DViewport/IFlexiblePointerTracker.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2DViewport/IFlexiblePointerTracker.h	Tue Oct 06 22:04:34 2026 +0200
@@ -26,6 +26,8 @@
 #include "PredeclaredTypes.h"
 
 #include "../Scene2D/PointerEvent.h"
+#include "../Scene2D/Scene2D.h"
+#include "../Toolbox/Windowing.h"
 
 
 namespace OrthancStone
@@ -40,7 +42,22 @@
   class IFlexiblePointerTracker : public boost::noncopyable
   {
   public:
-    virtual ~IFlexiblePointerTracker() {}
+    class IObserver : public boost::noncopyable
+    {
+    public:
+      virtual ~IObserver()
+      {
+      }
+
+      virtual void HandleGrayscaleWindowingChange(const Windowing& windowing) = 0;
+
+      virtual void HandleSceneTransformChange(const Scene2D& scene) = 0;
+    };
+
+
+    virtual ~IFlexiblePointerTracker()
+    {
+    }
 
     /**
     This method will be repeatedly called during user interaction
@@ -83,5 +100,7 @@
     tracker lifetime, it must be restored to its initial value
     */
     virtual void Cancel(Scene2D& scene) = 0;   // TODO Refactoring - Seems unused
+
+    virtual void SetObserver(IObserver* observer /* takes ownership */) = 0;
   };
 }
--- a/OrthancStone/Sources/Scene2DViewport/MeasureTrackers.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2DViewport/MeasureTrackers.h	Tue Oct 06 22:04:34 2026 +0200
@@ -22,7 +22,7 @@
 
 #pragma once
 
-#include "IFlexiblePointerTracker.h"
+#include "BaseFlexiblePointerTracker.h"
 #include "../Scene2D/Scene2D.h"
 #include "../Scene2D/PointerEvent.h"
 
@@ -36,7 +36,7 @@
 
 namespace OrthancStone
 {
-  class CreateMeasureTracker : public IFlexiblePointerTracker
+  class CreateMeasureTracker : public BaseFlexiblePointerTracker
   {
   private:
     bool  commitResult_;
@@ -63,7 +63,7 @@
   };
 
 
-  class EditMeasureTracker : public IFlexiblePointerTracker
+  class EditMeasureTracker : public BaseFlexiblePointerTracker
   {
   private:
     ScenePoint2D  originalClickPosition_;
--- a/OrthancStone/Sources/Scene2DViewport/OneGesturePointerTracker.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2DViewport/OneGesturePointerTracker.h	Tue Oct 06 22:04:34 2026 +0200
@@ -22,12 +22,8 @@
 
 #pragma once
 
-#include "IFlexiblePointerTracker.h"
+#include "BaseFlexiblePointerTracker.h"
 
-#include <Compatibility.h>  // For ORTHANC_OVERRIDE
-
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
 
 namespace OrthancStone
 {
@@ -43,7 +39,7 @@
   - implement Cancel to restore the state at initial tracker creation time
 
   */
-  class OneGesturePointerTracker : public IFlexiblePointerTracker
+  class OneGesturePointerTracker : public BaseFlexiblePointerTracker
   {
   private:
     bool  alive_;
--- a/OrthancStone/Sources/Scene2DViewport/ViewportController.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Scene2DViewport/ViewportController.h	Tue Oct 06 22:04:34 2026 +0200
@@ -87,7 +87,7 @@
   public:
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \
                                         SceneTransformChanged, \
-                                        ViewportController);
+                                        ViewportController);  // TODO Refactoring - Remove this
 
     explicit ViewportController(const boost::shared_ptr<IViewport>& viewport);
 
--- a/OrthancStone/Sources/Viewport/DefaultViewportInteractor.cpp	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Viewport/DefaultViewportInteractor.cpp	Tue Oct 06 22:04:34 2026 +0200
@@ -54,15 +54,7 @@
         if (scene.HasLayer(windowingLayer_) &&
             scene.GetLayer(windowingLayer_).GetType() == ISceneLayer::Type_FloatTexture)
         {
-          std::unique_ptr<GrayscaleWindowingSceneTracker> tracker(
-            new GrayscaleWindowingSceneTracker(scene, windowingLayer_, event, viewportWidth, viewportHeight));
-
-          if (grayscaleFactory_.get() != NULL)
-          {
-            tracker->SetObserver(grayscaleFactory_->Create());
-          }
-
-          return tracker.release();
+          return new GrayscaleWindowingSceneTracker(scene, windowingLayer_, event, viewportWidth, viewportHeight);
         }
         else
         {
@@ -92,6 +84,8 @@
     unsigned int                viewportWidth,
     unsigned int                viewportHeight)
   {
+    std::unique_ptr<IFlexiblePointerTracker> tracker;
+
     MouseAction action;
     
     switch (event.GetMouseButton())
@@ -112,7 +106,7 @@
         if (event.GetPositionsCount() == 1 ||
             event.GetPositionsCount() == 2)
         {
-          return new PinchZoomTracker(scene, event);
+          tracker.reset(new PinchZoomTracker(scene, event));
         }
         else
         {
@@ -123,7 +117,17 @@
         return NULL;
     }
 
-    return CreateTrackerInternal(scene, action, event, viewportWidth, viewportHeight);
+    if (tracker.get() == NULL)
+    {
+      tracker.reset(CreateTrackerInternal(scene, action, event, viewportWidth, viewportHeight));
+    }
+
+    if (observerFactory_.get() != NULL)
+    {
+      tracker->SetObserver(observerFactory_->CreateObserver());
+    }
+
+    return tracker.release();
   }
 
 
@@ -135,7 +139,7 @@
   }
 
 
-  void DefaultViewportInteractor::SetGrayscaleWindowingObserverFactory(GrayscaleWindowingSceneTracker::IObserverFactory* factory)
+  void DefaultViewportInteractor::SetObserverFactory(IObserverFactory* factory)
   {
     if (factory == NULL)
     {
@@ -143,7 +147,7 @@
     }
     else
     {
-      grayscaleFactory_.reset(factory);
+      observerFactory_.reset(factory);
     }
   }
 }
--- a/OrthancStone/Sources/Viewport/DefaultViewportInteractor.h	Tue Oct 06 21:05:36 2026 +0200
+++ b/OrthancStone/Sources/Viewport/DefaultViewportInteractor.h	Tue Oct 06 22:04:34 2026 +0200
@@ -22,15 +22,27 @@
 
 #pragma once
 
-#include "../Scene2D/GrayscaleWindowingSceneTracker.h"
 #include "IViewportInteractor.h"
 
 #include <Compatibility.h>  // For ORTHANC_OVERRIDE
 
+
 namespace OrthancStone
 {
   class DefaultViewportInteractor : public IViewportInteractor
   {
+  public:
+    class IObserverFactory : public boost::noncopyable
+    {
+    public:
+      virtual ~IObserverFactory()
+      {
+      }
+
+      virtual IFlexiblePointerTracker::IObserver* CreateObserver() = 0;
+    };
+
+
   private:
     // Index of the layer whose windowing is altered by grayscale windowing action
     int          windowingLayer_;
@@ -38,7 +50,7 @@
     MouseAction  middleButtonAction_;
     MouseAction  rightButtonAction_;
 
-    std::unique_ptr<GrayscaleWindowingSceneTracker::IObserverFactory>  grayscaleFactory_;
+    std::unique_ptr<IObserverFactory>  observerFactory_;
 
     IFlexiblePointerTracker* CreateTrackerInternal(Scene2D& scene,
                                                    MouseAction action,
@@ -108,6 +120,6 @@
     virtual void HandleMouseHover(Scene2D& scene,
                                   const PointerEvent& event) ORTHANC_OVERRIDE;
 
-    void SetGrayscaleWindowingObserverFactory(GrayscaleWindowingSceneTracker::IObserverFactory* factory /* takes ownership */);
+    void SetObserverFactory(IObserverFactory* factory /* takes ownership */);
   };
 }