]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
Use gtk_box_new() instead gtk_[v|h]box_new()
[~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) adj->value, NULL);
163     }
164   else
165     g_object_set (p->obj, p->spec->name, (int) adj->value, 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)adj->value)
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) adj->value, NULL);
215     }
216   else
217     g_object_set (p->obj, p->spec->name, (guint) adj->value, 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)adj->value)
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) adj->value, NULL);
251     }
252   else
253     g_object_set (p->obj, p->spec->name, (float) adj->value, 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) adj->value)
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) adj->value, NULL);
287     }
288   else
289     g_object_set (p->obj, p->spec->name, (double) adj->value, 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) != adj->value)
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   GdkColor color;
668
669   gtk_color_button_get_color (cb, &color);
670
671   if (is_child_property (p->spec))
672     {
673       GtkWidget *widget = GTK_WIDGET (p->obj);
674       GtkWidget *parent = gtk_widget_get_parent (widget);
675
676       gtk_container_child_set (GTK_CONTAINER (parent),
677                                widget, p->spec->name, &color, NULL);
678     }
679   else
680     g_object_set (p->obj, p->spec->name, &color, NULL);
681 }
682
683 static void
684 color_changed (GObject *object, GParamSpec *pspec, gpointer data)
685 {
686   GtkColorButton *cb = GTK_COLOR_BUTTON (data);
687   GValue val = { 0, };
688   GdkColor *color;
689   GdkColor cb_color;
690
691   g_value_init (&val, GDK_TYPE_COLOR);
692   get_property_value (object, pspec, &val);
693
694   color = g_value_get_boxed (&val);
695   gtk_color_button_get_color (cb, &cb_color);
696
697   if (color != NULL && !gdk_color_equal (color, &cb_color))
698     {
699       block_controller (G_OBJECT (cb));
700       gtk_color_button_set_color (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, FALSE, 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, FALSE, 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", 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   else if (type == G_TYPE_PARAM_BOXED &&
918            G_PARAM_SPEC_VALUE_TYPE (spec) == GDK_TYPE_COLOR)
919     {
920       prop_edit = gtk_color_button_new ();
921
922       g_object_connect_property (object, spec,
923                                  G_CALLBACK (color_changed),
924                                  prop_edit, G_OBJECT (prop_edit));
925
926       if (can_modify)
927         connect_controller (G_OBJECT (prop_edit), "color-set",
928                             object, spec, G_CALLBACK (color_modified));
929     }
930   else
931     {  
932       msg = g_strdup_printf ("uneditable property type: %s",
933                              g_type_name (G_PARAM_SPEC_TYPE (spec)));
934       prop_edit = gtk_label_new (msg);            
935       g_free (msg);
936       gtk_misc_set_alignment (GTK_MISC (prop_edit), 0.0, 0.5);
937     }
938   
939   return prop_edit;
940 }
941
942 static GtkWidget *
943 properties_from_type (GObject *object,
944                       GType    type)
945 {
946   GtkWidget *prop_edit;
947   GtkWidget *label;
948   GtkWidget *sw;
949   GtkWidget *vbox;
950   GtkWidget *table;
951   GParamSpec **specs;
952   guint n_specs;
953   int i;
954
955   if (G_TYPE_IS_INTERFACE (type))
956     {
957       gpointer vtable = g_type_default_interface_peek (type);
958       specs = g_object_interface_list_properties (vtable, &n_specs);
959     }
960   else
961     {
962       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
963       specs = g_object_class_list_properties (class, &n_specs);
964     }
965         
966   if (n_specs == 0) {
967     g_free (specs);
968     return NULL;
969   }
970   
971   table = gtk_table_new (n_specs, 2, FALSE);
972   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
973   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
974
975   i = 0;
976   while (i < n_specs)
977     {
978       GParamSpec *spec = specs[i];
979       gboolean can_modify;
980       
981       prop_edit = NULL;
982
983       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
984                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
985       
986       if ((spec->flags & G_PARAM_READABLE) == 0)
987         {
988           /* can't display unreadable properties */
989           ++i;
990           continue;
991         }
992       
993       if (spec->owner_type != type)
994         {
995           /* we're only interested in params of type */
996           ++i;
997           continue;
998         }
999
1000       label = gtk_label_new (g_param_spec_get_nick (spec));
1001       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1002       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1003       
1004       prop_edit = property_widget (object, spec, can_modify);
1005       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1006
1007       if (prop_edit)
1008         {
1009           if (!can_modify)
1010             gtk_widget_set_sensitive (prop_edit, FALSE);
1011
1012           if (g_param_spec_get_blurb (spec))
1013             gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
1014
1015           /* set initial value */
1016           g_object_notify (object, spec->name);
1017         }
1018       
1019       ++i;
1020     }
1021
1022
1023   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
1024   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1025
1026   sw = gtk_scrolled_window_new (NULL, NULL);
1027   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1028                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1029   
1030   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1031
1032   g_free (specs);
1033
1034   return sw;
1035 }
1036
1037 static GtkWidget *
1038 child_properties_from_object (GObject *object)
1039 {
1040   GtkWidget *prop_edit;
1041   GtkWidget *label;
1042   GtkWidget *sw;
1043   GtkWidget *vbox;
1044   GtkWidget *table;
1045   GtkWidget *parent;
1046   GParamSpec **specs;
1047   guint n_specs;
1048   gint i;
1049
1050   if (!GTK_IS_WIDGET (object))
1051     return NULL;
1052
1053   parent = gtk_widget_get_parent (GTK_WIDGET (object));
1054
1055   if (!parent)
1056     return NULL;
1057
1058   specs = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &n_specs);
1059
1060   table = gtk_table_new (n_specs, 2, FALSE);
1061   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1062   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1063
1064   i = 0;
1065   while (i < n_specs)
1066     {
1067       GParamSpec *spec = specs[i];
1068       gboolean can_modify;
1069       
1070       prop_edit = NULL;
1071
1072       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1073                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1074       
1075       if ((spec->flags & G_PARAM_READABLE) == 0)
1076         {
1077           /* can't display unreadable properties */
1078           ++i;
1079           continue;
1080         }
1081       
1082       label = gtk_label_new (g_param_spec_get_nick (spec));
1083       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1084       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1085       
1086       mark_child_property (spec);
1087       prop_edit = property_widget (object, spec, can_modify);
1088       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1089
1090       if (prop_edit)
1091         {
1092           if (!can_modify)
1093             gtk_widget_set_sensitive (prop_edit, FALSE);
1094
1095           if (g_param_spec_get_blurb (spec))
1096             gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
1097
1098           /* set initial value */
1099           gtk_widget_child_notify (GTK_WIDGET (object), spec->name);
1100         }
1101       
1102       ++i;
1103     }
1104
1105   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
1106   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1107
1108   sw = gtk_scrolled_window_new (NULL, NULL);
1109   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1110                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1111   
1112   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1113
1114   g_free (specs);
1115
1116   return sw;
1117 }
1118
1119 static void
1120 child_properties (GtkWidget *button, 
1121                   GObject   *object)
1122 {
1123   create_prop_editor (object, 0);
1124 }
1125
1126 static GtkWidget *
1127 children_from_object (GObject *object)
1128 {
1129   GList *children, *c;
1130   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1131   gchar *str;
1132   gint i;
1133
1134   if (!GTK_IS_CONTAINER (object))
1135     return NULL;
1136
1137   children = gtk_container_get_children (GTK_CONTAINER (object));
1138
1139   table = gtk_table_new (g_list_length (children), 2, FALSE);
1140   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1141   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1142  
1143   for (c = children, i = 0; c; c = c->next, i++)
1144     {
1145       object = c->data;
1146
1147       label = gtk_label_new ("Child");
1148       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1149       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1150
1151       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 5);
1152
1153       str = object_label (object, NULL);
1154       label = gtk_label_new (str);
1155       g_free (str);
1156       button = gtk_button_new_with_label ("Properties");
1157       g_signal_connect (button, "clicked",
1158                         G_CALLBACK (child_properties),
1159                         object);
1160
1161       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1162       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1163
1164       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1165     }
1166
1167   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
1168   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1169
1170   sw = gtk_scrolled_window_new (NULL, NULL);
1171   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1172                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1173   
1174   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1175
1176   g_list_free (children);
1177
1178   return sw;
1179 }
1180
1181 static GtkWidget *
1182 cells_from_object (GObject *object)
1183 {
1184   GList *cells, *c;
1185   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1186   gchar *str;
1187   gint i;
1188
1189   if (!GTK_IS_CELL_LAYOUT (object))
1190     return NULL;
1191
1192   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (object));
1193
1194   table = gtk_table_new (g_list_length (cells), 2, FALSE);
1195   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1196   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1197  
1198   for (c = cells, i = 0; c; c = c->next, i++)
1199     {
1200       object = c->data;
1201
1202       label = gtk_label_new ("Cell");
1203       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1204       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1205
1206       prop_edit = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 5);
1207
1208       str = object_label (object, NULL);
1209       label = gtk_label_new (str);
1210       g_free (str);
1211       button = gtk_button_new_with_label ("Properties");
1212       g_signal_connect (button, "clicked",
1213                         G_CALLBACK (child_properties),
1214                         object);
1215
1216       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1217       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1218
1219       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1220     }
1221
1222   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
1223   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1224
1225   sw = gtk_scrolled_window_new (NULL, NULL);
1226   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1227                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1228   
1229   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1230
1231   g_list_free (cells);
1232
1233   return sw;
1234 }
1235
1236 /* Pass zero for type if you want all properties */
1237 GtkWidget*
1238 create_prop_editor (GObject   *object,
1239                     GType      type)
1240 {
1241   GtkWidget *win;
1242   GtkWidget *notebook;
1243   GtkWidget *properties;
1244   GtkWidget *label;
1245   gchar *title;
1246   GType *ifaces;
1247   guint n_ifaces;
1248   
1249   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
1250     {
1251       gtk_window_present (GTK_WINDOW (win));
1252       return win;
1253     }
1254
1255   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1256   if (GTK_IS_WIDGET (object))
1257     gtk_window_set_screen (GTK_WINDOW (win),
1258                            gtk_widget_get_screen (GTK_WIDGET (object)));
1259
1260   /* hold a weak ref to the object we're editing */
1261   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
1262   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
1263
1264   if (type == 0)
1265     {
1266       notebook = gtk_notebook_new ();
1267       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
1268       
1269       gtk_container_add (GTK_CONTAINER (win), notebook);
1270       
1271       type = G_TYPE_FROM_INSTANCE (object);
1272
1273       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
1274       gtk_window_set_title (GTK_WINDOW (win), title);
1275       g_free (title);
1276       
1277       while (type)
1278         {
1279           properties = properties_from_type (object, type);
1280           if (properties)
1281             {
1282               label = gtk_label_new (g_type_name (type));
1283               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1284                                         properties, label);
1285             }
1286           
1287           type = g_type_parent (type);
1288         }
1289
1290       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
1291       while (n_ifaces--)
1292         {
1293           properties = properties_from_type (object, ifaces[n_ifaces]);
1294           if (properties)
1295             {
1296               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
1297               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1298                                         properties, label);
1299             }
1300         }
1301
1302       g_free (ifaces);
1303
1304       properties = child_properties_from_object (object);
1305       if (properties)
1306         {
1307           label = gtk_label_new ("Child properties");
1308           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1309                                     properties, label);
1310         }
1311
1312       properties = children_from_object (object);
1313       if (properties)
1314         {
1315           label = gtk_label_new ("Children");
1316           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1317                                     properties, label);
1318         }
1319
1320       properties = cells_from_object (object);
1321       if (properties)
1322         {
1323           label = gtk_label_new ("Cell renderers");
1324           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1325                                     properties, label);
1326         }
1327     }
1328   else
1329     {
1330       properties = properties_from_type (object, type);
1331       gtk_container_add (GTK_CONTAINER (win), properties);
1332       title = g_strdup_printf ("Properties of %s", g_type_name (type));
1333       gtk_window_set_title (GTK_WINDOW (win), title);
1334       g_free (title);
1335     }
1336   
1337   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
1338   
1339   gtk_widget_show_all (win);
1340
1341   return win;
1342 }
1343