]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
Fix a memory leak (#515039, Christian Persch)
[~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                     GtkSignalFunc  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, (GtkSignalFunc) 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, (GtkSignalFunc) 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, (GtkSignalFunc) 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, (GtkSignalFunc) 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, (GtkSignalFunc) 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, (GtkSignalFunc) 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, (GtkSignalFunc) 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, (GtkSignalFunc) 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, (GtkSignalFunc) 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                       GtkTooltips *tips)
901 {
902   GtkWidget *prop_edit;
903   GtkWidget *label;
904   GtkWidget *sw;
905   GtkWidget *vbox;
906   GtkWidget *table;
907   GParamSpec **specs;
908   guint n_specs;
909   int i;
910
911   if (G_TYPE_IS_INTERFACE (type))
912     {
913       gpointer vtable = g_type_default_interface_peek (type);
914       specs = g_object_interface_list_properties (vtable, &n_specs);
915     }
916   else
917     {
918       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
919       specs = g_object_class_list_properties (class, &n_specs);
920     }
921         
922   if (n_specs == 0) {
923     g_free (specs);
924     return NULL;
925   }
926   
927   table = gtk_table_new (n_specs, 2, FALSE);
928   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
929   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
930
931   i = 0;
932   while (i < n_specs)
933     {
934       GParamSpec *spec = specs[i];
935       gboolean can_modify;
936       
937       prop_edit = NULL;
938
939       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
940                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
941       
942       if ((spec->flags & G_PARAM_READABLE) == 0)
943         {
944           /* can't display unreadable properties */
945           ++i;
946           continue;
947         }
948       
949       if (spec->owner_type != type)
950         {
951           /* we're only interested in params of type */
952           ++i;
953           continue;
954         }
955
956       label = gtk_label_new (g_param_spec_get_nick (spec));
957       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
958       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
959       
960       prop_edit = property_widget (object, spec, can_modify);
961       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
962
963       if (prop_edit)
964         {
965           if (!can_modify)
966             gtk_widget_set_sensitive (prop_edit, FALSE);
967
968           if (g_param_spec_get_blurb (spec))
969             gtk_tooltips_set_tip (tips, prop_edit, g_param_spec_get_blurb (spec), NULL);
970           
971           /* set initial value */
972           g_object_notify (object, spec->name);
973         }
974       
975       ++i;
976     }
977
978
979   vbox = gtk_vbox_new (FALSE, 0);
980   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
981
982   sw = gtk_scrolled_window_new (NULL, NULL);
983   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
984                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
985   
986   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
987
988   g_free (specs);
989
990   return sw;
991 }
992
993 static GtkWidget *
994 child_properties_from_object (GObject     *object,
995                               GtkTooltips *tips)
996 {
997   GtkWidget *prop_edit;
998   GtkWidget *label;
999   GtkWidget *sw;
1000   GtkWidget *vbox;
1001   GtkWidget *table;
1002   GtkWidget *parent;
1003   GParamSpec **specs;
1004   guint n_specs;
1005   gint i;
1006
1007   if (!GTK_IS_WIDGET (object))
1008     return NULL;
1009
1010   parent = gtk_widget_get_parent (GTK_WIDGET (object));
1011
1012   if (!parent)
1013     return NULL;
1014
1015   specs = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &n_specs);
1016
1017   table = gtk_table_new (n_specs, 2, FALSE);
1018   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1019   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1020
1021   i = 0;
1022   while (i < n_specs)
1023     {
1024       GParamSpec *spec = specs[i];
1025       gboolean can_modify;
1026       
1027       prop_edit = NULL;
1028
1029       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
1030                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
1031       
1032       if ((spec->flags & G_PARAM_READABLE) == 0)
1033         {
1034           /* can't display unreadable properties */
1035           ++i;
1036           continue;
1037         }
1038       
1039       label = gtk_label_new (g_param_spec_get_nick (spec));
1040       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1041       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1042       
1043       mark_child_property (spec);
1044       prop_edit = property_widget (object, spec, can_modify);
1045       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1046
1047       if (prop_edit)
1048         {
1049           if (!can_modify)
1050             gtk_widget_set_sensitive (prop_edit, FALSE);
1051
1052           if (g_param_spec_get_blurb (spec))
1053             gtk_tooltips_set_tip (tips, prop_edit, g_param_spec_get_blurb (spec), NULL);
1054           
1055           /* set initial value */
1056           gtk_widget_child_notify (GTK_WIDGET (object), spec->name);
1057         }
1058       
1059       ++i;
1060     }
1061
1062   vbox = gtk_vbox_new (FALSE, 0);
1063   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1064
1065   sw = gtk_scrolled_window_new (NULL, NULL);
1066   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1067                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1068   
1069   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1070
1071   g_free (specs);
1072
1073   return sw;
1074 }
1075
1076 static void
1077 child_properties (GtkWidget *button, 
1078                   GObject   *object)
1079 {
1080   create_prop_editor (object, 0);
1081 }
1082
1083 static GtkWidget *
1084 children_from_object (GObject     *object,
1085                       GtkTooltips *tips)
1086 {
1087   GList *children, *c;
1088   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1089   gchar *str;
1090   gint i;
1091
1092   if (!GTK_IS_CONTAINER (object))
1093     return NULL;
1094
1095   children = gtk_container_get_children (GTK_CONTAINER (object));
1096
1097   table = gtk_table_new (g_list_length (children), 2, FALSE);
1098   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
1099   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
1100  
1101   for (c = children, i = 0; c; c = c->next, i++)
1102     {
1103       object = c->data;
1104
1105       label = gtk_label_new ("Child");
1106       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1107       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
1108
1109       prop_edit = gtk_hbox_new (FALSE, 5);
1110
1111       str = object_label (object);
1112       label = gtk_label_new (str);
1113       g_free (str);
1114       button = gtk_button_new_with_label ("Properties");
1115       g_signal_connect (button, "clicked",
1116                         G_CALLBACK (child_properties),
1117                         object);
1118
1119       gtk_container_add (GTK_CONTAINER (prop_edit), label);
1120       gtk_container_add (GTK_CONTAINER (prop_edit), button);
1121
1122       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
1123     }
1124
1125   vbox = gtk_vbox_new (FALSE, 0);
1126   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
1127
1128   sw = gtk_scrolled_window_new (NULL, NULL);
1129   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1130                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1131   
1132   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
1133
1134   g_list_free (children);
1135
1136   return sw;
1137 }
1138
1139 static GtkWidget *
1140 cells_from_object (GObject     *object,
1141                    GtkTooltips *tips)
1142 {
1143   GList *cells, *c;
1144   GtkWidget *table, *label, *prop_edit, *button, *vbox, *sw;
1145   gchar *str;
1146   gint i;
1147
1148   if (!GTK_IS_CELL_LAYOUT (object))
1149     return NULL;
1150
1151   cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (object));
1152
1153   table = gtk_table_new (g_list_length (cells), 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 = cells, i = 0; c; c = c->next, i++)
1158     {
1159       object = c->data;
1160
1161       label = gtk_label_new ("Cell");
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);
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 (cells);
1191
1192   return sw;
1193 }
1194 static void
1195 kill_tips (GtkWindow *win, GtkObject *tips)
1196 {
1197   gtk_object_destroy (tips);
1198   g_object_unref (tips);
1199 }
1200
1201 /* Pass zero for type if you want all properties */
1202 GtkWidget*
1203 create_prop_editor (GObject   *object,
1204                     GType      type)
1205 {
1206   GtkWidget *win;
1207   GtkWidget *notebook;
1208   GtkTooltips *tips;
1209   GtkWidget *properties;
1210   GtkWidget *label;
1211   gchar *title;
1212   GType *ifaces;
1213   guint n_ifaces;
1214   
1215   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
1216     {
1217       gtk_window_present (GTK_WINDOW (win));
1218       return win;
1219     }
1220
1221   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
1222   if (GTK_IS_WIDGET (object))
1223     gtk_window_set_screen (GTK_WINDOW (win),
1224                            gtk_widget_get_screen (GTK_WIDGET (object)));
1225
1226   tips = gtk_tooltips_new ();
1227   g_object_ref (tips);
1228   gtk_object_sink (GTK_OBJECT (tips));
1229
1230   /* Kill the tips when the widget goes away.  */
1231   g_signal_connect (win, "destroy", G_CALLBACK (kill_tips), tips);
1232
1233   /* hold a weak ref to the object we're editing */
1234   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
1235   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
1236
1237   if (type == 0)
1238     {
1239       notebook = gtk_notebook_new ();
1240       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
1241       
1242       gtk_container_add (GTK_CONTAINER (win), notebook);
1243       
1244       type = G_TYPE_FROM_INSTANCE (object);
1245
1246       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
1247       gtk_window_set_title (GTK_WINDOW (win), title);
1248       g_free (title);
1249       
1250       while (type)
1251         {
1252           properties = properties_from_type (object, type, tips);
1253           if (properties)
1254             {
1255               label = gtk_label_new (g_type_name (type));
1256               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1257                                         properties, label);
1258             }
1259           
1260           type = g_type_parent (type);
1261         }
1262
1263       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
1264       while (n_ifaces--)
1265         {
1266           properties = properties_from_type (object, ifaces[n_ifaces], tips);
1267           if (properties)
1268             {
1269               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
1270               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1271                                         properties, label);
1272             }
1273         }
1274
1275       g_free (ifaces);
1276
1277       properties = child_properties_from_object (object, tips);
1278       if (properties)
1279         {
1280           label = gtk_label_new ("Child properties");
1281           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1282                                     properties, label);
1283         }
1284
1285       properties = children_from_object (object, tips);
1286       if (properties)
1287         {
1288           label = gtk_label_new ("Children");
1289           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1290                                     properties, label);
1291         }
1292
1293       properties = cells_from_object (object, tips);
1294       if (properties)
1295         {
1296           label = gtk_label_new ("Cell renderers");
1297           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1298                                     properties, label);
1299         }
1300     }
1301   else
1302     {
1303       properties = properties_from_type (object, type, tips);
1304       gtk_container_add (GTK_CONTAINER (win), properties);
1305       title = g_strdup_printf ("Properties of %s", g_type_name (type));
1306       gtk_window_set_title (GTK_WINDOW (win), title);
1307       g_free (title);
1308     }
1309   
1310   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
1311   
1312   gtk_widget_show_all (win);
1313
1314   return win;
1315 }
1316