From 61cebc628bca282d60778ec2e95395092d38ede1 Mon Sep 17 00:00:00 2001
From: Bruce Toll <btoll@dhsus.com>
Date: Tue, 4 Aug 2015 11:06:37 -0400
Subject: [PATCH] Enable config of wheel mouse on interactive chart.

Support user-configurable mapping of wheel mouse modifiers to actions on
interactive charts using new setWheelActions() method.
---
 examples/charts/ChartsExample.C | 12 +++++++++
 src/Wt/Chart/WCartesianChart    | 60 ++++++++++++++++++++++++++++++++++++++++-
 src/Wt/Chart/WCartesianChart.C  | 28 ++++++++++++++++++-
 src/js/WCartesianChart.js       | 21 ++++++++++-----
 4 files changed, 113 insertions(+), 8 deletions(-)

diff --git a/examples/charts/ChartsExample.C b/examples/charts/ChartsExample.C
index 97e3045..0346166 100644
--- a/examples/charts/ChartsExample.C
+++ b/examples/charts/ChartsExample.C
@@ -308,6 +308,18 @@ ScatterPlotExample::ScatterPlotExample(WContainerWidget *parent):
   chart->setZoomEnabled(true);
   chart->setPanEnabled(true);
   chart->setCrosshairEnabled(true);
+  /*
+   * Set custom wheel actions for interactive chart.
+   */
+  WheelActions actions;
+  actions[ModNone] = ZoomXY;
+  actions[ModCtrl] = ZoomX;
+  chart->setWheelActions(actions);
+  /*
+   * NOTE: With C++11, the above can also be achieved with:
+   *
+   * chart->setWheelActions({{ModNone, ZoomXY}, {ModCtrl, ZoomX}});
+   */
 
   chart->setBackground(WColor(200,200,200));
 
diff --git a/src/Wt/Chart/WCartesianChart b/src/Wt/Chart/WCartesianChart
index 379318e..49eaadd 100644
--- a/src/Wt/Chart/WCartesianChart
+++ b/src/Wt/Chart/WCartesianChart
@@ -112,6 +112,48 @@ private:
 class WChart2DImplementation;
 class WChartPalette;
 
+/*! \brief Enumeration of mouse wheel modifiers for interactive charts.
+ *
+ * \sa WCartesianChart::WheelActions
+ * \sa WCartesianChart::setWheelActions(WheelActions)
+ *
+ * \ingroup charts
+ */
+enum WheelModifier {
+  ModNone,              //!< No Modifiers
+  ModShift,             //!< Shift Key
+  ModCtrl,              //!< Ctrl Key
+  ModCtrlShift,         //!< Ctrl + Shift Keys
+  ModAlt,               //!< Alt Key
+  ModAltShift,          //!< Alt + Shift Keys
+  ModAltCtrl,           //!< Alt + Ctrl Keys
+  ModAltCtrlShift       //!< Alt + Ctrl + Shift Keys
+};
+
+/*! \brief Enumeration of mouse wheel actions for interactive charts.
+ *
+ * \sa WCartesianChart::WheelActions
+ * \sa WCartesianChart::setWheelActions(WheelActions)
+ *
+ * \ingroup charts
+ */
+enum InteractiveAction {
+  ZoomX,                //!< Zoom x-axis
+  ZoomY,                //!< Zoom y-axis
+  ZoomXY,               //!< Zoom along both x and y-axes
+  PanX,                 //!< Pan x-axis
+  PanY                  //!< Pan y-axis
+};
+
+/*! \brief A map of mouse wheel actions for interactive charts, indexed by WheelModifier
+ *
+ * \sa WCartesianChart::setWheelActions(WheelActions)
+ * \sa WCartesianChart::wheelActions()
+ *
+ * \ingroup charts
+ */
+typedef std::map<WheelModifier, InteractiveAction> WheelActions;
+
 /*! \class WCartesianChart Wt/Chart/WCartesianChart Wt/Chart/WCartesianChart
  *  \brief A cartesian chart.
  *
@@ -691,7 +733,7 @@ public:
    *
    * When using the mouse, press the ctrl key while scrolling to zoom in/out a specific point
    * on the chart. If you press shift+ctrl, it will only zoom vertically. If you press alt+ctrl,
-   * it will only zoom horizontally.
+   * it will only zoom horizontally. To change these default mappings, use setWheelActions().
    *
    * When using touch, you can use a pinch gesture to zoom in/out. If the pinch gesture is
    * vertical/horizontal, it will zoom only vertically/horizontally, otherwise it will zoom
@@ -700,6 +742,7 @@ public:
    * The default value is \c false.
    *
    * \sa zoomEnabled()
+   * \sa setWheelActions(WheelActions)
    */
   void setZoomEnabled(bool zoom = true);
 
@@ -788,6 +831,18 @@ public:
    */
   bool rubberBandEffectEnabled() const;
 
+  /*! \brief Set the mapping of mouse wheel actions for interactive charts.
+   *
+   * \sa wheelActions()
+   */
+  void setWheelActions(WheelActions wheelActions);
+
+  /*! \brief Return the current mapping of mouse wheel actions for interactive charts.
+   *
+   * \sa setWheelActions()
+   */
+  WheelActions wheelActions() const { return wheelActions_; }
+
   /*! @}
    */
 
@@ -846,7 +901,10 @@ private:
 
   std::vector<WAxisSliderWidget *> axisSliderWidgets_;
 
+  WheelActions wheelActions_;
+
   void init();
+  static std::string wheelActionsToJson(WheelActions wheelActions);
 
 protected:
   virtual void modelColumnsInserted(const WModelIndex& parent,
diff --git a/src/Wt/Chart/WCartesianChart.C b/src/Wt/Chart/WCartesianChart.C
index 0f3fc79..ef0490c 100644
--- a/src/Wt/Chart/WCartesianChart.C
+++ b/src/Wt/Chart/WCartesianChart.C
@@ -867,6 +867,12 @@ void WCartesianChart::init()
     touchEnded().connect("function(o, e){var o=" + this->cObjJsRef() + ";if(o){o.touchEnd(o, e);}}");
     touchMoved().connect("function(o, e){var o=" + this->cObjJsRef() + ";if(o){o.touchMoved(o, e);}}");
   }
+
+  wheelActions_[ModNone] = PanY;
+  wheelActions_[ModAltCtrl] = ZoomX;
+  wheelActions_[ModCtrlShift] = ZoomY;
+  wheelActions_[ModCtrl] = ZoomXY;
+  wheelActions_[ModAltCtrlShift] = ZoomXY;
 }
 
 void WCartesianChart::setOrientation(Orientation orientation)
@@ -1696,7 +1702,8 @@ void WCartesianChart::paintEvent(WPaintDevice *paintDevice)
       if (i != 0) ss << ',';
       ss << '"' << axisSliderWidgets_[i]->id() << '"';
     }
-    ss << "]";
+    ss << "],";
+    ss << "wheelActions:" << wheelActionsToJson(wheelActions_);
     ss << "});";
 
 
@@ -2959,6 +2966,25 @@ bool WCartesianChart::rubberBandEffectEnabled() const
   return rubberBandEnabled_;
 }
 
+std::string WCartesianChart::wheelActionsToJson(WheelActions wheelActions) {
+  std::string wheelJson = "{";
+  for (WheelActions::iterator it = wheelActions.begin(); it != wheelActions.end(); ++it) {
+    if (it != wheelActions.begin())
+      wheelJson += ",";
+    wheelJson += boost::lexical_cast<std::string>(it->first) + ":" +
+      boost::lexical_cast<std::string>(it->second);
+  }
+  wheelJson += "}";
+  return wheelJson;
+}
+
+void WCartesianChart::setWheelActions(WheelActions wheelActions)
+{
+  wheelActions_ = wheelActions;
+
+  updateJSConfig("wheelActions", wheelActionsToJson(wheelActions_));
+}
+
 void WCartesianChart::clearPens()
 {
   for (PenMap::iterator it = pens_.begin();
diff --git a/src/js/WCartesianChart.js b/src/js/WCartesianChart.js
index 7bf8614..029d4e0 100644
--- a/src/js/WCartesianChart.js
+++ b/src/js/WCartesianChart.js
@@ -44,6 +44,8 @@ WT_DECLARE_WT_MEMBER
    var X_ONLY = 1, Y_ONLY = 2; // bit flags
    var X = 0, Y = 1;
    var LOOK_MODE = 0, CROSSHAIR_MODE = 1;
+   var WHEEL_ZOOM_X = 0, WHEEL_ZOOM_Y = 1, WHEEL_ZOOM_XY = 2, WHEEL_PAN_X = 3, WHEEL_PAN_Y = 4;
+
 
    function showCrosshair() {
       return config.crosshair || config.followCurve !== -1;
@@ -568,27 +570,34 @@ WT_DECLARE_WT_MEMBER
    }
 
    this.mouseWheel = function(o, event) {
+      var modifiers = (event.altKey << 2) + (event.ctrlKey << 1) + event.shiftKey;
+      var action = config.wheelActions[modifiers];
+      if (action === undefined) return;
+
       var c = WT.widgetCoordinates(target.canvas, event);
       if (!isPointInRect(c, config.area)) return;
       var w = WT.normalizeWheel(event);
-      if (!event.ctrlKey && config.pan) {
+      if ((action === WHEEL_PAN_X || action === WHEEL_PAN_Y) && config.pan) {
 	 var xBefore = transform(X)[4];
 	 var yBefore = transform(Y)[5];
-	 translate({x:-w.pixelX,y:-w.pixelY});
+	 if (action === WHEEL_PAN_Y)
+	    translate({x:-w.pixelX,y:-w.pixelY});
+	 else
+	    translate({x:-w.pixelY,y:-w.pixelX});
 	 if (xBefore !== transform(X)[4] ||
 	     yBefore !== transform(Y)[5]) {
 	    WT.cancelEvent(event);
 	 }
-      } else if (event.ctrlKey && config.zoom) {
+      } else if (config.zoom) {
 	 WT.cancelEvent(event);
 	 var d = -w.spinY;
 	 // Some browsers scroll horizontally when shift key pressed
 	 if (d === 0) d = -w.spinX;
-	 if (event.shiftKey && !event.altKey) {
+	 if (action === WHEEL_ZOOM_Y) {
 	    zoom(c, 0, d);
-	 } else if (event.altKey && !event.shiftKey) {
+	 } else if (action === WHEEL_ZOOM_X) {
 	    zoom(c, d, 0);
-	 } else {
+	 } else if (action === WHEEL_ZOOM_XY) {
 	    zoom(c, d, d);
 	 }
       }
-- 
1.9.1

