[glib/wip/gproperty-2: 55/57] docs: Update the GObject tutorial section on properties



commit b4e3aa15c7445c9d26ed7dd53e978d4a4edf39b3
Author: Emmanuele Bassi <ebassi gnome org>
Date:   Sun Jun 23 18:55:05 2013 +0100

    docs: Update the GObject tutorial section on properties
    
    Get rid of a bunch of Maman stuff, and use the new macros and functions
    instead of the existing GParamSpec API.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=648526

 docs/reference/gobject/tut_gobject.xml |  347 +++++++++++++-------------------
 1 files changed, 136 insertions(+), 211 deletions(-)
---
diff --git a/docs/reference/gobject/tut_gobject.xml b/docs/reference/gobject/tut_gobject.xml
index c3b8ed1..006793c 100644
--- a/docs/reference/gobject/tut_gobject.xml
+++ b/docs/reference/gobject/tut_gobject.xml
@@ -495,254 +495,179 @@ void g_object_run_dispose         (GObject     *object);
       One of GObject's nice features is its generic get/set mechanism for object
       properties. When an object
       is instantiated, the object's class_init handler should be used to register
-      the object's properties with <function><link 
linkend="g-object-class-install-properties">g_object_class_install_properties</link></function>
-      (implemented in <filename>gobject.c</filename>).
+      the object's properties with the <function>G_DEFINE_PROPERTIES</function>
+      macro.
     </para>
   
     <para>
       The best way to understand how object properties work is by looking at a real example
       on how it is used:
 <informalexample><programlisting>
-/************************************************/
-/* Implementation                               */
-/************************************************/
+typedef struct {
+  char *name;
+  gint age;
+} PersonPrivate;
 
-enum
-{
-  PROP_0,
-
-  PROP_MAMAN_NAME,
-  PROP_PAPA_NUMBER,
-
-  N_PROPERTIES
-};
-
-static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
+G_DEFINE_TYPE_WITH_PRIVATE (Person, person, G_TYPE_OBJECT)
 
 static void
-maman_bar_set_property (GObject      *object,
-                        guint         property_id,
-                        const GValue *value,
-                        GParamSpec   *pspec)
+person_finalize (GObject *gobject)
 {
-  MamanBar *self = MAMAN_BAR (object);
-
-  switch (property_id)
-    {
-    case PROP_MAMAN_NAME:
-      g_free (self-&gt;priv-&gt;name);
-      self-&gt;priv-&gt;name = g_value_dup_string (value);
-      g_print ("maman: %s\n", self-&gt;priv-&gt;name);
-      break;
-
-    case PROP_PAPA_NUMBER:
-      self-&gt;priv-&gt;papa_number = g_value_get_uchar (value);
-      g_print ("papa: &percnt;u\n", self-&gt;priv-&gt;papa_number);
-      break;
-
-    default:
-      /* We don't have any other property... */
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-      break;
-    }
-}
+  PersonPrivate *priv = person_get_instance_private ((Person *) gobject);
 
-static void
-maman_bar_get_property (GObject    *object,
-                        guint       property_id,
-                        GValue     *value,
-                        GParamSpec *pspec)
-{
-  MamanBar *self = MAMAN_BAR (object);
-
-  switch (property_id)
-    {
-    case PROP_MAMAN_NAME:
-      g_value_set_string (value, self-&gt;priv-&gt;name);
-      break;
-
-    case PROP_PAPA_NUMBER:
-      g_value_set_uchar (value, self-&gt;priv-&gt;papa_number);
-      break;
-
-    default:
-      /* We don't have any other property... */
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-      break;
-    }
+  g_free (priv->name);
+
+  G_OBJECT_CLASS (person_parent_class)->finalize (gobject);
 }
 
 static void
-maman_bar_class_init (MamanBarClass *klass)
+person_class_init (PersonClass *klass)
 {
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  G_OBJECT_CLASS (klass)->finalize = person_finalize;
 
-  gobject_class-&gt;set_property = maman_bar_set_property;
-  gobject_class-&gt;get_property = maman_bar_get_property;
-
-  obj_properties[PROP_NAME] =
-    g_param_spec_string ("maman-name",
-                         "Maman construct prop",
-                         "Set maman's name",
-                         "no-name-set" /* default value */,
-                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
-
-  obj_properties[PROP_NUMBER] =
-    g_param_spec_uchar ("papa-number",
-                        "Number of current Papa",
-                        "Set/Get papa's number",
-                        0  /* minimum value */,
-                        10 /* maximum value */,
-                        2  /* default value */,
-                        G_PARAM_READWRITE);
-
-  g_object_class_install_properties (gobject_class,
-                                     N_PROPERTIES,
-                                     obj_properties);
+  G_DEFINE_PROPERTIES (Person, person, klass,
+                       G_DEFINE_PROPERTY (Person, string, name, G_PROPERTY_READWRITE | G_PROPERTY_COPY_SET)
+                       G_DEFINE_PROPERTY (Person, uint, age, G_PROPERTY_READWRITE))
 }
+</programlisting></informalexample>
+      The code above will add two properties, <emphasis>name</emphasis> and
+      <emphasis>age</emphasis> to the <emphasis>Person</emphasis> class;
+      the name property is a string, and the age property is a generic unsigned
+      integer. Both properties are readable and writable. Additionally, the name
+      property is implemented by copying the string passed to the setter, but
+      the get function will return a pointer. By default, the properties will
+      be stored inside the private data structure on a Person instance, using
+      the given field name.
+    </para>
 
-/************************************************/
-/* Use                                          */
-/************************************************/
+    <para>
+      Once we define the properties, their types, their access semantics,
+      and their visibility, we can access them through the generic GObject
+      API:
+<informalexample><programlisting>
+Person *person;
+
+person = g_object_new (person_get_type (), NULL);
+
+g_object_set (person,
+              "name", "Rupert S. Monkey",
+              "age", 33,
+              NULL);
+</programlisting></informalexample>
+      The code above will set the two properties on the <emphasis>person</emphasis>
+      instance.
+    </para>
 
-GObject *bar;
-GValue val = G_VALUE_INIT;
+    <para>
+      It should be noted that using the generic GObject API should not be the
+      common pattern for setting properties in C. The generic API uses variadic
+      arguments, which have side effects in terms of error handling, type
+      inference, and argument termination; those side effects can lead to
+      hard to track bugs. One option to avoid the variadic arguments API is to
+      use the GValue-based one, e.g.
+<informalexample><programlisting>
+GValue name_value = G_VALUE_INIT;
+GValue age_value = G_VALUE_INIT;
 
-bar = g_object_new (MAMAN_TYPE_SUBBAR, NULL);
+g_value_init (&amp;name_value, G_TYPE_STRING);
+g_value_set_string (&amp;name_value, "Rupert S. Monkey");
 
-g_value_init (&amp;val, G_TYPE_CHAR);
-g_value_set_char (&amp;val, 11);
+g_value_init (&amp;age_value, G_TYPE_INT);
+g_value_set_int (&amp;age_value, 33);
 
-g_object_set_property (G_OBJECT (bar), "papa-number", &amp;val);
+g_object_set_property (G_OBJECT (person), "name", &amp;name_value);
+g_object_set_property (G_OBJECT (person), "age", &amp;age_value);
 
-g_value_unset (&amp;val);
+g_value_unset (&amp;name_value);
+g_value_unset (&amp;age_value);
 </programlisting></informalexample>
-      The client code just above looks simple but a lot of things happen under the hood:
-    </para>
-  
-    <para>
-      <function><link linkend="g-object-set-property">g_object_set_property</link></function> first ensures 
a property
-      with this name was registered in bar's class_init handler. If so it walks the class hierarchy,
-      from bottom, most derived type, to top, fundamental type to find the class
-      which registered that property. It then tries to convert the user-provided GValue
-      into a GValue whose type is that of the associated property.
+      But, as you can see from the example above, the amount of code necessary
+      balloons rapidly out of control.
     </para>
-  
-    <para>
-      If the user provides a signed char GValue, as is shown
-      here, and if the object's property was registered as an unsigned int, 
-      <function><link linkend="g-value-transform">g_value_transform</link></function> will try to transform 
the input signed char into
-      an unsigned int. Of course, the success of the transformation depends on the availability
-      of the required transform function. In practice, there will almost always be a transformation
-      <footnote>
-        <para>Its behaviour might not be what you expect but it is up to you to actually avoid
-          relying on these transformations.
-        </para>
-      </footnote>
-      which matches and conversion will be carried out if needed.
-    </para>
-  
-    <para>
-      After transformation, the <link linkend="GValue"><type>GValue</type></link> is validated by 
-      <function><link linkend="g-param-value-validate">g_param_value_validate</link></function> which makes 
sure the user's
-      data stored in the <link linkend="GValue"><type>GValue</type></link> matches the characteristics 
specified by
-      the property's <link linkend="GParamSpec"><type>GParamSpec</type></link>.
-      Here, the <link linkend="GParamSpec"><type>GParamSpec</type></link> we 
-      provided in class_init has a validation function which makes sure that the GValue
-      contains a value which respects the minimum and maximum bounds of the 
-      <link linkend="GParamSpec"><type>GParamSpec</type></link>. In the example above, the client's GValue 
does not
-      respect these constraints (it is set to 11, while the maximum is 10). As such, the
-      <function><link linkend="g-object-set-property">g_object_set_property</link></function> function will 
return with an error.
-    </para>
-  
-    <para>
-      If the user's GValue had been set to a valid value, <function><link 
linkend="g-object-set-property">g_object_set_property</link></function>
-      would have proceeded with calling the object's set_property class method. Here, since our
-      implementation of Foo did override this method, the code path would jump to
-      <function>foo_set_property</function> after having retrieved from the 
-      <link linkend="GParamSpec"><type>GParamSpec</type></link> the <emphasis>param_id</emphasis>
-      <footnote>
-        <para>
-          It should be noted that the param_id used here need only to uniquely identify each 
-          <link linkend="GParamSpec"><type>GParamSpec</type></link> within the <type>FooClass</type> such 
that the switch
-          used in the set and get methods actually works. Of course, this locally-unique 
-          integer is purely an optimization: it would have been possible to use a set of 
-          <emphasis>if (strcmp (a, b) == 0) {} else if (strcmp (a, b) == 0) {}</emphasis> statements.
-        </para>
-      </footnote>
-      which had been stored by
-      <function><link 
linkend="g-object-class-install-property">g_object_class_install_property</link></function>.
-    </para>
-  
-    <para>
-      Once the property has been set by the object's set_property class method, the code path
-      returns to <function><link linkend="g-object-set-property">g_object_set_property</link></function> 
which makes sure that
-      the "notify" signal is emitted on the object's instance with the changed property as
-      parameter unless notifications were frozen by <function><link 
linkend="g-object-freeze-notify">g_object_freeze_notify</link></function>.
-    </para>
-  
+
     <para>
-      <function><link linkend="g-object-thaw-notify">g_object_thaw_notify</link></function> can be used to 
re-enable notification of 
-      property modifications through the "notify" signal. It is important to remember that
-      even if properties are changed while property change notification is frozen, the "notify"
-      signal will be emitted once for each of these changed properties as soon as the property
-      change notification is thawed: no property change is lost for the "notify" signal. Signal
-      can only be delayed by the notification freezing mechanism.
+      The preferred way to access properties on a GObject is to use accessor
+      functions, which guarantee type safety when compiling code, and avoid
+      large amounts of code. For instance:
+<informalexample><programlisting>
+void
+person_set_name (Person *person, const char *name)
+{
+  GProperty *property;
+
+  property = g_object_class_find_property (G_OBJECT_GET_CLASS (person), "name");
+  g_property_set (property, person, name);
+}
+
+const char *
+person_get_name (Person *person)
+{
+  PersonPrivate *priv = person_get_instance_private (person);
+
+  return priv->name;
+}
+</programlisting></informalexample>
     </para>
-    
+
     <para>
-      It sounds like a tedious task to set up GValues every time when one wants to modify a property.
-      In practice one will rarely do this. The functions <function><link 
linkend="g-object-set-property">g_object_set_property</link></function>
-      and <function><link linkend="g-object-get-property">g_object_get_property</link></function>
-      are meant to be used by language bindings. For application there is an easier way and
-      that is described next.
+      GObject provides pre-processor macros to simplify the developer's job at
+      creating these functions, which result in cleaner and simpler code; for
+      instance, the example above can be effectively replaced by a single
+      macro:
+<informalexample><programlisting>
+G_DEFINE_PROPERTY_GET_SET (Person, person, const char *, name)
+</programlisting></informalexample>
+      The macro above will generate both the get function and the set one.
     </para>
 
-    <sect2 id="gobject-multi-properties">
-      <title>Accessing multiple properties at once</title>
-  
-      <para>
-        It is interesting to note that the <function><link 
linkend="g-object-set">g_object_set</link></function> and 
-        <function><link linkend="g-object-set-valist">g_object_set_valist</link></function> (vararg version) 
functions can be used to set
-        multiple properties at once. The client code shown above can then be re-written as:
-<programlisting>
-MamanBar *foo;
-foo = /* */;
-g_object_set (G_OBJECT (foo),
-              "papa-number", 2, 
-              "maman-name", "test", 
-              NULL);
-</programlisting>
-        This saves us from managing the GValues that we were needing to handle when using
-        <function><link linkend="g-object-set-property">g_object_set_property</link></function>.
-        The code above will trigger one notify signal emission for each property modified.
-      </para>
-    
+    <sect2>
+      <title>Property notification and binding</title>
+
       <para>
-        Of course, the _get versions are also available: <function><link 
linkend="g-object-get">g_object_get</link></function>
-        and <function><link linkend="g-object-get-valist">g_object_get_valist</link></function> (vararg 
version) can be used to get numerous
-        properties at once.
+        Every time a property is set to a new value, GObject will emit a signal
+        called <emphasis>notify</emphasis>; this signal can be used to update
+        ancillary code every time the state of an instance changes; for instance,
+        it is possible to update the text field of a GUI with the content of the
+        person's name, if the Person instance is changed by another part of the
+        GUI, or by another source.
       </para>
-      
+
       <para>
-        These high level functions have one drawback - they don't provide a return result.
-        One should pay attention to the argument types and ranges when using them.
-        A known source of errors is to e.g. pass a gfloat instead of a gdouble and thus
-        shifting all subsequent parameters by four bytes. Also forgetting the terminating
-        NULL will lead to unexpected behaviour.
+        The notification signal is detailed with the name of the property which
+        has changed, so it's possible to subscribe to changes to specific
+        properties, for instance:
+<informalexample><programlisting>
+static void
+on_name_change (GObject *gobject,
+                GParamSpec *pspec,
+                gpointer user_data)
+{
+  Person *person = (Person *) gobject;
+  GtkLabel *label = user_data;
+
+  gtk_label_set_text (label, person_get_name (person));
+}
+
+  ...
+  g_signal_connect (person, "notify::name", G_CALLBACK (on_name_change), ui_label);
+  ...
+</programlisting></informalexample>
+        The code above will update the contents of a GtkLabel using the name
+        property every time the property changes.
       </para>
-    
+
       <para>
-        Really attentive readers now understand how <function><link 
linkend="g-object-new">g_object_new</link></function>,
-        <function><link linkend="g-object-newv">g_object_newv</link></function> and <function><link 
linkend="g-object-new-valist">g_object_new_valist</link></function> 
-        work: they parse the user-provided variable number of parameters and invoke
-        <function><link linkend="g-object-set">g_object_set</link></function> on the parameters only after 
the object has been successfully constructed.
-        Of course, the "notify" signal will be emitted for each property set.
+        It is also possible to use the property bindings to tie together a
+        property on a source instance and a property on a target instance.
+        In the example above, we could bind together the name property on a
+        Person instance to the text property on a GtkLabel instance:
+<informalexample><programlisting>
+g_object_bind_property (person, "name", label, "text", G_BINDING_DEFAULT);
+</programlisting></informalexample>
+        Thus removing the need to have an explicit function doing so.
       </para>
-    
-    </sect2>
 
-<!-- @todo tell here about how to pass use handle properties in derived classes -->
+    </sect2>
 
   </sect1>
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]