]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
prop-editor: Implement property editing for GdkRGBA properties
[~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 #include <gtk/gtk.h>
23
24 #include "prop-editor.h"
25
26
27 typedef struct
28 {
29   gpointer instance;
30   GObject *alive_object;
31   gulong id;
32 } DisconnectData;
33
34 static void
35 disconnect_func (gpointer data)
36 {
37   DisconnectData *dd = data;
38
39   g_signal_handler_disconnect (dd->instance, dd->id);
40 }
41
42 static void
43 signal_removed (gpointer  data,
44                 GClosure *closure)
45 {
46   DisconnectData *dd = data;
47
48   g_object_steal_data (dd->alive_object, "alive-object-data");
49   g_free (dd);
50 }
51
52 static gboolean
53 is_child_property (GParamSpec *pspec)
54 {
55   return g_param_spec_get_qdata (pspec, g_quark_from_string ("is-child-prop")) != NULL;
56 }
57
58 static void
59 mark_child_property (GParamSpec *pspec)
60 {
61   g_param_spec_set_qdata (pspec, g_quark_from_string ("is-child-prop"),
62                           GINT_TO_POINTER (TRUE));
63 }
64
65 static void
66 g_object_connect_property (GObject     *object,
67                            GParamSpec  *spec,
68                            GCallback    func,
69                            gpointer     data,
70                            GObject     *alive_object)
71 {
72   GClosure *closure;
73   gchar *with_detail;
74   DisconnectData *dd;
75
76   if (is_child_property (spec))
77     with_detail = g_strconcat ("child-notify::", spec->name, NULL);
78   else
79     with_detail = g_strconcat ("notify::", spec->name, NULL);
80
81   dd = g_new (DisconnectData, 1);
82
83   closure = g_cclosure_new (func, data, NULL);
84
85   g_closure_add_invalidate_notifier (closure, dd, signal_removed);
86
87   dd->id = g_signal_connect_closure (object, with_detail,
88                                      closure, FALSE);
89
90   dd->instance = object;
91   dd->alive_object = alive_object;
92
93   g_object_set_data_full (G_OBJECT (alive_object),
94                           "alive-object-data",
95                           dd,
96                           disconnect_func);
97
98   g_free (with_detail);
99 }
100
101 typedef struct
102 {
103   GObject *obj;
104   GParamSpec *spec;
105   gulong modified_id;
106 } ObjectProperty;
107
108 static void
109 free_object_property (ObjectProperty *p)
110 {
111   g_free (p);
112 }
113
114 static void
115 connect_controller (GObject     *controller,
116                     const gchar *signal,
117                     GObject     *model,
118                     GParamSpec  *spec,
119                     GCallback    func)
120 {
121   ObjectProperty *p;
122
123   p = g_new (ObjectProperty, 1);
124   p->obj = model;
125   p->spec = spec;
126
127   p->modified_id = g_signal_connect_data (controller, signal, func, p,
128                                           (GClosureNotify)free_object_property,
129                                           0);
130   g_object_set_data (controller, "object-property", p);
131 }
132
133 static void
134 block_controller (GObject *controller)
135 {
136   ObjectProperty *p = g_object_get_data (controller, "object-property");
137
138   if (p)
139     g_signal_handler_block (controller, p->modified_id);
140 }
141
142 static void
143 unblock_controller (GObject *controller)
144 {
145   ObjectProperty *p = g_object_get_data (controller, "object-property");
146
147   if (p)
148     g_signal_handler_unblock (controller, p->modified_id);
149 }
150
151 static void
152 int_modified (GtkAdjustment *adj, gpointer data)
153 {
154   ObjectProperty *p = data;
155
156   if (is_child_property (p->spec))
157     {
158       GtkWidget *widget = GTK_WIDGET (p->obj);
159       GtkWidget *parent = gtk_widget_get_parent (widget);
160
161       gtk_container_child_set (GTK_CONTAINER (parent),
162                                widget, p->spec->name, (int) gtk_adjustment_get_value (adj), NULL);
163     }
164   else
165     g_object_set (p->obj, p->spec->name, (int) gtk_adjustment_get_value (adj), NULL);
166 }
167
168 static void
169 get_property_value (GObject *object, GParamSpec *pspec, GValue *value)
170 {
171   if (is_child_property (pspec))
172     {
173       GtkWidget *widget = GTK_WIDGET (object);
174       GtkWidget *parent = gtk_widget_get_parent (widget);
175
176       gtk_container_child_get_property (GTK_CONTAINER (parent),
177                                         widget, pspec->name, value);
178     }
179   else
180     g_object_get_property (object, pspec->name, value);
181 }
182
183 static void
184 int_changed (GObject *object, GParamSpec *pspec, gpointer data)
185 {
186   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
187   GValue val = G_VALUE_INIT;
188
189   g_value_init (&val, G_TYPE_INT);
190
191   get_property_value (object, pspec, &val);
192
193   if (g_value_get_int (&val) != (int)gtk_adjustment_get_value (adj))
194     {
195       block_controller (G_OBJECT (adj));
196       gtk_adjustment_set_value (adj, g_value_get_int (&val));
197       unblock_controller (G_OBJECT (adj));
198     }
199
200   g_value_unset (&val);
201 }
202
203 static void
204 uint_modified (GtkAdjustment *adj, gpointer data)
205 {
206   ObjectProperty *p = data;
207
208   if (is_child_property (p->spec))
209     {
210       GtkWidget *widget = GTK_WIDGET (p->obj);
211       GtkWidget *parent = gtk_widget_get_parent (widget);
212
213       gtk_container_child_set (GTK_CONTAINER (parent),
214                                widget, p->spec->name, (guint) gtk_adjustment_get_value (adj), NULL);
215     }
216   else
217     g_object_set (p->obj, p->spec->name, (guint) gtk_adjustment_get_value (adj), NULL);
218 }
219
220 static void
221 uint_changed (GObject *object, GParamSpec *pspec, gpointer data)
222 {
223   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
224   GValue val = G_VALUE_INIT;
225
226   g_value_init (&val, G_TYPE_UINT);
227   get_property_value (object, pspec, &val);
228
229   if (g_value_get_uint (&val) != (guint)gtk_adjustment_get_value (adj))
230     {
231       block_controller (G_OBJECT (adj));
232       gtk_adjustment_set_value (adj, g_value_get_uint (&val));
233       unblock_controller (G_OBJECT (adj));
234     }
235
236   g_value_unset (&val);
237 }
238
239 static void
240 float_modified (GtkAdjustment *adj, gpointer data)
241 {
242   ObjectProperty *p = data;
243
244   if (is_child_property (p->spec))
245     {
246       GtkWidget *widget = GTK_WIDGET (p->obj);
247       GtkWidget *parent = gtk_widget_get_parent (widget);
248
249       gtk_container_child_set (GTK_CONTAINER (parent),
250                                widget, p->spec->name, (float) gtk_adjustment_get_value (adj), NULL);
251     }
252   else
253     g_object_set (p->obj, p->spec->name, (float) gtk_adjustment_get_value (adj), NULL);
254 }
255
256 static void
257 float_changed (GObject *object, GParamSpec *pspec, gpointer data)
258 {
259   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
260   GValue val = G_VALUE_INIT;
261
262   g_value_init (&val, G_TYPE_FLOAT);
263   get_property_value (object, pspec, &val);
264
265   if (g_value_get_float (&val) != (float) gtk_adjustment_get_value (adj))
266     {
267       block_controller (G_OBJECT (adj));
268       gtk_adjustment_set_value (adj, g_value_get_float (&val));
269       unblock_controller (G_OBJECT (adj));
270     }
271
272   g_value_unset (&val);
273 }
274
275 static void
276 double_modified (GtkAdjustment *adj, gpointer data)
277 {
278   ObjectProperty *p = data;
279
280   if (is_child_property (p->spec))
281     {
282       GtkWidget *widget = GTK_WIDGET (p->obj);
283       GtkWidget *parent = gtk_widget_get_parent (widget);
284
285       gtk_container_child_set (GTK_CONTAINER (parent),
286                                widget, p->spec->name, (double) gtk_adjustment_get_value (adj), NULL);
287     }
288   else
289     g_object_set (p->obj, p->spec->name, (double) gtk_adjustment_get_value (adj), NULL);
290 }
291
292 static void
293 double_changed (GObject *object, GParamSpec *pspec, gpointer data)
294 {
295   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
296   GValue val = G_VALUE_INIT;
297
298   g_value_init (&val, G_TYPE_DOUBLE);
299   get_property_value (object, pspec, &val);
300
301   if (g_value_get_double (&val) != gtk_adjustment_get_value (adj))
302     {
303       block_controller (G_OBJECT (adj));
304       gtk_adjustment_set_value (adj, g_value_get_double (&val));
305       unblock_controller (G_OBJECT (adj));
306     }
307
308   g_value_unset (&val);
309 }
310
311 static void
312 string_modified (GtkEntry *entry, gpointer data)
313 {
314   ObjectProperty *p = data;
315   const gchar *text;
316
317   text = gtk_entry_get_text (entry);
318
319   if (is_child_property (p->spec))
320     {
321       GtkWidget *widget = GTK_WIDGET (p->obj);
322       GtkWidget *parent = gtk_widget_get_parent (widget);
323
324       gtk_container_child_set (GTK_CONTAINER (parent),
325                                widget, p->spec->name, text, NULL);
326     }
327   else
328     g_object_set (p->obj, p->spec->name, text, NULL);
329 }
330
331 static void
332 string_changed (GObject *object, GParamSpec *pspec, gpointer data)
333 {
334   GtkEntry *entry = GTK_ENTRY (data);
335   GValue val = G_VALUE_INIT;
336   const gchar *str;
337   const gchar *text;
338
339   g_value_init (&val, G_TYPE_STRING);
340   get_property_value (object, pspec, &val);
341
342   str = g_value_get_string (&val);
343   if (str == NULL)
344     str = "";
345   text = gtk_entry_get_text (entry);
346
347   if (strcmp (str, text) != 0)
348     {
349       block_controller (G_OBJECT (entry));
350       gtk_entry_set_text (entry, str);
351       unblock_controller (G_OBJECT (entry));
352     }
353
354   g_value_unset (&val);
355 }
356
357 static void
358 bool_modified (GtkToggleButton *tb, gpointer data)
359 {
360   ObjectProperty *p = data;
361
362   if (is_child_property (p->spec))
363     {
364       GtkWidget *widget = GTK_WIDGET (p->obj);
365       GtkWidget *parent = gtk_widget_get_parent (widget);
366
367       gtk_container_child_set (GTK_CONTAINER (parent), widget,
368                                p->spec->name, (int) gtk_toggle_button_get_active (tb),
369                                NULL);
370     }
371   else
372     g_object_set (p->obj, p->spec->name, (int) gtk_toggle_button_get_active (tb), NULL);
373 }
374
375 static void
376 bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
377 {
378   GtkToggleButton *tb = GTK_TOGGLE_BUTTON (data);
379   GtkWidget *child;
380   GValue val = G_VALUE_INIT;
381
382   g_value_init (&val, G_TYPE_BOOLEAN);
383   get_property_value (object, pspec, &val);
384
385   if (g_value_get_boolean (&val) != gtk_toggle_button_get_active (tb))
386     {
387       block_controller (G_OBJECT (tb));
388       gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
389       unblock_controller (G_OBJECT (tb));
390     }
391
392   child = gtk_bin_get_child (GTK_BIN (tb));
393   gtk_label_set_text (GTK_LABEL (child),
394                       g_value_get_boolean (&val) ? "TRUE" : "FALSE");
395
396   g_value_unset (&val);
397 }
398
399
400 static void
401 enum_modified (GtkComboBox *cb, gpointer data)
402 {
403   ObjectProperty *p = data;
404   gint i;
405   GEnumClass *eclass;
406
407   eclass = G_ENUM_CLASS (g_type_class_peek (p->spec->value_type));
408
409   i = gtk_combo_box_get_active (cb);
410
411
412   if (is_child_property (p->spec))
413     {
414       GtkWidget *widget = GTK_WIDGET (p->obj);
415       GtkWidget *parent = gtk_widget_get_parent (widget);
416
417       gtk_container_child_set (GTK_CONTAINER (parent),
418                                widget, p->spec->name, eclass->values[i].value, NULL);
419     }
420   else
421     g_object_set (p->obj, p->spec->name, eclass->values[i].value, NULL);
422 }
423
424 static void
425 enum_changed (GObject *object, GParamSpec *pspec, gpointer data)
426 {
427   GtkComboBox *cb = GTK_COMBO_BOX (data);
428   GValue val = G_VALUE_INIT;
429   GEnumClass *eclass;
430   gint i;
431
432   eclass = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
433
434   g_value_init (&val, pspec->value_type);
435   get_property_value (object, pspec, &val);
436
437   i = 0;
438   while (i < eclass->n_values)
439     {
440       if (eclass->values[i].value == g_value_get_enum (&val))
441         break;
442       ++i;
443     }
444
445   if (gtk_combo_box_get_active (cb) != i)
446     {
447       block_controller (G_OBJECT (cb));
448       gtk_combo_box_set_active (cb, i);
449       unblock_controller (G_OBJECT (cb));
450     }
451
452   g_value_unset (&val);
453
454 }
455
456 static void
457 flags_modified (GtkCheckButton *button, gpointer data)
458 {
459   ObjectProperty *p = data;
460   gboolean active;
461   GFlagsClass *fclass;
462   guint flags;
463   gint i;
464
465   fclass = G_FLAGS_CLASS (g_type_class_peek (p->spec->value_type));
466
467   active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
468   i = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "index"));
469
470   if (is_child_property (p->spec))
471     {
472       GtkWidget *widget = GTK_WIDGET (p->obj);
473       GtkWidget *parent = gtk_widget_get_parent (widget);
474
475       gtk_container_child_get (GTK_CONTAINER (parent),
476                                widget, p->spec->name, &flags, NULL);
477       if (active)
478         flags |= fclass->values[i].value;
479       else
480         flags &= ~fclass->values[i].value;
481
482       gtk_container_child_set (GTK_CONTAINER (parent),
483                                widget, p->spec->name, flags, NULL);
484     }
485   else
486     {
487       g_object_get (p->obj, p->spec->name, &flags, NULL);
488
489       if (active)
490         flags |= fclass->values[i].value;
491       else
492         flags &= ~fclass->values[i].value;
493
494       g_object_set (p->obj, p->spec->name, flags, NULL);
495     }
496 }
497
498 static void
499 flags_changed (GObject *object, GParamSpec *pspec, gpointer data)
500 {
501   GList *children, *c;
502   GValue val = G_VALUE_INIT;
503   GFlagsClass *fclass;
504   guint flags;
505   gint i;
506
507   fclass = G_FLAGS_CLASS (g_type_class_peek (pspec->value_type));
508
509   g_value_init (&val, pspec->value_type);
510   get_property_value (object, pspec, &val);
511   flags = g_value_get_flags (&val);
512   g_value_unset (&val);
513
514   children = gtk_container_get_children (GTK_CONTAINER (data));
515
516   for (c = children, i = 0; c; c = c->next, i++)
517     {
518       block_controller (G_OBJECT (c->data));
519       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (c->data),
520                                     (fclass->values[i].value & flags) != 0);
521       unblock_controller (G_OBJECT (c->data));
522     }
523
524   g_list_free (children);
525 }
526
527 static gunichar
528 unichar_get_value (GtkEntry *entry)
529 {
530   const gchar *text = gtk_entry_get_text (entry);
531
532   if (text[0])
533     return g_utf8_get_char (text);
534   else
535     return 0;
536 }
537
538 static void
539 unichar_modified (GtkEntry *entry, gpointer data)
540 {
541   ObjectProperty *p = data;
542   gunichar val = unichar_get_value (entry);
543
544   if (is_child_property (p->spec))
545     {
546       GtkWidget *widget = GTK_WIDGET (p->obj);
547       GtkWidget *parent = gtk_widget_get_parent (widget);
548
549       gtk_container_child_set (GTK_CONTAINER (parent),
550                                widget, p->spec->name, val, NULL);
551     }
552   else
553     g_object_set (p->obj, p->spec->name, val, NULL);
554 }
555
556 static void
557 unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
558 {
559   GtkEntry *entry = GTK_ENTRY (data);
560   gunichar new_val;
561   gunichar old_val = unichar_get_value (entry);
562   GValue val = G_VALUE_INIT;
563   gchar buf[7];
564   gint len;
565
566   g_value_init (&val, pspec->value_type);
567   get_property_value (object, pspec, &val);
568   new_val = (gunichar)g_value_get_uint (&val);
569
570   if (new_val != old_val)
571     {
572       if (!new_val)
573         len = 0;
574       else
575         len = g_unichar_to_utf8 (new_val, buf);
576
577       buf[len] = '\0';
578
579       block_controller (G_OBJECT (entry));
580       gtk_entry_set_text (entry, buf);
581       unblock_controller (G_OBJECT (entry));
582     }
583 }
584
585 static void
586 pointer_changed (GObject *object, GParamSpec *pspec, gpointer data)
587 {
588   GtkLabel *label = GTK_LABEL (data);
589   gchar *str;
590   gpointer ptr;
591
592   g_object_get (object, pspec->name, &ptr, NULL);
593
594   str = g_strdup_printf ("Pointer: %p", ptr);
595   gtk_label_set_text (label, str);
596   g_free (str);
597 }
598
599 static gchar *
600 object_label (GObject *obj, GParamSpec *pspec)
601 {
602   const gchar *name;
603
604   if (obj)
605     name = g_type_name (G_TYPE_FROM_INSTANCE (obj));
606   else if (pspec)
607     name = g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec));
608   else
609     name = "unknown";
610   return g_strdup_printf ("Object: %p (%s)", obj, name);
611 }
612
613 static void
614 object_changed (GObject *object, GParamSpec *pspec, gpointer data)
615 {
616   GtkWidget *label, *button;
617   gchar *str;
618   GObject *obj;
619
620   GList *children = gtk_container_get_children (GTK_CONTAINER (data));
621   label = GTK_WIDGET (children->data);
622   button = GTK_WIDGET (children->next->data);
623   g_object_get (object, pspec->name, &obj, NULL);
624   g_list_free (children);
625
626   str = object_label (obj, pspec);
627
628   gtk_label_set_text (GTK_LABEL (label), str);
629   gtk_widget_set_sensitive (button, G_IS_OBJECT (obj));
630
631   if (obj)
632     g_object_unref (obj);
633
634   g_free (str);
635 }
636
637 static void
638 model_destroy (gpointer data)
639 {
640   g_object_steal_data (data, "model-object");
641   gtk_widget_destroy (data);
642 }
643
644 static void
645 window_destroy (gpointer data)
646 {
647   g_object_steal_data (data, "prop-editor-win");
648 }
649
650 static void
651 object_properties (GtkWidget *button,
652                    GObject   *object)
653 {
654   gchar *name;
655   GObject *obj;
656
657   name = (gchar *) g_object_get_data (G_OBJECT (button), "property-name");
658   g_object_get (object, name, &obj, NULL);
659   if (G_IS_OBJECT (obj))
660     create_prop_editor (obj, 0);
661 }
662
663 static void
664 rgba_modified (GtkColorButton *cb, gpointer data)
665 {
666   ObjectProperty *p = data;
667   GdkRGBA color;
668
669   gtk_color_button_get_rgba (cb, &color);
670
671   if (is_child_property (p->spec))
672     {
673       GtkWidget *widget = GTK_WIDGET (p->obj);
674       GtkWidget *parent = gtk_widget_get_parent (widget);
675
676       gtk_container_child_set (GTK_CONTAINER (parent),
677                                widget, p->spec->name, &color, NULL);
678     }
679   else
680     g_object_set (p->obj, p->spec->name, &color, NULL);
681 }
682
683 static void
684 rgba_changed (GObject *object, GParamSpec *pspec, gpointer data)
685 {
686   GtkColorButton *cb = GTK_COLOR_BUTTON (data);
687   GValue val = G_VALUE_INIT;
688   GdkRGBA *color;
689   GdkRGBA cb_color;
690
691   g_value_init (&val, GDK_TYPE_RGBA);
692   get_property_value (object, pspec, &val);
693
694   color = g_value_get_boxed (&val);
695   gtk_color_button_get_rgba (cb, &cb_color);
696
697   if (color != NULL && !gdk_rgba_equal (color, &cb_color))
698     {
699       block_controller (G_OBJECT (cb));
700       gtk_color_button_set_rgba (cb, color);
701       unblock_controller (G_OBJECT (cb));
702     }
703
704   g_value_unset (&val);
705 }
706
707 static void
708 color_modified (GtkColorButton *cb, gpointer data)
709 {
710   ObjectProperty *p = data;
711   GdkColor color;
712
713   gtk_color_button_get_color (cb, &color);
714
715   if (is_child_property (p->spec))
716     {
717       GtkWidget *widget = GTK_WIDGET (p->obj);
718       GtkWidget *parent = gtk_widget_get_parent (widget);
719
720       gtk_container_child_set (GTK_CONTAINER (parent),
721                                widget, p->spec->name, &color, NULL);
722     }
723   else
724     g_object_set (p->obj, p->spec->name, &color, NULL);
725 }
726
727 static void
728 color_changed (GObject *object, GParamSpec *pspec, gpointer data)
729 {
730   GtkColorButton *cb = GTK_COLOR_BUTTON (data);
731   GValue val = G_VALUE_INIT;
732   GdkColor *color;
733   GdkColor cb_color;
734
735   g_value_init (&val, GDK_TYPE_COLOR);
736   get_property_value (object, pspec, &val);
737
738   color = g_value_get_boxed (&val);
739   gtk_color_button_get_color (cb, &cb_color);
740
741   if (color != NULL && !gdk_color_equal (color, &cb_color))
742     {
743       block_controller (G_OBJECT (cb));
744       gtk_color_button_set_color (cb, color);
745       unblock_controller (G_OBJECT (cb));
746     }
747
748   g_value_unset (&val);
749 }
750
751 static void
752 font_modified (GtkFontChooser *fb, GParamSpec *pspec, gpointer data)
753 {
754   ObjectProperty *p = data;
755   PangoFontDescription *font_desc;
756
757   font_desc = gtk_font_chooser_get_font_desc (fb);
758
759   if (is_child_property (p->spec))
760     {
761       GtkWidget *widget = GTK_WIDGET (p->obj);
762       GtkWidget *parent = gtk_widget_get_parent (widget);
763
764       gtk_container_child_set (GTK_CONTAINER (parent),
765                                widget, p->spec->name, font_desc, NULL);
766     }
767   else
768     g_object_set (p->obj, p->spec->name, font_desc, NULL);
769
770   pango_font_description_free (font_desc);
771 }
772
773 static void
774 font_changed (GObject *object, GParamSpec *pspec, gpointer data)
775 {
776   GtkFontChooser *fb = GTK_FONT_CHOOSER (data);
777   GValue val = G_VALUE_INIT;
778   const PangoFontDescription *font_desc;
779   PangoFontDescription *fb_font_desc;
780
781   g_value_init (&val, PANGO_TYPE_FONT_DESCRIPTION);
782   get_property_value (object, pspec, &val);
783
784   font_desc = g_value_get_boxed (&val);
785   fb_font_desc = gtk_font_chooser_get_font_desc (fb);
786
787   if (font_desc == NULL ||
788       (fb_font_desc != NULL &&
789        !pango_font_description_equal (fb_font_desc, font_desc)))
790     {
791       block_controller (G_OBJECT (fb));
792       gtk_font_chooser_set_font_desc (fb, font_desc);
793       unblock_controller (G_OBJECT (fb));
794     }
795
796   g_value_unset (&val);
797   pango_font_description_free (fb_font_desc);
798 }
799
800
801 static GtkWidget *
802 property_widget (GObject    *object,
803                  GParamSpec *spec,
804                  gboolean    can_modify)
805 {
806   GtkWidget *prop_edit;
807   GtkAdjustment *adj;
808   gchar *msg;
809   GType type = G_PARAM_SPEC_TYPE (spec);
810
811   if (type == G_TYPE_PARAM_INT)
812     {
813       adj = gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
814                                 G_PARAM_SPEC_INT (spec)->minimum,
815                                 G_PARAM_SPEC_INT (spec)->maximum,
816                                 1,
817                                 MAX ((G_PARAM_SPEC_INT (spec)->maximum - G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
818                                 0.0);
819
820       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
821
822       g_object_connect_property (object, spec,
823                                  G_CALLBACK (int_changed),
824                                  adj, G_OBJECT (adj));
825
826       if (can_modify)
827         connect_controller (G_OBJECT (adj), "value_changed",
828                             object, spec, G_CALLBACK (int_modified));
829     }
830   else if (type == G_TYPE_PARAM_UINT)
831     {
832       adj = gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
833                                 G_PARAM_SPEC_UINT (spec)->minimum,
834                                 G_PARAM_SPEC_UINT (spec)->maximum,
835                                 1,
836                                 MAX ((G_PARAM_SPEC_UINT (spec)->maximum - G_PARAM_SPEC_UINT (spec)->minimum) / 10, 1),
837                                 0.0);
838
839       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
840
841       g_object_connect_property (object, spec,
842                                  G_CALLBACK (uint_changed),
843                                  adj, G_OBJECT (adj));
844
845       if (can_modify)
846         connect_controller (G_OBJECT (adj), "value_changed",
847                             object, spec, G_CALLBACK (uint_modified));
848     }
849   else if (type == G_TYPE_PARAM_FLOAT)
850     {
851       adj = gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
852                                 G_PARAM_SPEC_FLOAT (spec)->minimum,
853                                 G_PARAM_SPEC_FLOAT (spec)->maximum,
854                                 0.1,
855                                 MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum - G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
856                                 0.0);
857
858       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
859
860       g_object_connect_property (object, spec,
861                                  G_CALLBACK (float_changed),
862                                  adj, G_OBJECT (adj));
863
864       if (can_modify)
865         connect_controller (G_OBJECT (adj), "value_changed",
866                             object, spec, G_CALLBACK (float_modified));
867     }
868   else if (type == G_TYPE_PARAM_DOUBLE)
869     {
870       adj = gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
871                                 G_PARAM_SPEC_DOUBLE (spec)->minimum,
872                                 G_PARAM_SPEC_DOUBLE (spec)->maximum,
873                                 0.1,
874                                 MAX ((G_PARAM_SPEC_DOUBLE (spec)->maximum - G_PARAM_SPEC_DOUBLE (spec)->minimum) / 10, 0.1),
875                                 0.0);
876
877       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
878
879       g_object_connect_property (object, spec,
880                                  G_CALLBACK (double_changed),
881                                  adj, G_OBJECT (adj));
882
883       if (can_modify)
884         connect_controller (G_OBJECT (adj), "value_changed",
885                             object, spec, G_CALLBACK (double_modified));
886     }
887   else if (type == G_TYPE_PARAM_STRING)
888     {
889       prop_edit = gtk_entry_new ();
890
891       g_object_connect_property (object, spec,
892                                  G_CALLBACK (string_changed),
893                                  prop_edit, G_OBJECT (prop_edit));
894
895       if (can_modify)
896         connect_controller (G_OBJECT (prop_edit), "changed",
897                             object, spec, G_CALLBACK (string_modified));
898     }
899   else if (type == G_TYPE_PARAM_BOOLEAN)
900     {
901       prop_edit = gtk_toggle_button_new_with_label ("");
902
903       g_object_connect_property (object, spec,
904                                  G_CALLBACK (bool_changed),
905                                  prop_edit, G_OBJECT (prop_edit));
906
907       if (can_modify)
908         connect_controller (G_OBJECT (prop_edit), "toggled",
909                             object, spec, G_CALLBACK (bool_modified));
910     }
911   else if (type == G_TYPE_PARAM_ENUM)
912     {
913       {
914         GEnumClass *eclass;
915         gint j;
916
917         prop_edit = gtk_combo_box_text_new ();
918
919         eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
920
921         j = 0;
922         while (j < eclass->n_values)
923           {
924             gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (prop_edit),
925                                             eclass->values[j].value_name);
926             ++j;
927           }
928
929         g_type_class_unref (eclass);
930
931         g_object_connect_property (object, spec,
932                                    G_CALLBACK (enum_changed),
933                                    prop_edit, G_OBJECT (prop_edit));
934
935         if (can_modify)
936           connect_controller (G_OBJECT (prop_edit), "changed",
937                               object, spec, G_CALLBACK (enum_modified));
938       }
939     }
940   else if (type == G_TYPE_PARAM_FLAGS)
941     {
942       {
943         GFlagsClass *fclass;
944         gint j;
945
946         prop_edit = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
947
948         fclass = G_FLAGS_CLASS (g_type_class_ref (spec->value_type));
949
950         for (j = 0; j < fclass->n_values; j++)
951           {
952             GtkWidget *b;
953
954             b = gtk_check_button_new_with_label (fclass->values[j].value_name);
955             g_object_set_data (G_OBJECT (b), "index", GINT_TO_POINTER (j));
956             gtk_widget_show (b);
957             gtk_box_pack_start (GTK_BOX (prop_edit), b, FALSE, FALSE, 0);
958             if (can_modify)
959               connect_controller (G_OBJECT (b), "toggled",
960                                   object, spec, G_CALLBACK (flags_modified));
961           }
962
963         g_type_class_unref (fclass);
964
965         g_object_connect_property (object, spec,
966                                    G_CALLBACK (flags_changed),
967                                    prop_edit, G_OBJECT (prop_edit));
968       }
969     }
970   else if (type == G_TYPE_PARAM_UNICHAR)
971     {
972       prop_edit = gtk_entry_new ();
973       gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
974
975       g_object_connect_property (object, spec,
976                                  G_CALLBACK (unichar_changed),
977                                  prop_edit, G_OBJECT (prop_edit));
978
979       if (can_modify)
980         connect_controller (G_OBJECT (prop_edit), "changed",
981                             object, spec, G_CALLBACK (unichar_modified));
982     }
983   else if (type == G_TYPE_PARAM_POINTER)
984     {
985       prop_edit = gtk_label_new ("");
986
987       g_object_connect_property (object, spec,
988                                  G_CALLBACK (pointer_changed),
989                                  prop_edit, G_OBJECT (prop_edit));
990     }
991   else if (type == G_TYPE_PARAM_OBJECT)
992     {
993       GtkWidget *label, *button;
994
995       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
996
997       label = gtk_label_new ("");
998       button = gtk_button_new_with_label ("Properties");
999       g_object_set_data (G_OBJECT (button), "property-name", (gpointer) spec->name);
1000       g_signal_connect (button, "clicked",
1001                         G_CALLBACK (object_properties),
1002                         object);
1003
1004       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1005       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1006
1007       g_object_connect_property (object, spec,
1008                                  G_CALLBACK (object_changed),
1009                                  prop_edit, G_OBJECT (label));
1010
1011       /* The Properties button is not really modifying, anyway */
1012       can_modify = TRUE;
1013     }
1014   else if (type == G_TYPE_PARAM_BOXED &&
1015            G_PARAM_SPEC_VALUE_TYPE (spec) == GDK_TYPE_RGBA)
1016     {
1017       prop_edit = gtk_color_button_new ();
1018       gtk_color_button_set_use_alpha (GTK_COLOR_BUTTON (prop_edit), TRUE);
1019
1020       g_object_connect_property (object, spec,
1021                                  G_CALLBACK (rgba_changed),
1022                                  prop_edit, G_OBJECT (prop_edit));
1023
1024       if (can_modify)
1025         connect_controller (G_OBJECT (prop_edit), "color-set",
1026                             object, spec, G_CALLBACK (rgba_modified));
1027     }
1028   else if (type == G_TYPE_PARAM_BOXED &&
1029            G_PARAM_SPEC_VALUE_TYPE (spec) == GDK_TYPE_COLOR)
1030     {
1031       prop_edit = gtk_color_button_new ();
1032
1033       g_object_connect_property (object, spec,
1034                                  G_CALLBACK (color_changed),
1035                                  prop_edit, G_OBJECT (prop_edit));
1036
1037       if (can_modify)
1038         connect_controller (G_OBJECT (prop_edit), "color-set",
1039                             object, spec, G_CALLBACK (color_modified));
1040     }
1041   else if (type == G_TYPE_PARAM_BOXED &&
1042            G_PARAM_SPEC_VALUE_TYPE (spec) == PANGO_TYPE_FONT_DESCRIPTION)
1043     {
1044       prop_edit = gtk_font_button_new ();
1045
1046       g_object_connect_property (object, spec,
1047                                  G_CALLBACK (font_changed),
1048                                  prop_edit, G_OBJECT (prop_edit));
1049
1050       if (can_modify)
1051         connect_controller (G_OBJECT (prop_edit), "notify::font-desc",
1052                             object, spec, G_CALLBACK (font_modified));
1053     }
1054   else
1055     {
1056       msg = g_strdup_printf ("uneditable property type: %s",
1057                              g_type_name (G_PARAM_SPEC_TYPE (spec)));
1058       prop_edit = gtk_label_new (msg);
1059       g_free (msg);
1060       gtk_widget_set_halign (prop_edit, GTK_ALIGN_START);
1061       gtk_widget_set_valign (prop_edit, GTK_ALIGN_CENTER);
1062     }
1063
1064   if (!can_modify)
1065     gtk_widget_set_sensitive (prop_edit, FALSE);
1066
1067   if (g_param_spec_get_blurb (spec))
1068     gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
1069
1070   return prop_edit;
1071 }
1072
1073 static GtkWidget *
1074 properties_from_type (GObject *object,
1075                       GType    type)
1076 {
1077   GtkWidget *prop_edit;
1078   GtkWidget *label;
1079   GtkWidget *sw;
1080   GtkWidget *vbox;
1081   GtkWidget *grid;
1082   GParamSpec **specs;
1083   guint n_specs;
1084   int i;
1085
1086   if (G_TYPE_IS_INTERFACE (type))
1087     {
1088       gpointer vtable = g_type_default_interface_peek (type);
1089       specs = g_object_interface_list_properties (vtable, &n_specs);
1090     }
1091   else
1092     {
1093       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
1094       specs = g_object_class_list_properties (class, &n_specs);
1095     }
1096
1097   if (n_specs == 0) {
1098     g_free (specs);
1099     return NULL;
1100   }
1101
1102   grid = gtk_grid_new ();
1103   gtk_grid_set_column_spacing (GTK_GRID (grid), 10);
1104   gtk_grid_set_row_spacing (GTK_GRID (grid), 3);
1105
1106   i = 0;
1107   while (i < n_specs)
1108     {
1109       GParamSpec *spec = specs[i];
1110       gboolean can_modify;
1111
1112       prop_edit = NULL;
1113
1114       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1115                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1116
1117       if ((spec->flags & G_PARAM_READABLE) == 0)
1118         {
1119           /* can't display unreadable properties */
1120           ++i;
1121           continue;
1122         }
1123
1124       if (spec->owner_type != type)
1125         {
1126           /* we're only interested in params of type */
1127           ++i;
1128           continue;
1129         }
1130
1131       label = gtk_label_new (g_param_spec_get_nick (spec));
1132       gtk_widget_set_halign (label, GTK_ALIGN_START);
1133       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1134       gtk_grid_attach (GTK_GRID (grid), label, 0, i, 1, 1);
1135
1136       prop_edit = property_widget (object, spec, can_modify);
1137       gtk_grid_attach (GTK_GRID (grid), prop_edit, 1, i, 1, 1);
1138
1139       /* set initial value */
1140       g_object_notify (object, spec->name);
1141
1142       ++i;
1143     }
1144
1145
1146   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1147   gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
1148
1149   sw = gtk_scrolled_window_new (NULL, NULL);
1150   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1151                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1152
1153   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1154
1155   g_free (specs);
1156
1157   return sw;
1158 }
1159
1160 static GtkWidget *
1161 child_properties_from_object (GObject *object)
1162 {
1163   GtkWidget *prop_edit;
1164   GtkWidget *label;
1165   GtkWidget *sw;
1166   GtkWidget *vbox;
1167   GtkWidget *grid;
1168   GtkWidget *parent;
1169   GParamSpec **specs;
1170   guint n_specs;
1171   gint i;
1172
1173   if (!GTK_IS_WIDGET (object))
1174     return NULL;
1175
1176   parent = gtk_widget_get_parent (GTK_WIDGET (object));
1177
1178   if (!parent)
1179     return NULL;
1180
1181   specs = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &n_specs);
1182
1183   grid = gtk_grid_new ();
1184   gtk_grid_set_column_spacing (GTK_GRID (grid), 10);
1185   gtk_grid_set_row_spacing (GTK_GRID (grid), 3);
1186
1187   i = 0;
1188   while (i < n_specs)
1189     {
1190       GParamSpec *spec = specs[i];
1191       gboolean can_modify;
1192
1193       prop_edit = NULL;
1194
1195       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1196                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1197
1198       if ((spec->flags & G_PARAM_READABLE) == 0)
1199         {
1200           /* can't display unreadable properties */
1201           ++i;
1202           continue;
1203         }
1204
1205       label = gtk_label_new (g_param_spec_get_nick (spec));
1206       gtk_widget_set_halign (label, GTK_ALIGN_START);
1207       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1208       gtk_grid_attach (GTK_GRID (grid), label, 0, i, 1, 1);
1209
1210       mark_child_property (spec);
1211       prop_edit = property_widget (object, spec, can_modify);
1212       gtk_grid_attach (GTK_GRID (grid), prop_edit, 1, i, 1, 1);
1213
1214       /* set initial value */
1215       gtk_widget_child_notify (GTK_WIDGET (object), spec->name);
1216
1217       ++i;
1218     }
1219
1220   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1221   gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
1222
1223   sw = gtk_scrolled_window_new (NULL, NULL);
1224   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1225                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1226
1227   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1228
1229   g_free (specs);
1230
1231   return sw;
1232 }
1233
1234 static void
1235 child_properties (GtkWidget *button,
1236                   GObject   *object)
1237 {
1238   create_prop_editor (object, 0);
1239 }
1240
1241 static GtkWidget *
1242 children_from_object (GObject *object)
1243 {
1244   GList *children, *c;
1245   GtkWidget *grid, *label, *prop_edit, *button, *vbox, *sw;
1246   gchar *str;
1247   gint i;
1248
1249   if (!GTK_IS_CONTAINER (object))
1250     return NULL;
1251
1252   children = gtk_container_get_children (GTK_CONTAINER (object));
1253
1254   grid = gtk_grid_new ();
1255   gtk_grid_set_column_spacing (GTK_GRID (grid), 10);
1256   gtk_grid_set_row_spacing (GTK_GRID (grid), 3);
1257
1258   for (c = children, i = 0; c; c = c->next, i++)
1259     {
1260       object = c->data;
1261
1262       label = gtk_label_new ("Child");
1263       gtk_widget_set_halign (label, GTK_ALIGN_START);
1264       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1265       gtk_grid_attach (GTK_GRID (grid), label, 0, i, 1, 1);
1266
1267       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
1268
1269       str = object_label (object, NULL);
1270       label = gtk_label_new (str);
1271       g_free (str);
1272       button = gtk_button_new_with_label ("Properties");
1273       g_signal_connect (button, "clicked",
1274                         G_CALLBACK (child_properties),
1275                         object);
1276
1277       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1278       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1279
1280       gtk_grid_attach (GTK_GRID (grid), prop_edit, 1, i, 1, 1);
1281     }
1282
1283   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1284   gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
1285
1286   sw = gtk_scrolled_window_new (NULL, NULL);
1287   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1288                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1289
1290   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1291
1292   g_list_free (children);
1293
1294   return sw;
1295 }
1296
1297 static GtkWidget *
1298 cells_from_object (GObject *object)
1299 {
1300   GList *cells, *c;
1301   GtkWidget *grid, *label, *prop_edit, *button, *vbox, *sw;
1302   gchar *str;
1303   gint i;
1304
1305   if (!GTK_IS_CELL_LAYOUT (object))
1306     return NULL;
1307
1308   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (object));
1309
1310   grid = gtk_grid_new ();
1311   gtk_grid_set_column_spacing (GTK_GRID (grid), 10);
1312   gtk_grid_set_row_spacing (GTK_GRID (grid), 3);
1313
1314   for (c = cells, i = 0; c; c = c->next, i++)
1315     {
1316       object = c->data;
1317
1318       label = gtk_label_new ("Cell");
1319       gtk_widget_set_halign (label, GTK_ALIGN_START);
1320       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1321       gtk_grid_attach (GTK_GRID (grid), label, 0, i, 1, 1);
1322
1323       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
1324
1325       str = object_label (object, NULL);
1326       label = gtk_label_new (str);
1327       g_free (str);
1328       button = gtk_button_new_with_label ("Properties");
1329       g_signal_connect (button, "clicked",
1330                         G_CALLBACK (child_properties),
1331                         object);
1332
1333       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1334       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1335
1336       gtk_grid_attach (GTK_GRID (grid), prop_edit, 1, i, 1, 1);
1337     }
1338
1339   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1340   gtk_box_pack_start (GTK_BOX (vbox), grid, FALSE, FALSE, 0);
1341
1342   sw = gtk_scrolled_window_new (NULL, NULL);
1343   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1344                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1345
1346   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1347
1348   g_list_free (cells);
1349
1350   return sw;
1351 }
1352
1353 /* Pass zero for type if you want all properties */
1354 GtkWidget*
1355 create_prop_editor (GObject   *object,
1356                     GType      type)
1357 {
1358   GtkWidget *win;
1359   GtkWidget *notebook;
1360   GtkWidget *properties;
1361   GtkWidget *label;
1362   gchar *title;
1363   GType *ifaces;
1364   guint n_ifaces;
1365
1366   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
1367     {
1368       gtk_window_present (GTK_WINDOW (win));
1369       return win;
1370     }
1371
1372   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1373   if (GTK_IS_WIDGET (object))
1374     gtk_window_set_screen (GTK_WINDOW (win),
1375                            gtk_widget_get_screen (GTK_WIDGET (object)));
1376
1377   /* hold a weak ref to the object we're editing */
1378   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
1379   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
1380
1381   if (type == 0)
1382     {
1383       notebook = gtk_notebook_new ();
1384       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
1385
1386       gtk_container_add (GTK_CONTAINER (win), notebook);
1387
1388       type = G_TYPE_FROM_INSTANCE (object);
1389
1390       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
1391       gtk_window_set_title (GTK_WINDOW (win), title);
1392       g_free (title);
1393
1394       while (type)
1395         {
1396           properties = properties_from_type (object, type);
1397           if (properties)
1398             {
1399               label = gtk_label_new (g_type_name (type));
1400               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1401                                         properties, label);
1402             }
1403
1404           type = g_type_parent (type);
1405         }
1406
1407       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
1408       while (n_ifaces--)
1409         {
1410           properties = properties_from_type (object, ifaces[n_ifaces]);
1411           if (properties)
1412             {
1413               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
1414               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1415                                         properties, label);
1416             }
1417         }
1418
1419       g_free (ifaces);
1420
1421       properties = child_properties_from_object (object);
1422       if (properties)
1423         {
1424           label = gtk_label_new ("Child properties");
1425           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1426                                     properties, label);
1427         }
1428
1429       properties = children_from_object (object);
1430       if (properties)
1431         {
1432           label = gtk_label_new ("Children");
1433           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1434                                     properties, label);
1435         }
1436
1437       properties = cells_from_object (object);
1438       if (properties)
1439         {
1440           label = gtk_label_new ("Cell renderers");
1441           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1442                                     properties, label);
1443         }
1444     }
1445   else
1446     {
1447       properties = properties_from_type (object, type);
1448       gtk_container_add (GTK_CONTAINER (win), properties);
1449       title = g_strdup_printf ("Properties of %s", g_type_name (type));
1450       gtk_window_set_title (GTK_WINDOW (win), title);
1451       g_free (title);
1452     }
1453
1454   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
1455
1456   gtk_widget_show_all (win);
1457
1458   return win;
1459 }
1460