]> Pileus Git - ~andy/gtk/blobdiff - tests/prop-editor.c
Don't leak the tooltip object.
[~andy/gtk] / tests / prop-editor.c
index ac96abf9db7db556ece226e838af8a0361dafe7a..0dc055b3d9d755218584ed4d07e354ed66c66e14 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 
+#include <config.h>
 #include <string.h>
 
+#undef GTK_DISABLE_DEPRECATED
 #include <gtk/gtk.h>
 
 #include "prop-editor.h"
 
-static void
-get_param_specs (GObject      *object,
-                 GParamSpec ***specs,
-                 gint         *n_specs)
-{
-  /* Use private interface for now, fix later */
-  *specs = G_OBJECT_GET_CLASS (object)->property_specs;
-  *n_specs = G_OBJECT_GET_CLASS (object)->n_property_specs;
-}
 
 typedef struct
 {
@@ -61,7 +54,7 @@ signal_removed (gpointer  data,
 static void
 g_object_connect_property (GObject *object,
                            const gchar *prop_name,
-                           GtkSignalFunc func,
+                           GCallback func,
                            gpointer data,
                            GObject *alive_object)
 {
@@ -93,6 +86,7 @@ typedef struct
 {
   GObject *obj;
   gchar *prop;
+  gint modified_id;
 } ObjectProperty;
 
 static void
@@ -115,9 +109,28 @@ connect_controller (GObject *controller,
   p->obj = model;
   p->prop = g_strdup (prop_name);
 
-  g_signal_connect_data (controller, signal, func, p,
-                         (GClosureNotify)free_object_property,
-                         FALSE, FALSE);
+  p->modified_id = g_signal_connect_data (controller, signal, func, p,
+                                         (GClosureNotify)free_object_property,
+                                         0);
+  g_object_set_data (controller, "object-property", p);
+}
+
+static void
+block_controller (GObject *controller)
+{
+  ObjectProperty *p = g_object_get_data (controller, "object-property");
+
+  if (p)
+    g_signal_handler_block (controller, p->modified_id);
+}
+
+static void
+unblock_controller (GObject *controller)
+{
+  ObjectProperty *p = g_object_get_data (controller, "object-property");
+
+  if (p)
+    g_signal_handler_unblock (controller, p->modified_id);
 }
 
 static void
@@ -138,7 +151,38 @@ int_changed (GObject *object, GParamSpec *pspec, gpointer data)
   g_object_get_property (object, pspec->name, &val);
 
   if (g_value_get_int (&val) != (int)adj->value)
-    gtk_adjustment_set_value (adj, g_value_get_int (&val));
+    {
+      block_controller (G_OBJECT (adj));
+      gtk_adjustment_set_value (adj, g_value_get_int (&val));
+      unblock_controller (G_OBJECT (adj));
+    }
+
+  g_value_unset (&val);
+}
+
+static void
+uint_modified (GtkAdjustment *adj, gpointer data)
+{
+  ObjectProperty *p = data;
+
+  g_object_set (p->obj, p->prop, (guint) adj->value, NULL);
+}
+
+static void
+uint_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkAdjustment *adj = GTK_ADJUSTMENT (data);
+  GValue val = { 0, };  
+
+  g_value_init (&val, G_TYPE_UINT);
+  g_object_get_property (object, pspec->name, &val);
+
+  if (g_value_get_uint (&val) != (guint)adj->value)
+    {
+      block_controller (G_OBJECT (adj));
+      gtk_adjustment_set_value (adj, g_value_get_uint (&val));
+      unblock_controller (G_OBJECT (adj));
+    }
 
   g_value_unset (&val);
 }
@@ -161,7 +205,38 @@ float_changed (GObject *object, GParamSpec *pspec, gpointer data)
   g_object_get_property (object, pspec->name, &val);
 
   if (g_value_get_float (&val) != (float) adj->value)
-    gtk_adjustment_set_value (adj, g_value_get_float (&val));
+    {
+      block_controller (G_OBJECT (adj));
+      gtk_adjustment_set_value (adj, g_value_get_float (&val));
+      unblock_controller (G_OBJECT (adj));
+    }
+
+  g_value_unset (&val);
+}
+
+static void
+double_modified (GtkAdjustment *adj, gpointer data)
+{
+  ObjectProperty *p = data;
+
+  g_object_set (p->obj, p->prop, (double) adj->value, NULL);
+}
+
+static void
+double_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkAdjustment *adj = GTK_ADJUSTMENT (data);
+  GValue val = { 0, };  
+
+  g_value_init (&val, G_TYPE_DOUBLE);
+  g_object_get_property (object, pspec->name, &val);
+
+  if (g_value_get_double (&val) != adj->value)
+    {
+      block_controller (G_OBJECT (adj));
+      gtk_adjustment_set_value (adj, g_value_get_double (&val));
+      unblock_controller (G_OBJECT (adj));
+    }
 
   g_value_unset (&val);
 }
@@ -194,7 +269,11 @@ string_changed (GObject *object, GParamSpec *pspec, gpointer data)
   text = gtk_entry_get_text (entry);
 
   if (strcmp (str, text) != 0)
-    gtk_entry_set_text (entry, str);
+    {
+      block_controller (G_OBJECT (entry));
+      gtk_entry_set_text (entry, str);
+      unblock_controller (G_OBJECT (entry));
+    }
   
   g_value_unset (&val);
 }
@@ -217,7 +296,11 @@ bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
   g_object_get_property (object, pspec->name, &val);
 
   if (g_value_get_boolean (&val) != tb->active)
-    gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
+    {
+      block_controller (G_OBJECT (tb));
+      gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
+      unblock_controller (G_OBJECT (tb));
+    }
 
   gtk_label_set_text (GTK_LABEL (GTK_BIN (tb)->child), g_value_get_boolean (&val) ?
                       "TRUE" : "FALSE");
@@ -266,7 +349,11 @@ enum_changed (GObject *object, GParamSpec *pspec, gpointer data)
     }
   
   if (gtk_option_menu_get_history (om) != i)
-    gtk_option_menu_set_history (om, i);
+    {
+      block_controller (G_OBJECT (om));
+      gtk_option_menu_set_history (om, i);
+      unblock_controller (G_OBJECT (om));
+    }
   
   g_value_unset (&val);
 
@@ -312,59 +399,317 @@ unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
       
       buf[len] = '\0';
       
+      block_controller (G_OBJECT (entry));
       gtk_entry_set_text (entry, buf);
+      unblock_controller (G_OBJECT (entry));
     }
 }
 
-void
+static void
+pointer_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkLabel *label = GTK_LABEL (data);
+  gchar *str;
+  gpointer ptr;
+  
+  g_object_get (object, pspec->name, &ptr, NULL);
+
+  str = g_strdup_printf ("Pointer: %p", ptr);
+  gtk_label_set_text (label, str);
+  g_free (str);
+}
+
+static void
+object_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+  GtkWidget *label, *button;
+  gchar *str;
+  GObject *obj;
+  const gchar *name;
+  
+  GList *children = gtk_container_get_children (GTK_CONTAINER (data)); 
+  label = GTK_WIDGET (children->data);
+  button = GTK_WIDGET (children->next->data);
+  g_object_get (object, pspec->name, &obj, NULL);
+  g_list_free (children);
+
+  if (obj)
+    name = g_type_name (G_TYPE_FROM_INSTANCE (obj));
+  else
+    name = "unknown";
+  str = g_strdup_printf ("Object: %p (%s)", obj, name);
+  
+  gtk_label_set_text (GTK_LABEL (label), str);
+  gtk_widget_set_sensitive (button, G_IS_OBJECT (obj));
+
+  g_free (str);
+}
+
+static void
 model_destroy (gpointer data)
 {
   g_object_steal_data (data, "model-object");
   gtk_widget_destroy (data);
 }
 
-void
+static void
 window_destroy (gpointer data)
 {
   g_object_steal_data (data, "prop-editor-win");
 }
 
-GtkWidget*
-create_prop_editor (GObject *object)
+static void
+object_properties (GtkWidget *button, 
+                  GObject   *object)
+{
+  gchar *name;
+  GObject *obj;
+
+  name = (gchar *) g_object_get_data (G_OBJECT (button), "property-name");
+  g_object_get (object, name, &obj, NULL);
+  if (G_IS_OBJECT (obj)) 
+    create_prop_editor (obj, 0);
+}
+static GtkWidget *
+property_widget (GObject *object, GParamSpec *spec, gboolean can_modify)
 {
-  GtkWidget *win;
-  GtkWidget *vbox;
-  GtkWidget *hbox;
-  GtkWidget *label;
   GtkWidget *prop_edit;
-  GtkWidget *sw;
-  gint n_specs = 0;
-  GParamSpec **specs = NULL;
-  gint i;
   GtkAdjustment *adj;
-  GtkTooltips *tips;
+  gchar *msg;
+  GType type = G_PARAM_SPEC_TYPE (spec);
 
-  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  if (type == G_TYPE_PARAM_INT)
+    {
+      adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
+                                               G_PARAM_SPEC_INT (spec)->minimum,
+                                               G_PARAM_SPEC_INT (spec)->maximum,
+                                               1,
+                                               MAX ((G_PARAM_SPEC_INT (spec)->maximum -
+                                                     G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
+                                               0.0));
+      
+      prop_edit = gtk_spin_button_new (adj, 1.0, 0);
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (int_changed),
+                                adj, G_OBJECT (adj));
+      
+      if (can_modify)
+       connect_controller (G_OBJECT (adj), "value_changed",
+                           object, spec->name, (GtkSignalFunc) int_modified);
+    }
+  else if (type == G_TYPE_PARAM_UINT)
+    {
+      adj = GTK_ADJUSTMENT (
+                           gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
+                                               G_PARAM_SPEC_UINT (spec)->minimum,
+                                               G_PARAM_SPEC_UINT (spec)->maximum,
+                                               1,
+                                               MAX ((G_PARAM_SPEC_UINT (spec)->maximum -
+                                                     G_PARAM_SPEC_UINT (spec)->minimum) / 10, 1),
+                                               0.0));
+      
+      prop_edit = gtk_spin_button_new (adj, 1.0, 0);
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (uint_changed),
+                                adj, G_OBJECT (adj));
+      
+      if (can_modify)
+       connect_controller (G_OBJECT (adj), "value_changed",
+                           object, spec->name, (GtkSignalFunc) uint_modified);
+    }
+  else if (type == G_TYPE_PARAM_FLOAT)
+    {
 
-  tips = gtk_tooltips_new ();
-  gtk_signal_connect_object (GTK_OBJECT (win), "destroy",
-                            GTK_SIGNAL_FUNC (gtk_object_destroy), GTK_OBJECT (tips));
+      adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
+                                               G_PARAM_SPEC_FLOAT (spec)->minimum,
+                                               G_PARAM_SPEC_FLOAT (spec)->maximum,
+                                               0.1,
+                                               MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
+                                                     G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
+                                               0.0));
+      
+      prop_edit = gtk_spin_button_new (adj, 0.1, 2);
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (float_changed),
+                                adj, G_OBJECT (adj));
+      
+      if (can_modify)
+       connect_controller (G_OBJECT (adj), "value_changed",
+                           object, spec->name, (GtkSignalFunc) float_modified);
+    }
+  else if (type == G_TYPE_PARAM_DOUBLE)
+    {
+      adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
+                                               G_PARAM_SPEC_DOUBLE (spec)->minimum,
+                                               G_PARAM_SPEC_DOUBLE (spec)->maximum,
+                                               0.1,
+                                               MAX ((G_PARAM_SPEC_DOUBLE (spec)->maximum -
+                                                     G_PARAM_SPEC_DOUBLE (spec)->minimum) / 10, 0.1),
+                                               0.0));
+      
+      prop_edit = gtk_spin_button_new (adj, 0.1, 2);
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (double_changed),
+                                adj, G_OBJECT (adj));
+      
+      if (can_modify)
+       connect_controller (G_OBJECT (adj), "value_changed",
+                           object, spec->name, (GtkSignalFunc) double_modified);
+    }
+  else if (type == G_TYPE_PARAM_STRING)
+    {
+      prop_edit = gtk_entry_new ();
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (string_changed),
+                                prop_edit, G_OBJECT (prop_edit));
+      
+      if (can_modify)
+       connect_controller (G_OBJECT (prop_edit), "changed",
+                           object, spec->name, (GtkSignalFunc) string_modified);
+    }
+  else if (type == G_TYPE_PARAM_BOOLEAN)
+    {
+      prop_edit = gtk_toggle_button_new_with_label ("");
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (bool_changed),
+                                prop_edit, G_OBJECT (prop_edit));
+      
+      if (can_modify)
+       connect_controller (G_OBJECT (prop_edit), "toggled",
+                           object, spec->name, (GtkSignalFunc) bool_modified);
+    }
+  else if (type == G_TYPE_PARAM_ENUM)
+    {
+      {
+       GtkWidget *menu;
+       GEnumClass *eclass;
+       gint j;
+       
+       prop_edit = gtk_option_menu_new ();
+       
+       menu = gtk_menu_new ();
+       
+       eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
+       
+       j = 0;
+       while (j < eclass->n_values)
+         {
+           GtkWidget *mi;
+           
+           mi = gtk_menu_item_new_with_label (eclass->values[j].value_name);
+           
+           gtk_widget_show (mi);
+           
+           gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+           
+           ++j;
+         }
+       
+       g_type_class_unref (eclass);
+       
+       gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
+       
+       g_object_connect_property (object, spec->name,
+                                  G_CALLBACK (enum_changed),
+                                  prop_edit, G_OBJECT (prop_edit));
+       
+       if (can_modify)
+         connect_controller (G_OBJECT (prop_edit), "changed",
+                             object, spec->name, (GtkSignalFunc) enum_modified);
+      }
+    }
+  else if (type == G_TYPE_PARAM_UNICHAR)
+    {
+      prop_edit = gtk_entry_new ();
+      gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (unichar_changed),
+                                prop_edit, G_OBJECT (prop_edit));
+      
+      if (can_modify)
+       connect_controller (G_OBJECT (prop_edit), "changed",
+                           object, spec->name, (GtkSignalFunc) unichar_modified);
+    }
+  else if (type == G_TYPE_PARAM_POINTER)
+    {
+      prop_edit = gtk_label_new ("");
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (pointer_changed),
+                                prop_edit, G_OBJECT (prop_edit));
+    }
+  else if (type == G_TYPE_PARAM_OBJECT)
+    {
+      GtkWidget *label, *button;
 
-  /* hold a weak ref to the object we're editing */
-  g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
-  g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
-  
-  vbox = gtk_vbox_new (TRUE, 2);
+      prop_edit = gtk_hbox_new (FALSE, 5);
 
-  sw = gtk_scrolled_window_new (NULL, NULL);
-  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
-                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-  
-  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
-  gtk_container_add (GTK_CONTAINER (win), sw);
+      label = gtk_label_new ("");
+      button = gtk_button_new_with_label ("Properties");
+      g_object_set_data (G_OBJECT (button), "property-name", spec->name);
+      g_signal_connect (button, "clicked", 
+                       G_CALLBACK (object_properties), 
+                       object);
+
+      gtk_container_add (GTK_CONTAINER (prop_edit), label);
+      gtk_container_add (GTK_CONTAINER (prop_edit), button);
+      
+      g_object_connect_property (object, spec->name,
+                                G_CALLBACK (object_changed),
+                                prop_edit, G_OBJECT (label));
+    }
+  else
+    {  
+      msg = g_strdup_printf ("uneditable property type: %s",
+                            g_type_name (G_PARAM_SPEC_TYPE (spec)));
+      prop_edit = gtk_label_new (msg);            
+      g_free (msg);
+      gtk_misc_set_alignment (GTK_MISC (prop_edit), 0.0, 0.5);
+    }
   
-  get_param_specs (object, &specs, &n_specs);
+  return prop_edit;
+}
+
+static GtkWidget *
+properties_from_type (GObject     *object,
+                     GType        type,
+                     GtkTooltips *tips)
+{
+  GtkWidget *prop_edit;
+  GtkWidget *label;
+  GtkWidget *sw;
+  GtkWidget *vbox;
+  GtkWidget *table;
+  GParamSpec **specs;
+  gint n_specs;
+  int i;
+
+  if (G_TYPE_IS_INTERFACE (type))
+    {
+      gpointer vtable = g_type_default_interface_peek (type);
+      specs = g_object_interface_list_properties (vtable, &n_specs);
+    }
+  else
+    {
+      GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
+      specs = g_object_class_list_properties (class, &n_specs);
+    }
+        
+  if (n_specs == 0)
+    return NULL;
   
+  table = gtk_table_new (n_specs, 2, FALSE);
+  gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
+  gtk_table_set_row_spacings (GTK_TABLE (table), 3);
+
   i = 0;
   while (i < n_specs)
     {
@@ -383,192 +728,27 @@ create_prop_editor (GObject *object)
           continue;
         }
       
-      switch (G_PARAM_SPEC_TYPE (spec))
-        {
-        case G_TYPE_PARAM_INT:
-          hbox = gtk_hbox_new (FALSE, 10);
-          label = gtk_label_new (spec->nick);
-          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-          adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
-                                                    G_PARAM_SPEC_INT (spec)->minimum,
-                                                    G_PARAM_SPEC_INT (spec)->maximum,
-                                                    1,
-                                                    MAX ((G_PARAM_SPEC_INT (spec)->maximum -
-                                                          G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
-                                                    0.0));
-
-          prop_edit = gtk_spin_button_new (adj, 1.0, 0);
-          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
-          
-          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
-
-          g_object_connect_property (object, spec->name,
-                                     GTK_SIGNAL_FUNC (int_changed),
-                                     adj, G_OBJECT (adj));
-
-          if (can_modify)
-            connect_controller (G_OBJECT (adj), "value_changed",
-                                object, spec->name, (GtkSignalFunc) int_modified);
-          break;
-
-        case G_TYPE_PARAM_FLOAT:
-          hbox = gtk_hbox_new (FALSE, 10);
-          label = gtk_label_new (spec->nick);
-          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-          adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
-                                                    G_PARAM_SPEC_FLOAT (spec)->minimum,
-                                                    G_PARAM_SPEC_FLOAT (spec)->maximum,
-                                                    0.1,
-                                                    MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
-                                                          G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
-                                                    0.0));
-
-          prop_edit = gtk_spin_button_new (adj, 0.1, 2);
-          
-          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
-          
-          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
-
-          g_object_connect_property (object, spec->name,
-                                     GTK_SIGNAL_FUNC (float_changed),
-                                     adj, G_OBJECT (adj));
-
-          if (can_modify)
-            connect_controller (G_OBJECT (adj), "value_changed",
-                                object, spec->name, (GtkSignalFunc) float_modified);
-          break;
-          
-        case G_TYPE_PARAM_STRING:
-          hbox = gtk_hbox_new (FALSE, 10);
-          label = gtk_label_new (spec->nick);
-          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-
-          prop_edit = gtk_entry_new ();
-          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
-          
-          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
-
-          g_object_connect_property (object, spec->name,
-                                     GTK_SIGNAL_FUNC (string_changed),
-                                     prop_edit, G_OBJECT (prop_edit));
-
-          if (can_modify)
-            connect_controller (G_OBJECT (prop_edit), "changed",
-                                object, spec->name, (GtkSignalFunc) string_modified);
-          break;
-
-        case G_TYPE_PARAM_BOOLEAN:
-          hbox = gtk_hbox_new (FALSE, 10);
-          label = gtk_label_new (spec->nick);
-          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-
-          prop_edit = gtk_toggle_button_new_with_label ("");
-          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
-          
-          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
-
-          g_object_connect_property (object, spec->name,
-                                     GTK_SIGNAL_FUNC (bool_changed),
-                                     prop_edit, G_OBJECT (prop_edit));
-
-          if (can_modify)
-            connect_controller (G_OBJECT (prop_edit), "toggled",
-                                object, spec->name, (GtkSignalFunc) bool_modified);
-          break;
-          
-        case G_TYPE_PARAM_ENUM:
-         {
-           GtkWidget *menu;
-           GEnumClass *eclass;
-           gint i;
-            
-           hbox = gtk_hbox_new (FALSE, 10);
-           label = gtk_label_new (spec->nick);
-           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-            
-           prop_edit = gtk_option_menu_new ();
-           
-           menu = gtk_menu_new ();
-
-           eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
-           
-           i = 0;
-           while (i < eclass->n_values)
-             {
-               GtkWidget *mi;
-                
-               mi = gtk_menu_item_new_with_label (eclass->values[i].value_name);
-               
-               gtk_widget_show (mi);
-                
-               gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
-                
-                  ++i;
-             }
-           
-           g_type_class_unref (eclass);
-           
-           gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
-           
-           gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
-            
-           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
-            
-           g_object_connect_property (object, spec->name,
-                                      GTK_SIGNAL_FUNC (enum_changed),
-                                      prop_edit, G_OBJECT (prop_edit));
-            
-           if (can_modify)
-             connect_controller (G_OBJECT (prop_edit), "changed",
-                                 object, spec->name, (GtkSignalFunc) enum_modified);
-         }
-         
-        case G_TYPE_PARAM_UNICHAR:
-          hbox = gtk_hbox_new (FALSE, 10);
-          label = gtk_label_new (spec->nick);
-          gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-          gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-
-         prop_edit = gtk_entry_new ();
-         gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
-          gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
-          
-          gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
-
-          g_object_connect_property (object, spec->name,
-                                     GTK_SIGNAL_FUNC (unichar_changed),
-                                     prop_edit, G_OBJECT (prop_edit));
-
-          if (can_modify)
-            connect_controller (G_OBJECT (prop_edit), "changed",
-                                object, spec->name, (GtkSignalFunc) unichar_modified);
-          break;
-
-       default:
-         {
-           gchar *msg = g_strdup_printf ("%s: don't know how to edit property type %s",
-                                         spec->nick, g_type_name (G_PARAM_SPEC_TYPE (spec)));
-           hbox = gtk_hbox_new (FALSE, 10);
-           label = gtk_label_new (msg);            
-           g_free (msg);
-           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
-         }
-        }
+      if (spec->owner_type != type)
+       {
+         /* we're only interested in params of type */
+         ++i;
+         continue;
+       }
+
+      label = gtk_label_new (g_param_spec_get_nick (spec));
+      gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+      gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
+      
+      prop_edit = property_widget (object, spec, can_modify);
+      gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
 
       if (prop_edit)
         {
           if (!can_modify)
             gtk_widget_set_sensitive (prop_edit, FALSE);
 
-         if (spec->blurb)
-           gtk_tooltips_set_tip (tips, prop_edit, spec->blurb, NULL);
+         if (g_param_spec_get_blurb (spec))
+           gtk_tooltips_set_tip (tips, prop_edit, g_param_spec_get_blurb (spec), NULL);
          
           /* set initial value */
           g_object_notify (object, spec->name);
@@ -577,7 +757,114 @@ create_prop_editor (GObject *object)
       ++i;
     }
 
-  gtk_window_set_default_size (GTK_WINDOW (win), 300, 500);
+
+  vbox = gtk_vbox_new (FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
+
+  sw = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+  
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
+
+  g_free (specs);
+
+  return sw;
+}
+
+static void
+kill_tips (GtkWindow *win, GtkObject *tips)
+{
+  gtk_object_destroy (tips);
+  g_object_unref (tips);
+}
+
+/* Pass zero for type if you want all properties */
+GtkWidget*
+create_prop_editor (GObject   *object,
+                   GType      type)
+{
+  GtkWidget *win;
+  GtkWidget *notebook;
+  GtkTooltips *tips;
+  GtkWidget *properties;
+  GtkWidget *label;
+  gchar *title;
+  GType *ifaces;
+  guint n_ifaces;
+  
+  if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
+    {
+      gtk_window_present (GTK_WINDOW (win));
+      return win;
+    }
+
+  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  if (GTK_IS_WIDGET (object))
+    gtk_window_set_screen (GTK_WINDOW (win),
+                          gtk_widget_get_screen (GTK_WIDGET (object)));
+
+  tips = gtk_tooltips_new ();
+  g_object_ref (tips);
+  gtk_object_sink (GTK_OBJECT (tips));
+
+  /* Kill the tips when the widget goes away.  */
+  g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (kill_tips), tips);
+
+  /* hold a weak ref to the object we're editing */
+  g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
+  g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
+
+  if (type == 0)
+    {
+      notebook = gtk_notebook_new ();
+      gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
+      
+      gtk_container_add (GTK_CONTAINER (win), notebook);
+      
+      type = G_TYPE_FROM_INSTANCE (object);
+
+      title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
+      gtk_window_set_title (GTK_WINDOW (win), title);
+      g_free (title);
+      
+      while (type)
+       {
+         properties = properties_from_type (object, type, tips);
+         if (properties)
+           {
+             label = gtk_label_new (g_type_name (type));
+             gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+                                       properties, label);
+           }
+         
+         type = g_type_parent (type);
+       }
+
+      ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
+      while (n_ifaces--)
+       {
+         properties = properties_from_type (object, ifaces[n_ifaces], tips);
+         if (properties)
+           {
+             label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
+             gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
+                                       properties, label);
+           }
+       }
+
+      g_free (ifaces);
+    }
+  else
+    {
+      properties = properties_from_type (object, type, tips);
+      gtk_container_add (GTK_CONTAINER (win), properties);
+      title = g_strdup_printf ("Properties of %s", g_type_name (type));
+      gtk_window_set_title (GTK_WINDOW (win), title);
+      g_free (title);
+    }
+  
+  gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
   
   gtk_widget_show_all (win);