]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
Updated Korean translation.
[~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 gunichar
454 unichar_get_value (GtkEntry *entry)
455 {
456   const gchar *text = gtk_entry_get_text (entry);
457   
458   if (text[0])
459     return g_utf8_get_char (text);
460   else
461     return 0;
462 }
463
464 static void
465 unichar_modified (GtkEntry *entry, gpointer data)
466 {
467   ObjectProperty *p = data;
468   gunichar val = unichar_get_value (entry);
469
470   if (is_child_property (p->spec))
471     {
472       GtkWidget *widget = GTK_WIDGET (p->obj);
473       GtkWidget *parent = gtk_widget_get_parent (widget);
474
475       gtk_container_child_set (GTK_CONTAINER (parent), 
476                                widget, p->spec->name, val, NULL);
477     }
478   else
479     g_object_set (p->obj, p->spec->name, val, NULL);
480 }
481
482 static void
483 unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
484 {
485   GtkEntry *entry = GTK_ENTRY (data);
486   gunichar new_val;
487   gunichar old_val = unichar_get_value (entry);
488   GValue val = { 0, };
489   gchar buf[7];
490   gint len;
491   
492   g_value_init (&val, pspec->value_type);
493   get_property_value (object, pspec, &val);
494   new_val = (gunichar)g_value_get_uint (&val);
495
496   if (new_val != old_val)
497     {
498       if (!new_val)
499         len = 0;
500       else
501         len = g_unichar_to_utf8 (new_val, buf);
502       
503       buf[len] = '\0';
504       
505       block_controller (G_OBJECT (entry));
506       gtk_entry_set_text (entry, buf);
507       unblock_controller (G_OBJECT (entry));
508     }
509 }
510
511 static void
512 pointer_changed (GObject *object, GParamSpec *pspec, gpointer data)
513 {
514   GtkLabel *label = GTK_LABEL (data);
515   gchar *str;
516   gpointer ptr;
517   
518   g_object_get (object, pspec->name, &ptr, NULL);
519
520   str = g_strdup_printf ("Pointer: %p", ptr);
521   gtk_label_set_text (label, str);
522   g_free (str);
523 }
524
525 static void
526 object_changed (GObject *object, GParamSpec *pspec, gpointer data)
527 {
528   GtkWidget *label, *button;
529   gchar *str;
530   GObject *obj;
531   const gchar *name;
532   
533   GList *children = gtk_container_get_children (GTK_CONTAINER (data)); 
534   label = GTK_WIDGET (children->data);
535   button = GTK_WIDGET (children->next->data);
536   g_object_get (object, pspec->name, &obj, NULL);
537   g_list_free (children);
538
539   if (obj)
540     name = g_type_name (G_TYPE_FROM_INSTANCE (obj));
541   else
542     name = "unknown";
543   str = g_strdup_printf ("Object: %p (%s)", obj, name);
544   
545   gtk_label_set_text (GTK_LABEL (label), str);
546   gtk_widget_set_sensitive (button, G_IS_OBJECT (obj));
547
548   if (obj)
549     g_object_unref (obj);
550
551   g_free (str);
552 }
553
554 static void
555 model_destroy (gpointer data)
556 {
557   g_object_steal_data (data, "model-object");
558   gtk_widget_destroy (data);
559 }
560
561 static void
562 window_destroy (gpointer data)
563 {
564   g_object_steal_data (data, "prop-editor-win");
565 }
566
567 static void
568 object_properties (GtkWidget *button, 
569                    GObject   *object)
570 {
571   gchar *name;
572   GObject *obj;
573
574   name = (gchar *) g_object_get_data (G_OBJECT (button), "property-name");
575   g_object_get (object, name, &obj, NULL);
576   if (G_IS_OBJECT (obj)) 
577     create_prop_editor (obj, 0);
578 }
579  
580 static GtkWidget *
581 property_widget (GObject    *object, 
582                  GParamSpec *spec, 
583                  gboolean    can_modify)
584 {
585   GtkWidget *prop_edit;
586   GtkAdjustment *adj;
587   gchar *msg;
588   GType type = G_PARAM_SPEC_TYPE (spec);
589
590   if (type == G_TYPE_PARAM_INT)
591     {
592       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
593                                                 G_PARAM_SPEC_INT (spec)->minimum,
594                                                 G_PARAM_SPEC_INT (spec)->maximum,
595                                                 1,
596                                                 MAX ((G_PARAM_SPEC_INT (spec)->maximum -
597                                                       G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
598                                                 0.0));
599       
600       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
601       
602       g_object_connect_property (object, spec, 
603                                  G_CALLBACK (int_changed), 
604                                  adj, G_OBJECT (adj));
605       
606       if (can_modify)
607         connect_controller (G_OBJECT (adj), "value_changed",
608                             object, spec, (GtkSignalFunc) int_modified);
609     }
610   else if (type == G_TYPE_PARAM_UINT)
611     {
612       adj = GTK_ADJUSTMENT (
613                             gtk_adjustment_new (G_PARAM_SPEC_UINT (spec)->default_value,
614                                                 G_PARAM_SPEC_UINT (spec)->minimum,
615                                                 G_PARAM_SPEC_UINT (spec)->maximum,
616                                                 1,
617                                                 MAX ((G_PARAM_SPEC_UINT (spec)->maximum -
618                                                       G_PARAM_SPEC_UINT (spec)->minimum) / 10, 1),
619                                                 0.0));
620       
621       prop_edit = gtk_spin_button_new (adj, 1.0, 0);
622       
623       g_object_connect_property (object, spec, 
624                                  G_CALLBACK (uint_changed), 
625                                  adj, G_OBJECT (adj));
626       
627       if (can_modify)
628         connect_controller (G_OBJECT (adj), "value_changed",
629                             object, spec, (GtkSignalFunc) uint_modified);
630     }
631   else if (type == G_TYPE_PARAM_FLOAT)
632     {
633
634       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
635                                                 G_PARAM_SPEC_FLOAT (spec)->minimum,
636                                                 G_PARAM_SPEC_FLOAT (spec)->maximum,
637                                                 0.1,
638                                                 MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
639                                                       G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
640                                                 0.0));
641       
642       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
643       
644       g_object_connect_property (object, spec, 
645                                  G_CALLBACK (float_changed), 
646                                  adj, G_OBJECT (adj));
647       
648       if (can_modify)
649         connect_controller (G_OBJECT (adj), "value_changed",
650                             object, spec, (GtkSignalFunc) float_modified);
651     }
652   else if (type == G_TYPE_PARAM_DOUBLE)
653     {
654       adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_DOUBLE (spec)->default_value,
655                                                 G_PARAM_SPEC_DOUBLE (spec)->minimum,
656                                                 G_PARAM_SPEC_DOUBLE (spec)->maximum,
657                                                 0.1,
658                                                 MAX ((G_PARAM_SPEC_DOUBLE (spec)->maximum -
659                                                       G_PARAM_SPEC_DOUBLE (spec)->minimum) / 10, 0.1),
660                                                 0.0));
661       
662       prop_edit = gtk_spin_button_new (adj, 0.1, 2);
663       
664       g_object_connect_property (object, spec, 
665                                  G_CALLBACK (double_changed), 
666                                  adj, G_OBJECT (adj));
667       
668       if (can_modify)
669         connect_controller (G_OBJECT (adj), "value_changed",
670                             object, spec, (GtkSignalFunc) double_modified);
671     }
672   else if (type == G_TYPE_PARAM_STRING)
673     {
674       prop_edit = gtk_entry_new ();
675       
676       g_object_connect_property (object, spec, 
677                                  G_CALLBACK (string_changed),
678                                  prop_edit, G_OBJECT (prop_edit));
679       
680       if (can_modify)
681         connect_controller (G_OBJECT (prop_edit), "changed",
682                             object, spec, (GtkSignalFunc) string_modified);
683     }
684   else if (type == G_TYPE_PARAM_BOOLEAN)
685     {
686       prop_edit = gtk_toggle_button_new_with_label ("");
687       
688       g_object_connect_property (object, spec, 
689                                  G_CALLBACK (bool_changed),
690                                  prop_edit, G_OBJECT (prop_edit));
691       
692       if (can_modify)
693         connect_controller (G_OBJECT (prop_edit), "toggled",
694                             object, spec, (GtkSignalFunc) bool_modified);
695     }
696   else if (type == G_TYPE_PARAM_ENUM)
697     {
698       {
699         GtkWidget *menu;
700         GEnumClass *eclass;
701         gint j;
702         
703         prop_edit = gtk_option_menu_new ();
704         
705         menu = gtk_menu_new ();
706         
707         eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
708         
709         j = 0;
710         while (j < eclass->n_values)
711           {
712             GtkWidget *mi;
713             
714             mi = gtk_menu_item_new_with_label (eclass->values[j].value_name);
715             
716             gtk_widget_show (mi);
717             
718             gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
719             
720             ++j;
721           }
722         
723         g_type_class_unref (eclass);
724         
725         gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
726         
727         g_object_connect_property (object, spec,
728                                    G_CALLBACK (enum_changed),
729                                    prop_edit, G_OBJECT (prop_edit));
730         
731         if (can_modify)
732           connect_controller (G_OBJECT (prop_edit), "changed",
733                               object, spec, (GtkSignalFunc) enum_modified);
734       }
735     }
736   else if (type == G_TYPE_PARAM_UNICHAR)
737     {
738       prop_edit = gtk_entry_new ();
739       gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
740       
741       g_object_connect_property (object, spec,
742                                  G_CALLBACK (unichar_changed),
743                                  prop_edit, G_OBJECT (prop_edit));
744       
745       if (can_modify)
746         connect_controller (G_OBJECT (prop_edit), "changed",
747                             object, spec, (GtkSignalFunc) unichar_modified);
748     }
749   else if (type == G_TYPE_PARAM_POINTER)
750     {
751       prop_edit = gtk_label_new ("");
752       
753       g_object_connect_property (object, spec, 
754                                  G_CALLBACK (pointer_changed),
755                                  prop_edit, G_OBJECT (prop_edit));
756     }
757   else if (type == G_TYPE_PARAM_OBJECT)
758     {
759       GtkWidget *label, *button;
760
761       prop_edit = gtk_hbox_new (FALSE, 5);
762
763       label = gtk_label_new ("");
764       button = gtk_button_new_with_label ("Properties");
765       g_object_set_data (G_OBJECT (button), "property-name", spec->name);
766       g_signal_connect (button, "clicked", 
767                         G_CALLBACK (object_properties), 
768                         object);
769
770       gtk_container_add (GTK_CONTAINER (prop_edit), label);
771       gtk_container_add (GTK_CONTAINER (prop_edit), button);
772       
773       g_object_connect_property (object, spec,
774                                  G_CALLBACK (object_changed),
775                                  prop_edit, G_OBJECT (label));
776     }
777   else
778     {  
779       msg = g_strdup_printf ("uneditable property type: %s",
780                              g_type_name (G_PARAM_SPEC_TYPE (spec)));
781       prop_edit = gtk_label_new (msg);            
782       g_free (msg);
783       gtk_misc_set_alignment (GTK_MISC (prop_edit), 0.0, 0.5);
784     }
785   
786   return prop_edit;
787 }
788
789 static GtkWidget *
790 properties_from_type (GObject     *object,
791                       GType        type,
792                       GtkTooltips *tips)
793 {
794   GtkWidget *prop_edit;
795   GtkWidget *label;
796   GtkWidget *sw;
797   GtkWidget *vbox;
798   GtkWidget *table;
799   GParamSpec **specs;
800   guint n_specs;
801   int i;
802
803   if (G_TYPE_IS_INTERFACE (type))
804     {
805       gpointer vtable = g_type_default_interface_peek (type);
806       specs = g_object_interface_list_properties (vtable, &n_specs);
807     }
808   else
809     {
810       GObjectClass *class = G_OBJECT_CLASS (g_type_class_peek (type));
811       specs = g_object_class_list_properties (class, &n_specs);
812     }
813         
814   if (n_specs == 0)
815     return NULL;
816   
817   table = gtk_table_new (n_specs, 2, FALSE);
818   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
819   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
820
821   i = 0;
822   while (i < n_specs)
823     {
824       GParamSpec *spec = specs[i];
825       gboolean can_modify;
826       
827       prop_edit = NULL;
828
829       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
830                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
831       
832       if ((spec->flags & G_PARAM_READABLE) == 0)
833         {
834           /* can't display unreadable properties */
835           ++i;
836           continue;
837         }
838       
839       if (spec->owner_type != type)
840         {
841           /* we're only interested in params of type */
842           ++i;
843           continue;
844         }
845
846       label = gtk_label_new (g_param_spec_get_nick (spec));
847       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
848       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
849       
850       prop_edit = property_widget (object, spec, can_modify);
851       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
852
853       if (prop_edit)
854         {
855           if (!can_modify)
856             gtk_widget_set_sensitive (prop_edit, FALSE);
857
858           if (g_param_spec_get_blurb (spec))
859             gtk_tooltips_set_tip (tips, prop_edit, g_param_spec_get_blurb (spec), NULL);
860           
861           /* set initial value */
862           g_object_notify (object, spec->name);
863         }
864       
865       ++i;
866     }
867
868
869   vbox = gtk_vbox_new (FALSE, 0);
870   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
871
872   sw = gtk_scrolled_window_new (NULL, NULL);
873   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
874                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
875   
876   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
877
878   g_free (specs);
879
880   return sw;
881 }
882
883 static GtkWidget *
884 child_properties_from_object (GObject     *object,
885                               GtkTooltips *tips)
886 {
887   GtkWidget *prop_edit;
888   GtkWidget *label;
889   GtkWidget *sw;
890   GtkWidget *vbox;
891   GtkWidget *table;
892   GtkWidget *parent;
893   GParamSpec **specs;
894   guint n_specs;
895   gint i;
896
897   if (!GTK_IS_WIDGET (object))
898     return NULL;
899
900   parent = gtk_widget_get_parent (GTK_WIDGET (object));
901
902   if (!parent)
903     return NULL;
904
905   specs = gtk_container_class_list_child_properties (G_OBJECT_GET_CLASS (parent), &n_specs);
906
907   table = gtk_table_new (n_specs, 2, FALSE);
908   gtk_table_set_col_spacing (GTK_TABLE (table), 0, 10);
909   gtk_table_set_row_spacings (GTK_TABLE (table), 3);
910
911   i = 0;
912   while (i < n_specs)
913     {
914       GParamSpec *spec = specs[i];
915       gboolean can_modify;
916       
917       prop_edit = NULL;
918
919       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
920                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
921       
922       if ((spec->flags & G_PARAM_READABLE) == 0)
923         {
924           /* can't display unreadable properties */
925           ++i;
926           continue;
927         }
928       
929       label = gtk_label_new (g_param_spec_get_nick (spec));
930       gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
931       gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, i, i + 1);
932       
933       mark_child_property (spec);
934       prop_edit = property_widget (object, spec, can_modify);
935       gtk_table_attach_defaults (GTK_TABLE (table), prop_edit, 1, 2, i, i + 1);
936
937       if (prop_edit)
938         {
939           if (!can_modify)
940             gtk_widget_set_sensitive (prop_edit, FALSE);
941
942           if (g_param_spec_get_blurb (spec))
943             gtk_tooltips_set_tip (tips, prop_edit, g_param_spec_get_blurb (spec), NULL);
944           
945           /* set initial value */
946           gtk_widget_child_notify (GTK_WIDGET (object), spec->name);
947         }
948       
949       ++i;
950     }
951
952   vbox = gtk_vbox_new (FALSE, 0);
953   gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
954
955   sw = gtk_scrolled_window_new (NULL, NULL);
956   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
957                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
958   
959   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
960
961   g_free (specs);
962
963   return sw;
964 }
965
966 static void
967 kill_tips (GtkWindow *win, GtkObject *tips)
968 {
969   gtk_object_destroy (tips);
970   g_object_unref (tips);
971 }
972
973 /* Pass zero for type if you want all properties */
974 GtkWidget*
975 create_prop_editor (GObject   *object,
976                     GType      type)
977 {
978   GtkWidget *win;
979   GtkWidget *notebook;
980   GtkTooltips *tips;
981   GtkWidget *properties;
982   GtkWidget *label;
983   gchar *title;
984   GType *ifaces;
985   guint n_ifaces;
986   
987   if ((win = g_object_get_data (G_OBJECT (object), "prop-editor-win")))
988     {
989       gtk_window_present (GTK_WINDOW (win));
990       return win;
991     }
992
993   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
994   if (GTK_IS_WIDGET (object))
995     gtk_window_set_screen (GTK_WINDOW (win),
996                            gtk_widget_get_screen (GTK_WIDGET (object)));
997
998   tips = gtk_tooltips_new ();
999   g_object_ref (tips);
1000   gtk_object_sink (GTK_OBJECT (tips));
1001
1002   /* Kill the tips when the widget goes away.  */
1003   g_signal_connect (win, "destroy", G_CALLBACK (kill_tips), tips);
1004
1005   /* hold a weak ref to the object we're editing */
1006   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
1007   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
1008
1009   if (type == 0)
1010     {
1011       notebook = gtk_notebook_new ();
1012       gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_LEFT);
1013       
1014       gtk_container_add (GTK_CONTAINER (win), notebook);
1015       
1016       type = G_TYPE_FROM_INSTANCE (object);
1017
1018       title = g_strdup_printf ("Properties of %s widget", g_type_name (type));
1019       gtk_window_set_title (GTK_WINDOW (win), title);
1020       g_free (title);
1021       
1022       while (type)
1023         {
1024           properties = properties_from_type (object, type, tips);
1025           if (properties)
1026             {
1027               label = gtk_label_new (g_type_name (type));
1028               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1029                                         properties, label);
1030             }
1031           
1032           type = g_type_parent (type);
1033         }
1034
1035       ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (object), &n_ifaces);
1036       while (n_ifaces--)
1037         {
1038           properties = properties_from_type (object, ifaces[n_ifaces], tips);
1039           if (properties)
1040             {
1041               label = gtk_label_new (g_type_name (ifaces[n_ifaces]));
1042               gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1043                                         properties, label);
1044             }
1045         }
1046
1047       g_free (ifaces);
1048
1049       properties = child_properties_from_object (object, tips);
1050       if (properties)
1051         {
1052           label = gtk_label_new ("Child properties");
1053           gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
1054                                     properties, label);
1055         }
1056     }
1057   else
1058     {
1059       properties = properties_from_type (object, type, tips);
1060       gtk_container_add (GTK_CONTAINER (win), properties);
1061       title = g_strdup_printf ("Properties of %s", g_type_name (type));
1062       gtk_window_set_title (GTK_WINDOW (win), title);
1063       g_free (title);
1064     }
1065   
1066   gtk_window_set_default_size (GTK_WINDOW (win), -1, 400);
1067   
1068   gtk_widget_show_all (win);
1069
1070   return win;
1071 }
1072