]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
gtk/gtkfilechooserutils.c gtk/gtkfilechooserprivate.[ch]: Basic framework
[~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
434   if (obj)
435     name = g_type_name (G_TYPE_FROM_INSTANCE (obj));
436   else
437     name = "unknown";
438   str = g_strdup_printf ("Object: %p (%s)", obj, name);
439   
440   gtk_label_set_text (GTK_LABEL (label), str);
441   gtk_widget_set_sensitive (button, G_IS_OBJECT (obj));
442
443   g_free (str);
444 }
445
446 static void
447 model_destroy (gpointer data)
448 {
449   g_object_steal_data (data, "model-object");
450   gtk_widget_destroy (data);
451 }
452
453 static void
454 window_destroy (gpointer data)
455 {
456   g_object_steal_data (data, "prop-editor-win");
457 }
458
459 static void
460 object_properties (GtkWidget *button, 
461                    GObject   *object)
462 {
463   gchar *name;
464   GObject *obj;
465
466   name = (gchar *) g_object_get_data (G_OBJECT (button), "property-name");
467   g_object_get (object, name, &obj, NULL);
468   if (G_IS_OBJECT (obj)) 
469     create_prop_editor (obj, 0);
470 }
471  
472 static GtkWidget *
473 property_widget (GObject *object, GParamSpec *spec, gboolean can_modify)
474 {
475   GtkWidget *prop_edit;
476   GtkAdjustment *adj;
477   gchar *msg;
478   GType type = G_PARAM_SPEC_TYPE (spec);
479
480   if (type == G_TYPE_PARAM_INT)
481     {
482       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
483                                                 G_PARAM_SPEC_INT (spec)->minimum,
484                                                 G_PARAM_SPEC_INT (spec)->maximum,
485                                                 1,
486                                                 MAX ((G_PARAM_SPEC_INT (spec)->maximum -
487                                                       G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
488                                                 0.0));
489       
490       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
491       
492       g_object_connect_property (object, spec->name,
493                                  G_CALLBACK (int_changed),
494                                  adj, G_OBJECT (adj));
495       
496       if (can_modify)
497         connect_controller (G_OBJECT (adj), "value_changed",
498                             object, spec->name, (GtkSignalFunc) int_modified);
499     }
500   else if (type == G_TYPE_PARAM_UINT)
501     {
502       adj = GTK_ADJUSTMENT (
503                             gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
504                                                 G_PARAM_SPEC_UINT (spec)->minimum,
505                                                 G_PARAM_SPEC_UINT (spec)->maximum,
506                                                 1,
507                                                 MAX ((G_PARAM_SPEC_UINT (spec)->maximum -
508                                                       G_PARAM_SPEC_UINT (spec)->minimum) / 10, 1),
509                                                 0.0));
510       
511       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
512       
513       g_object_connect_property (object, spec->name,
514                                  G_CALLBACK (uint_changed),
515                                  adj, G_OBJECT (adj));
516       
517       if (can_modify)
518         connect_controller (G_OBJECT (adj), "value_changed",
519                             object, spec->name, (GtkSignalFunc) uint_modified);
520     }
521   else if (type == G_TYPE_PARAM_FLOAT)
522     {
523
524       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
525                                                 G_PARAM_SPEC_FLOAT (spec)->minimum,
526                                                 G_PARAM_SPEC_FLOAT (spec)->maximum,
527                                                 0.1,
528                                                 MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
529                                                       G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
530                                                 0.0));
531       
532       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
533       
534       g_object_connect_property (object, spec->name,
535                                  G_CALLBACK (float_changed),
536                                  adj, G_OBJECT (adj));
537       
538       if (can_modify)
539         connect_controller (G_OBJECT (adj), "value_changed",
540                             object, spec->name, (GtkSignalFunc) float_modified);
541     }
542   else if (type == G_TYPE_PARAM_DOUBLE)
543     {
544       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
545                                                 G_PARAM_SPEC_DOUBLE (spec)->minimum,
546                                                 G_PARAM_SPEC_DOUBLE (spec)->maximum,
547                                                 0.1,
548                                                 MAX ((G_PARAM_SPEC_DOUBLE (spec)->maximum -
549                                                       G_PARAM_SPEC_DOUBLE (spec)->minimum) / 10, 0.1),
550                                                 0.0));
551       
552       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
553       
554       g_object_connect_property (object, spec->name,
555                                  G_CALLBACK (double_changed),
556                                  adj, G_OBJECT (adj));
557       
558       if (can_modify)
559         connect_controller (G_OBJECT (adj), "value_changed",
560                             object, spec->name, (GtkSignalFunc) double_modified);
561     }
562   else if (type == G_TYPE_PARAM_STRING)
563     {
564       prop_edit = gtk_entry_new ();
565       
566       g_object_connect_property (object, spec->name,
567                                  G_CALLBACK (string_changed),
568                                  prop_edit, G_OBJECT (prop_edit));
569       
570       if (can_modify)
571         connect_controller (G_OBJECT (prop_edit), "changed",
572                             object, spec->name, (GtkSignalFunc) string_modified);
573     }
574   else if (type == G_TYPE_PARAM_BOOLEAN)
575     {
576       prop_edit = gtk_toggle_button_new_with_label ("");
577       
578       g_object_connect_property (object, spec->name,
579                                  G_CALLBACK (bool_changed),
580                                  prop_edit, G_OBJECT (prop_edit));
581       
582       if (can_modify)
583         connect_controller (G_OBJECT (prop_edit), "toggled",
584                             object, spec->name, (GtkSignalFunc) bool_modified);
585     }
586   else if (type == G_TYPE_PARAM_ENUM)
587     {
588       {
589         GtkWidget *menu;
590         GEnumClass *eclass;
591         gint j;
592         
593         prop_edit = gtk_option_menu_new ();
594         
595         menu = gtk_menu_new ();
596         
597         eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
598         
599         j = 0;
600         while (j < eclass->n_values)
601           {
602             GtkWidget *mi;
603             
604             mi = gtk_menu_item_new_with_label (eclass->values[j].value_name);
605             
606             gtk_widget_show (mi);
607             
608             gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
609             
610             ++j;
611           }
612         
613         g_type_class_unref (eclass);
614         
615         gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
616         
617         g_object_connect_property (object, spec->name,
618                                    G_CALLBACK (enum_changed),
619                                    prop_edit, G_OBJECT (prop_edit));
620         
621         if (can_modify)
622           connect_controller (G_OBJECT (prop_edit), "changed",
623                               object, spec->name, (GtkSignalFunc) enum_modified);
624       }
625     }
626   else if (type == G_TYPE_PARAM_UNICHAR)
627     {
628       prop_edit = gtk_entry_new ();
629       gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
630       
631       g_object_connect_property (object, spec->name,
632                                  G_CALLBACK (unichar_changed),
633                                  prop_edit, G_OBJECT (prop_edit));
634       
635       if (can_modify)
636         connect_controller (G_OBJECT (prop_edit), "changed",
637                             object, spec->name, (GtkSignalFunc) unichar_modified);
638     }
639   else if (type == G_TYPE_PARAM_POINTER)
640     {
641       prop_edit = gtk_label_new ("");
642       
643       g_object_connect_property (object, spec->name,
644                                  G_CALLBACK (pointer_changed),
645                                  prop_edit, G_OBJECT (prop_edit));
646     }
647   else if (type == G_TYPE_PARAM_OBJECT)
648     {
649       GtkWidget *label, *button;
650
651       prop_edit = gtk_hbox_new (FALSE, 5);
652
653       label = gtk_label_new ("");
654       button = gtk_button_new_with_label ("Properties");
655       g_object_set_data (G_OBJECT (button), "property-name", spec->name);
656       g_signal_connect (button, "clicked", 
657                         G_CALLBACK (object_properties), 
658                         object);
659
660       gtk_container_add (GTK_CONTAINER (prop_edit), label);
661       gtk_container_add (GTK_CONTAINER (prop_edit), button);
662       
663       g_object_connect_property (object, spec->name,
664                                  G_CALLBACK (object_changed),
665                                  prop_edit, G_OBJECT (label));
666     }
667   else
668     {  
669       msg = g_strdup_printf ("uneditable property type: %s",
670                              g_type_name (G_PARAM_SPEC_TYPE (spec)));
671       prop_edit = gtk_label_new (msg);            
672       g_free (msg);
673       gtk_misc_set_alignment (GTK_MISC (prop_edit), 0.0, 0.5);
674     }
675   
676   return prop_edit;
677 }
678
679 static GtkWidget *
680 properties_from_type (GObject     *object,
681                       GType        type,
682                       GtkTooltips *tips)
683 {
684   GtkWidget *prop_edit;
685   GtkWidget *label;
686   GtkWidget *sw;
687   GtkWidget *vbox;
688   GtkWidget *table;
689   GParamSpec **specs;
690   gint n_specs;
691   int i;
692
693   if (G_TYPE_IS_INTERFACE (type))
694     {
695       gpointer vtable = g_type_default_interface_peek (type);
696       specs = g_object_interface_list_properties (vtable, &n_specs);
697     }
698   else
699     {
700       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
701       specs = g_object_class_list_properties (class, &n_specs);
702     }
703         
704   if (n_specs == 0)
705     return NULL;
706   
707   table = gtk_table_new (n_specs, 2, FALSE);
708   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
709   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
710
711   i = 0;
712   while (i < n_specs)
713     {
714       GParamSpec *spec = specs[i];
715       gboolean can_modify;
716       
717       prop_edit = NULL;
718
719       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
720                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
721       
722       if ((spec->flags & G_PARAM_READABLE) == 0)
723         {
724           /* can't display unreadable properties */
725           ++i;
726           continue;
727         }
728       
729       if (spec->owner_type != type)
730         {
731           /* we're only interested in params of type */
732           ++i;
733           continue;
734         }
735
736       label = gtk_label_new (g_param_spec_get_nick (spec));
737       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
738       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
739       
740       prop_edit = property_widget (object, spec, can_modify);
741       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
742
743       if (prop_edit)
744         {
745           if (!can_modify)
746             gtk_widget_set_sensitive (prop_edit, FALSE);
747
748           if (g_param_spec_get_blurb (spec))
749             gtk_tooltips_set_tip (tips, prop_edit, g_param_spec_get_blurb (spec), NULL);
750           
751           /* set initial value */
752           g_object_notify (object, spec->name);
753         }
754       
755       ++i;
756     }
757
758
759   vbox = gtk_vbox_new (FALSE, 0);
760   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
761
762   sw = gtk_scrolled_window_new (NULL, NULL);
763   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
764                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
765   
766   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
767
768   g_free (specs);
769
770   return sw;
771 }
772
773
774 /* Pass zero for type if you want all properties */
775 GtkWidget*
776 create_prop_editor (GObject   *object,
777                     GType      type)
778 {
779   GtkWidget *win;
780   GtkWidget *notebook;
781   GtkTooltips *tips;
782   GtkWidget *properties;
783   GtkWidget *label;
784   gchar *title;
785   GType *ifaces;
786   guint n_ifaces;
787   
788   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
789     {
790       gtk_window_present (GTK_WINDOW (win));
791       return win;
792     }
793
794   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
795   if (GTK_IS_WIDGET (object))
796     gtk_window_set_screen (GTK_WINDOW (win),
797                            gtk_widget_get_screen (GTK_WIDGET (object)));
798   
799   tips = gtk_tooltips_new ();
800   g_signal_connect_swapped (win, "destroy",
801                             G_CALLBACK (gtk_object_destroy), tips);
802
803   /* hold a weak ref to the object we're editing */
804   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
805   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
806
807   if (type == 0)
808     {
809       notebook = gtk_notebook_new ();
810       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
811       
812       gtk_container_add (GTK_CONTAINER (win), notebook);
813       
814       type = G_TYPE_FROM_INSTANCE (object);
815
816       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
817       gtk_window_set_title (GTK_WINDOW (win), title);
818       g_free (title);
819       
820       while (type)
821         {
822           properties = properties_from_type (object, type, tips);
823           if (properties)
824             {
825               label = gtk_label_new (g_type_name (type));
826               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
827                                         properties, label);
828             }
829           
830           type = g_type_parent (type);
831         }
832
833       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
834       while (n_ifaces--)
835         {
836           properties = properties_from_type (object, ifaces[n_ifaces], tips);
837           if (properties)
838             {
839               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
840               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
841                                         properties, label);
842             }
843         }
844
845       g_free (ifaces);
846     }
847   else
848     {
849       properties = properties_from_type (object, type, tips);
850       gtk_container_add (GTK_CONTAINER (win), properties);
851       title = g_strdup_printf ("Properties of %s", g_type_name (type));
852       gtk_window_set_title (GTK_WINDOW (win), title);
853     }
854   
855   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
856   
857   gtk_widget_show_all (win);
858
859   return win;
860 }
861