]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
bgo#514843 - [filechooser] Deal with corrupted .gtk-bookmarks gracefully
[~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 = { 0, };
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 = { 0, };
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 = { 0, };
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 = { 0, };
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 = { 0, };
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 = { 0, };
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 = { 0, };
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 = { 0, };
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 = { 0, };
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 color_modified (GtkColorButton *cb, gpointer data)
665 {
666   ObjectProperty *p = data;
667   GdkRGBA rgba;
668
669   gtk_color_button_get_rgba (cb, &rgba);
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, &rgba, NULL);
678     }
679   else
680     g_object_set (p->obj, p->spec->name, &rgba, NULL);
681 }
682
683 static void
684 color_changed (GObject *object, GParamSpec *pspec, gpointer data)
685 {
686   GtkColorButton *cb = GTK_COLOR_BUTTON (data);
687   GValue val = { 0, };
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 GtkWidget *
708 property_widget (GObject    *object,
709                  GParamSpec *spec,
710                  gboolean    can_modify)
711 {
712   GtkWidget *prop_edit;
713   GtkAdjustment *adj;
714   gchar *msg;
715   GType type = G_PARAM_SPEC_TYPE (spec);
716
717   if (type == G_TYPE_PARAM_INT)
718     {
719       adj = gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
720                                 G_PARAM_SPEC_INT (spec)->minimum,
721                                 G_PARAM_SPEC_INT (spec)->maximum,
722                                 1,
723                                 MAX ((G_PARAM_SPEC_INT (spec)->maximum - G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
724                                 0.0);
725
726       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
727
728       g_object_connect_property (object, spec,
729                                  G_CALLBACK (int_changed),
730                                  adj, G_OBJECT (adj));
731
732       if (can_modify)
733         connect_controller (G_OBJECT (adj), "value_changed",
734                             object, spec, G_CALLBACK (int_modified));
735     }
736   else if (type == G_TYPE_PARAM_UINT)
737     {
738       adj = gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
739                                 G_PARAM_SPEC_UINT (spec)->minimum,
740                                 G_PARAM_SPEC_UINT (spec)->maximum,
741                                 1,
742                                 MAX ((G_PARAM_SPEC_UINT (spec)->maximum - G_PARAM_SPEC_UINT (spec)->minimum) / 10, 1),
743                                 0.0);
744
745       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
746
747       g_object_connect_property (object, spec,
748                                  G_CALLBACK (uint_changed),
749                                  adj, G_OBJECT (adj));
750
751       if (can_modify)
752         connect_controller (G_OBJECT (adj), "value_changed",
753                             object, spec, G_CALLBACK (uint_modified));
754     }
755   else if (type == G_TYPE_PARAM_FLOAT)
756     {
757       adj = gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
758                                 G_PARAM_SPEC_FLOAT (spec)->minimum,
759                                 G_PARAM_SPEC_FLOAT (spec)->maximum,
760                                 0.1,
761                                 MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum - G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
762                                 0.0);
763
764       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
765
766       g_object_connect_property (object, spec,
767                                  G_CALLBACK (float_changed),
768                                  adj, G_OBJECT (adj));
769
770       if (can_modify)
771         connect_controller (G_OBJECT (adj), "value_changed",
772                             object, spec, G_CALLBACK (float_modified));
773     }
774   else if (type == G_TYPE_PARAM_DOUBLE)
775     {
776       adj = gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
777                                 G_PARAM_SPEC_DOUBLE (spec)->minimum,
778                                 G_PARAM_SPEC_DOUBLE (spec)->maximum,
779                                 0.1,
780                                 MAX ((G_PARAM_SPEC_DOUBLE (spec)->maximum - G_PARAM_SPEC_DOUBLE (spec)->minimum) / 10, 0.1),
781                                 0.0);
782
783       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
784
785       g_object_connect_property (object, spec,
786                                  G_CALLBACK (double_changed),
787                                  adj, G_OBJECT (adj));
788
789       if (can_modify)
790         connect_controller (G_OBJECT (adj), "value_changed",
791                             object, spec, G_CALLBACK (double_modified));
792     }
793   else if (type == G_TYPE_PARAM_STRING)
794     {
795       prop_edit = gtk_entry_new ();
796
797       g_object_connect_property (object, spec,
798                                  G_CALLBACK (string_changed),
799                                  prop_edit, G_OBJECT (prop_edit));
800
801       if (can_modify)
802         connect_controller (G_OBJECT (prop_edit), "changed",
803                             object, spec, G_CALLBACK (string_modified));
804     }
805   else if (type == G_TYPE_PARAM_BOOLEAN)
806     {
807       prop_edit = gtk_toggle_button_new_with_label ("");
808
809       g_object_connect_property (object, spec,
810                                  G_CALLBACK (bool_changed),
811                                  prop_edit, G_OBJECT (prop_edit));
812
813       if (can_modify)
814         connect_controller (G_OBJECT (prop_edit), "toggled",
815                             object, spec, G_CALLBACK (bool_modified));
816     }
817   else if (type == G_TYPE_PARAM_ENUM)
818     {
819       {
820         GEnumClass *eclass;
821         gint j;
822
823         prop_edit = gtk_combo_box_text_new ();
824
825         eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
826
827         j = 0;
828         while (j < eclass->n_values)
829           {
830             gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (prop_edit),
831                                             eclass->values[j].value_name);
832             ++j;
833           }
834
835         g_type_class_unref (eclass);
836
837         g_object_connect_property (object, spec,
838                                    G_CALLBACK (enum_changed),
839                                    prop_edit, G_OBJECT (prop_edit));
840
841         if (can_modify)
842           connect_controller (G_OBJECT (prop_edit), "changed",
843                               object, spec, G_CALLBACK (enum_modified));
844       }
845     }
846   else if (type == G_TYPE_PARAM_FLAGS)
847     {
848       {
849         GFlagsClass *fclass;
850         gint j;
851
852         prop_edit = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
853
854         fclass = G_FLAGS_CLASS (g_type_class_ref (spec->value_type));
855
856         for (j = 0; j < fclass->n_values; j++)
857           {
858             GtkWidget *b;
859
860             b = gtk_check_button_new_with_label (fclass->values[j].value_name);
861             g_object_set_data (G_OBJECT (b), "index", GINT_TO_POINTER (j));
862             gtk_widget_show (b);
863             gtk_box_pack_start (GTK_BOX (prop_edit), b, FALSE, FALSE, 0);
864             if (can_modify)
865               connect_controller (G_OBJECT (b), "toggled",
866                                   object, spec, G_CALLBACK (flags_modified));
867           }
868
869         g_type_class_unref (fclass);
870
871         g_object_connect_property (object, spec,
872                                    G_CALLBACK (flags_changed),
873                                    prop_edit, G_OBJECT (prop_edit));
874       }
875     }
876   else if (type == G_TYPE_PARAM_UNICHAR)
877     {
878       prop_edit = gtk_entry_new ();
879       gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
880
881       g_object_connect_property (object, spec,
882                                  G_CALLBACK (unichar_changed),
883                                  prop_edit, G_OBJECT (prop_edit));
884
885       if (can_modify)
886         connect_controller (G_OBJECT (prop_edit), "changed",
887                             object, spec, G_CALLBACK (unichar_modified));
888     }
889   else if (type == G_TYPE_PARAM_POINTER)
890     {
891       prop_edit = gtk_label_new ("");
892
893       g_object_connect_property (object, spec,
894                                  G_CALLBACK (pointer_changed),
895                                  prop_edit, G_OBJECT (prop_edit));
896     }
897   else if (type == G_TYPE_PARAM_OBJECT)
898     {
899       GtkWidget *label, *button;
900
901       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
902
903       label = gtk_label_new ("");
904       button = gtk_button_new_with_label ("Properties");
905       g_object_set_data (G_OBJECT (button), "property-name", (gpointer) spec->name);
906       g_signal_connect (button, "clicked",
907                         G_CALLBACK (object_properties),
908                         object);
909
910       gtk_container_add (GTK_CONTAINER (prop_edit), label);
911       gtk_container_add (GTK_CONTAINER (prop_edit), button);
912
913       g_object_connect_property (object, spec,
914                                  G_CALLBACK (object_changed),
915                                  prop_edit, G_OBJECT (label));
916
917       /* The Properties button is not really modifying, anyway */
918       can_modify = TRUE;
919     }
920   else if (type == G_TYPE_PARAM_BOXED &&
921            G_PARAM_SPEC_VALUE_TYPE (spec) == GDK_TYPE_COLOR)
922     {
923       prop_edit = gtk_color_button_new ();
924
925       g_object_connect_property (object, spec,
926                                  G_CALLBACK (color_changed),
927                                  prop_edit, G_OBJECT (prop_edit));
928
929       if (can_modify)
930         connect_controller (G_OBJECT (prop_edit), "color-set",
931                             object, spec, G_CALLBACK (color_modified));
932     }
933   else
934     {
935       msg = g_strdup_printf ("uneditable property type: %s",
936                              g_type_name (G_PARAM_SPEC_TYPE (spec)));
937       prop_edit = gtk_label_new (msg);
938       g_free (msg);
939       gtk_widget_set_halign (prop_edit, GTK_ALIGN_START);
940       gtk_widget_set_valign (prop_edit, GTK_ALIGN_CENTER);
941     }
942
943   if (!can_modify)
944     gtk_widget_set_sensitive (prop_edit, FALSE);
945
946   if (g_param_spec_get_blurb (spec))
947     gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
948
949   return prop_edit;
950 }
951
952 static GtkWidget *
953 properties_from_type (GObject *object,
954                       GType    type)
955 {
956   GtkWidget *prop_edit;
957   GtkWidget *label;
958   GtkWidget *sw;
959   GtkWidget *vbox;
960   GtkWidget *table;
961   GParamSpec **specs;
962   guint n_specs;
963   int i;
964
965   if (G_TYPE_IS_INTERFACE (type))
966     {
967       gpointer vtable = g_type_default_interface_peek (type);
968       specs = g_object_interface_list_properties (vtable, &n_specs);
969     }
970   else
971     {
972       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
973       specs = g_object_class_list_properties (class, &n_specs);
974     }
975
976   if (n_specs == 0) {
977     g_free (specs);
978     return NULL;
979   }
980
981   table = gtk_table_new (n_specs, 2, FALSE);
982   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
983   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
984
985   i = 0;
986   while (i < n_specs)
987     {
988       GParamSpec *spec = specs[i];
989       gboolean can_modify;
990
991       prop_edit = NULL;
992
993       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
994                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
995
996       if ((spec->flags & G_PARAM_READABLE) == 0)
997         {
998           /* can't display unreadable properties */
999           ++i;
1000           continue;
1001         }
1002
1003       if (spec->owner_type != type)
1004         {
1005           /* we're only interested in params of type */
1006           ++i;
1007           continue;
1008         }
1009
1010       label = gtk_label_new (g_param_spec_get_nick (spec));
1011       gtk_widget_set_halign (label, GTK_ALIGN_START);
1012       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1013       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1014
1015       prop_edit = property_widget (object, spec, can_modify);
1016       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1017
1018       /* set initial value */
1019       g_object_notify (object, spec->name);
1020
1021       ++i;
1022     }
1023
1024
1025   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1026   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1027
1028   sw = gtk_scrolled_window_new (NULL, NULL);
1029   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1030                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1031
1032   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1033
1034   g_free (specs);
1035
1036   return sw;
1037 }
1038
1039 static GtkWidget *
1040 child_properties_from_object (GObject *object)
1041 {
1042   GtkWidget *prop_edit;
1043   GtkWidget *label;
1044   GtkWidget *sw;
1045   GtkWidget *vbox;
1046   GtkWidget *table;
1047   GtkWidget *parent;
1048   GParamSpec **specs;
1049   guint n_specs;
1050   gint i;
1051
1052   if (!GTK_IS_WIDGET (object))
1053     return NULL;
1054
1055   parent = gtk_widget_get_parent (GTK_WIDGET (object));
1056
1057   if (!parent)
1058     return NULL;
1059
1060   specs = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &n_specs);
1061
1062   table = gtk_table_new (n_specs, 2, FALSE);
1063   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1064   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1065
1066   i = 0;
1067   while (i < n_specs)
1068     {
1069       GParamSpec *spec = specs[i];
1070       gboolean can_modify;
1071
1072       prop_edit = NULL;
1073
1074       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1075                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1076
1077       if ((spec->flags & G_PARAM_READABLE) == 0)
1078         {
1079           /* can't display unreadable properties */
1080           ++i;
1081           continue;
1082         }
1083
1084       label = gtk_label_new (g_param_spec_get_nick (spec));
1085       gtk_widget_set_halign (label, GTK_ALIGN_START);
1086       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1087       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1088
1089       mark_child_property (spec);
1090       prop_edit = property_widget (object, spec, can_modify);
1091       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1092
1093       /* set initial value */
1094       gtk_widget_child_notify (GTK_WIDGET (object), spec->name);
1095
1096       ++i;
1097     }
1098
1099   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1100   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1101
1102   sw = gtk_scrolled_window_new (NULL, NULL);
1103   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1104                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1105
1106   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1107
1108   g_free (specs);
1109
1110   return sw;
1111 }
1112
1113 static void
1114 child_properties (GtkWidget *button,
1115                   GObject   *object)
1116 {
1117   create_prop_editor (object, 0);
1118 }
1119
1120 static GtkWidget *
1121 children_from_object (GObject *object)
1122 {
1123   GList *children, *c;
1124   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1125   gchar *str;
1126   gint i;
1127
1128   if (!GTK_IS_CONTAINER (object))
1129     return NULL;
1130
1131   children = gtk_container_get_children (GTK_CONTAINER (object));
1132
1133   table = gtk_table_new (g_list_length (children), 2, FALSE);
1134   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1135   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1136
1137   for (c = children, i = 0; c; c = c->next, i++)
1138     {
1139       object = c->data;
1140
1141       label = gtk_label_new ("Child");
1142       gtk_widget_set_halign (label, GTK_ALIGN_START);
1143       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1144       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1145
1146       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
1147
1148       str = object_label (object, NULL);
1149       label = gtk_label_new (str);
1150       g_free (str);
1151       button = gtk_button_new_with_label ("Properties");
1152       g_signal_connect (button, "clicked",
1153                         G_CALLBACK (child_properties),
1154                         object);
1155
1156       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1157       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1158
1159       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1160     }
1161
1162   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1163   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1164
1165   sw = gtk_scrolled_window_new (NULL, NULL);
1166   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1167                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1168
1169   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1170
1171   g_list_free (children);
1172
1173   return sw;
1174 }
1175
1176 static GtkWidget *
1177 cells_from_object (GObject *object)
1178 {
1179   GList *cells, *c;
1180   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1181   gchar *str;
1182   gint i;
1183
1184   if (!GTK_IS_CELL_LAYOUT (object))
1185     return NULL;
1186
1187   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (object));
1188
1189   table = gtk_table_new (g_list_length (cells), 2, FALSE);
1190   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1191   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1192
1193   for (c = cells, i = 0; c; c = c->next, i++)
1194     {
1195       object = c->data;
1196
1197       label = gtk_label_new ("Cell");
1198       gtk_widget_set_halign (label, GTK_ALIGN_START);
1199       gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
1200       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1201
1202       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
1203
1204       str = object_label (object, NULL);
1205       label = gtk_label_new (str);
1206       g_free (str);
1207       button = gtk_button_new_with_label ("Properties");
1208       g_signal_connect (button, "clicked",
1209                         G_CALLBACK (child_properties),
1210                         object);
1211
1212       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1213       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1214
1215       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1216     }
1217
1218   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1219   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1220
1221   sw = gtk_scrolled_window_new (NULL, NULL);
1222   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1223                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1224
1225   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1226
1227   g_list_free (cells);
1228
1229   return sw;
1230 }
1231
1232 /* Pass zero for type if you want all properties */
1233 GtkWidget*
1234 create_prop_editor (GObject   *object,
1235                     GType      type)
1236 {
1237   GtkWidget *win;
1238   GtkWidget *notebook;
1239   GtkWidget *properties;
1240   GtkWidget *label;
1241   gchar *title;
1242   GType *ifaces;
1243   guint n_ifaces;
1244
1245   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
1246     {
1247       gtk_window_present (GTK_WINDOW (win));
1248       return win;
1249     }
1250
1251   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1252   if (GTK_IS_WIDGET (object))
1253     gtk_window_set_screen (GTK_WINDOW (win),
1254                            gtk_widget_get_screen (GTK_WIDGET (object)));
1255
1256   /* hold a weak ref to the object we're editing */
1257   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
1258   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
1259
1260   if (type == 0)
1261     {
1262       notebook = gtk_notebook_new ();
1263       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
1264
1265       gtk_container_add (GTK_CONTAINER (win), notebook);
1266
1267       type = G_TYPE_FROM_INSTANCE (object);
1268
1269       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
1270       gtk_window_set_title (GTK_WINDOW (win), title);
1271       g_free (title);
1272
1273       while (type)
1274         {
1275           properties = properties_from_type (object, type);
1276           if (properties)
1277             {
1278               label = gtk_label_new (g_type_name (type));
1279               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1280                                         properties, label);
1281             }
1282
1283           type = g_type_parent (type);
1284         }
1285
1286       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
1287       while (n_ifaces--)
1288         {
1289           properties = properties_from_type (object, ifaces[n_ifaces]);
1290           if (properties)
1291             {
1292               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
1293               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1294                                         properties, label);
1295             }
1296         }
1297
1298       g_free (ifaces);
1299
1300       properties = child_properties_from_object (object);
1301       if (properties)
1302         {
1303           label = gtk_label_new ("Child properties");
1304           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1305                                     properties, label);
1306         }
1307
1308       properties = children_from_object (object);
1309       if (properties)
1310         {
1311           label = gtk_label_new ("Children");
1312           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1313                                     properties, label);
1314         }
1315
1316       properties = cells_from_object (object);
1317       if (properties)
1318         {
1319           label = gtk_label_new ("Cell renderers");
1320           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1321                                     properties, label);
1322         }
1323     }
1324   else
1325     {
1326       properties = properties_from_type (object, type);
1327       gtk_container_add (GTK_CONTAINER (win), properties);
1328       title = g_strdup_printf ("Properties of %s", g_type_name (type));
1329       gtk_window_set_title (GTK_WINDOW (win), title);
1330       g_free (title);
1331     }
1332
1333   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
1334
1335   gtk_widget_show_all (win);
1336
1337   return win;
1338 }
1339