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