changeset 24523:501986e12b8b

Implement "pickableparts" property (bug #52795). * NEWS: Announce that property "PickableParts" has been implemented. * gendpropdoc.m: Document property. State that the property does nothing for figure and root objects. * graphics.in.h: Add allowed value "all" to base_properties::pickableparts and remove pickableparts property from all other objects. * gl-render.h (opengl_renderer::selecting): Add new boolean attribute and setter. * gl-render.cc (opengl_renderer::draw_axes): While in selection mode don't draw axes if "pickableparts" is "none". (opengl_renderer::draw_axes_x/y/zgrid, opengl_renderer::draw_axes_plane): While in selection mode, allow drawing grids/plane if "pickableparts" is "all" (opengl_renderer::draw_line): While in selection mode, allow drawing line and markers "pickableparts" is "all". (opengl_renderer::draw_patch): While in selection mode, allow drawing lines polygons and markers if "pickableparts" is "all". (opengl_renderer::draw_surface): ditto. (opengl_renderer::draw_all_lights): while in selection mode, allow drawing children objects if "pickableparts" is "all". * gl-select.cc (opengl_selector::draw): Put renderer in selection mode. * Canvas.cc (Canvas::canvasMousePressEvent): overhaul handling of currentObj.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Wed, 03 Jan 2018 21:21:41 +0100
parents ec5591efafe4
children a56d283ff18a
files NEWS doc/interpreter/genpropdoc.m libgui/graphics/Canvas.cc libgui/graphics/gl-select.cc libinterp/corefcn/gl-render.cc libinterp/corefcn/gl-render.h libinterp/corefcn/graphics.in.h
diffstat 7 files changed, 147 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Jan 04 14:25:11 2018 -0800
+++ b/NEWS	Wed Jan 03 21:21:41 2018 +0100
@@ -63,6 +63,9 @@
     explicitly instructed to perform an economy factorization by using a
     final argument of 0.
 
+ ** The graphic object property "PickableParts" has been implemented which
+    controls whether an object can accept mouse clicks.
+
  ** Text objects now implement the properties "BackgroundColor",
     "EdgeColor", "LineStyle", "LineWidth", and "Margin".
 
--- a/doc/interpreter/genpropdoc.m	Thu Jan 04 14:25:11 2018 -0800
+++ b/doc/interpreter/genpropdoc.m	Wed Jan 03 21:21:41 2018 +0100
@@ -165,11 +165,30 @@
 handle is not visible in its parent's \"children\" property.";
 
       case "hittest"
+        s.doc = "Specify whether __objname__ processes mouse events \
+or passes them to ancestors of the object.  When enabled, the object may \
+respond to mouse clicks by evaluating the @qcode{\"buttondownfcn\"}, showing \
+the uicontextmenu, and eventually becoming the root \
+@qcode{\"currentobject\"}.  This property is only relevant when the object \
+can accept mouse clicks which is determined by the @qcode{\"pickableparts\"} \
+property.  @xref{XREF__objname__pickableparts, , @w{pickableparts property}}.";
+
       case "interruptible"
       case "parent"
         s.doc = "Handle of the parent graphics object.";
         s.valid = valid_handle;
 
+      case "pickableparts"
+        s.doc = "Specify whether __objname__ will accept mouse clicks.  \
+By default, __prop__ is @qcode{\"visible\"} and only visible parts of the \
+__objname__ or its children may react to mouse clicks.  When __prop__ is \
+@qcode{\"all\"} both visible and invisible parts (or children) may react to \
+mouse clicks.  When __prop__ is @qcode{\"none\"} mouse clicks on the object \
+are ignored and transmitted to any objects underneath this one.  When an \
+object is configured to accept mouse clicks the @qcode{\"hittest\"} property \
+will determine how they are processed.  \
+@xref{XREF__objname__hittest, , @w{hittest property}}.";
+        
       case "selected"
       case "selectionhighlight"
       case "tag"
@@ -210,6 +229,12 @@
         s.doc = "Root figure has no parent graphics object.  __prop__ \
 is always empty.";
 
+      case "hittest"
+        s.doc = doc_unused;
+        
+      case "pickableparts"
+        s.doc = doc_unused;
+
       ## Specific properties
       case "callbackobject"
         s.doc = "Graphics handle of the current object whose callback is executing.";
@@ -296,6 +321,9 @@
       case "clipping"
         s.doc = doc_unused;
 
+      case "pickableparts"
+        s.doc = doc_unused;
+
       ## Specific properties
       case "alphamap"
         s.doc = sprintf (doc_notimpl, "Transparency");
@@ -721,9 +749,6 @@
 @xref{XREFaxesposition, , @w{position property}}.";
         s.valid = valid_4elvec;
 
-      case "pickableparts"
-        s.doc = doc_unused;
-
       case "plotboxaspectratio"
         s.doc = "@xref{XREFpbaspect, , pbaspect function}.  \
 __modemsg__.";
--- a/libgui/graphics/Canvas.cc	Thu Jan 04 14:25:11 2018 -0800
+++ b/libgui/graphics/Canvas.cc	Wed Jan 03 21:21:41 2018 +0100
@@ -617,6 +617,7 @@
       {
         graphics_object figObj (obj.get_ancestor ("figure"));
 
+        // Any click in a figure canvas makes it current
         if (figObj)
           {
             graphics_object root = gh_manager::get_object (0);
@@ -626,8 +627,16 @@
 
         graphics_object currentObj, axesObj;
 
+        // Retrieve selected object.   
         select_object (obj, event, currentObj, axesObj);
 
+        // currentObj may be invalid if, e.g., all objects under the mouse
+        // click had "hittest" -> "off" or "pickableparts" -> "none".  In that
+        // case, replace with underlying figObj which always accepts mouse
+        // clicks.
+        if (! currentObj.valid_object ())
+          currentObj = figObj;
+
         if (axesObj)
           {
             if (axesObj.get_properties ().handlevisibility_is ("on")
@@ -635,20 +644,8 @@
                 && axesObj.get_properties ().get_tag () != "colorbar")
               Utils::properties<figure> (figObj)
               .set_currentaxes (axesObj.get_handle ().as_octave_value ());
-            if (! currentObj)
-              currentObj = axesObj;
           }
 
-        if (! currentObj)
-          currentObj = obj;
-
-        if (currentObj.get_properties ().handlevisibility_is ("on"))
-          Utils::properties<figure> (figObj)
-          .set_currentobject (currentObj.get_handle ().as_octave_value ());
-        else
-          Utils::properties<figure> (figObj).set_currentobject (
-            octave::numeric_limits<double>::NaN ());
-
         Figure *fig = dynamic_cast<Figure *> (Backend::toolkitObject (figObj));
 
         MouseMode newMouseMode = NoMode;
@@ -659,33 +656,38 @@
         switch (newMouseMode)
           {
           case NoMode:
-            gh_manager::post_set (figObj.get_handle (), "selectiontype",
-                                  Utils::figureSelectionType (event, isdblclick), false);
-
-            updateCurrentPoint (figObj, obj, event);
-
-            gh_manager::post_callback (figObj.get_handle (),
-                                       "windowbuttondownfcn",
-                                       button_number (event));
+            {
+              // Update the figure "currentobject"
+              auto& fprop = Utils::properties<figure> (figObj);
+            
+              if (currentObj.get_properties ().handlevisibility_is ("on"))
+                fprop.set_currentobject (currentObj.get_handle ()
+                                         .as_octave_value ());
+              else
+                fprop.set_currentobject (Matrix ());
+            
+              // Update figure "selectiontype" and "currentpoint" 
+              gh_manager::post_set (
+                                    figObj.get_handle (), "selectiontype",
+                                    Utils::figureSelectionType (event, isdblclick), false);
 
-            if (currentObj.get ("buttondownfcn").isempty ())
-              {
-                graphics_object parentObj =
-                  gh_manager::get_object (currentObj.get_parent ());
+              updateCurrentPoint (figObj, obj, event);
 
-                if (parentObj.valid_object () && parentObj.isa ("hggroup"))
-                  gh_manager::post_callback (parentObj.get_handle (),
-                                             "buttondownfcn",
-                                             button_number (event));
-              }
-            else
-              gh_manager::post_callback (currentObj.get_handle (),
-                                         "buttondownfcn",
+              gh_manager::post_callback (figObj.get_handle (),
+                                         "windowbuttondownfcn",
                                          button_number (event));
 
-            if (event->button () == Qt::RightButton)
-              ContextMenu::executeAt (currentObj.get_properties (),
-                                      event->globalPos ());
+              // Execute the "buttondownfcn" of the selected object
+              if (! currentObj.get ("buttondownfcn").isempty ())
+                gh_manager::post_callback (currentObj.get_handle (),
+                                           "buttondownfcn",
+                                           button_number (event));
+
+              // Show context menu of the selected object
+              if (event->button () == Qt::RightButton)
+                ContextMenu::executeAt (currentObj.get_properties (),
+                                        event->globalPos ());
+            }
             break;
 
           case TextMode:
--- a/libgui/graphics/gl-select.cc	Thu Jan 04 14:25:11 2018 -0800
+++ b/libgui/graphics/gl-select.cc	Wed Jan 03 21:21:41 2018 +0100
@@ -140,7 +140,9 @@
 
     object_map[name] = go;
     glPushName (name);
+    set_selecting (true);
     opengl_renderer::draw (go, toplevel);
+    set_selecting (false);
     glPopName ();
   }
 
--- a/libinterp/corefcn/gl-render.cc	Thu Jan 04 14:25:11 2018 -0800
+++ b/libinterp/corefcn/gl-render.cc	Wed Jan 03 21:21:41 2018 +0100
@@ -625,7 +625,8 @@
   opengl_renderer::opengl_renderer (void)
     : toolkit (), xform (), xmin (), xmax (), ymin (), ymax (),
       zmin (), zmax (), xZ1 (), xZ2 (), marker_id (), filled_marker_id (),
-      camera_pos (), camera_dir (), interpreter ("none"), txt_renderer ()
+      camera_pos (), camera_dir (), interpreter ("none"), txt_renderer (), 
+      selecting (false)
   {
     // This constructor will fail if we don't have OpenGL or if the data
     // types we assumed in our public interface aren't compatible with the
@@ -1389,7 +1390,9 @@
 
     int xstate = props.get_xstate ();
 
-    if (props.is_visible () && xstate != AXE_DEPTH_DIR)
+    if (xstate != AXE_DEPTH_DIR
+        && (props.is_visible () 
+            || (selecting && props.pickableparts_is ("all"))))
       {
         int zstate = props.get_zstate ();
         bool x2Dtop = props.get_x2Dtop ();
@@ -1570,7 +1573,9 @@
 
     int ystate = props.get_ystate ();
 
-    if (ystate != AXE_DEPTH_DIR && props.is_visible ())
+    if (ystate != AXE_DEPTH_DIR && props.is_visible ()
+        && (props.is_visible () 
+            || (selecting && props.pickableparts_is ("all"))))
       {
         int zstate = props.get_zstate ();
         bool y2Dright = props.get_y2Dright ();
@@ -1750,7 +1755,9 @@
   {
     int zstate = props.get_zstate ();
 
-    if (zstate != AXE_DEPTH_DIR && props.is_visible ())
+    if (zstate != AXE_DEPTH_DIR && props.is_visible ()
+        && (props.is_visible () 
+            || (selecting && props.pickableparts_is ("all"))))
       {
         bool xySym = props.get_xySym ();
         bool zSign = props.get_zSign ();
@@ -1960,14 +1967,17 @@
       {
         graphics_object go = gh_manager::get_object (children(i));
 
-        if (go.get_properties ().is_visible ())
+        base_properties p = go.get_properties ();
+
+        if (p.is_visible ()
+            || (selecting && p.pickableparts_is ("all")))
           {
-            if (go.isa ("light"))
+            if (go.isa ("light") && ! selecting)
               {
                 if (num_lights < max_lights)
                   {
                     current_light = GL_LIGHT0 + num_lights;
-                    set_clipping (go.get_properties ().is_clipping ());
+                    set_clipping (p.is_clipping ());
                     draw (go);
                     num_lights++;
                   }
@@ -1976,9 +1986,10 @@
                                    "light: Maximum number of lights (%d) in these axes is "
                                    "exceeded.", max_lights);
               }
-            else if (go.isa ("hggroup"))
+            else if (go.isa ("hggroup")
+                     && ! (selecting && p.pickableparts_is ("none")))
               draw_all_lights (go.get_properties (), obj_list);
-            else
+            else if (! (selecting && p.pickableparts_is ("none")))
               obj_list.push_back (go);
           }
       }
@@ -2090,6 +2101,11 @@
     if (! props.is_visible () && props.get_tag () == "legend")
       return;
 
+    // Don't draw the axes and its children if we are in selection and
+    // pickable parts is "none".
+    if (selecting && props.pickableparts_is ("none"))
+      return;
+
     static double floatmax = std::numeric_limits<float>::max ();
 
     double x_min = props.get_x_min ();
@@ -2155,6 +2171,8 @@
   {
 #if defined (HAVE_OPENGL)
 
+    bool draw_all = selecting && props.pickableparts_is ("all");
+
     Matrix x = xform.xscale (props.get_xdata ().matrix_value ());
     Matrix y = xform.yscale (props.get_ydata ().matrix_value ());
     Matrix z = xform.zscale (props.get_zdata ().matrix_value ());
@@ -2251,11 +2269,15 @@
       {
         Matrix lc, fc;
 
-        if (props.markeredgecolor_is ("auto"))
+        if (draw_all)
+          lc = Matrix (1, 3, 0.0);
+        else if (props.markeredgecolor_is ("auto"))
           lc = props.get_color_rgb ();
         else if (! props.markeredgecolor_is ("none"))
           lc = props.get_markeredgecolor_rgb ();
 
+        if (draw_all)
+          fc = Matrix (1, 3, 0.0);
         if (props.markerfacecolor_is ("auto"))
           fc = props.get_color_rgb ();
         else if (! props.markerfacecolor_is ("none"))
@@ -2294,6 +2316,8 @@
   {
 #if defined (HAVE_OPENGL)
 
+    bool draw_all = selecting && props.pickableparts_is ("all");
+
     const Matrix x = xform.xscale (props.get_xdata ().matrix_value ());
     const Matrix y = xform.yscale (props.get_ydata ().matrix_value ());
     const Matrix z = xform.zscale (props.get_zdata ().matrix_value ());
@@ -2378,7 +2402,7 @@
     if (fc_mode == TEXTURE)
       tex = opengl_texture::create (props.get_color_data ());
 
-    if (! props.facecolor_is ("none"))
+    if (draw_all || ! props.facecolor_is ("none"))
       {
         if (fa_mode == 0)
           {
@@ -2835,11 +2859,13 @@
         // FIXME: check what to do with marker facecolor set to auto
         //        and facecolor set to none.
 
-        bool do_edge = ! props.markeredgecolor_is ("none");
-        bool do_face = ! props.markerfacecolor_is ("none");
-
-        Matrix mecolor = props.get_markeredgecolor_rgb ();
-        Matrix mfcolor = props.get_markerfacecolor_rgb ();
+        bool do_edge = draw_all || ! props.markeredgecolor_is ("none");
+        bool do_face = draw_all || ! props.markerfacecolor_is ("none");
+
+        Matrix mecolor = (draw_all ? Matrix (1, 3, 0.0) :
+                          props.get_markeredgecolor_rgb ());
+        Matrix mfcolor = (draw_all ? Matrix (1, 3, 0.0) :
+                          props.get_markerfacecolor_rgb ());
         Matrix cc (1, 3, 0.0);
 
         if (mecolor.isempty () && props.markeredgecolor_is ("auto"))
@@ -2922,6 +2948,7 @@
         return;
       }
 
+    bool draw_all = selecting && props.pickableparts_is ("all");
     const Matrix f = props.get_faces ().matrix_value ();
     const Matrix v = xform.scale (props.get_vertices ().matrix_value ());
     Matrix c;
@@ -2940,7 +2967,7 @@
     bool has_normals = (n.rows () == nv);
 
     int fc_mode = ((props.facecolor_is ("none")
-                    || props.facecolor_is_rgb ()) ? 0 :
+                    || props.facecolor_is_rgb () || draw_all) ? 0 :
                    (props.facecolor_is ("flat") ? 1 : 2));
     int fl_mode = (props.facelighting_is ("none") ? 0 :
                    (props.facelighting_is ("flat") ? 1 : 2));
@@ -2989,21 +3016,24 @@
         count_f(i) = count;
       }
 
-    if (fc_mode > 0 || ec_mode > 0)
+    if (draw_all || fc_mode > 0 || ec_mode > 0)
       {
-        c = props.get_color_data ().matrix_value ();
+        if (draw_all)
+          c = Matrix (1, 3, 0.0);
+        else
+          c = props.get_color_data ().matrix_value ();
 
         if (c.rows () == 1)
           {
             // Single color specifications, we can simplify a little bit
 
-            if (fc_mode > 0)
+            if (draw_all || fc_mode > 0)
               {
                 fcolor = c;
                 fc_mode = UNIFORM;
               }
 
-            if (ec_mode > 0)
+            if (draw_all || ec_mode > 0)
               {
                 ecolor = c;
                 ec_mode = UNIFORM;
@@ -3077,7 +3107,7 @@
     if (fl_mode > 0 || el_mode > 0)
       glMaterialf (LIGHT_MODE, GL_SHININESS, se);
 
-    if (! props.facecolor_is ("none"))
+    if (draw_all || ! props.facecolor_is ("none"))
       {
         // FIXME: adapt to double-radio property
         if (fa_mode == 0)
@@ -3176,7 +3206,8 @@
           }
       }
 
-    if (! props.edgecolor_is ("none") && ! props.linestyle_is ("none"))
+    if (draw_all
+        || (! props.edgecolor_is ("none") && ! props.linestyle_is ("none")))
       {
         // FIXME: adapt to double-radio property
         if (props.get_edgealpha_double () == 1)
@@ -3310,13 +3341,15 @@
         && ! (props.markeredgecolor_is ("none")
               && props.markerfacecolor_is ("none")))
       {
-        bool do_edge = ! props.markeredgecolor_is ("none");
-        bool do_face = ! props.markerfacecolor_is ("none");
-
-        Matrix mecolor = props.get_markeredgecolor_rgb ();
-        Matrix mfcolor = props.get_markerfacecolor_rgb ();
-
-        bool has_markerfacecolor = false;
+        bool do_edge = draw_all || ! props.markeredgecolor_is ("none");
+        bool do_face = draw_all || ! props.markerfacecolor_is ("none");
+
+        Matrix mecolor = (draw_all ? Matrix (1, 3, 0.0) :
+                          props.get_markeredgecolor_rgb ());
+        Matrix mfcolor = (draw_all ? Matrix (1, 3, 0.0) :
+                          props.get_markerfacecolor_rgb ());
+
+        bool has_markerfacecolor = draw_all || false;
 
         if ((mecolor.isempty () && ! props.markeredgecolor_is ("none"))
             || (mfcolor.isempty () && ! props.markerfacecolor_is ("none")))
--- a/libinterp/corefcn/gl-render.h	Thu Jan 04 14:25:11 2018 -0800
+++ b/libinterp/corefcn/gl-render.h	Wed Jan 03 21:21:41 2018 +0100
@@ -86,20 +86,24 @@
     virtual void init_gl_context (bool enhanced, const Matrix& backgroundColor);
     virtual void setup_opengl_transformation (const axes::properties& props);
 
+    virtual void set_clipbox (double x1, double x2, double y1, double y2,
+                              double z1, double z2);
+    virtual void set_clipping (bool on);
+    virtual void set_font (const base_properties& props);
     virtual void set_color (const Matrix& c);
-    virtual void set_polygon_offset (bool on, float offset = 0.0f);
+    virtual void set_interpreter (const caseless_str& interp)
+    {
+      interpreter = interp;
+    }
     virtual void set_linewidth (float w);
     virtual void set_linestyle (const std::string& s, bool stipple = false,
                                 double linewidth = 0.5);
     virtual void set_linecap (const std::string&) { };
     virtual void set_linejoin (const std::string&) { };
-    virtual void set_clipbox (double x1, double x2, double y1, double y2,
-                              double z1, double z2);
-    virtual void set_clipping (bool on);
-    virtual void set_font (const base_properties& props);
-    virtual void set_interpreter (const caseless_str& interp)
+    virtual void set_polygon_offset (bool on, float offset = 0.0f);
+    virtual void set_selecting (bool on)
     {
-      interpreter = interp;
+      selecting = on;
     }
 
     virtual void init_marker (const std::string& m, double size, float width);
@@ -216,6 +220,8 @@
     unsigned int current_light;
     int max_lights;
 
+    // Indicate we are drawing for selection purpose
+    bool selecting;
   private:
     class patch_tesselator;
   };
--- a/libinterp/corefcn/graphics.in.h	Thu Jan 04 14:25:11 2018 -0800
+++ b/libinterp/corefcn/graphics.in.h	Wed Jan 03 21:21:41 2018 +0100
@@ -2366,7 +2366,7 @@
     bool_property hittest , "on"
     bool_property interruptible , "on"
     handle_property parent fs , p
-    radio_property pickableparts , "{visible}|none"
+    radio_property pickableparts , "{visible}|all|none"
     bool_property selected , "off"
     bool_property selectionhighlight , "on"
     string_property tag s , ""
@@ -3158,9 +3158,6 @@
       callback_property windowscrollwheelfcn , Matrix ()
       radio_property windowstyle , "{normal}|modal|docked"
 
-      // Base properties which don't exist on object
-      // radio_property pickableparts h , "{visible}|none"
-
       // Octave-specific properties
       mutable string_property __gl_extensions__ hr , ""
       mutable string_property __gl_renderer__ hr , ""
@@ -3639,7 +3636,6 @@
       radio_property minorgridlinestyle , "{:}|-|--|-.|none"
       radio_property nextplot , "{replace}|add|replacechildren"
       array_property outerposition u , default_axes_outerposition ()
-      radio_property pickableparts , "{visible}|all|none"
       row_vector_property plotboxaspectratio mu , Matrix (1, 3, 1.0)
       radio_property plotboxaspectratiomode u , "{auto}|manual"
       array_property position u , default_axes_position ()
@@ -4313,7 +4309,6 @@
       color_property markeredgecolor , color_property (radio_values ("{auto}|none"), color_values (0, 0, 0))
       color_property markerfacecolor , color_property (radio_values ("auto|{none}"), color_values (0, 0, 0))
       double_property markersize , 6
-      radio_property pickableparts , "{visible}|all|none"
       row_vector_property xdata u , default_data ()
       string_property xdatasource , ""
       row_vector_property ydata u , default_data ()
@@ -4429,7 +4424,6 @@
       radio_property linestyle , "{-}|--|:|-.|none"
       double_property linewidth , 0.5
       double_property margin , 2
-      radio_property pickableparts , "{visible}|all|none"
       array_property position smu , Matrix (1, 3, 0.0)
       double_property rotation mu , 0
       text_label_property string u , ""
@@ -4595,7 +4589,6 @@
       array_property cdata u , default_image_cdata ()
       radio_property cdatamapping al , "scaled|{direct}"
       radio_property erasemode h , "{normal}|none|xor|background"
-      radio_property pickableparts , "{visible}|all|none"
       row_vector_property xdata mu , Matrix ()
       row_vector_property ydata mu , Matrix ()
       // hidden properties for limit computation
@@ -4883,7 +4876,6 @@
       color_property markerfacecolor , color_property (radio_values ("{none}|auto|flat"), color_values (0, 0, 0))
       double_property markersize , 6
       radio_property normalmode hsg , "{auto}|manual"
-      radio_property pickableparts , "{visible}|all|none"
       double_property specularcolorreflectance , 1.0
       double_property specularexponent , 10.0
       double_property specularstrength , 0.9
@@ -5096,7 +5088,6 @@
       double_property markersize , 6
       radio_property meshstyle , "{both}|row|column"
       radio_property normalmode hsg , "{auto}|manual"
-      radio_property pickableparts , "{visible}|all|none"
       double_property specularcolorreflectance , 1
       double_property specularexponent , 10
       double_property specularstrength , 0.9