changeset 19697:dfea01b3425f

more mouse interaction features for Qt plotting widget * graphics.in.h, graphics.cc (figure::properties::__mouse_mode__, figure::properties::__pan_mode__, figure::properties::__rotate_mode__, figure::properties::__zoom_mode__): New properties. (figure::properties::set___mouse_mode__): New function. (axes::properties::pan, axes::properties::rotate3d, axes::properties::zoom): New functions. Handle zoom, and pan modes. (axes::properties::clear_zoom_stack): New arg, do_unzoom. Conditionally call unzoom. (axes::properties::unzoom): Also restore view property. (axes::properties::rotate_view): Conditionall save state to zoom stack. (axes::properties::push_zoom_stack): New function. (axes::properties::pan, axes::properties::rotate3d): Delete properties. (axes::properties::update_xlim, axes::properties::update_ylim, axes::properties::update_zlim): Don't clear zoom stack. Delete do_clr_zoom argument. (axes::properties::set_pan, axes::properties::set_rotate3d): Delete. (F__zoom__): New function. * Canvas.h, Canvas.cc (Canvas::toggleAxes, Canvas::toggleGrid): New pure virtual functions. (Canvas::setCursor, Canvas::canvasToggleAxes, Canvas::canvasToggleGrid, Canvas::canvasMouseDoubleClickEvent, Canvas::canvasWheelEvent): New functions. (zoom_enabled, pan_enabled, pan_mode, rotate_enabled, rotate_mode): New static functions. (Canvas::canvasMouseMoveEvent): Call axes::properties::rotate3d to do rotation. Handle panning. (Canvas::canvasMousePressEvent): Also handle "unzoom" action when in pan and rotate modes. (Canvas::canvasMouseReleaseEvent): Zoom by factor if mouse has not moved from mouse press event. * Figure.h, Figure.cc (MouseMode): New enum value, TextMode. (Figure::m_mouseMode, Figure::m_lastMouseMode): Delete member variables. (Figure::mouseMode, Figure::setMouseMode): Get info from and save info to figure properties. (Figure::updateFigureToolBarAndMenuBar, Figure::toggleAxes, Figure::toggleGrid): New functions. (Figure::m_mouseModeGroup): New member variable. (Figure::createFigureToolBarAndMenuBar): Add actions for toggling Axes and Grid. Maintain pointer to MouseModeActionGroup. (mouse_mode_to_string, mouse_mode_from_string): New functions. * GLCanvas.h, GLCanvas.cc (GLCanvas::toggleAxes, GLCanvas::ToggleGrid, GLCanvas::mouseDoubleClickEvent, GLCanvas::wheelEvent): New functions. * MouseModeActionGroup.h, MouseModeActionGroup.cc (MouseModeActionGroup::mouseMode): Delete. (MouseModeActionGroup::setMode): New function. (MouseModeActionGroup::MouseModeActionGroup): Also include action to insert text in the list. * figure.m: Set default pan, rotate, and zoom mode properties for the figure object. * pan.m, rotate3d.m, zoom.m: Improve compatibility with Matlab. Set mode properties for figure. * __init_fltk__.cc: Cope with changes to graphics properties.
author John W. Eaton <jwe@octave.org>
date Fri, 06 Feb 2015 13:06:54 -0500
parents c728ae4d1790
children ff56a9899101
files libgui/graphics/Canvas.cc libgui/graphics/Canvas.h libgui/graphics/Figure.cc libgui/graphics/Figure.h libgui/graphics/GLCanvas.cc libgui/graphics/GLCanvas.h libgui/graphics/MouseModeActionGroup.cc libgui/graphics/MouseModeActionGroup.h libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h libinterp/dldfcn/__init_fltk__.cc scripts/plot/util/figure.m scripts/plot/util/pan.m scripts/plot/util/rotate3d.m scripts/plot/util/zoom.m
diffstat 15 files changed, 1127 insertions(+), 292 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/graphics/Canvas.cc	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/Canvas.cc	Fri Feb 06 13:06:54 2015 -0500
@@ -25,8 +25,11 @@
 #endif
 
 #include <QApplication>
+#include <QBitmap>
+#include <QCursor>
 #include <QList>
 #include <QMouseEvent>
+#include <QWheelEvent>
 #include <QRectF>
 
 #include "Backend.h"
@@ -51,6 +54,33 @@
   m_redrawBlocked = block;
 }
 
+void Canvas::setCursor (MouseMode mode)
+{
+  QWidget *w = qWidget ();
+
+  if (w)
+    {
+      static QCursor origCursor = w->cursor ();
+
+      switch (mode)
+        {
+        case PanMode:
+        case RotateMode:
+          w->setCursor (Qt::OpenHandCursor);
+          break;
+
+        case ZoomMode:
+          // FIXME: distinguish zoom in/out.
+          w->setCursor (QBitmap (":/images/zoom.png"));
+          break;
+
+        default:
+          w->setCursor (origCursor);
+          break;
+        }
+    }
+}
+
 void Canvas::updateCurrentPoint(const graphics_object& fig,
                                 const graphics_object& obj, QMouseEvent* event)
 {
@@ -86,6 +116,74 @@
     }
 }
 
+void Canvas::canvasToggleAxes (const graphics_handle& handle)
+{
+  gh_manager::auto_lock lock;
+
+  graphics_object go = gh_manager::get_object (handle);
+
+  if (go.valid_object ())
+    {
+      figure::properties& fp = Utils::properties<figure> (go);
+
+      graphics_handle ah = fp.get_currentaxes ();
+
+      graphics_object ax = gh_manager::get_object (ah);
+
+      if (ax.valid_object ())
+        {
+          axes::properties& ap = Utils::properties<axes> (ax);
+
+          if (ap.handlevisibility_is ("on"))
+            {
+              ap.set_visible (! ap.is_visible ());
+
+              redraw (true);
+            }
+        }
+    }
+}
+
+void Canvas::canvasToggleGrid (const graphics_handle& handle)
+{
+  gh_manager::auto_lock lock;
+
+  graphics_object go = gh_manager::get_object (handle);
+
+  if (go.valid_object ())
+    {
+      figure::properties& fp = Utils::properties<figure> (go);
+
+      graphics_handle ah = fp.get_currentaxes ();
+
+      graphics_object ax = gh_manager::get_object (ah);
+
+      if (ax.valid_object ())
+        {
+          axes::properties& ap = Utils::properties<axes> (ax);
+
+          if (ap.handlevisibility_is ("on") && ap.is_visible ())
+            {
+              std::string tmp;
+
+              // If any grid is off, then turn them all on.  If they are all
+              // on, then turn them off.
+
+              std::string state = ((ap.get_xgrid () == "off"
+                                    || ap.get_ygrid () == "off"
+                                    || ap.get_zgrid () == "off")
+                                   ? "on" : "off");
+
+              ap.set_xgrid (state);
+              ap.set_ygrid (state);
+              ap.set_zgrid (state);
+
+              redraw (true);
+            }
+        }
+    }
+}
+
 void Canvas::canvasPaintEvent (void)
 {
   if (! m_redrawBlocked)
@@ -111,60 +209,39 @@
       switch (m_mouseMode)
         {
         case RotateMode:
-            {
-              Matrix bb = ap.get_boundingbox (true);
-              Matrix view = ap.get_view ().matrix_value ();
+          {
+            ap.rotate3d (m_mouseCurrent.x (), event->x (),
+                         m_mouseCurrent.y (), event->y ());
 
-              // Compute new view angles
-              view(0) += ((m_mouseCurrent.x () - event->x ())
-                          * (180.0 / bb(2)));
-              view(1) += ((event->y () - m_mouseCurrent.y ())
-                          * (180.0 / bb(3)));
-
-              // Clipping
-              view(1) = std::min (view(1), 90.0);
-              view(1) = std::max (view(1), -90.0);
-              if (view(0) > 180.0)
-                view(0) -= 360.0;
-              else if (view(0) < -180.0)
-                view(0) += 360.0;
+            // Update current mouse position
+            m_mouseCurrent = event->pos ();
 
-              // Snapping
-              double snapMargin = 1.0;
-              for (int a = -90; a <= 90; a += 90)
-                if ((a - snapMargin) < view(1)
-                    && view(1) < (a + snapMargin))
-                  {
-                    view(1) = a;
-                    break;
-                  }
-              for (int a = -180; a <= 180; a += 180)
-                if ((a - snapMargin) < view(0)
-                    && view(0) < (a + snapMargin))
-                  {
-                    if (a == 180)
-                      view(0) = -180;
-                    else
-                      view(0) = a;
-                    break;
-                  }
+            // Force immediate redraw
+            redraw (true);
+          }
+          break;
 
-              // Update axes properties
-              ap.set_view (view);
-
-              // Update current mouse position
-              m_mouseCurrent = event->pos ();
-
-              // Force immediate redraw
-              redraw (true);
-            }
-          break;
         case ZoomMode:
-          m_mouseCurrent = event->pos();
+          m_mouseCurrent = event->pos ();
           redraw (true);
           break;
+
         case PanMode:
-          break;
+          {
+            ColumnVector p0 = ap.pixel2coord (m_mouseCurrent.x (),
+                                              m_mouseCurrent.y ());
+            ColumnVector p1 = ap.pixel2coord (event->x (),
+                                              event->y ());
+
+            ap.translate_view ("both", p0(0), p1(0), p0(1), p1(1));
+
+            // Update current mouse position
+            m_mouseCurrent = event->pos ();
+
+            // Force immediate redraw
+            redraw (true);
+          }
+
         default:
           break;
         }
@@ -184,6 +261,139 @@
     }
 }
 
+static bool
+pan_enabled (const graphics_object figObj)
+{
+  // Getting pan mode property:
+  octave_value ov_pm
+    = Utils::properties<figure> (figObj).get___pan_mode__ ();
+
+  octave_scalar_map pm = ov_pm.scalar_map_value ();
+
+  return pm.contents ("Enable").string_value () == "on";
+}
+
+static std::string
+pan_mode (const graphics_object figObj)
+{
+  // Getting pan mode property:
+  octave_value ov_pm
+    = Utils::properties<figure> (figObj).get___pan_mode__ ();
+
+  octave_scalar_map pm = ov_pm.scalar_map_value ();
+
+  return pm.contents ("Motion").string_value ();
+}
+
+static bool
+rotate_enabled (const graphics_object figObj)
+{
+  // Getting rotate mode property:
+  octave_value ov_rm
+    = Utils::properties<figure> (figObj).get___rotate_mode__ ();
+
+  octave_scalar_map rm = ov_rm.scalar_map_value ();
+
+  return rm.contents ("Enable").string_value () == "on";
+}
+
+static bool
+zoom_enabled (const graphics_object figObj)
+{
+  // Getting zoom mode property:
+  octave_value ov_zm
+    = Utils::properties<figure> (figObj).get___zoom_mode__ ();
+
+  octave_scalar_map zm = ov_zm.scalar_map_value ();
+
+  return zm.contents ("Enable").string_value () == "on";
+}
+
+static std::string
+zoom_mode (const graphics_object figObj)
+{
+  // Getting zoom mode property:
+  octave_value ov_zm
+    = Utils::properties<figure> (figObj).get___zoom_mode__ ();
+
+  octave_scalar_map zm = ov_zm.scalar_map_value ();
+
+  return zm.contents ("Motion").string_value ();
+}
+
+static std::string
+zoom_direction (const graphics_object figObj)
+{
+  // Getting zoom mode property:
+  octave_value ov_zm
+    = Utils::properties<figure> (figObj).get___zoom_mode__ ();
+
+  octave_scalar_map zm = ov_zm.scalar_map_value ();
+
+  return zm.contents ("Direction").string_value ();
+}
+
+void Canvas::canvasMouseDoubleClickEvent (QMouseEvent* event)
+{
+  if (event->buttons () != Qt::LeftButton)
+    return;
+
+  gh_manager::auto_lock lock;
+  graphics_object obj = gh_manager::get_object (m_handle);
+
+  if (obj.valid_object ())
+    {
+      graphics_object axesObj;
+
+      Matrix children = obj.get_properties ().get_children ();
+      octave_idx_type num_children = children.numel ();
+
+      for (int i = 0; i < num_children; i++)
+	{
+	  graphics_object childObj (gh_manager::get_object (children(i)));
+
+          if (childObj.isa ("axes"))
+            {
+              graphics_object go = selectFromAxes (childObj, event->pos ());
+
+              if (go)
+                {
+                  axesObj = childObj;
+                  break;
+                }
+            }
+        }
+
+      bool redrawFigure = true;
+
+      if (axesObj)
+        {
+          graphics_object figObj (obj.get_ancestor ("figure"));
+
+          if (axesObj.get_properties ().handlevisibility_is ("on"))
+            {
+              Utils::properties<figure> (figObj)
+                .set_currentaxes (axesObj.get_handle ().as_octave_value ());
+
+              if (pan_enabled (figObj) || rotate_enabled (figObj)
+                  || zoom_enabled (figObj))
+                {
+                  axes::properties& ap =
+                    Utils::properties<axes> (axesObj);
+
+                  ap.clear_zoom_stack ();
+                  ap.set_xlimmode ("auto");
+                  ap.set_ylimmode ("auto");
+                  ap.set_zlimmode ("auto");
+                }
+            }
+
+          if (redrawFigure)
+            redraw (false);
+        }
+    }
+}
+
 void Canvas::canvasMousePressEvent (QMouseEvent* event)
 {
   gh_manager::auto_lock lock;
@@ -285,9 +495,14 @@
             ContextMenu::executeAt (currentObj.get_properties (),
                                     event->globalPos ());
           break;
+
+        case TextMode:
+          // Handle text insertion here.
+          break;
+
+        case PanMode:
         case RotateMode:
         case ZoomMode:
-        case PanMode:
           if (axesObj)
             {
               if (event->buttons () == Qt::LeftButton
@@ -297,8 +512,7 @@
                   m_mouseAxes = axesObj.get_handle ();
                   m_mouseMode = newMouseMode;
                 }
-              else if (newMouseMode == ZoomMode
-                       && event->modifiers () == Qt::NoModifier)
+              else if (event->modifiers () == Qt::NoModifier)
                 {
                   switch (event->buttons ())
                     {
@@ -329,9 +543,7 @@
 
 void Canvas::canvasMouseReleaseEvent (QMouseEvent* event)
 {
-  if (m_mouseMode == ZoomMode
-      && m_mouseAxes.ok ()
-      && m_mouseAnchor != event->pos ())
+  if (m_mouseMode == ZoomMode && m_mouseAxes.ok ())
     {
       gh_manager::auto_lock lock;
       graphics_object ax = gh_manager::get_object (m_mouseAxes);
@@ -340,20 +552,37 @@
         {
           axes::properties& ap = Utils::properties<axes> (ax);
 
-          ColumnVector p0 = ap.pixel2coord (m_mouseAnchor.x (),
-                                            m_mouseAnchor.y ());
-          ColumnVector p1 = ap.pixel2coord (event->x (),
-                                            event->y ());
+          graphics_object obj = gh_manager::get_object (m_handle);
+
+          graphics_object figObj (obj.get_ancestor ("figure"));
+
+          std::string zm = zoom_mode (figObj);
+
+          if (m_mouseAnchor == event->pos ())
+            {
+              // FIXME: check direction here.
+
+              double factor = 2.0;
 
-          Matrix xl (1, 2, 0.0);
-          Matrix yl (1, 2, 0.0);
+              ap.zoom (zm, factor);
+            }
+          else
+            {
+              ColumnVector p0 = ap.pixel2coord (m_mouseAnchor.x (),
+                                                m_mouseAnchor.y ());
+              ColumnVector p1 = ap.pixel2coord (event->x (),
+                                                event->y ());
 
-          xl(0) = std::min (p0(0), p1(0));
-          xl(1) = std::max (p0(0), p1(0));
-          yl(0) = std::min (p0(1), p1(1));
-          yl(1) = std::max (p0(1), p1(1));
+              Matrix xl (1, 2, 0.0);
+              Matrix yl (1, 2, 0.0);
 
-          ap.zoom (xl, yl);
+              xl(0) = std::min (p0(0), p1(0));
+              xl(1) = std::max (p0(0), p1(0));
+              yl(0) = std::min (p0(1), p1(1));
+              yl(1) = std::max (p0(1), p1(1));
+
+              ap.zoom (zm, xl, yl);
+            }
 
           redraw (false);
         }
@@ -377,6 +606,111 @@
   m_mouseMode = NoMode;
 }
 
+void Canvas::canvasWheelEvent (QWheelEvent* event)
+{
+  gh_manager::auto_lock lock;
+  graphics_object obj = gh_manager::get_object (m_handle);
+
+  if (obj.valid_object ())
+    {
+      std::string mode;
+
+      graphics_object axesObj;
+
+      Matrix children = obj.get_properties ().get_children ();
+      octave_idx_type num_children = children.numel ();
+
+      for (int i = 0; i < num_children; i++)
+	{
+	  graphics_object childObj (gh_manager::get_object (children(i)));
+
+          if (childObj.isa ("axes"))
+            {
+              graphics_object go = selectFromAxes (childObj, event->pos ());
+
+              if (go)
+                {
+                  axesObj = childObj;
+                  break;
+                }
+            }
+        }
+
+      if (axesObj)
+        {
+          MouseMode newMouseMode = NoMode;
+
+          graphics_object figObj (obj.get_ancestor ("figure"));
+
+          Figure* fig = dynamic_cast<Figure*> (Backend::toolkitObject (figObj));
+
+          if (fig)
+            newMouseMode = fig->mouseMode ();
+
+          if (axesObj.get_properties ().handlevisibility_is ("on"))
+            {
+              Utils::properties<figure> (figObj)
+                .set_currentaxes (axesObj.get_handle ().as_octave_value ());
+
+              if (zoom_enabled (figObj))
+                {
+                  newMouseMode = ZoomMode;
+
+                  mode = zoom_mode (figObj);
+                }
+              else if (pan_enabled (figObj))
+                {
+                  newMouseMode = PanMode;
+
+                  mode = pan_mode (figObj);
+                }
+            }
+
+          bool redrawFigure = true;
+
+          switch (newMouseMode)
+            {
+            case ZoomMode:
+              {
+                axes::properties& ap = Utils::properties<axes> (axesObj);
+
+                double factor = event->delta () > 0 ? 2.0 : 0.5;
+
+                ap.zoom (mode, factor);
+
+#if 0
+                Matrix view = ap.get_view ().matrix_value ();
+                if (view(1) != 90)
+                  {
+                    Matrix zlimits = ap.get_zlim ().matrix_value ();
+                    zlimits = factor * zlimits;
+                    ap.set_zlim (zlimits);
+                  }
+#endif
+              }
+              break;
+
+            case PanMode:
+              {
+                axes::properties& ap = Utils::properties<axes> (axesObj);
+
+                double factor = event->delta () > 0 ? 0.1 : -0.1;
+
+                ap.pan (mode, factor);
+              }
+              break;
+
+            default:
+              redrawFigure = false;
+              break;
+            }
+
+          if (redrawFigure)
+            redraw (false);
+        }
+    }
+}
+
 bool Canvas::canvasKeyPressEvent (QKeyEvent* event)
 {
   if (m_eventMask & KeyPress)
--- a/libgui/graphics/Canvas.h	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/Canvas.h	Fri Feb 06 13:06:54 2015 -0500
@@ -31,6 +31,7 @@
 
 class QKeyEvent;
 class QMouseEvent;
+class QWheelEvent;
 class QWidget;
 
 namespace QtHandles
@@ -55,11 +56,16 @@
   void clearEventMask (int m) { m_eventMask &= (~m); }
   void setEventMask (int m) { m_eventMask = m; }
 
+  void setCursor (MouseMode mode);
+
   virtual QWidget* qWidget (void) = 0;
 
   static Canvas* create (const std::string& name, QWidget* parent,
                          const graphics_handle& handle);
 
+  virtual void toggleAxes (const graphics_handle& handle) = 0;
+  virtual void toggleGrid (const graphics_handle& handle) = 0;
+
 protected:
   virtual void draw (const graphics_handle& handle) = 0;
   virtual void drawZoomBox (const QPoint& p1, const QPoint& p2) = 0;
@@ -75,10 +81,14 @@
       m_eventMask (0)
     { }
 
+  void canvasToggleAxes (const graphics_handle& handle);
+  void canvasToggleGrid (const graphics_handle& handle);
   void canvasPaintEvent (void);
+  void canvasMouseDoubleClickEvent (QMouseEvent* event);
   void canvasMouseMoveEvent (QMouseEvent* event);
   void canvasMousePressEvent (QMouseEvent* event);
   void canvasMouseReleaseEvent (QMouseEvent* event);
+  void canvasWheelEvent (QWheelEvent* event);
   bool canvasKeyPressEvent (QKeyEvent* event);
   bool canvasKeyReleaseEvent (QKeyEvent* event);
 
--- a/libgui/graphics/Figure.cc	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/Figure.cc	Fri Feb 06 13:06:54 2015 -0500
@@ -104,9 +104,9 @@
 }
 
 Figure::Figure (const graphics_object& go, FigureWindow* win)
-     : Object (go, win), m_blockUpdates (false), m_mouseMode (NoMode),
-       m_lastMouseMode (NoMode), m_figureToolBar (0), m_menuBar (0),
-       m_innerRect (), m_outerRect ()
+     : Object (go, win), m_blockUpdates (false), m_figureToolBar (0),
+       m_menuBar (0), m_innerRect (), m_outerRect (),
+       m_mouseModeGroup (0)
 {
   m_container = new Container (win);
   win->setCentralWidget (m_container);
@@ -163,6 +163,64 @@
 {
 }
 
+static std::string mouse_mode_to_string (MouseMode mode)
+{
+  switch (mode)
+    {
+    case NoMode:
+      return "none";
+
+    case RotateMode:
+      return "rotate";
+
+    case ZoomMode:
+      return "zoom";
+
+    case PanMode:
+      return "pan";
+
+    case TextMode:
+      return "text";
+
+    case SelectMode:
+      return "select";
+
+    default:
+      break;
+    }
+
+  return "none";
+}
+
+static MouseMode mouse_mode_from_string (const std::string& mode)
+{
+  if (mode == "none")
+    return NoMode;
+  else if (mode == "rotate")
+    return RotateMode;
+  else if (mode == "zoom")
+    return ZoomMode;
+  else if (mode == "pan")
+    return PanMode;
+  else if (mode == "text")
+    return TextMode;
+  else if (mode == "select")
+    return SelectMode;
+  else
+    return NoMode;
+}
+
+MouseMode Figure::mouseMode (void)
+{
+  gh_manager::auto_lock lock;
+
+  const figure::properties& fp = properties<figure> ();
+
+  std::string mode = fp.get___mouse_mode__ ();
+
+  return mouse_mode_from_string (mode);
+}
+
 void Figure::createFigureToolBarAndMenuBar (void)
 {
   QMainWindow* win = qWidget<QMainWindow> ();
@@ -171,10 +229,18 @@
   m_figureToolBar->setMovable (false);
   m_figureToolBar->setFloatable (false);
 
-  MouseModeActionGroup* mouseModeGroup = new MouseModeActionGroup (win);
-  connect (mouseModeGroup, SIGNAL (modeChanged (MouseMode)),
+  m_mouseModeGroup = new MouseModeActionGroup (win);
+  connect (m_mouseModeGroup, SIGNAL (modeChanged (MouseMode)),
            SLOT (setMouseMode (MouseMode)));
-  m_figureToolBar->addActions (mouseModeGroup->actions ());
+  m_figureToolBar->addActions (m_mouseModeGroup->actions ());
+
+  QAction *toggle_axes = m_figureToolBar->addAction ("Axes");
+  connect (toggle_axes, SIGNAL (triggered (void)),
+           this, SLOT (toggleAxes (void)));
+
+  QAction *toggle_grid = m_figureToolBar->addAction ("Grid");
+  connect (toggle_grid, SIGNAL (triggered (void)),
+           this, SLOT (toggleGrid (void)));
 
   m_menuBar = new MenuBar (win);
   win->setMenuBar (m_menuBar);
@@ -199,7 +265,7 @@
   editMenu->addAction (tr ("&Paste"), this, SLOT (editPaste(void)),
                        Qt::CTRL|Qt::Key_V)->setEnabled (false);
   editMenu->addSeparator ();
-  editMenu->addActions (mouseModeGroup->actions ());
+  editMenu->addActions (m_mouseModeGroup->actions ());
 
   QMenu* helpMenu = m_menuBar->addMenu (tr ("&Help"));
   helpMenu->menuAction ()->setObjectName ("builtinMenu");
@@ -210,6 +276,16 @@
   m_menuBar->addReceiver (this);
 }
 
+void Figure::updateFigureToolBarAndMenuBar (void)
+{
+  if (m_mouseModeGroup)
+    {
+      m_blockUpdates = true;
+      m_mouseModeGroup->setMode (mouseMode ());
+      m_blockUpdates = false;
+    }
+}
+
 Container* Figure::innerContainer (void)
 {
   return m_container;
@@ -233,6 +309,8 @@
       if (obj)
         obj->slotRedraw ();
     }
+
+  updateFigureToolBarAndMenuBar ();
 }
 
 void Figure::beingDeleted (void)
@@ -342,14 +420,6 @@
       m_blockUpdates = false;
 
       updateBoundingBox (false);
-
-      if (visible)
-        m_mouseMode = m_lastMouseMode;
-      else
-        {
-          m_lastMouseMode = m_mouseMode;
-          m_mouseMode = NoMode;
-        }
     }
 }
 
@@ -600,6 +670,23 @@
                       ABOUT_TEXT);
 }
 
+void Figure::setMouseMode (MouseMode mode)
+{
+  if (m_blockUpdates)
+    return;
+
+  gh_manager::auto_lock lock;
+
+  figure::properties& fp = properties<figure> ();
+
+  fp.set___mouse_mode__ (mouse_mode_to_string (mode));
+
+  Canvas* canvas = m_container->canvas (m_handle);
+
+  if (canvas)
+    canvas->setCursor (mode);
+}
+
 void Figure::fileNewFigure (void)
 {
 }
@@ -674,4 +761,20 @@
   redraw ();
 }
 
+void Figure::toggleAxes (void)
+{
+  Canvas* canvas = m_container->canvas (m_handle);
+
+  if (canvas)
+    canvas->toggleAxes (m_handle);
+}
+  
+void Figure::toggleGrid (void)
+{
+  Canvas* canvas = m_container->canvas (m_handle);
+
+  if (canvas)
+    canvas->toggleGrid (m_handle);
+}
+  
 }; // namespace QtHandles
--- a/libgui/graphics/Figure.h	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/Figure.h	Fri Feb 06 13:06:54 2015 -0500
@@ -37,11 +37,15 @@
 
 enum MouseMode
 {
+  // NOTE: These values must match the order of the buttons in the
+  // MouseModeActionGroup object.
+
   NoMode        = 0,
   RotateMode    = 1,
   ZoomMode      = 2,
   PanMode       = 3,
-  SelectMode    = 4
+  TextMode      = 4,
+  SelectMode    = 5
 };
 
 class Container;
@@ -49,6 +53,8 @@
 class MenuBar;
 class ToolBar;
 
+class MouseModeActionGroup;
+
 class Figure :
   public Object,
   public MenuContainer,
@@ -64,7 +70,7 @@
 
   static Figure* create (const graphics_object& go);
 
-  MouseMode mouseMode (void) { return m_mouseMode; }
+  MouseMode mouseMode (void);
 
   Container* innerContainer (void);
   QWidget* menu (void);
@@ -93,10 +99,12 @@
   void addCustomToolBar (QToolBar* bar, bool visible);
   void showCustomToolBar (QToolBar* bar, bool visible);
 
+  void updateFigureToolBarAndMenuBar (void);
+
   static void updateBoundingBoxHelper (void*);
 
 private slots:
-  void setMouseMode (MouseMode mode) { m_mouseMode = mode; }
+  void setMouseMode (MouseMode mode);
   void fileNewFigure (void);
   void fileCloseFigure (void);
   void editCopy (void);
@@ -105,6 +113,8 @@
   void helpAboutQtHandles (void);
   void updateMenuBar (void);
   void updateContainer (void);
+  void toggleAxes (void);
+  void toggleGrid (void);
 
 signals:
   void asyncUpdate (void);
@@ -112,11 +122,11 @@
 private:
   Container* m_container;
   bool m_blockUpdates;
-  MouseMode m_mouseMode, m_lastMouseMode;
   QToolBar* m_figureToolBar;
   MenuBar* m_menuBar;
   QRect m_innerRect;
   QRect m_outerRect;
+  MouseModeActionGroup* m_mouseModeGroup;
 };
 
 }; // namespace QtHandles
--- a/libgui/graphics/GLCanvas.cc	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/GLCanvas.cc	Fri Feb 06 13:06:54 2015 -0500
@@ -60,6 +60,16 @@
     }
 }
 
+void GLCanvas::toggleAxes (const graphics_handle& gh)
+{
+  canvasToggleAxes (gh);
+}
+
+void GLCanvas::toggleGrid (const graphics_handle& gh)
+{
+  canvasToggleGrid (gh);
+}
+
 graphics_object GLCanvas::selectFromAxes (const graphics_object& ax,
                                           const QPoint& pt)
 {
@@ -119,6 +129,11 @@
   canvasPaintEvent ();
 }
 
+void GLCanvas::mouseDoubleClickEvent (QMouseEvent* xevent)
+{
+  canvasMouseDoubleClickEvent (xevent);
+}
+
 void GLCanvas::mouseMoveEvent (QMouseEvent* xevent)
 {
   canvasMouseMoveEvent (xevent);
@@ -134,6 +149,11 @@
   canvasMouseReleaseEvent (xevent);
 }
 
+void GLCanvas::wheelEvent (QWheelEvent* xevent)
+{
+  canvasWheelEvent (xevent);
+}
+
 void GLCanvas::keyPressEvent (QKeyEvent* xevent)
 {
   if (! canvasKeyPressEvent (xevent))
--- a/libgui/graphics/GLCanvas.h	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/GLCanvas.h	Fri Feb 06 13:06:54 2015 -0500
@@ -37,6 +37,8 @@
   ~GLCanvas (void);
 
   void draw (const graphics_handle& handle);
+  void toggleAxes (const graphics_handle& handle);
+  void toggleGrid (const graphics_handle& handle);
   void drawZoomBox (const QPoint& p1, const QPoint& p2);
   void resize (int /* x */, int /* y */,
                int /* width */, int /* height */) { }
@@ -46,9 +48,11 @@
 
 protected:
   void paintGL (void);
+  void mouseDoubleClickEvent (QMouseEvent* event);
   void mouseMoveEvent (QMouseEvent* event);
   void mousePressEvent (QMouseEvent* event);
   void mouseReleaseEvent (QMouseEvent* event);
+  void wheelEvent (QWheelEvent* event);
   void keyPressEvent (QKeyEvent* event);
   void keyReleaseEvent (QKeyEvent* event);
 };
--- a/libgui/graphics/MouseModeActionGroup.cc	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/MouseModeActionGroup.cc	Fri Feb 06 13:06:54 2015 -0500
@@ -42,10 +42,13 @@
                                  tr ("Zoom"), this));
   m_actions.append (new QAction (QIcon (":/images/pan.png"),
                                  tr ("Pan"), this));
+  m_actions.append (new QAction (QIcon::fromTheme ("insert-text"),
+                                 tr ("Insert Text"), this));
   m_actions.append (new QAction (QIcon (":/images/select.png"),
                                  tr ("Select"), this));
-  m_actions[2]->setEnabled (false);
+
   m_actions[3]->setEnabled (false);
+  m_actions[4]->setEnabled (false);
 
   foreach (QAction* a, m_actions)
     {
@@ -83,11 +86,10 @@
     }
 }
 
-MouseMode MouseModeActionGroup::mouseMode (void) const
+void MouseModeActionGroup::setMode (MouseMode mode)
 {
-  int i = (m_current ? -1 : m_actions.indexOf (m_current));
-
-  return static_cast<MouseMode> (i+1);
+  for (int i = 0; i < m_actions.size (); i++)
+    m_actions[i]->setChecked (i == mode - 1);
 }
-
+  
 };
--- a/libgui/graphics/MouseModeActionGroup.h	Fri Feb 06 08:31:49 2015 -0800
+++ b/libgui/graphics/MouseModeActionGroup.h	Fri Feb 06 13:06:54 2015 -0500
@@ -42,7 +42,8 @@
   ~MouseModeActionGroup (void);
 
   QList<QAction*> actions (void) const { return m_actions; }
-  MouseMode mouseMode (void) const;
+
+  void setMode (MouseMode mode);
 
 signals:
   void modeChanged (MouseMode mode);
--- a/libinterp/corefcn/graphics.cc	Fri Feb 06 08:31:49 2015 -0800
+++ b/libinterp/corefcn/graphics.cc	Fri Feb 06 13:06:54 2015 -0500
@@ -1809,6 +1809,32 @@
   mark_modified ();
 }
 
+void
+figure::properties::set___mouse_mode__ (const octave_value& val)
+{
+  if (! error_state)
+    {
+      if (__mouse_mode__.set (val, true))
+        {
+          std::string mode = __mouse_mode__.current_value ();
+
+          octave_scalar_map pm = get___pan_mode__ ().scalar_map_value ();
+          pm.setfield ("Enable", mode == "pan" ? "on" : "off");
+          set___pan_mode__ (pm);
+
+          octave_scalar_map rm = get___rotate_mode__ ().scalar_map_value ();
+          rm.setfield ("Enable", mode == "rotate" ? "on" : "off");
+          set___rotate_mode__ (rm);
+
+          octave_scalar_map zm = get___zoom_mode__ ().scalar_map_value ();
+          zm.setfield ("Enable", mode == "zoom" ? "on" : "off");
+          set___zoom_mode__ (zm);
+
+          mark_modified ();
+        }
+    }
+}
+
 // ---------------------------------------------------------------------
 
 void
@@ -5041,8 +5067,6 @@
   update_transform ();
   sync_positions ();
   override_defaults (obj);
-  // Disable rotate3d and select pan for 2D axes
-  set_rotate3d (get_rotate3d ());
 }
 
 void
@@ -6488,27 +6512,6 @@
 }
 
 void
-axes::properties::set_rotate3d (const octave_value& v)
-{
-  graphics_object parent_obj =
-    gh_manager::get_object (get_parent ());
-
-  int ndim = calc_dimensions (parent_obj);
-  rotate3d.set (v, false, false);
-  if (rotate3d_is ("on"))
-    {
-      // Disable rotate3d for 2D plots
-      if (ndim == 2)
-        {
-          rotate3d.set ("off", false, false);
-          pan.set ("on", false, false);
-        }
-      else
-        pan.set ("off", false, false);
-    }
-}
-
-void
 axes::properties::set_units (const octave_value& v)
 {
   if (! error_state)
@@ -7564,7 +7567,8 @@
 }
 
 void
-axes::properties::zoom_about_point (double x, double y, double factor,
+axes::properties::zoom_about_point (const std::string& mode,
+                                    double x, double y, double factor,
                                     bool push_to_zoom_stack)
 {
   // FIXME: Do we need error checking here?
@@ -7588,29 +7592,70 @@
   xlims = do_zoom (x, factor, xlims, xscale_is ("log"));
   ylims = do_zoom (y, factor, ylims, yscale_is ("log"));
 
-  zoom (xlims, ylims, push_to_zoom_stack);
-}
-
-void
-axes::properties::zoom (const Matrix& xl, const Matrix& yl,
+  zoom (mode, xlims, ylims, push_to_zoom_stack);
+}
+
+void
+axes::properties::zoom (const std::string& mode, double factor,
+                        bool push_to_zoom_stack)
+{
+  // FIXME: Do we need error checking here?
+  Matrix xlims = get_xlim ().matrix_value ();
+  Matrix ylims = get_ylim ().matrix_value ();
+
+  double x = (xlims(0) + xlims(1)) / 2;
+  double y = (ylims(0) + ylims(1)) / 2;
+
+  zoom_about_point (mode, x, y, factor, push_to_zoom_stack);
+}
+
+void
+axes::properties::push_zoom_stack (void)
+{
+  // FIXME: Maybe make the size of the undo stack configurable.  A limit
+  // of 500 elements means 100 pan, rotate, or zoom actions are stored
+  // and may be undone.
+
+  if (zoom_stack.size () >= 500)
+    {
+      for (int i = 0; i < 5; i++)
+        zoom_stack.pop_back ();
+    }
+
+  zoom_stack.push_front (xlimmode.get ());
+  zoom_stack.push_front (xlim.get ());
+  zoom_stack.push_front (ylimmode.get ());
+  zoom_stack.push_front (ylim.get ());
+  zoom_stack.push_front (view.get ());
+}
+
+void
+axes::properties::zoom (const std::string& mode,
+                        const Matrix& xl, const Matrix& yl,
                         bool push_to_zoom_stack)
 {
   if (push_to_zoom_stack)
-    {
-      zoom_stack.push_front (xlimmode.get ());
-      zoom_stack.push_front (xlim.get ());
-      zoom_stack.push_front (ylimmode.get ());
-      zoom_stack.push_front (ylim.get ());
-    }
-
-  xlim = xl;
-  xlimmode = "manual";
-  ylim = yl;
-  ylimmode = "manual";
+    push_zoom_stack ();
+
+  if (mode == "horizontal" || mode == "both")
+    {
+      xlim = xl;
+      xlimmode = "manual";
+    }
+
+  if (mode == "vertical" || mode == "both")
+    {
+      ylim = yl;
+      ylimmode = "manual";
+    }
 
   update_transform ();
-  update_xlim (false);
-  update_ylim (false);
+
+  if (mode == "horizontal" || mode == "both")
+    update_xlim ();
+
+  if (mode == "vertical" || mode == "both")
+    update_ylim ();
 }
 
 static Matrix
@@ -7674,7 +7719,9 @@
 }
 
 void
-axes::properties::translate_view (double x0, double x1, double y0, double y1)
+axes::properties::translate_view (const std::string& mode,
+                                  double x0, double x1, double y0, double y1,
+                                  bool push_to_zoom_stack)
 {
   // FIXME: Do we need error checking here?
   Matrix xlims = get_xlim ().matrix_value ();
@@ -7697,12 +7744,82 @@
   xlims = do_translate (x0, x1, xlims, xscale_is ("log"));
   ylims = do_translate (y0, y1, ylims, yscale_is ("log"));
 
-  zoom (xlims, ylims, false);
-}
-
-void
-axes::properties::rotate_view (double delta_el, double delta_az)
-{
+  zoom (mode, xlims, ylims, push_to_zoom_stack);
+}
+
+void
+axes::properties::pan (const std::string& mode, double factor,
+                       bool push_to_zoom_stack)
+{
+  // FIXME: Do we need error checking here?
+  Matrix xlims = get_xlim ().matrix_value ();
+  Matrix ylims = get_ylim ().matrix_value ();
+
+  double x0 = (xlims(0) + xlims(1)) / 2;
+  double y0 = (ylims(0) + ylims(1)) / 2;
+
+  double x1 = x0 + (xlims(1) - xlims(0)) * factor;
+  double y1 = y0 + (ylims(1) - ylims(0)) * factor;
+
+  translate_view (mode, x0, x1, y0, y1, push_to_zoom_stack);
+}
+
+void
+axes::properties::rotate3d (double x0, double x1, double y0, double y1,
+                            bool push_to_zoom_stack)
+{
+  if (push_to_zoom_stack)
+    push_zoom_stack ();
+
+  Matrix bb = get_boundingbox (true);
+  Matrix new_view = get_view ().matrix_value ();
+
+  // Compute new view angles
+  new_view(0) += ((x0 - x1) * (180.0 / bb(2)));
+  new_view(1) += ((y1 - y0) * (180.0 / bb(3)));
+
+  // Clipping
+  new_view(1) = std::min (new_view(1), 90.0);
+  new_view(1) = std::max (new_view(1), -90.0);
+  if (new_view(0) > 180.0)
+    new_view(0) -= 360.0;
+  else if (new_view(0) < -180.0)
+    new_view(0) += 360.0;
+
+  // Snapping
+  double snapMargin = 1.0;
+  for (int a = -90; a <= 90; a += 90)
+    {
+      if ((a - snapMargin) < new_view(1)
+          && new_view(1) < (a + snapMargin))
+        {
+          new_view(1) = a;
+          break;
+        }
+    }
+
+  for (int a = -180; a <= 180; a += 180)
+    if ((a - snapMargin) < new_view(0)
+        && new_view(0) < (a + snapMargin))
+      {
+        if (a == 180)
+          new_view(0) = -180;
+        else
+          new_view(0) = a;
+        break;
+      }
+
+  // Update axes properties
+  set_view (new_view);
+}
+
+void
+axes::properties::rotate_view (double delta_el, double delta_az,
+                               bool push_to_zoom_stack)
+{
+  if (push_to_zoom_stack)
+    push_zoom_stack ();
+
   Matrix v = get_view ().matrix_value ();
 
   v(1) += delta_el;
@@ -7715,36 +7832,49 @@
   v(0) = fmod (v(0) - delta_az + 720,360);
 
   set_view (v);
+
   update_transform ();
 }
 
 void
 axes::properties::unzoom (void)
 {
-  if (zoom_stack.size () >= 4)
-    {
+  if (zoom_stack.size () >= 5)
+    {
+      view = zoom_stack.front ();
+      zoom_stack.pop_front ();
+
       ylim = zoom_stack.front ();
       zoom_stack.pop_front ();
+
       ylimmode = zoom_stack.front ();
       zoom_stack.pop_front ();
+
       xlim = zoom_stack.front ();
       zoom_stack.pop_front ();
+
       xlimmode = zoom_stack.front ();
       zoom_stack.pop_front ();
 
       update_transform ();
-      update_xlim (false);
-      update_ylim (false);
-    }
-}
-
-void
-axes::properties::clear_zoom_stack (void)
-{
-  while (zoom_stack.size () > 4)
+
+      update_xlim ();
+      update_ylim ();
+
+      update_view ();
+    }
+}
+
+void
+axes::properties::clear_zoom_stack (bool do_unzoom)
+{
+  size_t items_to_leave_on_stack = do_unzoom ? 5 : 0;
+
+  while (zoom_stack.size () > items_to_leave_on_stack)
     zoom_stack.pop_front ();
 
-  unzoom ();
+  if (do_unzoom)
+    unzoom ();
 }
 
 void
@@ -11658,3 +11788,74 @@
 
   return octave_value ();
 }
+
+DEFUN (__zoom__, args, ,
+       "-*- texinfo -*-\n\
+@deftypefn  {Built-in Function} {} __zoom__ (@var{axes}, @var{mode}, @var{factor})\n\
+@deftypefnx  {Built-in Function} {} __zoom__ (@var{axes}, \"out\")\n\
+@deftypefnx  {Built-in Function} {} __zoom__ (@var{axes}, \"reset\")\n\
+Undocumented internal function.\n\
+@end deftypefn")
+{
+  octave_value retval;
+
+  int nargin = args.length ();
+
+  if (nargin != 2 && nargin != 3)
+    {
+      print_usage ();
+      return retval;
+    }
+
+  double h = args(0).double_value ();
+
+  if (error_state)
+    return retval;
+
+  gh_manager::auto_lock guard;
+
+  graphics_handle handle = gh_manager::lookup (h);
+
+  if (! handle.ok ())
+    {
+      error ("__zoom__: invalid handle");
+      return retval;
+    }
+
+  graphics_object ax = gh_manager::get_object (handle);
+
+  axes::properties& ax_props =
+    dynamic_cast<axes::properties&> (ax.get_properties ());
+
+  if (nargin == 2)
+    {
+      std::string opt = args(1).string_value ();
+
+      if (error_state)
+        return retval;
+
+      if (opt == "out" || opt == "reset")
+        {
+          if (opt == "out")
+            {
+              ax_props.clear_zoom_stack ();
+              Vdrawnow_requested = true;
+            }
+          else
+            ax_props.clear_zoom_stack (false);
+
+        }
+    }
+  else
+    {
+      std::string mode = args(1).string_value ();
+      double factor = args(2).scalar_value ();
+
+      if (error_state)
+        return retval;
+
+      ax_props.zoom (mode, factor);
+    }
+
+  return retval;
+}
--- a/libinterp/corefcn/graphics.in.h	Fri Feb 06 08:31:49 2015 -0800
+++ b/libinterp/corefcn/graphics.in.h	Fri Feb 06 13:06:54 2015 -0500
@@ -3453,6 +3453,10 @@
       string_property xvisual , ""
       radio_property xvisualmode , "{auto}|manual"
       // Octave-specific properties
+      radio_property __mouse_mode__ hS , "{none}|pan|rotate|select|text|zoom"
+      any_property __pan_mode__ h , Matrix ()
+      any_property __rotate_mode__ h , Matrix ()
+      any_property __zoom_mode__ h , Matrix ()
       bool_property __enhanced__ h , "on"
       string_property __graphics_toolkit__ s , gtk_manager::default_toolkit ()
       any_property __guidata__ h , Matrix ()
@@ -3752,14 +3756,29 @@
     ColumnVector coord2pixel (double x, double y, double z) const
     { return get_transform ().transform (x, y, z); }
 
-    void zoom_about_point (double x, double y, double factor,
-                           bool push_to_zoom_stack = true);
-    void zoom (const Matrix& xl, const Matrix& yl,
+    void zoom_about_point (const std::string& mode, double x, double y,
+                           double factor, bool push_to_zoom_stack = true);
+    void zoom (const std::string& mode, double factor,
+               bool push_to_zoom_stack = true);
+    void zoom (const std::string& mode, const Matrix& xl, const Matrix& yl,
                bool push_to_zoom_stack = true);
-    void translate_view (double x0, double x1, double y0, double y1);
-    void rotate_view (double delta_az, double delta_el);
+
+    void translate_view (const std::string& mode,
+                         double x0, double x1, double y0, double y1,
+                         bool push_to_zoom_stack = true);
+
+    void pan (const std::string& mode, double factor,
+              bool push_to_zoom_stack = true);
+
+    void rotate3d (double x0, double x1, double y0, double y1,
+                   bool push_to_zoom_stack = true);
+
+    void rotate_view (double delta_az, double delta_el,
+                      bool push_to_zoom_stack = true);
+
     void unzoom (void);
-    void clear_zoom_stack (void);
+    void push_zoom_stack (void);
+    void clear_zoom_stack (bool do_unzoom = true);
 
     void update_units (const caseless_str& old_units);
 
@@ -3792,13 +3811,6 @@
 
     void delete_text_child (handle_property& h);
 
-    void set_pan (const octave_value& val)
-    {
-      pan.set (val, false, false);
-      if (pan_is ("on") || pan_is ("xon") || pan_is ("yon"))
-        rotate3d.set ("off", false, false);
-    }
-
     // See the genprops.awk script for an explanation of the
     // properties declarations.
     // Programming note: Keep property list sorted if new ones are added.
@@ -3842,12 +3854,10 @@
       double_property mouse_wheel_zoom , 0.05
       radio_property nextplot , "add|replacechildren|{replace}"
       array_property outerposition u , default_axes_outerposition ()
-      radio_property pan s , "{on}|xon|yon|off"
       array_property plotboxaspectratio mu , Matrix (1, 3, 1.0)
       radio_property plotboxaspectratiomode u , "{auto}|manual"
       array_property position u , default_axes_position ()
       radio_property projection , "{orthographic}|perspective"
-      radio_property rotate3d S , "{off}|on"
       radio_property tickdir mu , "{in}|out"
       radio_property tickdirmode u , "{auto}|manual"
       array_property ticklength u , default_axes_ticklength ()
@@ -4163,7 +4173,7 @@
                             double min_pos, double max_neg,
                             bool logscale);
 
-    void update_xlim (bool do_clr_zoom = true)
+    void update_xlim ()
     {
       if (xtickmode.is ("auto"))
         calc_ticks_and_lims (xlim, xtick, xmtick, xlimmode.is ("auto"),
@@ -4175,13 +4185,10 @@
 
       update_xscale ();
 
-      if (do_clr_zoom)
-        zoom_stack.clear ();
-
       update_axes_layout ();
     }
 
-    void update_ylim (bool do_clr_zoom = true)
+    void update_ylim (void)
     {
       if (ytickmode.is ("auto"))
         calc_ticks_and_lims (ylim, ytick, ymtick, ylimmode.is ("auto"),
@@ -4193,9 +4200,6 @@
 
       update_yscale ();
 
-      if (do_clr_zoom)
-        zoom_stack.clear ();
-
       update_axes_layout ();
     }
 
@@ -4211,8 +4215,6 @@
 
       update_zscale ();
 
-      zoom_stack.clear ();
-
       update_axes_layout ();
     }
 
--- a/libinterp/dldfcn/__init_fltk__.cc	Fri Feb 06 08:31:49 2015 -0800
+++ b/libinterp/dldfcn/__init_fltk__.cc	Fri Feb 06 13:06:54 2015 -0500
@@ -970,9 +970,9 @@
     else if (widg == togglegrid)
       toggle_grid ();
     else if (widg == panzoom)
-      set_on_ax_obj ("pan", "on");
+      fp.set___mouse_mode__ ("pan");
     else if (widg == rotate)
-      set_on_ax_obj ("rotate3d", "on");
+      fp.set___mouse_mode__ ("rotate");
     else if (widg == help)
       fl_message ("%s", help_text);
   }
@@ -1300,6 +1300,36 @@
     fp.set_boundingbox (outerposition2position (bb), true, false);
   }
 
+  bool pan_enabled (void)
+  {
+    // Getting pan mode property:
+    octave_value ov_pm = fp.get___pan_mode__ ();
+
+    octave_scalar_map pm = ov_pm.scalar_map_value ();
+
+    return pm.contents ("Enable").string_value () == "on";
+  }
+
+  std::string pan_mode (void)
+  {
+    // Getting pan mode property:
+    octave_value ov_pm = fp.get___pan_mode__ ();
+
+    octave_scalar_map pm = ov_pm.scalar_map_value ();
+
+    return pm.contents ("Motion").string_value ();
+  }
+
+  bool rotate_enabled (void)
+  {
+    // Getting rotate mode property:
+    octave_value ov_rm = fp.get___rotate_mode__ ();
+
+    octave_scalar_map rm = ov_rm.scalar_map_value ();
+
+    return rm.contents ("Enable").string_value () == "on";
+  }
+
   int handle (int event)
   {
     if (event == FL_FOCUS)
@@ -1363,12 +1393,12 @@
 
                 case 'p':
                 case 'P':
-                  set_on_ax_obj ("pan", "on");
+                  fp.set___mouse_mode__ ("pan");
                   return 1;
 
                 case 'r':
                 case 'R':
-                  set_on_ax_obj ("rotate3d", "on");
+                  fp.set___mouse_mode__ ("rotate");
                   return 1;
                 }
             }
@@ -1483,7 +1513,7 @@
                     // Don't pan or rotate legend
                     if (ap.get_tag ().compare ("legend") < 0)
                       {
-                        if (ap.rotate3d_is ("on"))
+                        if (rotate_enabled ())
                           view2status (ax_obj);
                         else
                           pixel2status (ax_obj, pos_x, pos_y,
@@ -1497,13 +1527,9 @@
                                            Fl::event_y () - menu_dy (),
                                            x1, y1);
 
-                        if (ap.pan_is ("on"))
-                          ap.translate_view (x0, x1, y0, y1);
-                        else if (ap.pan_is ("xon"))
-                          ap.translate_view (x0, x1, y1, y1);
-                        else if (ap.pan_is ("yon"))
-                          ap.translate_view (x1, x1, y0, y1);
-                        else if (ap.rotate3d_is ("on"))
+                        if (pan_enabled ())
+                          ap.translate_view ("both", x0, x1, y0, y1);
+                        else if (rotate_enabled ())
                           {
                             double daz, del;
                             daz = (Fl::event_x () - pos_x) / pos(2) * 360;
@@ -1569,7 +1595,7 @@
                   pixel2pos (ax, Fl::event_x (), Fl::event_y () - menu_dy (),
                              x1, y1);
 
-                  ap.zoom_about_point (x1, y1, factor, false);
+                  ap.zoom_about_point ("both", x1, y1, factor, false);
                   mark_modified ();
                   return 1;
                 }
@@ -1632,7 +1658,7 @@
                                 yl(0) = y1;
                                 yl(1) = y0;
                               }
-                            ap.zoom (xl, yl);
+                            ap.zoom ("both", xl, yl);
                           }
                         mark_modified ();
                         return 1;
--- a/scripts/plot/util/figure.m	Fri Feb 06 08:31:49 2015 -0800
+++ b/scripts/plot/util/figure.m	Fri Feb 06 13:06:54 2015 -0500
@@ -85,6 +85,7 @@
   if (init_new_figure)
     f = __go_figure__ (f, varargin{:});
     __add_default_menu__ (f);
+    __add_default_mouse_modes__ (f);
   elseif (nargs > 0)
     set (f, varargin{:});
   endif
@@ -103,6 +104,22 @@
 
 endfunction
 
+function __add_default_mouse_modes__ (fig)
+
+  set (fig, "__pan_mode__", struct ("Enable", "off",
+                                    "Motion", "both",
+                                    "FigureHandle", fig));
+
+  set (fig, "__rotate_mode__", struct ("Enable", "off",
+                                       "RotateStyle", "box",
+                                       "FigureHandle", fig));
+
+  set (fig, "__zoom_mode__", struct ("Enable", "off",
+                                     "Motion", "both",
+                                     "Direction", "in",
+                                     "FigureHandle", fig));
+
+endfunction
 
 %!test
 %! hf = figure ("visible", "off");
--- a/scripts/plot/util/pan.m	Fri Feb 06 08:31:49 2015 -0800
+++ b/scripts/plot/util/pan.m	Fri Feb 06 13:06:54 2015 -0500
@@ -19,59 +19,98 @@
 ## -*- texinfo -*-
 ## @deftypefn  {Command} {} pan
 ## @deftypefnx {Command} {} pan on
+## @deftypefnx {Command} {} pan off
 ## @deftypefnx {Command} {} pan xon
 ## @deftypefnx {Command} {} pan yon
-## @deftypefnx {Command} {} pan off
-## @deftypefnx {Function File} {} pan (@var{hax}, @dots{})
-## Control panning mode of interactive graph in GUI.
+## @deftypefnx {Function File} {} pan (@var{hfig}, @var{option})
+## Control the interactive panning mode of a figure in the GUI.
 ##
-## The function state input may be either @qcode{"on"}, @qcode{"xon"},
-## @qcode{"yon"} or @qcode{"off"}.
+## Given the option @qcode{"on"} or @qcode{"off"}, set the interactive
+## pan mode on or off.
 ##
-## If it is omitted the current state is toggled (@qcode{"xon"} and
-## @qcode{"yon"} are treated as @qcode{"on"}).
+## With no arguments, toggle the current pan mode on or off.
 ##
-## @qcode{"xon"} limits panning to the x-axis, @qcode{"yon"} to the
-## y-axis.
-##
-## If the first argument @var{hax} is an axes handle, then operate on
-## this axis rather than the current axes returned by @code{gca}.
+## Given the option @qcode{"xon"} or @qcode{"yon"}, enable pan mode
+## for the x or y axis only.
 ##
-## To query the current mode use the @code{get}
-## function.  For example:
+## If the first argument @var{hfig} is a figure, then operate on
+## the given figure rather than the current figure as returned by
+## @code{gcf}.
 ##
-## @example
-## mode = get (gca, "pan");
-## @end example
 ## @seealso{rotate3d, zoom}
 ## @end deftypefn
 
 function pan (varargin)
 
-  if (numel (varargin) > 0 && isaxes (varargin{1}))
-    hax = varargin{1};
-    varargin(1) = [];
-  else
-    hax = gca ();
+  hfig = NaN;
+
+  nargs = nargin;
+
+  if (nargs > 2)
+    print_usage ();
+  endif
+
+  if (nargin == 1 && nargout > 0 && isfigure (varargin{1}))
+    error ("pan_object_handle = pan (hfig): not implemented");
   endif
 
-  toolkit = get (ancestor (hax, "figure"), "__graphics_toolkit__");
-  if (! strcmp (toolkit, "fltk"))
-    warning ("pan: Only implemented for graphics_toolkit FLTK");
+  if (nargs == 2)
+    hfig = varargin{1};
+    if (isfigure (hfig))
+      varargin(1) = [];
+      nargs--;
+    else
+      error ("pan: expecting figure handle as first argument");
+    endif
+  endif
+
+  if (isnan (hfig))
+    hfig = gcf ();
   endif
 
-  if (numel (varargin) > 1)
-    print_usage ();
-  elseif (numel (varargin) == 0)
-    # toggle
-    m = get (hax, "pan");
-    if (findstr (m, "on") > 0)
-      set (hax, "pan", "off");
+  if (nargs == 0)
+    pm = get (hfig, "__pan_mode__");
+    if (strcmp (pm.Enable, "on"))
+      pm.Enable = "off";
     else
-      set (hax, "pan", "on");
+      pm.Enable = "on";
     endif
-  elseif (numel (varargin) == 1)
-    set (hax, "pan", varargin{1});
+    set (hfig, "__pan_mode__", pm);
+  elseif (nargs == 1)
+    arg = varargin{1};
+    if (ischar (arg))
+      switch (arg)
+        case {"on", "off", "xon", "yon"}
+          pm = get (hfig, "__pan_mode__");
+          switch (arg)
+            case {"on", "off"}
+              pm.Enable = arg;
+              pm.Motion = "both";
+            case "xon"
+              pm.Enable = "on";
+              pm.Motion = "horizontal";
+            case "yon"
+              pm.Enable = "on";
+              pm.Motion = "vertical";
+          endswitch
+          set (hfig, "__pan_mode__", pm);
+          if (strcmp (arg, "off"))
+            set (hfig, "__mouse_mode__", "none");
+          else
+            ## FIXME: Is there a better way other than calling these
+            ## functions to set the other mouse mode Enable fields to
+            ## "off"?
+            rotate3d ("off");
+            zoom ("off");
+            set (hfig, "__mouse_mode__", "pan");
+          endif
+
+        otherwise
+          error ("pan: unrecognized option '%s'", arg);
+      endswitch
+    else
+      error ("pan: wrong type argument '%s'", class (arg));
+    endif
   endif
 
 endfunction
--- a/scripts/plot/util/rotate3d.m	Fri Feb 06 08:31:49 2015 -0800
+++ b/scripts/plot/util/rotate3d.m	Fri Feb 06 13:06:54 2015 -0500
@@ -20,53 +20,85 @@
 ## @deftypefn  {Command} {} rotate3d
 ## @deftypefnx {Command} {} rotate3d on
 ## @deftypefnx {Command} {} rotate3d off
-## @deftypefnx {Function File} {} rotate3d (@var{hax}, @dots{})
-## Control 3-D rotation mode of interactive graph in GUI.
+## @deftypefnx {Function File} {} rotate3d (@var{hfig}, @var{option})
+## Control the interactive 3-D rotation mode of a figure in the GUI.
 ##
-## The function state input may be either @qcode{"on"} or @qcode{"off"}
-## and can only be set for 3-D plots.
+## Given the option @qcode{"on"} or @qcode{"off"}, set the interactive
+## rotate mode on or off.
 ##
-## If the first argument @var{hax} is an axes handle, then operate on
-## this axis rather than the current axes returned by @code{gca}.
+## With no arguments, toggle the current rotate mode on or off.
 ##
-## To query the current mode use the @code{get} function.  For example:
+## If the first argument @var{hfig} is a figure, then operate on
+## the given figure rather than the current figure as returned by
+## @code{gcf}.
 ##
-## @example
-## mode = get (gca, "rotate3d");
-## @end example
 ## @seealso{pan, zoom}
 ## @end deftypefn
 
 function rotate3d (varargin)
 
-  if (numel (varargin) > 0 && isaxes (varargin{1}))
-    hax = varargin{1};
-    varargin(1) = [];
-  else
-    hax = gca ();
+  hfig = NaN;
+
+  nargs = nargin;
+
+  if (nargs > 2)
+    print_usage ();
+  endif
+
+  if (nargin == 1 && nargout > 0 && isfigure (varargin{1}))
+    error ("rotate_object_handle = rotate3d (hfig): not implemented");
   endif
 
-  toolkit = get (ancestor (hax, "figure"), "__graphics_toolkit__");
-  if (! strcmp (toolkit, "fltk"))
-    warning ("rotate3d: Only implemented for graphics_toolkit FLTK");
+  if (nargs == 2)
+    hfig = varargin{1};
+    if (isfigure (hfig))
+      varargin(1) = [];
+      nargs--;
+    else
+      error ("rotate3d: expecting figure handle as first argument");
+    endif
+  endif
+
+  if (isnan (hfig))
+    hfig = gcf ();
   endif
 
-  ndims = __calc_dimensions__ (hax);
-  if (ndims == 2)
-    warning ("rotate3d: Only available for 3D plots");
-  else
-    if (numel (varargin) > 1)
-      print_usage ();
-    elseif (numel (varargin) == 0)
-      # toggle
-      m = get (hax, "pan");
-      if (strcmp (get (hax, "rotate3d"), "on"))
-        set (hax, "rotate3d", "off");
-      else
-        set (hax, "rotate3d", "on");
-      endif
-    elseif (numel (varargin) == 1)
-      set (hax, "rotate3d", varargin{1});
+  if (nargs == 0)
+    rm = get (hfig, "__rotate_mode__");
+    if (strcmp (rm.Enable, "on"))
+      rm.Enable = "off";
+    else
+      rm.Enable = "on";
+    endif
+    set (hfig, "__rotate_mode__", rm);
+  elseif (nargs == 1)
+    arg = varargin{1};
+    if (ischar (arg))
+      switch (arg)
+        case {"on", "off"}
+          rm = get (hfig, "__rotate_mode__");
+          switch (arg)
+            case {"on", "off"}
+              rm.Enable = arg;
+              rm.Motion = "both";
+          endswitch
+          set (hfig, "__rotate_mode__", rm);
+          if (strcmp (arg, "off"))
+            set (hfig, "__mouse_mode__", "none");
+          else
+            ## FIXME: Is there a better way other than calling these
+            ## functions to set the other mouse mode Enable fields to
+            ## "off"?
+            pan ("off");
+            zoom ("off");
+            set (hfig, "__mouse_mode__", "rotate");
+          endif
+
+        otherwise
+          error ("rotate3d: unrecognized option '%s'", arg);
+      endswitch
+    else
+      error ("rotate3d: wrong type argument '%s'", class (arg));
     endif
   endif
 
--- a/scripts/plot/util/zoom.m	Fri Feb 06 08:31:49 2015 -0800
+++ b/scripts/plot/util/zoom.m	Fri Feb 06 13:06:54 2015 -0500
@@ -17,10 +17,17 @@
 ## <http://www.gnu.org/licenses/>.
 
 ## -*- texinfo -*-
-## @deftypefn  {Command} {} zoom (@var{factor})
+## @deftypefn {Command} {} zoom
+## @deftypefnx  {Command} {} zoom (@var{factor})
+## @deftypefnx {Command} {} zoom on
+## @deftypefnx {Command} {} zoom off
+## @deftypefnx {Command} {} zoom xon
+## @deftypefnx {Command} {} zoom yon
 ## @deftypefnx {Command} {} zoom out
 ## @deftypefnx {Command} {} zoom reset
-## Zoom the current axes object.
+## @deftypefnx {Command} {} zoom (@var{hfig}, @var{option})
+## Zoom the current axes object or control the interactive zoom mode of
+## a figure in the GUI.
 ##
 ## Given a numeric argument greater than zero, zoom by the given factor.
 ## If the zoom factor is greater than one, zoom in on the plot.  If the
@@ -28,21 +35,27 @@
 ## three-element vector, then the elements specify the zoom factors for
 ## the x, y, and z axes respectively.
 ##
+## Given the option @qcode{"on"} or @qcode{"off"}, set the interactive
+## zoom mode on or off.
+##
+## With no arguments, toggle the current zoom mode on or off.
+##
+## Given the option @qcode{"xon"} or @qcode{"yon"}, enable zoom mode
+## for the x or y axis only.
+##
 ## Given the option @qcode{"out"}, zoom to the initial zoom setting.
 ##
 ## Given the option @qcode{"reset"}, store the current zoom setting so
 ## that @code{zoom out} will return to this zoom level.
 ##
+## If the first argument @var{hfig} is a figure, then operate on
+## the given figure rather than the current figure as returned by
+## @code{gcf}.
+##
 ## @seealso{pan, rotate3d}
 ## @end deftypefn
 
 ## Eventually we need to also support these features:
-## @deftypefn {Command} {} zoom
-## @deftypefnx {Command} {} zoom on
-## @deftypefnx {Command} {} zoom off
-## @deftypefnx {Command} {} zoom xon
-## @deftypefnx {Command} {} zoom yon
-## @deftypefnx {Command} {} zoom (@var{hfig}, @var{option})
 ## @deftypefnx {Command} {zoom_object_handle =} zoom (@var{hfig})
 
 function zoom (varargin)
@@ -74,62 +87,83 @@
   endif
 
   if (nargs == 0)
-    error ("zoom: toggling zoom mode is not implemented");
+    zm = get (hfig, "__zoom_mode__");
+    if (strcmp (zm.Enable, "on"))
+      zm.Enable = "off";
+    else
+      zm.Enable = "on";
+    endif
+    set (hfig, "__zoom_mode__", zm);
   elseif (nargs == 1)
     arg = varargin{1};
     if (isnumeric (arg))
       factor = arg;
       switch (numel (factor))
-        case 3
-          xfactor = factor(1);
-          yfactor = factor(2);
-          zfactor = factor(3);
         case 2
           xfactor = factor(1);
           yfactor = factor(2);
-          zfactor = 1;
         case 1
-          xfactor = yfactor = zfactor = factor;
+          xfactor = yfactor = factor;
         otherwise
           error ("zoom: invalid factor");
       endswitch
-      if (xfactor < 0 || yfactor < 0 || zfactor < 0)
+      if (xfactor < 0 || yfactor < 0)
         error ("zoom: factor must be greater than 1");
-      elseif (xfactor == 1 && yfactor == 1 && zfactor == 1)
+      elseif (xfactor == 1 && yfactor == 1)
         return;
       endif
       cax = get (hfig, "currentaxes");
       if (! isempty (cax))
-        limits = axis ();
-        initial_zoom = getappdata (cax, "initial_zoom");
-        if (isempty (initial_zoom))
-          setappdata (cax, "__initial_zoom__", limits);
+        if (xfactor != 1)
+          if (yfactor != 1)
+            mode = "both";
+          else
+            mode = "horizontal";
+          endif
+        else
+          if (yfactor != 1)
+            mode = "vertical";
+          endif
         endif
-        limits(1:2) /= xfactor;
-        limits(3:4) /= yfactor;
-        if (numel (limits) > 4)
-          limits(5:6) /= zfactor;
-        endif
-        axis (cax, limits);
+        __zoom__ (cax, mode, factor);
       endif
     elseif (ischar (arg))
       switch (arg)
         case {"on", "off", "xon", "yon"}
-          error ("zoom %s: not implemented", arg);
+          zm = get (hfig, "__zoom_mode__");
+          switch (arg)
+            case {"on", "off"}
+              zm.Enable = arg;
+              zm.Motion = "both";
+            case "xon"
+              zm.Enable = "on";
+              zm.Motion = "horizontal";
+            case "yon"
+              zm.Enable = "on";
+              zm.Motion = "vertical";
+          endswitch
+          set (hfig, "__zoom_mode__", zm);
+          if (strcmp (arg, "off"))
+            set (hfig, "__mouse_mode__", "none");
+          else
+            ## FIXME: Is there a better way other than calling these
+            ## functions to set the other mouse mode Enable fields to
+            ## "off"?
+            pan ("off");
+            rotate3d ("off");
+            set (hfig, "__mouse_mode__", "zoom");
+          endif
 
         case "out"
           cax = get (hfig, "currentaxes");
           if (! isempty (cax))
-            initial_zoom = getappdata (cax, "__initial_zoom__");
-            if (! isempty (initial_zoom))
-              axis (cax, initial_zoom);
-            endif
+            __zoom__ (cax, "out");
           endif
 
         case "reset"
           cax = get (hfig, "currentaxes");
           if (! isempty (cax))
-            setappdata (cax, "__initial_zoom__", axis ());
+            __zoom__ (cax, "reset");
           endif
 
         otherwise