changeset 7864:56f781f38f0b

Add dynamic property creation
author Michael Goffioul <michael.goffioul@gmail.com>
date Thu, 24 Apr 2008 16:43:31 +0200
parents 2a62d45fa21d
children b74039822fd2
files src/ChangeLog src/graphics.cc src/graphics.h.in
diffstat 3 files changed, 459 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/src/ChangeLog	Fri Apr 04 22:18:01 2008 +0300
+++ b/src/ChangeLog	Thu Apr 24 16:43:31 2008 +0200
@@ -47,6 +47,29 @@
 
 2008-06-04  Michael Goffioul <michael.goffioul@gmail.com>
 
+	* graphics.h.in (base_property::clone, string_property::clone,
+	radio_property::clone, color_property::clone, double_property::clone,
+	double_radio_property::clone, array_property::clone,
+	row_vector_property::clone, bool_property::clone,
+	handle_property::clone, callback_property::clone, any_property::clone,
+	property::clone): New virtual method.
+	(property::create): New static method to create dynamic properties.
+	(base_properties::has_property): New method to check the existence of
+	a property.
+	(any_property::any_property, row_vector_property::row_vector_property,
+	double_radio_property::double_radio_property): New copy constructors.
+	* graphics.cc (lookup_object_name): New static utility function.
+	(make_graphics_object_from_type): Likewise.
+	(gh_manager::do_make_graphics_handle): Use it.
+	(dprop_obj_map): New static map used for chaching purpose in dynamic
+	property creation.
+	(property::create): New static method to create dynamic properties.
+	(base_properties::has_property): New method to check the existence of
+	a property.
+	(base_properties::override_defaults): Check parent validity.
+	(Faddlistener): Documentation fix.
+	(Faddproperty): New function to create dynamic properties.
+
 	* genprops.awk (emit_get_array): Don't produce get_xxx_property
 	accessors.
 	* graphics.h.in (array_property::get_limits): New method to return the
--- a/src/graphics.cc	Fri Apr 04 22:18:01 2008 +0300
+++ b/src/graphics.cc	Thu Apr 24 16:43:31 2008 +0200
@@ -456,6 +456,79 @@
     }
 }
 
+static bool
+lookup_object_name (const caseless_str& name, caseless_str& go_name,
+		    caseless_str& rest)
+{
+  int len = name.length ();
+  int offset = 0;
+  bool result = false;
+
+  if (len >= 4)
+    {
+      caseless_str pfx = name.substr (0, 4);
+
+      if (pfx.compare ("axes") || pfx.compare ("line")
+	  || pfx.compare ("text"))
+	offset = 4;
+      else if (len >= 5)
+	{
+	  pfx = name.substr (0, 5);
+
+	  if (pfx.compare ("image") || pfx.compare ("patch"))
+	    offset = 5;
+	  else if (len >= 6)
+	    {
+	      pfx = name.substr (0, 6);
+
+	      if (pfx.compare ("figure"))
+		offset = 6;
+	      else if (len >= 7)
+		{
+		  pfx = name.substr (0, 7);
+
+		  if (pfx.compare ("surface"))
+		    offset = 7;
+		}
+	    }
+	}
+
+      if (offset > 0)
+	{
+	  go_name = pfx;
+	  rest = name.substr (offset);
+	  result = true;
+	}
+    }
+
+  return result;
+}
+
+static base_graphics_object*
+make_graphics_object_from_type (const caseless_str& type,
+				const graphics_handle& h = graphics_handle (),
+				const graphics_handle& p = graphics_handle ())
+{
+  base_graphics_object *go = 0;
+
+  if (type.compare ("figure"))
+    go = new figure (h, p);
+  else if (type.compare ("axes"))
+    go = new axes (h, p);
+  else if (type.compare ("line"))
+    go = new line (h, p);
+  else if (type.compare ("text"))
+    go = new text (h, p);
+  else if (type.compare ("image"))
+    go = new image (h, p);
+  else if (type.compare ("patch"))
+    go = new patch (h, p);
+  else if (type.compare ("surface"))
+    go = new surface (h, p);
+
+  return go;
+}
+
 // ---------------------------------------------------------------------
 
 void
@@ -764,6 +837,161 @@
     execute_callback (cb, h, data);
 }
 
+// Used to cache dummy graphics objects from which dynamic
+// properties can be cloned.
+static std::map<caseless_str, graphics_object> dprop_obj_map;
+
+property
+property::create (const std::string& name, const graphics_handle& h,
+		  const caseless_str& type, const octave_value_list& args)
+{
+  property retval;
+
+  if (type.compare ("string"))
+    {
+      std::string val = (args.length () > 0 ? args(0).string_value () : "");
+
+      if (! error_state)
+	retval = property (new string_property (name, h, val));
+    }
+  else if (type.compare ("any"))
+    {
+      octave_value val =
+	  (args.length () > 0 ? args(0) : octave_value (Matrix ()));
+
+      retval = property (new any_property (name, h, val));
+    }
+  else if (type.compare ("radio"))
+    {
+      if (args.length () > 0)
+	{
+	  std::string vals = args(0).string_value ();
+
+	  if (! error_state)
+	    {
+	      retval = property (new radio_property (name, h, vals));
+
+	      if (args.length () > 1)
+		retval.set (args(1));
+	    }
+	  else
+	    error ("addproperty: invalid argument for radio property, expected a string value");
+	}
+      else
+	error ("addproperty: missing possible values for radio property");
+    }
+  else if (type.compare ("double"))
+    {
+      double d = (args.length () > 0 ? args(0).double_value () : 0);
+
+      if (! error_state)
+	retval = property (new double_property (name, h, d));
+    }
+  else if (type.compare ("handle"))
+    {
+      double hh = (args.length () > 0 ? args(0).double_value () : octave_NaN);
+
+      if (! error_state)
+	{
+	  graphics_handle gh (hh);
+
+	  retval = property (new handle_property (name, h, gh));
+	}
+    }
+  else if (type.compare ("boolean"))
+    {
+      retval = property (new bool_property (name, h, false));
+
+      if (args.length () > 0)
+	retval.set (args(0));
+    }
+  else if (type.compare ("data"))
+    {
+      retval = property (new array_property (name, h, Matrix ()));
+
+      if (args.length () > 0)
+	{
+	  retval.set (args(0));
+
+	  // FIXME: additional argument could define constraints
+	  //        but is this really useful...?
+	}
+    }
+  else if (type.compare ("color"))
+    {
+      color_values cv (0, 0, 0);
+      radio_values rv;
+
+      if (args.length () > 1)
+	rv = radio_values (args(1).string_value ());
+
+      if (! error_state)
+	{
+	  retval = property (new color_property (name, h, cv, rv));
+
+	  if (! error_state)
+	    {
+	      if (args.length () > 0
+		  && ! args(0).is_empty ())
+		retval.set (args(0));
+	      else
+		retval.set (rv.default_value ());
+	    }
+	}
+    }
+  else
+    {
+      caseless_str go_name, go_rest;
+
+      if (lookup_object_name (type, go_name, go_rest))
+	{
+	  graphics_object go;
+
+	  std::map<caseless_str, graphics_object>::const_iterator it =
+	      dprop_obj_map.find (go_name);
+
+	  if (it == dprop_obj_map.end ())
+	    {
+	      base_graphics_object *bgo =
+		  make_graphics_object_from_type (go_name);
+
+	      if (bgo)
+		{
+		  go = graphics_object (bgo);
+
+		  dprop_obj_map[go_name] = go;
+		}
+	    }
+	  else
+	    go = it->second;
+
+	  if (go.valid_object ())
+	    {
+	      property prop = go.get_properties ().get_property (go_rest);
+
+	      if (! error_state)
+		{
+		  retval = prop.clone ();
+
+		  retval.set_parent (h);
+		  retval.set_name (name);
+
+		  if (args.length () > 0)
+		    retval.set (args(0));
+		}
+	    }
+	  else
+	    error ("addproperty: invalid object type (= %s)",
+		   go_name.c_str ());
+	}
+      else
+	error ("addproperty: unsupported type for dynamic property (= %s)",
+	       type.c_str ());
+    }
+  
+  return retval;
+}
+
 // ---------------------------------------------------------------------
 
 void
@@ -1433,6 +1661,25 @@
     }
 }
 
+bool
+base_properties::has_property (const caseless_str& name)
+{
+  property p;
+
+  unwind_protect::begin_frame("base_properties::has_property");
+
+  unwind_protect_bool (discard_error_messages);
+  unwind_protect_int (error_state);
+
+  discard_error_messages = true;
+
+  p = get_property (name);
+
+  unwind_protect::run_frame ("base_properties::has_property");
+
+  return (p.ok ());
+}
+
 void
 base_properties::remove_child (const graphics_handle& h)
 {
@@ -1502,7 +1749,9 @@
 base_properties::override_defaults (base_graphics_object& obj)
 {
   graphics_object parent_obj = gh_manager::get_object (get_parent ());
-  parent_obj.override_defaults (obj);
+
+  if (parent_obj)
+    parent_obj.override_defaults (obj);
 }
 
 void
@@ -3266,20 +3515,8 @@
 
   base_graphics_object *go = 0;
 
-  if (go_name == "figure")
-    go = new figure (h, p);
-  else if (go_name == "axes")
-    go = new axes (h, p);
-  else if (go_name == "line")
-    go = new line (h, p);
-  else if (go_name == "text")
-    go = new text (h, p);
-  else if (go_name == "image")
-    go = new image (h, p);
-  else if (go_name == "patch")
-    go = new patch (h, p);
-  else if (go_name == "surface")
-    go = new surface (h, p);
+  go = make_graphics_object_from_type (go_name, h, p);
+  
   if (go)
     {
       handle_map[h] = graphics_object (go);
@@ -3961,6 +4198,8 @@
 as described above. The next elements of the cell array are passed\n\
 as additional arguments to the function.\n\
 \n\
+Example:\n\
+\n\
 @example\n\
 function my_listener (h, dummy, p1)\n\
   fprintf (\"my_listener called with p1=%s\\n\", p1);\n\
@@ -4007,6 +4246,118 @@
   return retval;
 }
 
+DEFUN (addproperty, args, ,
+   "-*- texinfo -*-\n\
+@deftypefn {Built-in Function} {} addproperty (@var{name}, @var{h}, @var{type}, [@var{arg}, ...])\n\
+Create a new property named @var{name} in graphics object @var{h}.\n\
+@var{type} determines the type of the property to create. @var{args}\n\
+usually contains the default value of the property, but additional\n\
+arguments might be given, depending on the type of the property.\n\
+\n\
+The supported property types are:\n\
+\n\
+@table @code\n\
+@item string\n\
+A string property. @var{arg} contains the default string value.\n\
+@item any\n\
+An un-typed property. This kind of property can hold any octave\n\
+value. @var{args} contains the default value.\n\
+@item radio\n\
+A string property with a limited set of accepted values. The first\n\
+argument must be a string with all accepted values separated by\n\
+a vertical bar ('|'). The default value can be marked by enclosing\n\
+it with a '@{' '@}' pair. The default value may also be given as\n\
+an optional second string argument.\n\
+@item boolean\n\
+A boolean property. This property type is equivalent to a radio\n\
+property with \"on|off\" as accepted values. @var{arg} contains\n\
+the default property value.\n\
+@item double\n\
+A scalar double property. @var{arg} contains the default value.\n\
+@item handle\n\
+A handle property. This kind of property holds the handle of a\n\
+graphics object. @var{arg} contains the default handle value.\n\
+When no default value is given, the property is initialized to\n\
+the empty matrix.\n\
+@item data\n\
+A data (matrix) property. @var{arg} contains the default data\n\
+value. When no default value is given, the data is initialized to\n\
+the empty matrix.\n\
+@item color\n\
+A color property. @var{arg} contains the default color value.\n\
+When no default color is given, the property is set to black.\n\
+An optional second string argument may be given to specify an\n\
+additional set of accepted string values (like a radio property).\n\
+@end table\n\
+\n\
+@var{type} may also be the concatenation of a core object type and\n\
+a valid property name for that object type. The property created\n\
+then has the same characteristics as the referenced property (type,\n\
+possible values, hidden state...). This allows to clone an existing\n\
+property into the graphics object @var{h}.\n\
+\n\
+Examples:\n\
+\n\
+@example\n\
+addproperty (\"my_property\", gcf, \"string\", \"a string value\");\n\
+addproperty (\"my_radio\", gcf, \"radio\", \"val_1|val_2|@{val_3@}\");\n\
+addproperty (\"my_style\", gcf, \"linelinestyle\", \"--\");\n\
+@end example\n\
+\n\
+@end deftypefn")
+{
+  octave_value retval;
+
+  if (args.length () >= 3)
+    {
+      std::string name = args(0).string_value ();
+
+      if (! error_state)
+	{
+	  double h = args(1).double_value ();
+
+	  if (! error_state)
+	    {
+	      graphics_handle gh = gh_manager::lookup (h);
+
+	      if (gh.ok ())
+		{
+		  graphics_object go = gh_manager::get_object (gh);
+
+		  std::string type = args(2).string_value ();
+
+		  if (! error_state)
+		    {
+		      if (! go.get_properties ().has_property (name))
+			{
+			  property p = property::create (name, gh, type,
+							 args.splice (0, 3));
+
+			  if (! error_state)
+			    go.get_properties ().insert_property (name, p);
+			}
+		      else
+			error ("addproperty: a `%s' property already exists in the graphics object",
+			       name.c_str ());
+		    }
+		  else
+		    error ("addproperty: invalid property type, expected a string value");
+		}
+	      else
+		error ("addproperty: invalid graphics object (= %g)", h);
+	    }
+	  else
+	    error ("addproperty: invalid handle value");
+	}
+      else
+	error ("addproperty: invalid property name, expected a string value");
+    }
+  else
+    print_usage ();
+
+  return retval;
+}
+
 octave_value
 get_property_from_handle (double handle, const std::string& property,
 			  const std::string& func)
--- a/src/graphics.h.in	Fri Apr 04 22:18:01 2008 +0300
+++ b/src/graphics.h.in	Thu Apr 24 16:43:31 2008 +0200
@@ -399,6 +399,9 @@
 
   OCTINTERP_API void run_listeners (listener_mode mode = POSTSET);
 
+  virtual base_property* clone (void) const
+    { return new base_property (*this); }
+
 protected:
   virtual void do_set (const octave_value&)
     { error ("set: invalid property \"%s\"", name.c_str ()); }
@@ -439,6 +442,8 @@
       return *this;
     }
 
+  base_property* clone (void) const { return new string_property (*this); }
+
 protected:
   void do_set (const octave_value& val)
     {
@@ -534,6 +539,8 @@
       return *this;
     }
 
+  base_property* clone (void) const { return new radio_property (*this); }
+
 protected:
   void do_set (const octave_value& newval) 
   {
@@ -694,6 +701,8 @@
 
   operator octave_value (void) const { return get (); }
 
+  base_property* clone (void) const { return new color_property (*this); }
+
 protected:
   OCTINTERP_API void do_set (const octave_value& newval);
 
@@ -727,6 +736,8 @@
       return *this;
     }
 
+  base_property* clone (void) const { return new double_property (*this); }
+
 protected:
   void do_set (const octave_value& v)
     {
@@ -766,6 +777,11 @@
 	radio_val (v.radio_val), current_val (v.current_val)
   { }
 
+  double_radio_property (const double_radio_property& p)
+    : base_property (p), current_type (p.current_type),
+      dval (p.dval), radio_val (p.radio_val),
+      current_val (p.current_val) { }
+
   octave_value get (void) const
   {
     if (current_type == double_t)
@@ -805,6 +821,9 @@
 
   operator octave_value (void) const { return get (); }
 
+  base_property* clone (void) const
+    { return new double_radio_property (*this); }
+
 protected:
   OCTINTERP_API void do_set (const octave_value& v);
 
@@ -869,6 +888,16 @@
       return *this;
     }
 
+  base_property* clone (void) const
+    {
+      array_property *p = new array_property (*this);
+
+      p->type_constraints = type_constraints;
+      p->size_constraints = size_constraints;
+      
+      return p;
+    }
+
 protected:
   void do_set (const octave_value& v)
     {
@@ -908,6 +937,13 @@
     add_constraint (dim_vector (1, -1));
   }
 
+  row_vector_property (const row_vector_property& p)
+    : array_property (p)
+  {
+    add_constraint (dim_vector (-1, 1));
+    add_constraint (dim_vector (1, -1));
+  }
+
   void add_constraint (const std::string& type)
   {
     array_property::add_constraint (type);
@@ -933,6 +969,16 @@
     return *this;
   }
 
+  base_property* clone (void) const
+    {
+      row_vector_property *p = new row_vector_property (*this);
+
+      p->type_constraints = type_constraints;
+      p->size_constraints = size_constraints;
+
+      return p;
+    }
+
 protected:
   void do_set (const octave_value& v)
   {
@@ -983,6 +1029,8 @@
       return *this;
     }
 
+  base_property* clone (void) const { return new bool_property (*this); }
+
 protected:
   void do_set (const octave_value& val)
     {
@@ -1022,6 +1070,8 @@
       return *this;
     }
 
+  base_property* clone (void) const { return new handle_property (*this); }
+
 protected:
   OCTINTERP_API void do_set (const octave_value& v);
 
@@ -1038,6 +1088,9 @@
                   const octave_value& m = Matrix ())
     : base_property (nm, h), data (m) { }
 
+  any_property (const any_property& p)
+    : base_property (p), data (p.data) { }
+
   octave_value get (void) const { return data; }
 
   any_property& operator = (const octave_value& val)
@@ -1046,6 +1099,8 @@
       return *this;
     }
 
+  base_property* clone (void) const { return new any_property (*this); }
+
 protected:
   void do_set (const octave_value& v) { data = v; }
 
@@ -1062,6 +1117,9 @@
                      const octave_value& m)
     : base_property (nm, h), callback (m) { }
 
+  callback_property (const callback_property& p)
+    : base_property (p), callback (p.callback) { }
+
   octave_value get (void) const { return callback; }
 
   OCTINTERP_API void execute (const octave_value& data = octave_value ()) const;
@@ -1077,6 +1135,8 @@
       return *this;
     }
 
+  base_property* clone (void) const { return new callback_property (*this); }
+
 protected:
   void do_set (const octave_value& v)
     {
@@ -1167,6 +1227,14 @@
   void run_listeners (listener_mode mode = POSTSET)
     { rep->run_listeners (mode); }
 
+  OCTINTERP_API static
+      property create (const std::string& name, const graphics_handle& parent,
+		       const caseless_str& type,
+		       const octave_value_list& args);
+
+  property clone (void) const
+    { return property (rep->clone ()); }
+
   /*
   const string_property& as_string_property (void) const
     { return *(dynamic_cast<string_property*> (rep)); }
@@ -1467,6 +1535,8 @@
 
   virtual property get_property (const caseless_str&);
 
+  bool has_property (const caseless_str&);
+
   std::string get_tag (void) const { return tag.string_value (); }
 
   graphics_handle get_parent (void) const { return parent.handle_value (); }