]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
Fixes #133995, patch by Morten Welinder <mortenw@gnome.org>.
[~andy/gtk] / tests / prop-editor.c
1 /* prop-editor.c
2  * Copyright (C) 2000  Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <string.h>
21
22 #undef GTK_DISABLE_DEPRECATED
23 #include <gtk/gtk.h>
24
25 #include "prop-editor.h"
26
27
28 typedef struct
29 {
30   gpointer instance;
31   GObject *alive_object;
32   guint id;
33 } DisconnectData;
34
35 static void
36 disconnect_func (gpointer data)
37 {
38   DisconnectData *dd = data;
39   
40   g_signal_handler_disconnect (dd->instance, dd->id);
41 }
42
43 static void
44 signal_removed (gpointer  data,
45                 GClosure *closure)
46 {
47   DisconnectData *dd = data;
48
49   g_object_steal_data (dd->alive_object, "alive-object-data");
50   g_free (dd);
51 }
52
53 static void
54 g_object_connect_property (GObject *object,
55                            const gchar *prop_name,
56                            GCallback func,
57                            gpointer data,
58                            GObject *alive_object)
59 {
60   GClosure *closure;
61   gchar *with_detail = g_strconcat ("notify::", prop_name, NULL);
62   DisconnectData *dd;
63
64   dd = g_new (DisconnectData, 1);
65
66   closure = g_cclosure_new (func, data, NULL);
67
68   g_closure_add_invalidate_notifier (closure, dd, signal_removed);
69
70   dd->id = g_signal_connect_closure (object, with_detail,
71                                      closure, FALSE);
72
73   dd->instance = object;
74   dd->alive_object = alive_object;
75   
76   g_object_set_data_full (G_OBJECT (alive_object),
77                           "alive-object-data",
78                           dd,
79                           disconnect_func);
80   
81   g_free (with_detail);
82 }
83
84 typedef struct 
85 {
86   GObject *obj;
87   gchar *prop;
88   gint modified_id;
89 } ObjectProperty;
90
91 static void
92 free_object_property (ObjectProperty *p)
93 {
94   g_free (p->prop);
95   g_free (p);
96 }
97
98 static void
99 connect_controller (GObject *controller,
100                     const gchar *signal,
101                     GObject *model,
102                     const gchar *prop_name,
103                     GtkSignalFunc func)
104 {
105   ObjectProperty *p;
106
107   p = g_new (ObjectProperty, 1);
108   p->obj = model;
109   p->prop = g_strdup (prop_name);
110
111   p->modified_id = g_signal_connect_data (controller, signal, func, p,
112                                           (GClosureNotify)free_object_property,
113                                           0);
114   g_object_set_data (controller, "object-property", p);
115 }
116
117 static void
118 block_controller (GObject *controller)
119 {
120   ObjectProperty *p = g_object_get_data (controller, "object-property");
121
122   if (p)
123     g_signal_handler_block (controller, p->modified_id);
124 }
125
126 static void
127 unblock_controller (GObject *controller)
128 {
129   ObjectProperty *p = g_object_get_data (controller, "object-property");
130
131   if (p)
132     g_signal_handler_unblock (controller, p->modified_id);
133 }
134
135 static void
136 int_modified (GtkAdjustment *adj, gpointer data)
137 {
138   ObjectProperty *p = data;
139
140   g_object_set (p->obj, p->prop, (int) adj->value, NULL);
141 }
142
143 static void
144 int_changed (GObject *object, GParamSpec *pspec, gpointer data)
145 {
146   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
147   GValue val = { 0, };  
148
149   g_value_init (&val, G_TYPE_INT);
150   g_object_get_property (object, pspec->name, &val);
151
152   if (g_value_get_int (&val) != (int)adj->value)
153     {
154       block_controller (G_OBJECT (adj));
155       gtk_adjustment_set_value (adj, g_value_get_int (&val));
156       unblock_controller (G_OBJECT (adj));
157     }
158
159   g_value_unset (&val);
160 }
161
162 static void
163 uint_modified (GtkAdjustment *adj, gpointer data)
164 {
165   ObjectProperty *p = data;
166
167   g_object_set (p->obj, p->prop, (guint) adj->value, NULL);
168 }
169
170 static void
171 uint_changed (GObject *object, GParamSpec *pspec, gpointer data)
172 {
173   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
174   GValue val = { 0, };  
175
176   g_value_init (&val, G_TYPE_UINT);
177   g_object_get_property (object, pspec->name, &val);
178
179   if (g_value_get_uint (&val) != (guint)adj->value)
180     {
181       block_controller (G_OBJECT (adj));
182       gtk_adjustment_set_value (adj, g_value_get_uint (&val));
183       unblock_controller (G_OBJECT (adj));
184     }
185
186   g_value_unset (&val);
187 }
188
189 static void
190 float_modified (GtkAdjustment *adj, gpointer data)
191 {
192   ObjectProperty *p = data;
193
194   g_object_set (p->obj, p->prop, (float) adj->value, NULL);
195 }
196
197 static void
198 float_changed (GObject *object, GParamSpec *pspec, gpointer data)
199 {
200   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
201   GValue val = { 0, };  
202
203   g_value_init (&val, G_TYPE_FLOAT);
204   g_object_get_property (object, pspec->name, &val);
205
206   if (g_value_get_float (&val) != (float) adj->value)
207     {
208       block_controller (G_OBJECT (adj));
209       gtk_adjustment_set_value (adj, g_value_get_float (&val));
210       unblock_controller (G_OBJECT (adj));
211     }
212
213   g_value_unset (&val);
214 }
215
216 static void
217 double_modified (GtkAdjustment *adj, gpointer data)
218 {
219   ObjectProperty *p = data;
220
221   g_object_set (p->obj, p->prop, (double) adj->value, NULL);
222 }
223
224 static void
225 double_changed (GObject *object, GParamSpec *pspec, gpointer data)
226 {
227   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
228   GValue val = { 0, };  
229
230   g_value_init (&val, G_TYPE_DOUBLE);
231   g_object_get_property (object, pspec->name, &val);
232
233   if (g_value_get_double (&val) != adj->value)
234     {
235       block_controller (G_OBJECT (adj));
236       gtk_adjustment_set_value (adj, g_value_get_double (&val));
237       unblock_controller (G_OBJECT (adj));
238     }
239
240   g_value_unset (&val);
241 }
242
243 static void
244 string_modified (GtkEntry *entry, gpointer data)
245 {
246   ObjectProperty *p = data;
247   const gchar *text;
248
249   text = gtk_entry_get_text (entry);
250
251   g_object_set (p->obj, p->prop, text, NULL);
252 }
253
254 static void
255 string_changed (GObject *object, GParamSpec *pspec, gpointer data)
256 {
257   GtkEntry *entry = GTK_ENTRY (data);
258   GValue val = { 0, };  
259   const gchar *str;
260   const gchar *text;
261   
262   g_value_init (&val, G_TYPE_STRING);
263   g_object_get_property (object, pspec->name, &val);
264
265   str = g_value_get_string (&val);
266   if (str == NULL)
267     str = "";
268   text = gtk_entry_get_text (entry);
269
270   if (strcmp (str, text) != 0)
271     {
272       block_controller (G_OBJECT (entry));
273       gtk_entry_set_text (entry, str);
274       unblock_controller (G_OBJECT (entry));
275     }
276   
277   g_value_unset (&val);
278 }
279
280 static void
281 bool_modified (GtkToggleButton *tb, gpointer data)
282 {
283   ObjectProperty *p = data;
284
285   g_object_set (p->obj, p->prop, (int) tb->active, NULL);
286 }
287
288 static void
289 bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
290 {
291   GtkToggleButton *tb = GTK_TOGGLE_BUTTON (data);
292   GValue val = { 0, };  
293   
294   g_value_init (&val, G_TYPE_BOOLEAN);
295   g_object_get_property (object, pspec->name, &val);
296
297   if (g_value_get_boolean (&val) != tb->active)
298     {
299       block_controller (G_OBJECT (tb));
300       gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
301       unblock_controller (G_OBJECT (tb));
302     }
303
304   gtk_label_set_text (GTK_LABEL (GTK_BIN (tb)->child), g_value_get_boolean (&val) ?
305                       "TRUE" : "FALSE");
306   
307   g_value_unset (&val);
308 }
309
310
311 static void
312 enum_modified (GtkOptionMenu *om, gpointer data)
313 {
314   ObjectProperty *p = data;
315   gint i;
316   GParamSpec *spec;
317   GEnumClass *eclass;
318   
319   spec = g_object_class_find_property (G_OBJECT_GET_CLASS (p->obj),
320                                        p->prop);
321
322   eclass = G_ENUM_CLASS (g_type_class_peek (spec->value_type));
323   
324   i = gtk_option_menu_get_history (om);
325
326   g_object_set (p->obj, p->prop, eclass->values[i].value, NULL);
327 }
328
329 static void
330 enum_changed (GObject *object, GParamSpec *pspec, gpointer data)
331 {
332   GtkOptionMenu *om = GTK_OPTION_MENU (data);
333   GValue val = { 0, };  
334   GEnumClass *eclass;
335   gint i;
336
337   eclass = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
338   
339   g_value_init (&val, pspec->value_type);
340   g_object_get_property (object, pspec->name, &val);
341
342   i = 0;
343   while (i < eclass->n_values)
344     {
345       if (eclass->values[i].value == g_value_get_enum (&val))
346         break;
347       ++i;
348     }
349   
350   if (gtk_option_menu_get_history (om) != i)
351     {
352       block_controller (G_OBJECT (om));
353       gtk_option_menu_set_history (om, i);
354       unblock_controller (G_OBJECT (om));
355     }
356   
357   g_value_unset (&val);
358
359 }
360
361 static gunichar
362 unichar_get_value (GtkEntry *entry)
363 {
364   const gchar *text = gtk_entry_get_text (entry);
365   
366   if (text[0])
367     return g_utf8_get_char (text);
368   else
369     return 0;
370 }
371
372 static void
373 unichar_modified (GtkEntry *entry, gpointer data)
374 {
375   ObjectProperty *p = data;
376   gunichar val = unichar_get_value (entry);
377
378   g_object_set (p->obj, p->prop, val, NULL);
379 }
380
381 static void
382 unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
383 {
384   GtkEntry *entry = GTK_ENTRY (data);
385   gunichar new_val;
386   gunichar old_val = unichar_get_value (entry);
387   gchar buf[7];
388   gint len;
389
390   g_object_get (object, pspec->name, &new_val, NULL);
391
392   if (new_val != old_val)
393     {
394       if (!new_val)
395         len = 0;
396       else
397         len = g_unichar_to_utf8 (new_val, buf);
398       
399       buf[len] = '\0';
400       
401       block_controller (G_OBJECT (entry));
402       gtk_entry_set_text (entry, buf);
403       unblock_controller (G_OBJECT (entry));
404     }
405 }
406
407 static void
408 pointer_changed (GObject *object, GParamSpec *pspec, gpointer data)
409 {
410   GtkLabel *label = GTK_LABEL (data);
411   gchar *str;
412   gpointer ptr;
413   
414   g_object_get (object, pspec->name, &ptr, NULL);
415
416   str = g_strdup_printf ("Pointer: %p", ptr);
417   gtk_label_set_text (label, str);
418   g_free (str);
419 }
420
421 static void
422 object_changed (GObject *object, GParamSpec *pspec, gpointer data)
423 {
424   GtkWidget *label, *button;
425   gchar *str;
426   GObject *obj;
427   const gchar *name;
428   
429   GList *children = gtk_container_get_children (GTK_CONTAINER (data)); 
430   label = GTK_WIDGET (children->data);
431   button = GTK_WIDGET (children->next->data);
432   g_object_get (object, pspec->name, &obj, NULL);
433   g_list_free (children);
434
435   if (obj)
436     name = g_type_name (G_TYPE_FROM_INSTANCE (obj));
437   else
438     name = "unknown";
439   str = g_strdup_printf ("Object: %p (%s)", obj, name);
440   
441   gtk_label_set_text (GTK_LABEL (label), str);
442   gtk_widget_set_sensitive (button, G_IS_OBJECT (obj));
443
444   g_free (str);
445 }
446
447 static void
448 model_destroy (gpointer data)
449 {
450   g_object_steal_data (data, "model-object");
451   gtk_widget_destroy (data);
452 }
453
454 static void
455 window_destroy (gpointer data)
456 {
457   g_object_steal_data (data, "prop-editor-win");
458 }
459
460 static void
461 object_properties (GtkWidget *button, 
462                    GObject   *object)
463 {
464   gchar *name;
465   GObject *obj;
466
467   name = (gchar *) g_object_get_data (G_OBJECT (button), "property-name");
468   g_object_get (object, name, &obj, NULL);
469   if (G_IS_OBJECT (obj)) 
470     create_prop_editor (obj, 0);
471 }
472  
473 static GtkWidget *
474 property_widget (GObject *object, GParamSpec *spec, gboolean can_modify)
475 {
476   GtkWidget *prop_edit;
477   GtkAdjustment *adj;
478   gchar *msg;
479   GType type = G_PARAM_SPEC_TYPE (spec);
480
481   if (type == G_TYPE_PARAM_INT)
482     {
483       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
484                                                 G_PARAM_SPEC_INT (spec)->minimum,
485                                                 G_PARAM_SPEC_INT (spec)->maximum,
486                                                 1,
487                                                 MAX ((G_PARAM_SPEC_INT (spec)->maximum -
488                                                       G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
489                                                 0.0));
490       
491       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
492       
493       g_object_connect_property (object, spec->name,
494                                  G_CALLBACK (int_changed),
495                                  adj, G_OBJECT (adj));
496       
497       if (can_modify)
498         connect_controller (G_OBJECT (adj), "value_changed",
499                             object, spec->name, (GtkSignalFunc) int_modified);
500     }
501   else if (type == G_TYPE_PARAM_UINT)
502     {
503       adj = GTK_ADJUSTMENT (
504                             gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
505                                                 G_PARAM_SPEC_UINT (spec)->minimum,
506                                                 G_PARAM_SPEC_UINT (spec)->maximum,
507                                                 1,
508                                                 MAX ((G_PARAM_SPEC_UINT (spec)->maximum -
509                                                       G_PARAM_SPEC_UINT (spec)->minimum) / 10, 1),
510                                                 0.0));
511       
512       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
513       
514       g_object_connect_property (object, spec->name,
515                                  G_CALLBACK (uint_changed),
516                                  adj, G_OBJECT (adj));
517       
518       if (can_modify)
519         connect_controller (G_OBJECT (adj), "value_changed",
520                             object, spec->name, (GtkSignalFunc) uint_modified);
521     }
522   else if (type == G_TYPE_PARAM_FLOAT)
523     {
524
525       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
526                                                 G_PARAM_SPEC_FLOAT (spec)->minimum,
527                                                 G_PARAM_SPEC_FLOAT (spec)->maximum,
528                                                 0.1,
529                                                 MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
530                                                       G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
531                                                 0.0));
532       
533       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
534       
535       g_object_connect_property (object, spec->name,
536                                  G_CALLBACK (float_changed),
537                                  adj, G_OBJECT (adj));
538       
539       if (can_modify)
540         connect_controller (G_OBJECT (adj), "value_changed",
541                             object, spec->name, (GtkSignalFunc) float_modified);
542     }
543   else if (type == G_TYPE_PARAM_DOUBLE)
544     {
545       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
546                                                 G_PARAM_SPEC_DOUBLE (spec)->minimum,
547                                                 G_PARAM_SPEC_DOUBLE (spec)->maximum,
548                                                 0.1,
549                                                 MAX ((G_PARAM_SPEC_DOUBLE (spec)->maximum -
550                                                       G_PARAM_SPEC_DOUBLE (spec)->minimum) / 10, 0.1),
551                                                 0.0));
552       
553       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
554       
555       g_object_connect_property (object, spec->name,
556                                  G_CALLBACK (double_changed),
557                                  adj, G_OBJECT (adj));
558       
559       if (can_modify)
560         connect_controller (G_OBJECT (adj), "value_changed",
561                             object, spec->name, (GtkSignalFunc) double_modified);
562     }
563   else if (type == G_TYPE_PARAM_STRING)
564     {
565       prop_edit = gtk_entry_new ();
566       
567       g_object_connect_property (object, spec->name,
568                                  G_CALLBACK (string_changed),
569                                  prop_edit, G_OBJECT (prop_edit));
570       
571       if (can_modify)
572         connect_controller (G_OBJECT (prop_edit), "changed",
573                             object, spec->name, (GtkSignalFunc) string_modified);
574     }
575   else if (type == G_TYPE_PARAM_BOOLEAN)
576     {
577       prop_edit = gtk_toggle_button_new_with_label ("");
578       
579       g_object_connect_property (object, spec->name,
580                                  G_CALLBACK (bool_changed),
581                                  prop_edit, G_OBJECT (prop_edit));
582       
583       if (can_modify)
584         connect_controller (G_OBJECT (prop_edit), "toggled",
585                             object, spec->name, (GtkSignalFunc) bool_modified);
586     }
587   else if (type == G_TYPE_PARAM_ENUM)
588     {
589       {
590         GtkWidget *menu;
591         GEnumClass *eclass;
592         gint j;
593         
594         prop_edit = gtk_option_menu_new ();
595         
596         menu = gtk_menu_new ();
597         
598         eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
599         
600         j = 0;
601         while (j < eclass->n_values)
602           {
603             GtkWidget *mi;
604             
605             mi = gtk_menu_item_new_with_label (eclass->values[j].value_name);
606             
607             gtk_widget_show (mi);
608             
609             gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
610             
611             ++j;
612           }
613         
614         g_type_class_unref (eclass);
615         
616         gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
617         
618         g_object_connect_property (object, spec->name,
619                                    G_CALLBACK (enum_changed),
620                                    prop_edit, G_OBJECT (prop_edit));
621         
622         if (can_modify)
623           connect_controller (G_OBJECT (prop_edit), "changed",
624                               object, spec->name, (GtkSignalFunc) enum_modified);
625       }
626     }
627   else if (type == G_TYPE_PARAM_UNICHAR)
628     {
629       prop_edit = gtk_entry_new ();
630       gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
631       
632       g_object_connect_property (object, spec->name,
633                                  G_CALLBACK (unichar_changed),
634                                  prop_edit, G_OBJECT (prop_edit));
635       
636       if (can_modify)
637         connect_controller (G_OBJECT (prop_edit), "changed",
638                             object, spec->name, (GtkSignalFunc) unichar_modified);
639     }
640   else if (type == G_TYPE_PARAM_POINTER)
641     {
642       prop_edit = gtk_label_new ("");
643       
644       g_object_connect_property (object, spec->name,
645                                  G_CALLBACK (pointer_changed),
646                                  prop_edit, G_OBJECT (prop_edit));
647     }
648   else if (type == G_TYPE_PARAM_OBJECT)
649     {
650       GtkWidget *label, *button;
651
652       prop_edit = gtk_hbox_new (FALSE, 5);
653
654       label = gtk_label_new ("");
655       button = gtk_button_new_with_label ("Properties");
656       g_object_set_data (G_OBJECT (button), "property-name", spec->name);
657       g_signal_connect (button, "clicked", 
658                         G_CALLBACK (object_properties), 
659                         object);
660
661       gtk_container_add (GTK_CONTAINER (prop_edit), label);
662       gtk_container_add (GTK_CONTAINER (prop_edit), button);
663       
664       g_object_connect_property (object, spec->name,
665                                  G_CALLBACK (object_changed),
666                                  prop_edit, G_OBJECT (label));
667     }
668   else
669     {  
670       msg = g_strdup_printf ("uneditable property type: %s",
671                              g_type_name (G_PARAM_SPEC_TYPE (spec)));
672       prop_edit = gtk_label_new (msg);            
673       g_free (msg);
674       gtk_misc_set_alignment (GTK_MISC (prop_edit), 0.0, 0.5);
675     }
676   
677   return prop_edit;
678 }
679
680 static GtkWidget *
681 properties_from_type (GObject     *object,
682                       GType        type,
683                       GtkTooltips *tips)
684 {
685   GtkWidget *prop_edit;
686   GtkWidget *label;
687   GtkWidget *sw;
688   GtkWidget *vbox;
689   GtkWidget *table;
690   GParamSpec **specs;
691   gint n_specs;
692   int i;
693
694   if (G_TYPE_IS_INTERFACE (type))
695     {
696       gpointer vtable = g_type_default_interface_peek (type);
697       specs = g_object_interface_list_properties (vtable, &n_specs);
698     }
699   else
700     {
701       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
702       specs = g_object_class_list_properties (class, &n_specs);
703     }
704         
705   if (n_specs == 0)
706     return NULL;
707   
708   table = gtk_table_new (n_specs, 2, FALSE);
709   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
710   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
711
712   i = 0;
713   while (i < n_specs)
714     {
715       GParamSpec *spec = specs[i];
716       gboolean can_modify;
717       
718       prop_edit = NULL;
719
720       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
721                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
722       
723       if ((spec->flags & G_PARAM_READABLE) == 0)
724         {
725           /* can't display unreadable properties */
726           ++i;
727           continue;
728         }
729       
730       if (spec->owner_type != type)
731         {
732           /* we're only interested in params of type */
733           ++i;
734           continue;
735         }
736
737       label = gtk_label_new (g_param_spec_get_nick (spec));
738       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
739       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
740       
741       prop_edit = property_widget (object, spec, can_modify);
742       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
743
744       if (prop_edit)
745         {
746           if (!can_modify)
747             gtk_widget_set_sensitive (prop_edit, FALSE);
748
749           if (g_param_spec_get_blurb (spec))
750             gtk_tooltips_set_tip (tips, prop_edit, g_param_spec_get_blurb (spec), NULL);
751           
752           /* set initial value */
753           g_object_notify (object, spec->name);
754         }
755       
756       ++i;
757     }
758
759
760   vbox = gtk_vbox_new (FALSE, 0);
761   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
762
763   sw = gtk_scrolled_window_new (NULL, NULL);
764   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
765                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
766   
767   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
768
769   g_free (specs);
770
771   return sw;
772 }
773
774
775 /* Pass zero for type if you want all properties */
776 GtkWidget*
777 create_prop_editor (GObject   *object,
778                     GType      type)
779 {
780   GtkWidget *win;
781   GtkWidget *notebook;
782   GtkTooltips *tips;
783   GtkWidget *properties;
784   GtkWidget *label;
785   gchar *title;
786   GType *ifaces;
787   guint n_ifaces;
788   
789   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
790     {
791       gtk_window_present (GTK_WINDOW (win));
792       return win;
793     }
794
795   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
796   if (GTK_IS_WIDGET (object))
797     gtk_window_set_screen (GTK_WINDOW (win),
798                            gtk_widget_get_screen (GTK_WIDGET (object)));
799   
800   tips = gtk_tooltips_new ();
801   g_signal_connect_swapped (win, "destroy",
802                             G_CALLBACK (gtk_object_destroy), tips);
803
804   /* hold a weak ref to the object we're editing */
805   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
806   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
807
808   if (type == 0)
809     {
810       notebook = gtk_notebook_new ();
811       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
812       
813       gtk_container_add (GTK_CONTAINER (win), notebook);
814       
815       type = G_TYPE_FROM_INSTANCE (object);
816
817       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
818       gtk_window_set_title (GTK_WINDOW (win), title);
819       g_free (title);
820       
821       while (type)
822         {
823           properties = properties_from_type (object, type, tips);
824           if (properties)
825             {
826               label = gtk_label_new (g_type_name (type));
827               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
828                                         properties, label);
829             }
830           
831           type = g_type_parent (type);
832         }
833
834       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
835       while (n_ifaces--)
836         {
837           properties = properties_from_type (object, ifaces[n_ifaces], tips);
838           if (properties)
839             {
840               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
841               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
842                                         properties, label);
843             }
844         }
845
846       g_free (ifaces);
847     }
848   else
849     {
850       properties = properties_from_type (object, type, tips);
851       gtk_container_add (GTK_CONTAINER (win), properties);
852       title = g_strdup_printf ("Properties of %s", g_type_name (type));
853       gtk_window_set_title (GTK_WINDOW (win), title);
854       g_free (title);
855     }
856   
857   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
858   
859   gtk_widget_show_all (win);
860
861   return win;
862 }
863