changeset 1561:cf652990abb1

tunable mouse actions
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 20 Aug 2020 17:44:35 +0200
parents b4ccd4963d37
children 2a4a6b967053
files Applications/StoneWebViewer/WebApplication/app.js Applications/StoneWebViewer/WebApplication/index.html Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/StoneEnumerations.h OrthancStone/Sources/Viewport/DefaultViewportInteractor.cpp OrthancStone/Sources/Viewport/DefaultViewportInteractor.h
diffstat 6 files changed, 275 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/Applications/StoneWebViewer/WebApplication/app.js	Thu Aug 20 13:57:52 2020 +0200
+++ b/Applications/StoneWebViewer/WebApplication/app.js	Thu Aug 20 17:44:35 2020 +0200
@@ -104,6 +104,7 @@
       leftMode: 'grid',   // Can be 'small', 'grid' or 'full'
       leftVisible: true,
       viewportLayoutButtonsVisible: false,
+      mouseActionsVisible: false,
       activeViewport: 0,
       showInfo: true,
       showReferenceLines: true,
@@ -193,7 +194,7 @@
     }
   },
   methods: {
-    FitContent() {
+    FitContent: function() {
       // This function can be used even if WebAssembly is not initialized yet
       if (typeof stone._AllViewportsUpdateSize !== 'undefined') {
         this.$nextTick(function () {
@@ -202,7 +203,7 @@
       }
     },
     
-    GetActiveSeries() {
+    GetActiveSeries: function() {
       var s = [];
 
       if ('tags' in this.viewport1Series)
@@ -220,7 +221,7 @@
       return s;
     },
 
-    GetActiveCanvas() {
+    GetActiveCanvas: function() {
       if (this.activeViewport == 1) {
         return 'canvas1';
       }
@@ -445,48 +446,57 @@
       }
     },
 
-    SetWindowing(center, width) {
+    SetWindowing: function(center, width) {
       var canvas = this.GetActiveCanvas();
       if (canvas != '') {
         stone.SetWindowing(canvas, center, width);
       }
     },
 
-    SetDefaultWindowing() {
+    SetDefaultWindowing: function() {
       var canvas = this.GetActiveCanvas();
       if (canvas != '') {
         stone.SetDefaultWindowing(canvas);
       }
     },
 
-    InvertContrast() {
+    InvertContrast: function() {
       var canvas = this.GetActiveCanvas();
       if (canvas != '') {
         stone.InvertContrast(canvas);
       }
     },
 
-    FlipX() {
+    FlipX: function() {
       var canvas = this.GetActiveCanvas();
       if (canvas != '') {
         stone.FlipX(canvas);
       }
     },
 
-    FlipY() {
+    FlipY: function() {
       var canvas = this.GetActiveCanvas();
       if (canvas != '') {
         stone.FlipY(canvas);
       }
     },
 
-    ApplyPreferences() {
+    ApplyPreferences: function() {
       this.modalPreferences = false;
 
       if ((stone.IsSoftwareRendering() != 0) != this.settingSoftwareRendering) {
         document.location.reload();
       }
-    }
+    },
+
+    HideAllTooltips: function() {
+      $('[data-toggle="tooltip"]').tooltip('hide');
+    },
+
+    SetMouseButtonActions: function(left, middle, right) {
+      this.mouseActionsVisible = false;
+      stone.SetMouseButtonActions(left, middle, right);
+    }    
   },
   
   mounted: function() {
@@ -609,7 +619,11 @@
 
 $(document).ready(function() {
   // Enable support for tooltips in Bootstrap
-  //$('[data-toggle="tooltip"]').tooltip();
+  $('[data-toggle="tooltip"]').tooltip({
+    placement: 'bottom',
+    container: 'body',
+    trigger: 'hover'
+  });
 
   //app.modalWarning = true;
 
--- a/Applications/StoneWebViewer/WebApplication/index.html	Thu Aug 20 13:57:52 2020 +0200
+++ b/Applications/StoneWebViewer/WebApplication/index.html	Thu Aug 20 17:44:35 2020 +0200
@@ -255,7 +255,8 @@
                 <div class="tbGroup__toggl">
                   <button class="wvButton"
                           v-bind:class="{ 'wvButton--underline' : !viewportLayoutButtonsVisible }"
-                          @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible">
+                          data-toggle="tooltip" data-title="Change layout"
+                          @click="viewportLayoutButtonsVisible = !viewportLayoutButtonsVisible;HideAllTooltips()">
                     <i class="fa fa-th"></i>
                   </button>
                 </div>
@@ -285,6 +286,46 @@
               </div>
             </div>
 
+
+            <div class="ng-scope inline-object">
+              <div class="tbGroup">
+                <div class="tbGroup__toggl">
+                  <button class="wvButton"
+                          v-bind:class="{ 'wvButton--underline' : !mouseActionsVisible }"
+                          data-toggle="tooltip" data-title="Mouse actions"
+                          @click="mouseActionsVisible = !mouseActionsVisible;HideAllTooltips()">
+                    <i class="fa fa-mouse-pointer"></i>
+                  </button>
+                </div>
+                
+                <div class="tbGroup__buttons--bottom" v-show="mouseActionsVisible">
+                  <div class="inline-object">
+                    <button class="wvButton"
+                            data-toggle="tooltip" data-title="Combined tool"
+                            @click="SetMouseButtonActions(stone.MouseAction.GRAYSCALE_WINDOWING, stone.MouseAction.PAN, stone.MouseAction.ZOOM)">
+                      <i class="far fa-hand-point-up"></i>
+                    </button>
+                  </div>
+                  <div class="inline-object">
+                    <button class="wvButton"
+                            data-toggle="tooltip" data-title="Zoom"
+                            @click="SetMouseButtonActions(stone.MouseAction.ZOOM, stone.MouseAction.ZOOM, stone.MouseAction.ZOOM)">
+                      <i class="fas fa-search"></i>
+                    </button>
+                  </div>
+                  <div class="inline-object">
+                    <button class="wvButton"
+                            data-toggle="tooltip" data-title="Pan"
+                            @click="SetMouseButtonActions(stone.MouseAction.PAN, stone.MouseAction.PAN, stone.MouseAction.PAN)">
+                      <i class="fas fa-arrows-alt"></i>
+                    </button>
+                  </div>
+                </div>
+              </div>
+            </div>
+
+
+
             <!--div class="ng-scope inline-object">
               <button class="wvButton--underline text-center active">
                 <i class="fa fa-hand-pointer-o"></i>
@@ -305,31 +346,39 @@
 
             <div class="ng-scope inline-object">
               <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Invert contrast"
                       v-on:click="InvertContrast()">
                 <i class="fa fa-adjust"></i>
               </button>
             </div>
             
             <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center" id="windowing-popover">
+              <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Change windowing"
+                      id="windowing-popover">
                 <i class="fa fa-sun"></i>
               </button>
             </div>
 
             <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center" v-on:click="FlipX()">
+              <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Flip horizontally"
+                      v-on:click="FlipX()">
                 <i class="fas fa-exchange-alt"></i>
               </button>
             </div>
 
             <div class="ng-scope inline-object">
-              <button class="wvButton--underline text-center" v-on:click="FlipY()">
+              <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Flip vertically"
+                      v-on:click="FlipY()">
                 <i class="fas fa-exchange-alt fa-rotate-90"></i>
               </button>
             </div>
             
             <div class="ng-scope inline-object">
               <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Show image information"
                       v-bind:class="{ 'active' : showInfo }"
                       v-on:click="showInfo = !showInfo">
                 <i class="fa fa-info-circle"></i>
@@ -338,6 +387,7 @@
 
             <div class="ng-scope inline-object">
               <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="Reference lines"
                       v-bind:class="{ 'active' : showReferenceLines }"
                       v-on:click="showReferenceLines = !showReferenceLines">
                 <i class="fa fa-bars"></i>
@@ -346,6 +396,7 @@
 
             <div class="ng-scope inline-object">
               <button class="wvButton--underline text-center"
+                      data-toggle="tooltip" data-title="User preferences"
                       v-on:click="modalPreferences = true">
                 <i class="fa fa-user"></i>
               </button>
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Thu Aug 20 13:57:52 2020 +0200
+++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp	Thu Aug 20 17:44:35 2020 +0200
@@ -79,6 +79,7 @@
 #include <Toolbox/DicomInstanceParameters.h>
 #include <Toolbox/GeometryToolbox.h>
 #include <Toolbox/SortedFrames.h>
+#include <Viewport/DefaultViewportInteractor.h>
 #include <Viewport/WebAssemblyCairoViewport.h>
 #include <Viewport/WebGLViewport.h>
 
@@ -99,10 +100,19 @@
 
 enum EMSCRIPTEN_KEEPALIVE DisplayedFrameQuality
 {
-DisplayedFrameQuality_None,
-  DisplayedFrameQuality_Low,
-  DisplayedFrameQuality_High
-  };
+  DisplayedFrameQuality_None,
+    DisplayedFrameQuality_Low,
+    DisplayedFrameQuality_High
+    };
+
+
+enum EMSCRIPTEN_KEEPALIVE MouseAction
+{
+  MouseAction_GrayscaleWindowing,
+    MouseAction_Zoom,
+    MouseAction_Pan,
+    MouseAction_Rotate
+    };
   
 
 
@@ -1372,6 +1382,27 @@
     }
   }
 
+
+  void SaveCurrentWindowing()
+  {
+    // Save the current windowing (that could have been altered by
+    // GrayscaleWindowingSceneTracker), so that it can be reused
+    // by the next frames
+
+    std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
+
+    OrthancStone::Scene2D& scene = lock->GetController().GetScene();
+
+    if (scene.HasLayer(LAYER_TEXTURE) &&
+        scene.GetLayer(LAYER_TEXTURE).GetType() == OrthancStone::ISceneLayer::Type_FloatTexture)
+    {
+      OrthancStone::FloatTextureSceneLayer& layer =
+        dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(scene.GetLayer(LAYER_TEXTURE));
+      layer.GetWindowing(windowingCenter_, windowingWidth_);
+    }
+  }
+  
+
   bool DisplayFrame(unsigned int& quality,
                     size_t index)
   {
@@ -1386,22 +1417,7 @@
     FramesCache::Accessor accessor(*cache_, sopInstanceUid, frame);
     if (accessor.IsValid())
     {
-      {
-        std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
-
-        OrthancStone::Scene2D& scene = lock->GetController().GetScene();
-
-        // Save the current windowing (that could have been altered by
-        // GrayscaleWindowingSceneTracker), so that it can be reused
-        // by the next frames
-        if (scene.HasLayer(LAYER_TEXTURE) &&
-            scene.GetLayer(LAYER_TEXTURE).GetType() == OrthancStone::ISceneLayer::Type_FloatTexture)
-        {
-          OrthancStone::FloatTextureSceneLayer& layer =
-            dynamic_cast<OrthancStone::FloatTextureSceneLayer&>(scene.GetLayer(LAYER_TEXTURE));
-          layer.GetWindowing(windowingCenter_, windowingWidth_);
-        }
-      }
+      SaveCurrentWindowing();
       
       quality = accessor.GetQuality();
 
@@ -1842,12 +1858,14 @@
   void FlipX()
   {
     flipX_ = !flipX_;
+    SaveCurrentWindowing();
     UpdateCurrentTextureParameters();
   }
 
   void FlipY()
   {
     flipY_ = !flipY_;
+    SaveCurrentWindowing();
     UpdateCurrentTextureParameters();
   }
 
@@ -1873,6 +1891,21 @@
       }
     }
   }
+
+  void SetMouseButtonActions(OrthancStone::MouseAction leftAction,
+                             OrthancStone::MouseAction middleAction,
+                             OrthancStone::MouseAction rightAction)
+  {
+    std::unique_ptr<OrthancStone::DefaultViewportInteractor> interactor(
+      new OrthancStone::DefaultViewportInteractor);
+    
+    interactor->SetLeftButtonAction(leftAction);
+    interactor->SetMiddleButtonAction(middleAction);
+    interactor->SetRightButtonAction(rightAction);
+
+    assert(viewport_ != NULL);
+    viewport_->AcquireInteractor(interactor.release());
+  }
 };
 
 
@@ -1981,7 +2014,9 @@
 static boost::shared_ptr<OrthancStone::WebAssemblyLoadersContext> context_;
 static std::string stringBuffer_;
 static bool softwareRendering_ = false;
-
+static OrthancStone::MouseAction leftButtonAction_ = OrthancStone::MouseAction_GrayscaleWindowing;
+static OrthancStone::MouseAction middleButtonAction_ = OrthancStone::MouseAction_Pan;
+static OrthancStone::MouseAction rightButtonAction_ = OrthancStone::MouseAction_Zoom;
 
 
 static void FormatTags(std::string& target,
@@ -2027,6 +2062,7 @@
     std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_->Lock());
     boost::shared_ptr<ViewerViewport> viewport(
       ViewerViewport::Create(*lock, source_, canvas, cache_, softwareRendering_));
+    viewport->SetMouseButtonActions(leftButtonAction_, middleButtonAction_, rightButtonAction_);
     viewport->AcquireObserver(new WebAssemblyObserver);
     allViewports_[canvas] = viewport;
     return viewport;
@@ -2038,6 +2074,30 @@
 }
 
 
+
+static OrthancStone::MouseAction ConvertMouseAction(int action)
+{
+  switch (action)
+  {
+    case MouseAction_GrayscaleWindowing:
+      return OrthancStone::MouseAction_GrayscaleWindowing;
+      
+    case MouseAction_Zoom:
+      return OrthancStone::MouseAction_Zoom;
+      
+    case MouseAction_Pan:
+      return OrthancStone::MouseAction_Pan;
+      
+    case MouseAction_Rotate:
+      return OrthancStone::MouseAction_Rotate;
+
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+  }
+}
+
+
+
 extern "C"
 {
   int main(int argc, char const *argv[]) 
@@ -2377,4 +2437,25 @@
   {
     return softwareRendering_;
   }  
+
+
+  EMSCRIPTEN_KEEPALIVE
+  void SetMouseButtonActions(int leftAction,
+                             int middleAction,
+                             int rightAction)
+  {
+    try
+    {
+      leftButtonAction_ = ConvertMouseAction(leftAction);
+      middleButtonAction_ = ConvertMouseAction(middleAction);
+      rightButtonAction_ = ConvertMouseAction(rightAction);
+      
+      for (Viewports::iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
+      {
+        assert(it->second != NULL);
+        it->second->SetMouseButtonActions(leftButtonAction_, middleButtonAction_, rightButtonAction_);
+      }
+    }
+    EXTERN_CATCH_EXCEPTIONS;
+  }
 }
--- a/OrthancStone/Sources/StoneEnumerations.h	Thu Aug 20 13:57:52 2020 +0200
+++ b/OrthancStone/Sources/StoneEnumerations.h	Thu Aug 20 17:44:35 2020 +0200
@@ -132,6 +132,14 @@
     SliceAction_FastMinus
   };
 
+  enum MouseAction
+  {
+    MouseAction_Pan,
+    MouseAction_Zoom,
+    MouseAction_Rotate,
+    MouseAction_GrayscaleWindowing
+  };
+
   SopClassUid StringToSopClassUid(const std::string& source);
 
   void ComputeWindowing(float& targetCenter,
--- a/OrthancStone/Sources/Viewport/DefaultViewportInteractor.cpp	Thu Aug 20 13:57:52 2020 +0200
+++ b/OrthancStone/Sources/Viewport/DefaultViewportInteractor.cpp	Thu Aug 20 17:44:35 2020 +0200
@@ -25,30 +25,64 @@
 #include "../Scene2D/RotateSceneTracker.h"
 #include "../Scene2D/ZoomSceneTracker.h"
 
+#include <OrthancException.h>
+
 namespace OrthancStone
 {
+  IFlexiblePointerTracker* DefaultViewportInteractor::CreateTrackerInternal(
+    boost::shared_ptr<IViewport> viewport,
+    MouseAction action,
+    const PointerEvent& event,
+    unsigned int viewportWidth,
+    unsigned int viewportHeight)
+  {
+    switch (action)
+    {
+      case MouseAction_Rotate:
+        return new RotateSceneTracker(viewport, event);
+
+      case MouseAction_GrayscaleWindowing:
+        return new GrayscaleWindowingSceneTracker(
+          viewport, windowingLayer_, event, viewportWidth, viewportHeight);
+
+      case MouseAction_Pan:
+        return new PanSceneTracker(viewport, event);
+      
+      case MouseAction_Zoom:
+        return new ZoomSceneTracker(viewport, event, viewportHeight);
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
   IFlexiblePointerTracker* DefaultViewportInteractor::CreateTracker(
     boost::shared_ptr<IViewport>  viewport,
     const PointerEvent&           event,
     unsigned int                  viewportWidth,
     unsigned int                  viewportHeight)
   {
+    MouseAction action;
+    
     switch (event.GetMouseButton())
     {
       case MouseButton_Left:
-        //return new RotateSceneTracker(viewport, event);
-
-        return new GrayscaleWindowingSceneTracker(
-          viewport, windowingLayer_, event, viewportWidth, viewportHeight);
+        action = leftButtonAction_;
+        break;
 
       case MouseButton_Middle:
-        return new PanSceneTracker(viewport, event);
+        action = middleButtonAction_;
+        break;
       
       case MouseButton_Right:
-        return new ZoomSceneTracker(viewport, event, viewportHeight);
+        action = rightButtonAction_;
+        break;
 
       default:
         return NULL;
     }
+
+    return CreateTrackerInternal(viewport, action, event, viewportWidth, viewportHeight);
   }
 }
--- a/OrthancStone/Sources/Viewport/DefaultViewportInteractor.h	Thu Aug 20 13:57:52 2020 +0200
+++ b/OrthancStone/Sources/Viewport/DefaultViewportInteractor.h	Thu Aug 20 17:44:35 2020 +0200
@@ -27,13 +27,24 @@
   class DefaultViewportInteractor : public IViewportInteractor
   {
   private:
-    // Index of the layer whose windowing is altered by clicking the
-    // left mouse button
-    int  windowingLayer_;
+    // Index of the layer whose windowing is altered by grayscale windowing action
+    int          windowingLayer_;
+    MouseAction  leftButtonAction_;
+    MouseAction  middleButtonAction_;
+    MouseAction  rightButtonAction_;
+
+    IFlexiblePointerTracker* CreateTrackerInternal(boost::shared_ptr<IViewport> viewport,
+                                                   MouseAction action,
+                                                   const PointerEvent& event,
+                                                   unsigned int viewportWidth,
+                                                   unsigned int viewportHeight);
     
   public:
     DefaultViewportInteractor() :
-      windowingLayer_(0)
+      windowingLayer_(0),
+      leftButtonAction_(MouseAction_GrayscaleWindowing),
+      middleButtonAction_(MouseAction_Pan),
+      rightButtonAction_(MouseAction_Zoom)
     {
     }
 
@@ -46,6 +57,36 @@
     {
       windowingLayer_ = layerIndex;
     }
+
+    MouseAction GetLeftButtonAction() const
+    {
+      return leftButtonAction_;
+    }
+
+    void SetLeftButtonAction(MouseAction action)
+    {
+      leftButtonAction_ = action;
+    }
+
+    MouseAction GetMiddleButtonAction() const
+    {
+      return middleButtonAction_;
+    }
+
+    void SetMiddleButtonAction(MouseAction action)
+    {
+      middleButtonAction_ = action;
+    }
+
+    MouseAction GetRightButtonAction() const
+    {
+      return rightButtonAction_;
+    }
+
+    void SetRightButtonAction(MouseAction action)
+    {
+      rightButtonAction_ = action;
+    }
     
     virtual IFlexiblePointerTracker* CreateTracker(boost::shared_ptr<IViewport> viewport,
                                                    const PointerEvent& event,