]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
Use GtkComboBox in the property editor code
[~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   guint 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   gint 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), 
368                                widget, p->spec->name, (int) tb->active, NULL);
369     }
370   else
371     g_object_set (p->obj, p->spec->name, (int) tb->active, NULL);
372 }
373
374 static void
375 bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
376 {
377   GtkToggleButton *tb = GTK_TOGGLE_BUTTON (data);
378   GValue val = { 0, };  
379   
380   g_value_init (&val, G_TYPE_BOOLEAN);
381   get_property_value (object, pspec, &val);
382
383   if (g_value_get_boolean (&val) != tb->active)
384     {
385       block_controller (G_OBJECT (tb));
386       gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
387       unblock_controller (G_OBJECT (tb));
388     }
389
390   gtk_label_set_text (GTK_LABEL (GTK_BIN (tb)->child), g_value_get_boolean (&val) ?
391                       "TRUE" : "FALSE");
392   
393   g_value_unset (&val);
394 }
395
396
397 static void
398 enum_modified (GtkComboBox *cb, gpointer data)
399 {
400   ObjectProperty *p = data;
401   gint i;
402   GEnumClass *eclass;
403   
404   eclass = G_ENUM_CLASS (g_type_class_peek (p->spec->value_type));
405
406   i = gtk_combo_box_get_active (cb);
407
408
409   if (is_child_property (p->spec))
410     {
411       GtkWidget *widget = GTK_WIDGET (p->obj);
412       GtkWidget *parent = gtk_widget_get_parent (widget);
413
414       gtk_container_child_set (GTK_CONTAINER (parent), 
415                                widget, p->spec->name, eclass->values[i].value, NULL);
416     }
417   else
418     g_object_set (p->obj, p->spec->name, eclass->values[i].value, NULL);
419 }
420
421 static void
422 enum_changed (GObject *object, GParamSpec *pspec, gpointer data)
423 {
424   GtkComboBox *cb = GTK_COMBO_BOX (data);
425   GValue val = { 0, };  
426   GEnumClass *eclass;
427   gint i;
428
429   eclass = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
430   
431   g_value_init (&val, pspec->value_type);
432   get_property_value (object, pspec, &val);
433
434   i = 0;
435   while (i < eclass->n_values)
436     {
437       if (eclass->values[i].value == g_value_get_enum (&val))
438         break;
439       ++i;
440     }
441   
442   if (gtk_combo_box_get_active (cb) != i)
443     {
444       block_controller (G_OBJECT (cb));
445       gtk_combo_box_set_active (cb, i);
446       unblock_controller (G_OBJECT (cb));
447     }
448   
449   g_value_unset (&val);
450
451 }
452
453 static void
454 flags_modified (GtkCheckButton *button, gpointer data)
455 {
456   ObjectProperty *p = data;
457   gboolean active;
458   GFlagsClass *fclass;
459   guint flags;
460   gint i;
461   
462   fclass = G_FLAGS_CLASS (g_type_class_peek (p->spec->value_type));
463   
464   active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
465   i = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "index"));
466
467   if (is_child_property (p->spec))
468     {
469       GtkWidget *widget = GTK_WIDGET (p->obj);
470       GtkWidget *parent = gtk_widget_get_parent (widget);
471
472       gtk_container_child_get (GTK_CONTAINER (parent), 
473                                widget, p->spec->name, &flags, NULL);
474       if (active)
475         flags |= fclass->values[i].value;
476       else
477         flags &= ~fclass->values[i].value;
478
479       gtk_container_child_set (GTK_CONTAINER (parent), 
480                                widget, p->spec->name, flags, NULL);
481     }
482   else
483     {
484       g_object_get (p->obj, p->spec->name, &flags, NULL);
485
486       if (active)
487         flags |= fclass->values[i].value;
488       else
489         flags &= ~fclass->values[i].value;
490
491       g_object_set (p->obj, p->spec->name, flags, NULL);
492     }
493 }
494
495 static void
496 flags_changed (GObject *object, GParamSpec *pspec, gpointer data)
497 {
498   GList *children, *c;
499   GValue val = { 0, };  
500   GFlagsClass *fclass;
501   guint flags;
502   gint i;
503
504   fclass = G_FLAGS_CLASS (g_type_class_peek (pspec->value_type));
505   
506   g_value_init (&val, pspec->value_type);
507   get_property_value (object, pspec, &val);
508   flags = g_value_get_flags (&val);
509   g_value_unset (&val);
510
511   children = gtk_container_get_children (GTK_CONTAINER (data));
512
513   for (c = children, i = 0; c; c = c->next, i++)
514     {
515       block_controller (G_OBJECT (c->data));
516       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (c->data),
517                                     (fclass->values[i].value & flags) != 0);
518       unblock_controller (G_OBJECT (c->data));
519     }
520
521   g_list_free (children);
522 }
523
524 static gunichar
525 unichar_get_value (GtkEntry *entry)
526 {
527   const gchar *text = gtk_entry_get_text (entry);
528   
529   if (text[0])
530     return g_utf8_get_char (text);
531   else
532     return 0;
533 }
534
535 static void
536 unichar_modified (GtkEntry *entry, gpointer data)
537 {
538   ObjectProperty *p = data;
539   gunichar val = unichar_get_value (entry);
540
541   if (is_child_property (p->spec))
542     {
543       GtkWidget *widget = GTK_WIDGET (p->obj);
544       GtkWidget *parent = gtk_widget_get_parent (widget);
545
546       gtk_container_child_set (GTK_CONTAINER (parent), 
547                                widget, p->spec->name, val, NULL);
548     }
549   else
550     g_object_set (p->obj, p->spec->name, val, NULL);
551 }
552
553 static void
554 unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
555 {
556   GtkEntry *entry = GTK_ENTRY (data);
557   gunichar new_val;
558   gunichar old_val = unichar_get_value (entry);
559   GValue val = { 0, };
560   gchar buf[7];
561   gint len;
562   
563   g_value_init (&val, pspec->value_type);
564   get_property_value (object, pspec, &val);
565   new_val = (gunichar)g_value_get_uint (&val);
566
567   if (new_val != old_val)
568     {
569       if (!new_val)
570         len = 0;
571       else
572         len = g_unichar_to_utf8 (new_val, buf);
573       
574       buf[len] = '\0';
575       
576       block_controller (G_OBJECT (entry));
577       gtk_entry_set_text (entry, buf);
578       unblock_controller (G_OBJECT (entry));
579     }
580 }
581
582 static void
583 pointer_changed (GObject *object, GParamSpec *pspec, gpointer data)
584 {
585   GtkLabel *label = GTK_LABEL (data);
586   gchar *str;
587   gpointer ptr;
588   
589   g_object_get (object, pspec->name, &ptr, NULL);
590
591   str = g_strdup_printf ("Pointer: %p", ptr);
592   gtk_label_set_text (label, str);
593   g_free (str);
594 }
595
596 static gchar *
597 object_label (GObject *obj, GParamSpec *pspec)
598 {
599   const gchar *name;
600
601   if (obj)
602     name = g_type_name (G_TYPE_FROM_INSTANCE (obj));
603   else if (pspec)
604     name = g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec));
605   else
606     name = "unknown";
607   return g_strdup_printf ("Object: %p (%s)", obj, name);
608 }
609
610 static void
611 object_changed (GObject *object, GParamSpec *pspec, gpointer data)
612 {
613   GtkWidget *label, *button;
614   gchar *str;
615   GObject *obj;
616   
617   GList *children = gtk_container_get_children (GTK_CONTAINER (data)); 
618   label = GTK_WIDGET (children->data);
619   button = GTK_WIDGET (children->next->data);
620   g_object_get (object, pspec->name, &obj, NULL);
621   g_list_free (children);
622
623   str = object_label (obj, pspec);
624   
625   gtk_label_set_text (GTK_LABEL (label), str);
626   gtk_widget_set_sensitive (button, G_IS_OBJECT (obj));
627
628   if (obj)
629     g_object_unref (obj);
630
631   g_free (str);
632 }
633
634 static void
635 model_destroy (gpointer data)
636 {
637   g_object_steal_data (data, "model-object");
638   gtk_widget_destroy (data);
639 }
640
641 static void
642 window_destroy (gpointer data)
643 {
644   g_object_steal_data (data, "prop-editor-win");
645 }
646
647 static void
648 object_properties (GtkWidget *button, 
649                    GObject   *object)
650 {
651   gchar *name;
652   GObject *obj;
653
654   name = (gchar *) g_object_get_data (G_OBJECT (button), "property-name");
655   g_object_get (object, name, &obj, NULL);
656   if (G_IS_OBJECT (obj)) 
657     create_prop_editor (obj, 0);
658 }
659  
660 static void
661 color_modified (GtkColorButton *cb, gpointer data)
662 {
663   ObjectProperty *p = data;
664   GdkColor color;
665
666   gtk_color_button_get_color (cb, &color);
667
668   if (is_child_property (p->spec))
669     {
670       GtkWidget *widget = GTK_WIDGET (p->obj);
671       GtkWidget *parent = gtk_widget_get_parent (widget);
672
673       gtk_container_child_set (GTK_CONTAINER (parent),
674                                widget, p->spec->name, &color, NULL);
675     }
676   else
677     g_object_set (p->obj, p->spec->name, &color, NULL);
678 }
679
680 static void
681 color_changed (GObject *object, GParamSpec *pspec, gpointer data)
682 {
683   GtkColorButton *cb = GTK_COLOR_BUTTON (data);
684   GValue val = { 0, };
685   GdkColor *color;
686   GdkColor cb_color;
687
688   g_value_init (&val, GDK_TYPE_COLOR);
689   get_property_value (object, pspec, &val);
690
691   color = g_value_get_boxed (&val);
692   gtk_color_button_get_color (cb, &cb_color);
693
694   if (color != NULL && !gdk_color_equal (color, &cb_color))
695     {
696       block_controller (G_OBJECT (cb));
697       gtk_color_button_set_color (cb, color);
698       unblock_controller (G_OBJECT (cb));
699     }
700
701   g_value_unset (&val);
702 }
703
704 static GtkWidget *
705 property_widget (GObject    *object, 
706                  GParamSpec *spec, 
707                  gboolean    can_modify)
708 {
709   GtkWidget *prop_edit;
710   GtkAdjustment *adj;
711   gchar *msg;
712   GType type = G_PARAM_SPEC_TYPE (spec);
713
714   if (type == G_TYPE_PARAM_INT)
715     {
716       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
717                                                 G_PARAM_SPEC_INT (spec)->minimum,
718                                                 G_PARAM_SPEC_INT (spec)->maximum,
719                                                 1,
720                                                 MAX ((G_PARAM_SPEC_INT (spec)->maximum -
721                                                       G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
722                                                 0.0));
723       
724       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
725       
726       g_object_connect_property (object, spec, 
727                                  G_CALLBACK (int_changed), 
728                                  adj, G_OBJECT (adj));
729       
730       if (can_modify)
731         connect_controller (G_OBJECT (adj), "value_changed",
732                             object, spec, G_CALLBACK (int_modified));
733     }
734   else if (type == G_TYPE_PARAM_UINT)
735     {
736       adj = GTK_ADJUSTMENT (
737                             gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
738                                                 G_PARAM_SPEC_UINT (spec)->minimum,
739                                                 G_PARAM_SPEC_UINT (spec)->maximum,
740                                                 1,
741                                                 MAX ((G_PARAM_SPEC_UINT (spec)->maximum -
742                                                       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
758       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
759                                                 G_PARAM_SPEC_FLOAT (spec)->minimum,
760                                                 G_PARAM_SPEC_FLOAT (spec)->maximum,
761                                                 0.1,
762                                                 MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
763                                                       G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
764                                                 0.0));
765       
766       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
767       
768       g_object_connect_property (object, spec, 
769                                  G_CALLBACK (float_changed), 
770                                  adj, G_OBJECT (adj));
771       
772       if (can_modify)
773         connect_controller (G_OBJECT (adj), "value_changed",
774                             object, spec, G_CALLBACK (float_modified));
775     }
776   else if (type == G_TYPE_PARAM_DOUBLE)
777     {
778       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
779                                                 G_PARAM_SPEC_DOUBLE (spec)->minimum,
780                                                 G_PARAM_SPEC_DOUBLE (spec)->maximum,
781                                                 0.1,
782                                                 MAX ((G_PARAM_SPEC_DOUBLE (spec)->maximum -
783                                                       G_PARAM_SPEC_DOUBLE (spec)->minimum) / 10, 0.1),
784                                                 0.0));
785       
786       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
787       
788       g_object_connect_property (object, spec, 
789                                  G_CALLBACK (double_changed), 
790                                  adj, G_OBJECT (adj));
791       
792       if (can_modify)
793         connect_controller (G_OBJECT (adj), "value_changed",
794                             object, spec, G_CALLBACK (double_modified));
795     }
796   else if (type == G_TYPE_PARAM_STRING)
797     {
798       prop_edit = gtk_entry_new ();
799       
800       g_object_connect_property (object, spec, 
801                                  G_CALLBACK (string_changed),
802                                  prop_edit, G_OBJECT (prop_edit));
803       
804       if (can_modify)
805         connect_controller (G_OBJECT (prop_edit), "changed",
806                             object, spec, G_CALLBACK (string_modified));
807     }
808   else if (type == G_TYPE_PARAM_BOOLEAN)
809     {
810       prop_edit = gtk_toggle_button_new_with_label ("");
811       
812       g_object_connect_property (object, spec, 
813                                  G_CALLBACK (bool_changed),
814                                  prop_edit, G_OBJECT (prop_edit));
815       
816       if (can_modify)
817         connect_controller (G_OBJECT (prop_edit), "toggled",
818                             object, spec, G_CALLBACK (bool_modified));
819     }
820   else if (type == G_TYPE_PARAM_ENUM)
821     {
822       {
823         GEnumClass *eclass;
824         gint j;
825         
826         prop_edit = gtk_combo_box_new_text ();
827         
828         eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
829         
830         j = 0;
831         while (j < eclass->n_values)
832           {
833             gtk_combo_box_append_text (GTK_COMBO_BOX (prop_edit),
834                                        eclass->values[j].value_name);
835             ++j;
836           }
837         
838         g_type_class_unref (eclass);
839         
840         g_object_connect_property (object, spec,
841                                    G_CALLBACK (enum_changed),
842                                    prop_edit, G_OBJECT (prop_edit));
843         
844         if (can_modify)
845           connect_controller (G_OBJECT (prop_edit), "changed",
846                               object, spec, G_CALLBACK (enum_modified));
847       }
848     }
849   else if (type == G_TYPE_PARAM_FLAGS)
850     {
851       {
852         GFlagsClass *fclass;
853         gint j;
854         
855         prop_edit = gtk_vbox_new (FALSE, 0);
856         
857         fclass = G_FLAGS_CLASS (g_type_class_ref (spec->value_type));
858         
859         for (j = 0; j < fclass->n_values; j++)
860           {
861             GtkWidget *b;
862             
863             b = gtk_check_button_new_with_label (fclass->values[j].value_name);
864             g_object_set_data (G_OBJECT (b), "index", GINT_TO_POINTER (j));
865             gtk_widget_show (b);
866             gtk_box_pack_start (GTK_BOX (prop_edit), b, FALSE, FALSE, 0);
867             if (can_modify) 
868               connect_controller (G_OBJECT (b), "toggled",
869                                   object, spec, G_CALLBACK (flags_modified));
870           }
871         
872         g_type_class_unref (fclass);
873         
874         g_object_connect_property (object, spec,
875                                    G_CALLBACK (flags_changed),
876                                    prop_edit, G_OBJECT (prop_edit));
877       }
878     }
879   else if (type == G_TYPE_PARAM_UNICHAR)
880     {
881       prop_edit = gtk_entry_new ();
882       gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
883       
884       g_object_connect_property (object, spec,
885                                  G_CALLBACK (unichar_changed),
886                                  prop_edit, G_OBJECT (prop_edit));
887       
888       if (can_modify)
889         connect_controller (G_OBJECT (prop_edit), "changed",
890                             object, spec, G_CALLBACK (unichar_modified));
891     }
892   else if (type == G_TYPE_PARAM_POINTER)
893     {
894       prop_edit = gtk_label_new ("");
895       
896       g_object_connect_property (object, spec, 
897                                  G_CALLBACK (pointer_changed),
898                                  prop_edit, G_OBJECT (prop_edit));
899     }
900   else if (type == G_TYPE_PARAM_OBJECT)
901     {
902       GtkWidget *label, *button;
903
904       prop_edit = gtk_hbox_new (FALSE, 5);
905
906       label = gtk_label_new ("");
907       button = gtk_button_new_with_label ("Properties");
908       g_object_set_data (G_OBJECT (button), "property-name", spec->name);
909       g_signal_connect (button, "clicked", 
910                         G_CALLBACK (object_properties), 
911                         object);
912
913       gtk_container_add (GTK_CONTAINER (prop_edit), label);
914       gtk_container_add (GTK_CONTAINER (prop_edit), button);
915       
916       g_object_connect_property (object, spec,
917                                  G_CALLBACK (object_changed),
918                                  prop_edit, G_OBJECT (label));
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_misc_set_alignment (GTK_MISC (prop_edit), 0.0, 0.5);
940     }
941   
942   return prop_edit;
943 }
944
945 static GtkWidget *
946 properties_from_type (GObject *object,
947                       GType    type)
948 {
949   GtkWidget *prop_edit;
950   GtkWidget *label;
951   GtkWidget *sw;
952   GtkWidget *vbox;
953   GtkWidget *table;
954   GParamSpec **specs;
955   guint n_specs;
956   int i;
957
958   if (G_TYPE_IS_INTERFACE (type))
959     {
960       gpointer vtable = g_type_default_interface_peek (type);
961       specs = g_object_interface_list_properties (vtable, &n_specs);
962     }
963   else
964     {
965       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
966       specs = g_object_class_list_properties (class, &n_specs);
967     }
968         
969   if (n_specs == 0) {
970     g_free (specs);
971     return NULL;
972   }
973   
974   table = gtk_table_new (n_specs, 2, FALSE);
975   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
976   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
977
978   i = 0;
979   while (i < n_specs)
980     {
981       GParamSpec *spec = specs[i];
982       gboolean can_modify;
983       
984       prop_edit = NULL;
985
986       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
987                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
988       
989       if ((spec->flags & G_PARAM_READABLE) == 0)
990         {
991           /* can't display unreadable properties */
992           ++i;
993           continue;
994         }
995       
996       if (spec->owner_type != type)
997         {
998           /* we're only interested in params of type */
999           ++i;
1000           continue;
1001         }
1002
1003       label = gtk_label_new (g_param_spec_get_nick (spec));
1004       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1005       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1006       
1007       prop_edit = property_widget (object, spec, can_modify);
1008       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1009
1010       if (prop_edit)
1011         {
1012           if (!can_modify)
1013             gtk_widget_set_sensitive (prop_edit, FALSE);
1014
1015           if (g_param_spec_get_blurb (spec))
1016             gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
1017
1018           /* set initial value */
1019           g_object_notify (object, spec->name);
1020         }
1021       
1022       ++i;
1023     }
1024
1025
1026   vbox = gtk_vbox_new (FALSE, 0);
1027   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1028
1029   sw = gtk_scrolled_window_new (NULL, NULL);
1030   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1031                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1032   
1033   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1034
1035   g_free (specs);
1036
1037   return sw;
1038 }
1039
1040 static GtkWidget *
1041 child_properties_from_object (GObject *object)
1042 {
1043   GtkWidget *prop_edit;
1044   GtkWidget *label;
1045   GtkWidget *sw;
1046   GtkWidget *vbox;
1047   GtkWidget *table;
1048   GtkWidget *parent;
1049   GParamSpec **specs;
1050   guint n_specs;
1051   gint i;
1052
1053   if (!GTK_IS_WIDGET (object))
1054     return NULL;
1055
1056   parent = gtk_widget_get_parent (GTK_WIDGET (object));
1057
1058   if (!parent)
1059     return NULL;
1060
1061   specs = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &n_specs);
1062
1063   table = gtk_table_new (n_specs, 2, FALSE);
1064   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1065   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1066
1067   i = 0;
1068   while (i < n_specs)
1069     {
1070       GParamSpec *spec = specs[i];
1071       gboolean can_modify;
1072       
1073       prop_edit = NULL;
1074
1075       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1076                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1077       
1078       if ((spec->flags & G_PARAM_READABLE) == 0)
1079         {
1080           /* can't display unreadable properties */
1081           ++i;
1082           continue;
1083         }
1084       
1085       label = gtk_label_new (g_param_spec_get_nick (spec));
1086       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
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       if (prop_edit)
1094         {
1095           if (!can_modify)
1096             gtk_widget_set_sensitive (prop_edit, FALSE);
1097
1098           if (g_param_spec_get_blurb (spec))
1099             gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
1100
1101           /* set initial value */
1102           gtk_widget_child_notify (GTK_WIDGET (object), spec->name);
1103         }
1104       
1105       ++i;
1106     }
1107
1108   vbox = gtk_vbox_new (FALSE, 0);
1109   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1110
1111   sw = gtk_scrolled_window_new (NULL, NULL);
1112   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1113                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1114   
1115   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1116
1117   g_free (specs);
1118
1119   return sw;
1120 }
1121
1122 static void
1123 child_properties (GtkWidget *button, 
1124                   GObject   *object)
1125 {
1126   create_prop_editor (object, 0);
1127 }
1128
1129 static GtkWidget *
1130 children_from_object (GObject *object)
1131 {
1132   GList *children, *c;
1133   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1134   gchar *str;
1135   gint i;
1136
1137   if (!GTK_IS_CONTAINER (object))
1138     return NULL;
1139
1140   children = gtk_container_get_children (GTK_CONTAINER (object));
1141
1142   table = gtk_table_new (g_list_length (children), 2, FALSE);
1143   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1144   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1145  
1146   for (c = children, i = 0; c; c = c->next, i++)
1147     {
1148       object = c->data;
1149
1150       label = gtk_label_new ("Child");
1151       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1152       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1153
1154       prop_edit = gtk_hbox_new (FALSE, 5);
1155
1156       str = object_label (object, NULL);
1157       label = gtk_label_new (str);
1158       g_free (str);
1159       button = gtk_button_new_with_label ("Properties");
1160       g_signal_connect (button, "clicked",
1161                         G_CALLBACK (child_properties),
1162                         object);
1163
1164       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1165       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1166
1167       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1168     }
1169
1170   vbox = gtk_vbox_new (FALSE, 0);
1171   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1172
1173   sw = gtk_scrolled_window_new (NULL, NULL);
1174   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1175                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1176   
1177   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1178
1179   g_list_free (children);
1180
1181   return sw;
1182 }
1183
1184 static GtkWidget *
1185 cells_from_object (GObject *object)
1186 {
1187   GList *cells, *c;
1188   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1189   gchar *str;
1190   gint i;
1191
1192   if (!GTK_IS_CELL_LAYOUT (object))
1193     return NULL;
1194
1195   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (object));
1196
1197   table = gtk_table_new (g_list_length (cells), 2, FALSE);
1198   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1199   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1200  
1201   for (c = cells, i = 0; c; c = c->next, i++)
1202     {
1203       object = c->data;
1204
1205       label = gtk_label_new ("Cell");
1206       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1207       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1208
1209       prop_edit = gtk_hbox_new (FALSE, 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_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1223     }
1224
1225   vbox = gtk_vbox_new (FALSE, 0);
1226   gtk_box_pack_start (GTK_BOX (vbox), table, 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 (cells);
1235
1236   return sw;
1237 }
1238
1239 /* Pass zero for type if you want all properties */
1240 GtkWidget*
1241 create_prop_editor (GObject   *object,
1242                     GType      type)
1243 {
1244   GtkWidget *win;
1245   GtkWidget *notebook;
1246   GtkWidget *properties;
1247   GtkWidget *label;
1248   gchar *title;
1249   GType *ifaces;
1250   guint n_ifaces;
1251   
1252   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
1253     {
1254       gtk_window_present (GTK_WINDOW (win));
1255       return win;
1256     }
1257
1258   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1259   if (GTK_IS_WIDGET (object))
1260     gtk_window_set_screen (GTK_WINDOW (win),
1261                            gtk_widget_get_screen (GTK_WIDGET (object)));
1262
1263   /* hold a weak ref to the object we're editing */
1264   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
1265   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
1266
1267   if (type == 0)
1268     {
1269       notebook = gtk_notebook_new ();
1270       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
1271       
1272       gtk_container_add (GTK_CONTAINER (win), notebook);
1273       
1274       type = G_TYPE_FROM_INSTANCE (object);
1275
1276       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
1277       gtk_window_set_title (GTK_WINDOW (win), title);
1278       g_free (title);
1279       
1280       while (type)
1281         {
1282           properties = properties_from_type (object, type);
1283           if (properties)
1284             {
1285               label = gtk_label_new (g_type_name (type));
1286               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1287                                         properties, label);
1288             }
1289           
1290           type = g_type_parent (type);
1291         }
1292
1293       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
1294       while (n_ifaces--)
1295         {
1296           properties = properties_from_type (object, ifaces[n_ifaces]);
1297           if (properties)
1298             {
1299               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
1300               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1301                                         properties, label);
1302             }
1303         }
1304
1305       g_free (ifaces);
1306
1307       properties = child_properties_from_object (object);
1308       if (properties)
1309         {
1310           label = gtk_label_new ("Child properties");
1311           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1312                                     properties, label);
1313         }
1314
1315       properties = children_from_object (object);
1316       if (properties)
1317         {
1318           label = gtk_label_new ("Children");
1319           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1320                                     properties, label);
1321         }
1322
1323       properties = cells_from_object (object);
1324       if (properties)
1325         {
1326           label = gtk_label_new ("Cell renderers");
1327           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1328                                     properties, label);
1329         }
1330     }
1331   else
1332     {
1333       properties = properties_from_type (object, type);
1334       gtk_container_add (GTK_CONTAINER (win), properties);
1335       title = g_strdup_printf ("Properties of %s", g_type_name (type));
1336       gtk_window_set_title (GTK_WINDOW (win), title);
1337       g_free (title);
1338     }
1339   
1340   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
1341   
1342   gtk_widget_show_all (win);
1343
1344   return win;
1345 }
1346