]> Pileus Git - ~andy/gtk/blob - tests/prop-editor.c
ac96abf9db7db556ece226e838af8a0361dafe7a
[~andy/gtk] / tests / prop-editor.c
1 /* prop-editor.c
2  * Copyright (C) 2000  Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <string.h>
21
22 #include <gtk/gtk.h>
23
24 #include "prop-editor.h"
25
26 static void
27 get_param_specs (GObject      *object,
28                  GParamSpec ***specs,
29                  gint         *n_specs)
30 {
31   /* Use private interface for now, fix later */
32   *specs = G_OBJECT_GET_CLASS (object)->property_specs;
33   *n_specs = G_OBJECT_GET_CLASS (object)->n_property_specs;
34 }
35
36 typedef struct
37 {
38   gpointer instance;
39   GObject *alive_object;
40   guint id;
41 } DisconnectData;
42
43 static void
44 disconnect_func (gpointer data)
45 {
46   DisconnectData *dd = data;
47   
48   g_signal_handler_disconnect (dd->instance, dd->id);
49 }
50
51 static void
52 signal_removed (gpointer  data,
53                 GClosure *closure)
54 {
55   DisconnectData *dd = data;
56
57   g_object_steal_data (dd->alive_object, "alive-object-data");
58   g_free (dd);
59 }
60
61 static void
62 g_object_connect_property (GObject *object,
63                            const gchar *prop_name,
64                            GtkSignalFunc func,
65                            gpointer data,
66                            GObject *alive_object)
67 {
68   GClosure *closure;
69   gchar *with_detail = g_strconcat ("notify::", prop_name, NULL);
70   DisconnectData *dd;
71
72   dd = g_new (DisconnectData, 1);
73
74   closure = g_cclosure_new (func, data, NULL);
75
76   g_closure_add_invalidate_notifier (closure, dd, signal_removed);
77
78   dd->id = g_signal_connect_closure (object, with_detail,
79                                      closure, FALSE);
80
81   dd->instance = object;
82   dd->alive_object = alive_object;
83   
84   g_object_set_data_full (G_OBJECT (alive_object),
85                           "alive-object-data",
86                           dd,
87                           disconnect_func);
88   
89   g_free (with_detail);
90 }
91
92 typedef struct 
93 {
94   GObject *obj;
95   gchar *prop;
96 } ObjectProperty;
97
98 static void
99 free_object_property (ObjectProperty *p)
100 {
101   g_free (p->prop);
102   g_free (p);
103 }
104
105 static void
106 connect_controller (GObject *controller,
107                     const gchar *signal,
108                     GObject *model,
109                     const gchar *prop_name,
110                     GtkSignalFunc func)
111 {
112   ObjectProperty *p;
113
114   p = g_new (ObjectProperty, 1);
115   p->obj = model;
116   p->prop = g_strdup (prop_name);
117
118   g_signal_connect_data (controller, signal, func, p,
119                          (GClosureNotify)free_object_property,
120                          FALSE, FALSE);
121 }
122
123 static void
124 int_modified (GtkAdjustment *adj, gpointer data)
125 {
126   ObjectProperty *p = data;
127
128   g_object_set (p->obj, p->prop, (int) adj->value, NULL);
129 }
130
131 static void
132 int_changed (GObject *object, GParamSpec *pspec, gpointer data)
133 {
134   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
135   GValue val = { 0, };  
136
137   g_value_init (&val, G_TYPE_INT);
138   g_object_get_property (object, pspec->name, &val);
139
140   if (g_value_get_int (&val) != (int)adj->value)
141     gtk_adjustment_set_value (adj, g_value_get_int (&val));
142
143   g_value_unset (&val);
144 }
145
146 static void
147 float_modified (GtkAdjustment *adj, gpointer data)
148 {
149   ObjectProperty *p = data;
150
151   g_object_set (p->obj, p->prop, (float) adj->value, NULL);
152 }
153
154 static void
155 float_changed (GObject *object, GParamSpec *pspec, gpointer data)
156 {
157   GtkAdjustment *adj = GTK_ADJUSTMENT (data);
158   GValue val = { 0, };  
159
160   g_value_init (&val, G_TYPE_FLOAT);
161   g_object_get_property (object, pspec->name, &val);
162
163   if (g_value_get_float (&val) != (float) adj->value)
164     gtk_adjustment_set_value (adj, g_value_get_float (&val));
165
166   g_value_unset (&val);
167 }
168
169 static void
170 string_modified (GtkEntry *entry, gpointer data)
171 {
172   ObjectProperty *p = data;
173   const gchar *text;
174
175   text = gtk_entry_get_text (entry);
176
177   g_object_set (p->obj, p->prop, text, NULL);
178 }
179
180 static void
181 string_changed (GObject *object, GParamSpec *pspec, gpointer data)
182 {
183   GtkEntry *entry = GTK_ENTRY (data);
184   GValue val = { 0, };  
185   const gchar *str;
186   const gchar *text;
187   
188   g_value_init (&val, G_TYPE_STRING);
189   g_object_get_property (object, pspec->name, &val);
190
191   str = g_value_get_string (&val);
192   if (str == NULL)
193     str = "";
194   text = gtk_entry_get_text (entry);
195
196   if (strcmp (str, text) != 0)
197     gtk_entry_set_text (entry, str);
198   
199   g_value_unset (&val);
200 }
201
202 static void
203 bool_modified (GtkToggleButton *tb, gpointer data)
204 {
205   ObjectProperty *p = data;
206
207   g_object_set (p->obj, p->prop, (int) tb->active, NULL);
208 }
209
210 static void
211 bool_changed (GObject *object, GParamSpec *pspec, gpointer data)
212 {
213   GtkToggleButton *tb = GTK_TOGGLE_BUTTON (data);
214   GValue val = { 0, };  
215   
216   g_value_init (&val, G_TYPE_BOOLEAN);
217   g_object_get_property (object, pspec->name, &val);
218
219   if (g_value_get_boolean (&val) != tb->active)
220     gtk_toggle_button_set_active (tb, g_value_get_boolean (&val));
221
222   gtk_label_set_text (GTK_LABEL (GTK_BIN (tb)->child), g_value_get_boolean (&val) ?
223                       "TRUE" : "FALSE");
224   
225   g_value_unset (&val);
226 }
227
228
229 static void
230 enum_modified (GtkOptionMenu *om, gpointer data)
231 {
232   ObjectProperty *p = data;
233   gint i;
234   GParamSpec *spec;
235   GEnumClass *eclass;
236   
237   spec = g_object_class_find_property (G_OBJECT_GET_CLASS (p->obj),
238                                        p->prop);
239
240   eclass = G_ENUM_CLASS (g_type_class_peek (spec->value_type));
241   
242   i = gtk_option_menu_get_history (om);
243
244   g_object_set (p->obj, p->prop, eclass->values[i].value, NULL);
245 }
246
247 static void
248 enum_changed (GObject *object, GParamSpec *pspec, gpointer data)
249 {
250   GtkOptionMenu *om = GTK_OPTION_MENU (data);
251   GValue val = { 0, };  
252   GEnumClass *eclass;
253   gint i;
254
255   eclass = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
256   
257   g_value_init (&val, pspec->value_type);
258   g_object_get_property (object, pspec->name, &val);
259
260   i = 0;
261   while (i < eclass->n_values)
262     {
263       if (eclass->values[i].value == g_value_get_enum (&val))
264         break;
265       ++i;
266     }
267   
268   if (gtk_option_menu_get_history (om) != i)
269     gtk_option_menu_set_history (om, i);
270   
271   g_value_unset (&val);
272
273 }
274
275 static gunichar
276 unichar_get_value (GtkEntry *entry)
277 {
278   const gchar *text = gtk_entry_get_text (entry);
279   
280   if (text[0])
281     return g_utf8_get_char (text);
282   else
283     return 0;
284 }
285
286 static void
287 unichar_modified (GtkEntry *entry, gpointer data)
288 {
289   ObjectProperty *p = data;
290   gunichar val = unichar_get_value (entry);
291
292   g_object_set (p->obj, p->prop, val, NULL);
293 }
294
295 static void
296 unichar_changed (GObject *object, GParamSpec *pspec, gpointer data)
297 {
298   GtkEntry *entry = GTK_ENTRY (data);
299   gunichar new_val;
300   gunichar old_val = unichar_get_value (entry);
301   gchar buf[7];
302   gint len;
303
304   g_object_get (object, pspec->name, &new_val, NULL);
305
306   if (new_val != old_val)
307     {
308       if (!new_val)
309         len = 0;
310       else
311         len = g_unichar_to_utf8 (new_val, buf);
312       
313       buf[len] = '\0';
314       
315       gtk_entry_set_text (entry, buf);
316     }
317 }
318
319 void
320 model_destroy (gpointer data)
321 {
322   g_object_steal_data (data, "model-object");
323   gtk_widget_destroy (data);
324 }
325
326 void
327 window_destroy (gpointer data)
328 {
329   g_object_steal_data (data, "prop-editor-win");
330 }
331
332 GtkWidget*
333 create_prop_editor (GObject *object)
334 {
335   GtkWidget *win;
336   GtkWidget *vbox;
337   GtkWidget *hbox;
338   GtkWidget *label;
339   GtkWidget *prop_edit;
340   GtkWidget *sw;
341   gint n_specs = 0;
342   GParamSpec **specs = NULL;
343   gint i;
344   GtkAdjustment *adj;
345   GtkTooltips *tips;
346
347   win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
348
349   tips = gtk_tooltips_new ();
350   gtk_signal_connect_object (GTK_OBJECT (win), "destroy",
351                              GTK_SIGNAL_FUNC (gtk_object_destroy), GTK_OBJECT (tips));
352
353   /* hold a weak ref to the object we're editing */
354   g_object_set_data_full (G_OBJECT (object), "prop-editor-win", win, model_destroy);
355   g_object_set_data_full (G_OBJECT (win), "model-object", object, window_destroy);
356   
357   vbox = gtk_vbox_new (TRUE, 2);
358
359   sw = gtk_scrolled_window_new (NULL, NULL);
360   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
361                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
362   
363   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), vbox);
364   gtk_container_add (GTK_CONTAINER (win), sw);
365   
366   get_param_specs (object, &specs, &n_specs);
367   
368   i = 0;
369   while (i < n_specs)
370     {
371       GParamSpec *spec = specs[i];
372       gboolean can_modify;
373       
374       prop_edit = NULL;
375
376       can_modify = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
377                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
378       
379       if ((spec->flags & G_PARAM_READABLE) == 0)
380         {
381           /* can't display unreadable properties */
382           ++i;
383           continue;
384         }
385       
386       switch (G_PARAM_SPEC_TYPE (spec))
387         {
388         case G_TYPE_PARAM_INT:
389           hbox = gtk_hbox_new (FALSE, 10);
390           label = gtk_label_new (spec->nick);
391           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
392           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
393           adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_INT (spec)->default_value,
394                                                     G_PARAM_SPEC_INT (spec)->minimum,
395                                                     G_PARAM_SPEC_INT (spec)->maximum,
396                                                     1,
397                                                     MAX ((G_PARAM_SPEC_INT (spec)->maximum -
398                                                           G_PARAM_SPEC_INT (spec)->minimum) / 10, 1),
399                                                     0.0));
400
401           prop_edit = gtk_spin_button_new (adj, 1.0, 0);
402           gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
403           
404           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
405
406           g_object_connect_property (object, spec->name,
407                                      GTK_SIGNAL_FUNC (int_changed),
408                                      adj, G_OBJECT (adj));
409
410           if (can_modify)
411             connect_controller (G_OBJECT (adj), "value_changed",
412                                 object, spec->name, (GtkSignalFunc) int_modified);
413           break;
414
415         case G_TYPE_PARAM_FLOAT:
416           hbox = gtk_hbox_new (FALSE, 10);
417           label = gtk_label_new (spec->nick);
418           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
419           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
420           adj = GTK_ADJUSTMENT (gtk_adjustment_new (G_PARAM_SPEC_FLOAT (spec)->default_value,
421                                                     G_PARAM_SPEC_FLOAT (spec)->minimum,
422                                                     G_PARAM_SPEC_FLOAT (spec)->maximum,
423                                                     0.1,
424                                                     MAX ((G_PARAM_SPEC_FLOAT (spec)->maximum -
425                                                           G_PARAM_SPEC_FLOAT (spec)->minimum) / 10, 0.1),
426                                                     0.0));
427
428           prop_edit = gtk_spin_button_new (adj, 0.1, 2);
429           
430           gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
431           
432           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
433
434           g_object_connect_property (object, spec->name,
435                                      GTK_SIGNAL_FUNC (float_changed),
436                                      adj, G_OBJECT (adj));
437
438           if (can_modify)
439             connect_controller (G_OBJECT (adj), "value_changed",
440                                 object, spec->name, (GtkSignalFunc) float_modified);
441           break;
442           
443         case G_TYPE_PARAM_STRING:
444           hbox = gtk_hbox_new (FALSE, 10);
445           label = gtk_label_new (spec->nick);
446           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
447           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
448
449           prop_edit = gtk_entry_new ();
450           gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
451           
452           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
453
454           g_object_connect_property (object, spec->name,
455                                      GTK_SIGNAL_FUNC (string_changed),
456                                      prop_edit, G_OBJECT (prop_edit));
457
458           if (can_modify)
459             connect_controller (G_OBJECT (prop_edit), "changed",
460                                 object, spec->name, (GtkSignalFunc) string_modified);
461           break;
462
463         case G_TYPE_PARAM_BOOLEAN:
464           hbox = gtk_hbox_new (FALSE, 10);
465           label = gtk_label_new (spec->nick);
466           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
467           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
468
469           prop_edit = gtk_toggle_button_new_with_label ("");
470           gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
471           
472           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
473
474           g_object_connect_property (object, spec->name,
475                                      GTK_SIGNAL_FUNC (bool_changed),
476                                      prop_edit, G_OBJECT (prop_edit));
477
478           if (can_modify)
479             connect_controller (G_OBJECT (prop_edit), "toggled",
480                                 object, spec->name, (GtkSignalFunc) bool_modified);
481           break;
482           
483         case G_TYPE_PARAM_ENUM:
484           {
485             GtkWidget *menu;
486             GEnumClass *eclass;
487             gint i;
488             
489             hbox = gtk_hbox_new (FALSE, 10);
490             label = gtk_label_new (spec->nick);
491             gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
492             gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
493             
494             prop_edit = gtk_option_menu_new ();
495             
496             menu = gtk_menu_new ();
497
498             eclass = G_ENUM_CLASS (g_type_class_ref (spec->value_type));
499             
500             i = 0;
501             while (i < eclass->n_values)
502               {
503                 GtkWidget *mi;
504                 
505                 mi = gtk_menu_item_new_with_label (eclass->values[i].value_name);
506                 
507                 gtk_widget_show (mi);
508                 
509                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
510                 
511                   ++i;
512               }
513             
514             g_type_class_unref (eclass);
515             
516             gtk_option_menu_set_menu (GTK_OPTION_MENU (prop_edit), menu);
517             
518             gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
519             
520             gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
521             
522             g_object_connect_property (object, spec->name,
523                                        GTK_SIGNAL_FUNC (enum_changed),
524                                        prop_edit, G_OBJECT (prop_edit));
525             
526             if (can_modify)
527               connect_controller (G_OBJECT (prop_edit), "changed",
528                                   object, spec->name, (GtkSignalFunc) enum_modified);
529           }
530           
531         case G_TYPE_PARAM_UNICHAR:
532           hbox = gtk_hbox_new (FALSE, 10);
533           label = gtk_label_new (spec->nick);
534           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
535           gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
536
537           prop_edit = gtk_entry_new ();
538           gtk_entry_set_max_length (GTK_ENTRY (prop_edit), 1);
539           gtk_box_pack_end (GTK_BOX (hbox), prop_edit, FALSE, FALSE, 0);
540           
541           gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); 
542
543           g_object_connect_property (object, spec->name,
544                                      GTK_SIGNAL_FUNC (unichar_changed),
545                                      prop_edit, G_OBJECT (prop_edit));
546
547           if (can_modify)
548             connect_controller (G_OBJECT (prop_edit), "changed",
549                                 object, spec->name, (GtkSignalFunc) unichar_modified);
550           break;
551
552         default:
553           {
554             gchar *msg = g_strdup_printf ("%s: don't know how to edit property type %s",
555                                           spec->nick, g_type_name (G_PARAM_SPEC_TYPE (spec)));
556             hbox = gtk_hbox_new (FALSE, 10);
557             label = gtk_label_new (msg);            
558             g_free (msg);
559             gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
560             gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
561             gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
562           }
563         }
564
565       if (prop_edit)
566         {
567           if (!can_modify)
568             gtk_widget_set_sensitive (prop_edit, FALSE);
569
570           if (spec->blurb)
571             gtk_tooltips_set_tip (tips, prop_edit, spec->blurb, NULL);
572           
573           /* set initial value */
574           g_object_notify (object, spec->name);
575         }
576       
577       ++i;
578     }
579
580   gtk_window_set_default_size (GTK_WINDOW (win), 300, 500);
581   
582   gtk_widget_show_all (win);
583
584   return win;
585 }
586