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