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