]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
Implement property editor for GdkColor properties
[~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 #undef GTK_DISABLE_DEPRECATED
23 #include <gtk/gtk.h>
24
25 #include "prop-editor.h"
26
27
28 typedef struct
29 {
30   gpointer instance;
31   GObject *alive_object;
32   guint id;
33 } DisconnectData;
34
35 static void
36 disconnect_func (gpointer data)
37 {
38   DisconnectData *dd = data;
39   
40   g_signal_handler_disconnect (dd->instance, dd->id);
41 }
42
43 static void
44 signal_removed (gpointer  data,
45                 GClosure *closure)
46 {
47   DisconnectData *dd = data;
48
49   g_object_steal_data (dd->alive_object, "alive-object-data");
50   g_free (dd);
51 }
52
53 static gboolean
54 is_child_property (GParamSpec *pspec)
55 {
56   return g_param_spec_get_qdata (pspec, g_quark_from_string ("is-child-prop")) != NULL;
57 }
58
59 static void
60 mark_child_property (GParamSpec *pspec)
61 {
62   g_param_spec_set_qdata (pspec, g_quark_from_string ("is-child-prop"), 
63                           GINT_TO_POINTER (TRUE));
64 }
65
66 static void
67 g_object_connect_property (GObject     *object,
68                            GParamSpec  *spec,
69                            GCallback    func,
70                            gpointer     data,
71                            GObject     *alive_object)
72 {
73   GClosure *closure;
74   gchar *with_detail;
75   DisconnectData *dd;
76
77   if (is_child_property (spec))
78     with_detail = g_strconcat ("child-notify::", spec->name, NULL);
79   else
80     with_detail = g_strconcat ("notify::", spec->name, NULL);
81
82   dd = g_new (DisconnectData, 1);
83
84   closure = g_cclosure_new (func, data, NULL);
85
86   g_closure_add_invalidate_notifier (closure, dd, signal_removed);
87
88   dd->id = g_signal_connect_closure (object, with_detail,
89                                      closure, FALSE);
90
91   dd->instance = object;
92   dd->alive_object = alive_object;
93   
94   g_object_set_data_full (G_OBJECT (alive_object),
95                           "alive-object-data",
96                           dd,
97                           disconnect_func);
98   
99   g_free (with_detail);
100 }
101
102 typedef struct 
103 {
104   GObject *obj;
105   GParamSpec *spec;
106   gint modified_id;
107 } ObjectProperty;
108
109 static void
110 free_object_property (ObjectProperty *p)
111 {
112   g_free (p);
113 }
114
115 static void
116 connect_controller (GObject     *controller,
117                     const gchar *signal,
118                     GObject     *model,
119                     GParamSpec  *spec,
120                     GCallback    func)
121 {
122   ObjectProperty *p;
123
124   p = g_new (ObjectProperty, 1);
125   p->obj = model;
126   p->spec = spec;
127
128   p->modified_id = g_signal_connect_data (controller, signal, func, p,
129                                           (GClosureNotify)free_object_property,
130                                           0);
131   g_object_set_data (controller, "object-property", p);
132 }
133
134 static void
135 block_controller (GObject *controller)
136 {
137   ObjectProperty *p = g_object_get_data (controller, "object-property");
138
139   if (p)
140     g_signal_handler_block (controller, p->modified_id);
141 }
142
143 static void
144 unblock_controller (GObject *controller)
145 {
146   ObjectProperty *p = g_object_get_data (controller, "object-property");
147
148   if (p)
149     g_signal_handler_unblock (controller, p->modified_id);
150 }
151
152 static void
153 int_modified (GtkAdjustment *adj, gpointer data)
154 {
155   ObjectProperty *p = data;
156
157   if (is_child_property (p->spec))
158     {
159       GtkWidget *widget = GTK_WIDGET (p->obj);
160       GtkWidget *parent = gtk_widget_get_parent (widget);
161
162       gtk_container_child_set (GTK_CONTAINER (parent), 
163                                widget, p->spec->name, (int) adj->value, NULL);
164     }
165   else
166     g_object_set (p->obj, p->spec->name, (int) adj->value, NULL);
167 }
168
169 static void
170 get_property_value (GObject *object, GParamSpec *pspec, GValue *value)
171 {
172   if (is_child_property (pspec))
173     {
174       GtkWidget *widget = GTK_WIDGET (object);
175       GtkWidget *parent = gtk_widget_get_parent (widget);
176
177       gtk_container_child_get_property (GTK_CONTAINER (parent),
178                                         widget, pspec->name, value);
179     }
180   else
181     g_object_get_property (object, pspec->name, value);
182 }
183
184 static void
185 int_changed (GObject *object, GParamSpec *pspec, gpointer data)
186 {
187   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
188   GValue val = { 0, };  
189
190   g_value_init (&val, G_TYPE_INT);
191
192   get_property_value (object, pspec, &val);
193
194   if (g_value_get_int (&val) != (int)adj->value)
195     {
196       block_controller (G_OBJECT (adj));
197       gtk_adjustment_set_value (adj, g_value_get_int (&val));
198       unblock_controller (G_OBJECT (adj));
199     }
200
201   g_value_unset (&val);
202 }
203
204 static void
205 uint_modified (GtkAdjustment *adj, gpointer data)
206 {
207   ObjectProperty *p = data;
208
209   if (is_child_property (p->spec))
210     {
211       GtkWidget *widget = GTK_WIDGET (p->obj);
212       GtkWidget *parent = gtk_widget_get_parent (widget);
213
214       gtk_container_child_set (GTK_CONTAINER (parent), 
215                                widget, p->spec->name, (guint) adj->value, NULL);
216     }
217   else
218     g_object_set (p->obj, p->spec->name, (guint) adj->value, NULL);
219 }
220
221 static void
222 uint_changed (GObject *object, GParamSpec *pspec, gpointer data)
223 {
224   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
225   GValue val = { 0, };  
226
227   g_value_init (&val, G_TYPE_UINT);
228   get_property_value (object, pspec, &val);
229
230   if (g_value_get_uint (&val) != (guint)adj->value)
231     {
232       block_controller (G_OBJECT (adj));
233       gtk_adjustment_set_value (adj, g_value_get_uint (&val));
234       unblock_controller (G_OBJECT (adj));
235     }
236
237   g_value_unset (&val);
238 }
239
240 static void
241 float_modified (GtkAdjustment *adj, gpointer data)
242 {
243   ObjectProperty *p = data;
244
245   if (is_child_property (p->spec))
246     {
247       GtkWidget *widget = GTK_WIDGET (p->obj);
248       GtkWidget *parent = gtk_widget_get_parent (widget);
249
250       gtk_container_child_set (GTK_CONTAINER (parent), 
251                                widget, p->spec->name, (float) adj->value, NULL);
252     }
253   else
254     g_object_set (p->obj, p->spec->name, (float) adj->value, NULL);
255 }
256
257 static void
258 float_changed (GObject *object, GParamSpec *pspec, gpointer data)
259 {
260   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
261   GValue val = { 0, };  
262
263   g_value_init (&val, G_TYPE_FLOAT);
264   get_property_value (object, pspec, &val);
265
266   if (g_value_get_float (&val) != (float) adj->value)
267     {
268       block_controller (G_OBJECT (adj));
269       gtk_adjustment_set_value (adj, g_value_get_float (&val));
270       unblock_controller (G_OBJECT (adj));
271     }
272
273   g_value_unset (&val);
274 }
275
276 static void
277 double_modified (GtkAdjustment *adj, gpointer data)
278 {
279   ObjectProperty *p = data;
280
281   if (is_child_property (p->spec))
282     {
283       GtkWidget *widget = GTK_WIDGET (p->obj);
284       GtkWidget *parent = gtk_widget_get_parent (widget);
285
286       gtk_container_child_set (GTK_CONTAINER (parent), 
287                                widget, p->spec->name, (double) adj->value, NULL);
288     }
289   else
290     g_object_set (p->obj, p->spec->name, (double) adj->value, NULL);
291 }
292
293 static void
294 double_changed (GObject *object, GParamSpec *pspec, gpointer data)
295 {
296   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
297   GValue val = { 0, };  
298
299   g_value_init (&val, G_TYPE_DOUBLE);
300   get_property_value (object, pspec, &val);
301
302   if (g_value_get_double (&val) != adj->value)
303     {
304       block_controller (G_OBJECT (adj));
305       gtk_adjustment_set_value (adj, g_value_get_double (&val));
306       unblock_controller (G_OBJECT (adj));
307     }
308
309   g_value_unset (&val);
310 }
311
312 static void
313 string_modified (GtkEntry *entry, gpointer data)
314 {
315   ObjectProperty *p = data;
316   const gchar *text;
317
318   text = gtk_entry_get_text (entry);
319
320   if (is_child_property (p->spec))
321     {
322       GtkWidget *widget = GTK_WIDGET (p->obj);
323       GtkWidget *parent = gtk_widget_get_parent (widget);
324
325       gtk_container_child_set (GTK_CONTAINER (parent), 
326                                widget, p->spec->name, text, NULL);
327     }
328   else
329     g_object_set (p->obj, p->spec->name, text, NULL);
330 }
331
332 static void
333 string_changed (GObject *object, GParamSpec *pspec, gpointer data)
334 {
335   GtkEntry *entry = GTK_ENTRY (data);
336   GValue val = { 0, };  
337   const gchar *str;
338   const gchar *text;
339   
340   g_value_init (&val, G_TYPE_STRING);
341   get_property_value (object, pspec, &val);
342
343   str = g_value_get_string (&val);
344   if (str == NULL)
345     str = "";
346   text = gtk_entry_get_text (entry);
347
348   if (strcmp (str, text) != 0)
349     {
350       block_controller (G_OBJECT (entry));
351       gtk_entry_set_text (entry, str);
352       unblock_controller (G_OBJECT (entry));
353     }
354   
355   g_value_unset (&val);
356 }
357
358 static void
359 bool_modified (GtkToggleButton *tb, gpointer data)
360 {
361   ObjectProperty *p = data;
362
363   if (is_child_property (p->spec))
364     {
365       GtkWidget *widget = GTK_WIDGET (p->obj);
366       GtkWidget *parent = gtk_widget_get_parent (widget);
367
368       gtk_container_child_set (GTK_CONTAINER (parent), 
369                                widget, p->spec->name, (int) tb->active, NULL);
370     }
371   else
372     g_object_set (p->obj, p->spec->name, (int) tb->active, NULL);
373 }
374
375 static void
376 bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
377 {
378   GtkToggleButton *tb = GTK_TOGGLE_BUTTON (data);
379   GValue val = { 0, };  
380   
381   g_value_init (&val, G_TYPE_BOOLEAN);
382   get_property_value (object, pspec, &val);
383
384   if (g_value_get_boolean (&val) != tb->active)
385     {
386       block_controller (G_OBJECT (tb));
387       gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
388       unblock_controller (G_OBJECT (tb));
389     }
390
391   gtk_label_set_text (GTK_LABEL (GTK_BIN (tb)->child), g_value_get_boolean (&val) ?
392                       "TRUE" : "FALSE");
393   
394   g_value_unset (&val);
395 }
396
397
398 static void
399 enum_modified (GtkOptionMenu *om, gpointer data)
400 {
401   ObjectProperty *p = data;
402   gint i;
403   GEnumClass *eclass;
404   
405   eclass = G_ENUM_CLASS (g_type_class_peek (p->spec->value_type));
406   
407   i = gtk_option_menu_get_history (om);
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   GtkOptionMenu *om = GTK_OPTION_MENU (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_option_menu_get_history (om) != i)
443     {
444       block_controller (G_OBJECT (om));
445       gtk_option_menu_set_history (om, i);
446       unblock_controller (G_OBJECT (om));
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         GtkWidget *menu;
824         GEnumClass *eclass;
825         gint j;
826         
827         prop_edit = gtk_option_menu_new ();
828         
829         menu = gtk_menu_new ();
830         
831         eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
832         
833         j = 0;
834         while (j < eclass->n_values)
835           {
836             GtkWidget *mi;
837             
838             mi = gtk_menu_item_new_with_label (eclass->values[j].value_name);
839             
840             gtk_widget_show (mi);
841             
842             gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
843             
844             ++j;
845           }
846         
847         g_type_class_unref (eclass);
848         
849         gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
850         
851         g_object_connect_property (object, spec,
852                                    G_CALLBACK (enum_changed),
853                                    prop_edit, G_OBJECT (prop_edit));
854         
855         if (can_modify)
856           connect_controller (G_OBJECT (prop_edit), "changed",
857                               object, spec, G_CALLBACK (enum_modified));
858       }
859     }
860   else if (type == G_TYPE_PARAM_FLAGS)
861     {
862       {
863         GFlagsClass *fclass;
864         gint j;
865         
866         prop_edit = gtk_vbox_new (FALSE, 0);
867         
868         fclass = G_FLAGS_CLASS (g_type_class_ref (spec->value_type));
869         
870         for (j = 0; j < fclass->n_values; j++)
871           {
872             GtkWidget *b;
873             
874             b = gtk_check_button_new_with_label (fclass->values[j].value_name);
875             g_object_set_data (G_OBJECT (b), "index", GINT_TO_POINTER (j));
876             gtk_widget_show (b);
877             gtk_box_pack_start (GTK_BOX (prop_edit), b, FALSE, FALSE, 0);
878             if (can_modify) 
879               connect_controller (G_OBJECT (b), "toggled",
880                                   object, spec, G_CALLBACK (flags_modified));
881           }
882         
883         g_type_class_unref (fclass);
884         
885         g_object_connect_property (object, spec,
886                                    G_CALLBACK (flags_changed),
887                                    prop_edit, G_OBJECT (prop_edit));
888       }
889     }
890   else if (type == G_TYPE_PARAM_UNICHAR)
891     {
892       prop_edit = gtk_entry_new ();
893       gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
894       
895       g_object_connect_property (object, spec,
896                                  G_CALLBACK (unichar_changed),
897                                  prop_edit, G_OBJECT (prop_edit));
898       
899       if (can_modify)
900         connect_controller (G_OBJECT (prop_edit), "changed",
901                             object, spec, G_CALLBACK (unichar_modified));
902     }
903   else if (type == G_TYPE_PARAM_POINTER)
904     {
905       prop_edit = gtk_label_new ("");
906       
907       g_object_connect_property (object, spec, 
908                                  G_CALLBACK (pointer_changed),
909                                  prop_edit, G_OBJECT (prop_edit));
910     }
911   else if (type == G_TYPE_PARAM_OBJECT)
912     {
913       GtkWidget *label, *button;
914
915       prop_edit = gtk_hbox_new (FALSE, 5);
916
917       label = gtk_label_new ("");
918       button = gtk_button_new_with_label ("Properties");
919       g_object_set_data (G_OBJECT (button), "property-name", spec->name);
920       g_signal_connect (button, "clicked", 
921                         G_CALLBACK (object_properties), 
922                         object);
923
924       gtk_container_add (GTK_CONTAINER (prop_edit), label);
925       gtk_container_add (GTK_CONTAINER (prop_edit), button);
926       
927       g_object_connect_property (object, spec,
928                                  G_CALLBACK (object_changed),
929                                  prop_edit, G_OBJECT (label));
930     }
931   else if (type == G_TYPE_PARAM_BOXED &&
932            G_PARAM_SPEC_VALUE_TYPE (spec) == GDK_TYPE_COLOR)
933     {
934       prop_edit = gtk_color_button_new ();
935
936       g_object_connect_property (object, spec,
937                                  G_CALLBACK (color_changed),
938                                  prop_edit, G_OBJECT (prop_edit));
939
940       if (can_modify)
941         connect_controller (G_OBJECT (prop_edit), "color-set",
942                             object, spec, G_CALLBACK (color_modified));
943     }
944   else
945     {  
946       msg = g_strdup_printf ("uneditable property type: %s",
947                              g_type_name (G_PARAM_SPEC_TYPE (spec)));
948       prop_edit = gtk_label_new (msg);            
949       g_free (msg);
950       gtk_misc_set_alignment (GTK_MISC (prop_edit), 0.0, 0.5);
951     }
952   
953   return prop_edit;
954 }
955
956 static GtkWidget *
957 properties_from_type (GObject *object,
958                       GType    type)
959 {
960   GtkWidget *prop_edit;
961   GtkWidget *label;
962   GtkWidget *sw;
963   GtkWidget *vbox;
964   GtkWidget *table;
965   GParamSpec **specs;
966   guint n_specs;
967   int i;
968
969   if (G_TYPE_IS_INTERFACE (type))
970     {
971       gpointer vtable = g_type_default_interface_peek (type);
972       specs = g_object_interface_list_properties (vtable, &n_specs);
973     }
974   else
975     {
976       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
977       specs = g_object_class_list_properties (class, &n_specs);
978     }
979         
980   if (n_specs == 0) {
981     g_free (specs);
982     return NULL;
983   }
984   
985   table = gtk_table_new (n_specs, 2, FALSE);
986   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
987   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
988
989   i = 0;
990   while (i < n_specs)
991     {
992       GParamSpec *spec = specs[i];
993       gboolean can_modify;
994       
995       prop_edit = NULL;
996
997       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
998                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
999       
1000       if ((spec->flags & G_PARAM_READABLE) == 0)
1001         {
1002           /* can't display unreadable properties */
1003           ++i;
1004           continue;
1005         }
1006       
1007       if (spec->owner_type != type)
1008         {
1009           /* we're only interested in params of type */
1010           ++i;
1011           continue;
1012         }
1013
1014       label = gtk_label_new (g_param_spec_get_nick (spec));
1015       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1016       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1017       
1018       prop_edit = property_widget (object, spec, can_modify);
1019       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1020
1021       if (prop_edit)
1022         {
1023           if (!can_modify)
1024             gtk_widget_set_sensitive (prop_edit, FALSE);
1025
1026           if (g_param_spec_get_blurb (spec))
1027             gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
1028
1029           /* set initial value */
1030           g_object_notify (object, spec->name);
1031         }
1032       
1033       ++i;
1034     }
1035
1036
1037   vbox = gtk_vbox_new (FALSE, 0);
1038   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1039
1040   sw = gtk_scrolled_window_new (NULL, NULL);
1041   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1042                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1043   
1044   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1045
1046   g_free (specs);
1047
1048   return sw;
1049 }
1050
1051 static GtkWidget *
1052 child_properties_from_object (GObject *object)
1053 {
1054   GtkWidget *prop_edit;
1055   GtkWidget *label;
1056   GtkWidget *sw;
1057   GtkWidget *vbox;
1058   GtkWidget *table;
1059   GtkWidget *parent;
1060   GParamSpec **specs;
1061   guint n_specs;
1062   gint i;
1063
1064   if (!GTK_IS_WIDGET (object))
1065     return NULL;
1066
1067   parent = gtk_widget_get_parent (GTK_WIDGET (object));
1068
1069   if (!parent)
1070     return NULL;
1071
1072   specs = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &n_specs);
1073
1074   table = gtk_table_new (n_specs, 2, FALSE);
1075   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1076   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1077
1078   i = 0;
1079   while (i < n_specs)
1080     {
1081       GParamSpec *spec = specs[i];
1082       gboolean can_modify;
1083       
1084       prop_edit = NULL;
1085
1086       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1087                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1088       
1089       if ((spec->flags & G_PARAM_READABLE) == 0)
1090         {
1091           /* can't display unreadable properties */
1092           ++i;
1093           continue;
1094         }
1095       
1096       label = gtk_label_new (g_param_spec_get_nick (spec));
1097       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1098       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1099       
1100       mark_child_property (spec);
1101       prop_edit = property_widget (object, spec, can_modify);
1102       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1103
1104       if (prop_edit)
1105         {
1106           if (!can_modify)
1107             gtk_widget_set_sensitive (prop_edit, FALSE);
1108
1109           if (g_param_spec_get_blurb (spec))
1110             gtk_widget_set_tooltip_text (prop_edit, g_param_spec_get_blurb (spec));
1111
1112           /* set initial value */
1113           gtk_widget_child_notify (GTK_WIDGET (object), spec->name);
1114         }
1115       
1116       ++i;
1117     }
1118
1119   vbox = gtk_vbox_new (FALSE, 0);
1120   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1121
1122   sw = gtk_scrolled_window_new (NULL, NULL);
1123   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1124                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1125   
1126   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1127
1128   g_free (specs);
1129
1130   return sw;
1131 }
1132
1133 static void
1134 child_properties (GtkWidget *button, 
1135                   GObject   *object)
1136 {
1137   create_prop_editor (object, 0);
1138 }
1139
1140 static GtkWidget *
1141 children_from_object (GObject *object)
1142 {
1143   GList *children, *c;
1144   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1145   gchar *str;
1146   gint i;
1147
1148   if (!GTK_IS_CONTAINER (object))
1149     return NULL;
1150
1151   children = gtk_container_get_children (GTK_CONTAINER (object));
1152
1153   table = gtk_table_new (g_list_length (children), 2, FALSE);
1154   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1155   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1156  
1157   for (c = children, i = 0; c; c = c->next, i++)
1158     {
1159       object = c->data;
1160
1161       label = gtk_label_new ("Child");
1162       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1163       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1164
1165       prop_edit = gtk_hbox_new (FALSE, 5);
1166
1167       str = object_label (object, NULL);
1168       label = gtk_label_new (str);
1169       g_free (str);
1170       button = gtk_button_new_with_label ("Properties");
1171       g_signal_connect (button, "clicked",
1172                         G_CALLBACK (child_properties),
1173                         object);
1174
1175       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1176       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1177
1178       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1179     }
1180
1181   vbox = gtk_vbox_new (FALSE, 0);
1182   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1183
1184   sw = gtk_scrolled_window_new (NULL, NULL);
1185   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1186                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1187   
1188   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1189
1190   g_list_free (children);
1191
1192   return sw;
1193 }
1194
1195 static GtkWidget *
1196 cells_from_object (GObject *object)
1197 {
1198   GList *cells, *c;
1199   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1200   gchar *str;
1201   gint i;
1202
1203   if (!GTK_IS_CELL_LAYOUT (object))
1204     return NULL;
1205
1206   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (object));
1207
1208   table = gtk_table_new (g_list_length (cells), 2, FALSE);
1209   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1210   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1211  
1212   for (c = cells, i = 0; c; c = c->next, i++)
1213     {
1214       object = c->data;
1215
1216       label = gtk_label_new ("Cell");
1217       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1218       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1219
1220       prop_edit = gtk_hbox_new (FALSE, 5);
1221
1222       str = object_label (object, NULL);
1223       label = gtk_label_new (str);
1224       g_free (str);
1225       button = gtk_button_new_with_label ("Properties");
1226       g_signal_connect (button, "clicked",
1227                         G_CALLBACK (child_properties),
1228                         object);
1229
1230       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1231       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1232
1233       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1234     }
1235
1236   vbox = gtk_vbox_new (FALSE, 0);
1237   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1238
1239   sw = gtk_scrolled_window_new (NULL, NULL);
1240   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1241                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1242   
1243   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1244
1245   g_list_free (cells);
1246
1247   return sw;
1248 }
1249
1250 /* Pass zero for type if you want all properties */
1251 GtkWidget*
1252 create_prop_editor (GObject   *object,
1253                     GType      type)
1254 {
1255   GtkWidget *win;
1256   GtkWidget *notebook;
1257   GtkWidget *properties;
1258   GtkWidget *label;
1259   gchar *title;
1260   GType *ifaces;
1261   guint n_ifaces;
1262   
1263   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
1264     {
1265       gtk_window_present (GTK_WINDOW (win));
1266       return win;
1267     }
1268
1269   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1270   if (GTK_IS_WIDGET (object))
1271     gtk_window_set_screen (GTK_WINDOW (win),
1272                            gtk_widget_get_screen (GTK_WIDGET (object)));
1273
1274   /* hold a weak ref to the object we're editing */
1275   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
1276   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
1277
1278   if (type == 0)
1279     {
1280       notebook = gtk_notebook_new ();
1281       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
1282       
1283       gtk_container_add (GTK_CONTAINER (win), notebook);
1284       
1285       type = G_TYPE_FROM_INSTANCE (object);
1286
1287       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
1288       gtk_window_set_title (GTK_WINDOW (win), title);
1289       g_free (title);
1290       
1291       while (type)
1292         {
1293           properties = properties_from_type (object, type);
1294           if (properties)
1295             {
1296               label = gtk_label_new (g_type_name (type));
1297               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1298                                         properties, label);
1299             }
1300           
1301           type = g_type_parent (type);
1302         }
1303
1304       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
1305       while (n_ifaces--)
1306         {
1307           properties = properties_from_type (object, ifaces[n_ifaces]);
1308           if (properties)
1309             {
1310               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
1311               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1312                                         properties, label);
1313             }
1314         }
1315
1316       g_free (ifaces);
1317
1318       properties = child_properties_from_object (object);
1319       if (properties)
1320         {
1321           label = gtk_label_new ("Child properties");
1322           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1323                                     properties, label);
1324         }
1325
1326       properties = children_from_object (object);
1327       if (properties)
1328         {
1329           label = gtk_label_new ("Children");
1330           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1331                                     properties, label);
1332         }
1333
1334       properties = cells_from_object (object);
1335       if (properties)
1336         {
1337           label = gtk_label_new ("Cell renderers");
1338           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1339                                     properties, label);
1340         }
1341     }
1342   else
1343     {
1344       properties = properties_from_type (object, type);
1345       gtk_container_add (GTK_CONTAINER (win), properties);
1346       title = g_strdup_printf ("Properties of %s", g_type_name (type));
1347       gtk_window_set_title (GTK_WINDOW (win), title);
1348       g_free (title);
1349     }
1350   
1351   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
1352   
1353   gtk_widget_show_all (win);
1354
1355   return win;
1356 }
1357