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