1 /*
2 *
3 * Copyright (C) 2010-2011 Colomban Wendling <ban@herbesfolles.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include "gwh-settings.h"
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <stdarg.h>
26 #include <glib.h>
27 #include <glib/gi18n-lib.h>
28 #include <glib-object.h>
29 #include <gtk/gtk.h>
30
31
32 #if ! GTK_CHECK_VERSION (3, 0, 0)
33 /* make gtk_adjustment_new() return a real GtkAdjustment, not a GtkObject */
34 # define gtk_adjustment_new(v, l, u, si, pi, ps) \
35 (GtkAdjustment *) (gtk_adjustment_new ((v), (l), (u), (si), (pi), (ps)))
36 #endif
37
38
39 struct _GwhSettingsPrivate
40 {
41 GPtrArray *prop_array;
42 };
43
44
G_DEFINE_TYPE(GwhSettings,gwh_settings,G_TYPE_OBJECT)45 G_DEFINE_TYPE (GwhSettings, gwh_settings, G_TYPE_OBJECT)
46
47
48 static void
49 gwh_settings_get_property (GObject *object,
50 guint prop_id,
51 GValue *value,
52 GParamSpec *pspec)
53 {
54 GwhSettings *self = GWH_SETTINGS (object);
55
56 if (G_LIKELY (prop_id > 0 && prop_id <= self->priv->prop_array->len)) {
57 GValue *prop_value;
58
59 prop_value = g_ptr_array_index (self->priv->prop_array, prop_id - 1);
60 g_value_copy (prop_value, value);
61 } else {
62 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
63 }
64 }
65
66 static void
gwh_settings_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)67 gwh_settings_set_property (GObject *object,
68 guint prop_id,
69 const GValue *value,
70 GParamSpec *pspec)
71 {
72 GwhSettings *self = GWH_SETTINGS (object);
73
74 if (G_LIKELY (prop_id > 0 && prop_id <= self->priv->prop_array->len)) {
75 GValue *prop_value;
76
77 prop_value = g_ptr_array_index (self->priv->prop_array, prop_id - 1);
78 g_value_copy (value, prop_value);
79 } else {
80 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
81 }
82 }
83
84 static GObject *
gwh_settings_constructor(GType gtype,guint n_properties,GObjectConstructParam * properties)85 gwh_settings_constructor (GType gtype,
86 guint n_properties,
87 GObjectConstructParam *properties)
88 {
89 static GObject *obj = NULL;
90
91 if (G_UNLIKELY (! obj)) {
92 obj = G_OBJECT_CLASS (gwh_settings_parent_class)->constructor (gtype,
93 n_properties,
94 properties);
95 }
96
97 /* reffing the object unconditionally will leak one ref (the first one), but
98 * since we don't want the object to ever die, this is needed. the other
99 * solution would be to force users to keep their reference all along, but
100 * then if one drop its reference before somebody else get her one, the
101 * settings would be implicitly "reset" to their default values */
102 return g_object_ref (obj);
103 }
104
105 static void
free_prop_item(gpointer data,gpointer user_data)106 free_prop_item (gpointer data,
107 gpointer user_data)
108 {
109 g_value_unset (data);
110 g_free (data);
111 }
112
113 static void
gwh_settings_finalize(GObject * object)114 gwh_settings_finalize (GObject *object)
115 {
116 GwhSettings *self = GWH_SETTINGS (object);
117
118 g_ptr_array_foreach (self->priv->prop_array, free_prop_item, NULL);
119 g_ptr_array_free (self->priv->prop_array, TRUE);
120 self->priv->prop_array = NULL;
121
122 G_OBJECT_CLASS (gwh_settings_parent_class)->finalize (object);
123 }
124
125 static void
gwh_settings_class_init(GwhSettingsClass * klass)126 gwh_settings_class_init (GwhSettingsClass *klass)
127 {
128 GObjectClass *object_class = G_OBJECT_CLASS (klass);
129
130 object_class->constructor = gwh_settings_constructor;
131 object_class->finalize = gwh_settings_finalize;
132 object_class->get_property = gwh_settings_get_property;
133 object_class->set_property = gwh_settings_set_property;
134
135 g_type_class_add_private (klass, sizeof (GwhSettingsPrivate));
136 }
137
138 static void
gwh_settings_init(GwhSettings * self)139 gwh_settings_init (GwhSettings *self)
140 {
141 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GWH_TYPE_SETTINGS,
142 GwhSettingsPrivate);
143 self->priv->prop_array = g_ptr_array_new ();
144 }
145
146
147 /*----------------------------- Begin public API -----------------------------*/
148
149 GwhSettings *
gwh_settings_get_default(void)150 gwh_settings_get_default (void)
151 {
152 return g_object_new (GWH_TYPE_SETTINGS, NULL);
153 }
154
155 static gboolean
is_pspec_installed(GObject * obj,const GParamSpec * pspec)156 is_pspec_installed (GObject *obj,
157 const GParamSpec *pspec)
158 {
159 GParamSpec **pspecs;
160 guint n_props;
161 guint i;
162 gboolean installed = FALSE;
163
164 pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (obj), &n_props);
165 for (i = 0; ! installed && i < n_props; i++) {
166 installed = (pspec->value_type == pspecs[i]->value_type &&
167 strcmp (pspec->name, pspecs[i]->name) == 0);
168 }
169 g_free (pspecs);
170
171 return installed;
172 }
173
174 void
gwh_settings_install_property(GwhSettings * self,GParamSpec * pspec)175 gwh_settings_install_property (GwhSettings *self,
176 GParamSpec *pspec)
177 {
178 GValue *value;
179
180 g_return_if_fail (GWH_IS_SETTINGS (self));
181 g_return_if_fail (G_IS_PARAM_SPEC (pspec));
182
183 /* a bit hackish, but allows to install the same property twice because the
184 * class will not be destroyed if the plugin gets reloaded. safe since the
185 * object is a singleton that will never de destroyed, and the plugin is
186 * resident, so the object is still valid after a reload. */
187 if (is_pspec_installed (G_OBJECT (self), pspec)) {
188 return;
189 }
190
191 value = g_value_init (g_malloc0 (sizeof *value), pspec->value_type);
192 switch (G_TYPE_FUNDAMENTAL (pspec->value_type)) {
193 #define HANDLE_BASIC_TYPE(NAME, Name, name) \
194 case G_TYPE_##NAME: \
195 g_value_set_##name (value, ((GParamSpec##Name*)pspec)->default_value); \
196 break;
197
198 HANDLE_BASIC_TYPE (BOOLEAN, Boolean, boolean)
199 HANDLE_BASIC_TYPE (CHAR, Char, char)
200 HANDLE_BASIC_TYPE (UCHAR, UChar, uchar)
201 HANDLE_BASIC_TYPE (INT, Int, int)
202 HANDLE_BASIC_TYPE (UINT, UInt, uint)
203 HANDLE_BASIC_TYPE (LONG, Long, long)
204 HANDLE_BASIC_TYPE (ULONG, ULong, ulong)
205 HANDLE_BASIC_TYPE (INT64, Int64, int64)
206 HANDLE_BASIC_TYPE (UINT64, UInt64, uint64)
207 HANDLE_BASIC_TYPE (FLOAT, Float, float)
208 HANDLE_BASIC_TYPE (DOUBLE, Double, double)
209 HANDLE_BASIC_TYPE (ENUM, Enum, enum)
210 HANDLE_BASIC_TYPE (FLAGS, Flags, flags)
211 HANDLE_BASIC_TYPE (STRING, String, string)
212
213 #undef HANDLE_BASIC_TYPE
214
215 case G_TYPE_PARAM:
216 case G_TYPE_BOXED:
217 case G_TYPE_POINTER:
218 case G_TYPE_OBJECT:
219 /* nothing to do with these types, default GValue is fine since they have
220 * no default value */
221 break;
222
223 default:
224 g_critical ("Unsupported property type \"%s\" for property \"%s\"",
225 G_VALUE_TYPE_NAME (value), pspec->name);
226 g_value_unset (value);
227 g_free (value);
228 return;
229 }
230 g_ptr_array_add (self->priv->prop_array, value);
231 g_object_class_install_property (G_OBJECT_GET_CLASS (self),
232 self->priv->prop_array->len, pspec);
233 }
234
235 static void
get_key_and_group_from_property_name(const gchar * name,gchar ** group,gchar ** key)236 get_key_and_group_from_property_name (const gchar *name,
237 gchar **group,
238 gchar **key)
239 {
240 const gchar *sep;
241
242 sep = strchr (name, '-');
243 if (sep && sep[1] != 0) {
244 *group = g_strndup (name, (gsize)(sep - name));
245 *key = g_strdup (&sep[1]);
246 } else {
247 *group = g_strdup ("general");
248 *key = g_strdup (name);
249 }
250 }
251
252 static gboolean
key_file_set_value(GKeyFile * kf,const gchar * name,const GValue * value,GError ** error)253 key_file_set_value (GKeyFile *kf,
254 const gchar *name,
255 const GValue *value,
256 GError **error)
257 {
258 gboolean success = TRUE;
259 gchar *group;
260 gchar *key;
261
262 get_key_and_group_from_property_name (name, &group, &key);
263 switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value))) {
264 case G_TYPE_BOOLEAN:
265 g_key_file_set_boolean (kf, group, key, g_value_get_boolean (value));
266 break;
267
268 case G_TYPE_ENUM: {
269 GEnumClass *enum_class;
270 GEnumValue *enum_value;
271 gint val = g_value_get_enum (value);
272
273 enum_class = g_type_class_ref (G_VALUE_TYPE (value));
274 enum_value = g_enum_get_value (enum_class, val);
275 if (! enum_value) {
276 g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
277 "Value \"%d\" is not valid for key \"%s::%s\"",
278 val, group, key);
279 } else {
280 g_key_file_set_string (kf, group, key, enum_value->value_nick);
281 }
282 g_type_class_unref (enum_class);
283 break;
284 }
285
286 case G_TYPE_INT:
287 g_key_file_set_integer (kf, group, key, g_value_get_int (value));
288 break;
289
290 case G_TYPE_STRING:
291 g_key_file_set_string (kf, group, key, g_value_get_string (value));
292 break;
293
294 case G_TYPE_BOXED:
295 if (G_VALUE_HOLDS (value, G_TYPE_STRV)) {
296 gchar **val = g_value_get_boxed (value);
297
298 g_key_file_set_string_list (kf, group, key, (const gchar *const *) val,
299 val ? g_strv_length (val) : 0);
300 break;
301 }
302 /* fallthrough */
303
304 default:
305 g_set_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
306 "Unsupported setting type \"%s\" for setting \"%s::%s\"",
307 G_VALUE_TYPE_NAME (value), group, key);
308 success = FALSE;
309 }
310 g_free (group);
311 g_free (key);
312
313 return success;
314 }
315
316 gboolean
gwh_settings_save_to_file(GwhSettings * self,const gchar * filename,GError ** error)317 gwh_settings_save_to_file (GwhSettings *self,
318 const gchar *filename,
319 GError **error)
320 {
321 GParamSpec **pspecs;
322 guint n_props;
323 guint i;
324 gboolean success = TRUE;
325 GKeyFile *key_file;
326
327 g_return_val_if_fail (GWH_IS_SETTINGS (self), FALSE);
328 g_return_val_if_fail (filename != NULL, FALSE);
329
330 key_file = g_key_file_new ();
331 g_key_file_load_from_file (key_file, filename, G_KEY_FILE_KEEP_COMMENTS |
332 G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
333 pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (self), &n_props);
334 for (i = 0; success && i < n_props; i++) {
335 GValue value = {0};
336
337 g_value_init (&value, pspecs[i]->value_type);
338 g_object_get_property (G_OBJECT (self), pspecs[i]->name, &value);
339 success = key_file_set_value (key_file, pspecs[i]->name, &value, error);
340 g_value_unset (&value);
341 }
342 g_free (pspecs);
343 if (success) {
344 gchar *data;
345 gsize length;
346
347 data = g_key_file_to_data (key_file, &length, error);
348 if (! data) {
349 success = FALSE;
350 } else {
351 success = g_file_set_contents (filename, data, length, error);
352 g_free (data);
353 }
354 }
355 g_key_file_free (key_file);
356
357 return success;
358 }
359
360 static gboolean
key_file_get_value(GKeyFile * kf,const gchar * name,GValue * value,GError ** error)361 key_file_get_value (GKeyFile *kf,
362 const gchar *name,
363 GValue *value,
364 GError **error)
365 {
366 GError *err = NULL;
367 gchar *group;
368 gchar *key;
369
370 get_key_and_group_from_property_name (name, &group, &key);
371 switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value))) {
372 case G_TYPE_BOOLEAN: {
373 gboolean val;
374
375 val = g_key_file_get_boolean (kf, group, key, &err);
376 if (! err) {
377 g_value_set_boolean (value, val);
378 }
379 break;
380 }
381
382 case G_TYPE_ENUM: {
383 gchar *str;
384 GEnumClass *enum_class;
385 GEnumValue *enum_value;
386
387 enum_class = g_type_class_ref (G_VALUE_TYPE (value));
388 str = g_key_file_get_string (kf, group, key, &err);
389 if (! err) {
390 enum_value = g_enum_get_value_by_nick (enum_class, str);
391 if (! enum_value) {
392 g_set_error (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
393 "Value \"%s\" is not valid for key \"%s::%s\"",
394 str, group, key);
395 } else {
396 g_value_set_enum (value, enum_value->value);
397 }
398 g_free (str);
399 }
400 g_type_class_unref (enum_class);
401 break;
402 }
403
404 case G_TYPE_INT: {
405 gint val;
406
407 val = g_key_file_get_integer (kf, group, key, &err);
408 if (! err) {
409 g_value_set_int (value, val);
410 }
411 break;
412 }
413
414 case G_TYPE_STRING: {
415 gchar *val;
416
417 val = g_key_file_get_string (kf, group, key, &err);
418 if (! err) {
419 g_value_take_string (value, val);
420 }
421 break;
422 }
423
424 case G_TYPE_BOXED:
425 if (G_VALUE_HOLDS (value, G_TYPE_STRV)) {
426 gchar **val;
427
428 val = g_key_file_get_string_list (kf, group, key, NULL, &err);
429 if (! err) {
430 g_value_take_boxed (value, val);
431 }
432 break;
433 }
434 /* fallthrough */
435
436 default:
437 g_set_error (&err, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_INVALID_VALUE,
438 "Unsupported setting type \"%s\" for setting \"%s::%s\"",
439 G_VALUE_TYPE_NAME (value), group, key);
440 }
441 if (err) {
442 g_warning ("%s::%s loading failed: %s", group, key, err->message);
443 g_propagate_error (error, err);
444 }
445 g_free (group);
446 g_free (key);
447
448 return err == NULL;
449 }
450
451 gboolean
gwh_settings_load_from_file(GwhSettings * self,const gchar * filename,GError ** error)452 gwh_settings_load_from_file (GwhSettings *self,
453 const gchar *filename,
454 GError **error)
455 {
456 gboolean success = TRUE;
457 GKeyFile *key_file;
458
459 g_return_val_if_fail (GWH_IS_SETTINGS (self), FALSE);
460 g_return_val_if_fail (filename != NULL, FALSE);
461
462 key_file = g_key_file_new ();
463 success = g_key_file_load_from_file (key_file, filename, 0, error);
464 if (success) {
465 GParamSpec **pspecs;
466 guint n_props;
467 guint i;
468
469 pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (self), &n_props);
470 for (i = 0; i < n_props; i++) {
471 GValue value = {0};
472
473 g_value_init (&value, pspecs[i]->value_type);
474 /* ignore the error since it's likely this one is simply missing and other
475 * will be loaded without any problem */
476 if (key_file_get_value (key_file, pspecs[i]->name, &value, NULL)) {
477 g_object_set_property (G_OBJECT (self), pspecs[i]->name, &value);
478 }
479 g_value_unset (&value);
480 }
481 g_free (pspecs);
482 }
483 g_key_file_free (key_file);
484
485 return success;
486 }
487
488
489
490
491 /* display/edit widgets stuff */
492
493 /*
494 * gwh_settings_widget_<type>_notify:
495 * calls user's callback with appropriate arguments
496 * gwh_settings_widget_<type>_notify_callback:
497 * a callback connected to a signal on the widget.
498 * calls gwh_settings_widget_<type>_notify
499 * gwh_settings_widget_<type>_new:
500 * creates the widget
501 * gwh_settings_widget_<type>_sync:
502 * syncs widget's value with its setting
503 */
504
505
506 typedef struct _GwhSettingsWidgetNotifyData
507 {
508 GwhSettings *settings;
509 GCallback callback;
510 gpointer user_data;
511 } GwhSettingsWidgetNotifyData;
512
513
514 static void
gwh_settings_widget_boolean_notify(GObject * tbutton,GwhSettingsWidgetNotifyData * data)515 gwh_settings_widget_boolean_notify (GObject *tbutton,
516 GwhSettingsWidgetNotifyData *data)
517 {
518 ((GwhSettingsWidgetBooleanNotify)data->callback) (data->settings,
519 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tbutton)),
520 data->user_data);
521 }
522
523 #define gwh_settings_widget_boolean_notify_callback gwh_settings_widget_boolean_notify
524
525 static GtkWidget *
gwh_settings_widget_boolean_new(GwhSettings * self,const GValue * value,GParamSpec * pspec,gboolean * needs_label)526 gwh_settings_widget_boolean_new (GwhSettings *self,
527 const GValue *value,
528 GParamSpec *pspec,
529 gboolean *needs_label)
530 {
531 GtkWidget *widget;
532
533 widget = gtk_check_button_new_with_label (g_param_spec_get_nick (pspec));
534 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
535 g_value_get_boolean (value));
536 *needs_label = FALSE;
537
538 return widget;
539 }
540
541 static void
gwh_settings_widget_boolean_sync(GwhSettings * self,GParamSpec * pspec,const GValue * old_value,GtkWidget * widget)542 gwh_settings_widget_boolean_sync (GwhSettings *self,
543 GParamSpec *pspec,
544 const GValue *old_value,
545 GtkWidget *widget)
546 {
547 gboolean val = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
548
549 if (g_value_get_boolean (old_value) != val) {
550 g_object_set (self, pspec->name, val, NULL);
551 }
552 }
553
554 static void
gwh_settings_widget_enum_notify(GObject * object,GwhSettingsWidgetNotifyData * data)555 gwh_settings_widget_enum_notify (GObject *object,
556 GwhSettingsWidgetNotifyData *data)
557 {
558 GtkTreeIter iter;
559 GtkComboBox *cbox = GTK_COMBO_BOX (object);
560
561 if (gtk_combo_box_get_active_iter (cbox, &iter)) {
562 gint val;
563
564 gtk_tree_model_get (gtk_combo_box_get_model (cbox), &iter, 0, &val, -1);
565 ((GwhSettingsWidgetEnumNotify)data->callback) (data->settings, val,
566 data->user_data);
567 }
568 }
569
570 #define gwh_settings_widget_enum_notify_callback gwh_settings_widget_enum_notify
571
572 static GtkWidget *
gwh_settings_widget_enum_new(GwhSettings * self,const GValue * value,GParamSpec * pspec,gboolean * needs_label)573 gwh_settings_widget_enum_new (GwhSettings *self,
574 const GValue *value,
575 GParamSpec *pspec,
576 gboolean *needs_label)
577 {
578 GtkWidget *widget;
579 GEnumClass *enum_class;
580 guint i;
581 GtkListStore *store;
582 GtkCellRenderer *renderer;
583 gint active = 0;
584
585 store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
586 enum_class = g_type_class_ref (G_VALUE_TYPE (value));
587 for (i = 0; i < enum_class->n_values; i++) {
588 GtkTreeIter iter;
589
590 gtk_list_store_append (store, &iter);
591 gtk_list_store_set (store, &iter,
592 0, enum_class->values[i].value,
593 1, _(enum_class->values[i].value_nick),
594 -1);
595 if (g_value_get_enum (value) == enum_class->values[i].value) {
596 active = i;
597 }
598 }
599 g_type_class_unref (enum_class);
600
601 widget = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
602 renderer = gtk_cell_renderer_text_new ();
603 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), renderer, TRUE);
604 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), renderer,
605 "text", 1, NULL);
606 gtk_combo_box_set_active (GTK_COMBO_BOX (widget), active);
607
608 *needs_label = TRUE;
609
610 return widget;
611 }
612
613 static void
gwh_settings_widget_enum_sync(GwhSettings * self,GParamSpec * pspec,const GValue * old_value,GtkWidget * widget)614 gwh_settings_widget_enum_sync (GwhSettings *self,
615 GParamSpec *pspec,
616 const GValue *old_value,
617 GtkWidget *widget)
618 {
619 GtkTreeIter iter;
620
621 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter)) {
622 GtkTreeModel *model;
623 gint val;
624
625 model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
626 gtk_tree_model_get (model, &iter, 0, &val, -1);
627 if (g_value_get_enum (old_value) != val) {
628 g_object_set (self, pspec->name, val, NULL);
629 }
630 }
631 }
632
633 static void
gwh_settings_widget_int_notify(GObject * spin,GwhSettingsWidgetNotifyData * data)634 gwh_settings_widget_int_notify (GObject *spin,
635 GwhSettingsWidgetNotifyData *data)
636 {
637 ((GwhSettingsWidgetIntNotify)data->callback) (data->settings,
638 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin)),
639 data->user_data);
640 }
641
642 #define gwh_settings_widget_int_notify_callback gwh_settings_widget_int_notify
643
644 static GtkWidget *
gwh_settings_widget_int_new(GwhSettings * self,const GValue * value,GParamSpec * pspec,gboolean * needs_label)645 gwh_settings_widget_int_new (GwhSettings *self,
646 const GValue *value,
647 GParamSpec *pspec,
648 gboolean *needs_label)
649 {
650 GtkWidget *button;
651 GtkAdjustment *adj;
652 GParamSpecInt *pspec_int = G_PARAM_SPEC_INT (pspec);
653
654 adj = gtk_adjustment_new ((gdouble)g_value_get_int (value),
655 (gdouble)pspec_int->minimum,
656 (gdouble)pspec_int->maximum,
657 1.0, 10.0, 0.0);
658 button = gtk_spin_button_new (adj, 0.0, 0);
659 *needs_label = TRUE;
660
661 return button;
662 }
663
664 static void
gwh_settings_widget_int_sync(GwhSettings * self,GParamSpec * pspec,const GValue * old_value,GtkWidget * widget)665 gwh_settings_widget_int_sync (GwhSettings *self,
666 GParamSpec *pspec,
667 const GValue *old_value,
668 GtkWidget *widget)
669 {
670 gint val = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
671
672 if (g_value_get_int (old_value) != val) {
673 g_object_set (self, pspec->name, val, NULL);
674 }
675 }
676
677 static void
gwh_settings_widget_string_notify(GObject * entry,GwhSettingsWidgetNotifyData * data)678 gwh_settings_widget_string_notify (GObject *entry,
679 GwhSettingsWidgetNotifyData *data)
680 {
681 ((GwhSettingsWidgetStringNotify)data->callback) (data->settings,
682 gtk_entry_get_text (GTK_ENTRY (entry)),
683 data->user_data);
684 }
685
686 static void
gwh_settings_widget_string_notify_callback(GObject * object,GParamSpec * pspec,GwhSettingsWidgetNotifyData * data)687 gwh_settings_widget_string_notify_callback (GObject *object,
688 GParamSpec *pspec,
689 GwhSettingsWidgetNotifyData *data)
690 {
691 gwh_settings_widget_string_notify (object, data);
692 }
693
694 static GtkWidget *
gwh_settings_widget_string_new(GwhSettings * self,const GValue * value,GParamSpec * pspec,gboolean * needs_label)695 gwh_settings_widget_string_new (GwhSettings *self,
696 const GValue *value,
697 GParamSpec *pspec,
698 gboolean *needs_label)
699 {
700 GtkWidget *widget;
701
702 widget = gtk_entry_new ();
703 gtk_entry_set_text (GTK_ENTRY (widget), g_value_get_string (value));
704 *needs_label = TRUE;
705
706 return widget;
707 }
708
709 static void
gwh_settings_widget_string_sync(GwhSettings * self,GParamSpec * pspec,const GValue * old_value,GtkWidget * widget)710 gwh_settings_widget_string_sync (GwhSettings *self,
711 GParamSpec *pspec,
712 const GValue *old_value,
713 GtkWidget *widget)
714 {
715 const gchar *val = gtk_entry_get_text (GTK_ENTRY (widget));
716 const gchar *old_val = g_value_get_string (old_value);
717
718 if (g_strcmp0 (old_val, val) != 0) {
719 g_object_set (self, pspec->name, val, NULL);
720 }
721 }
722
723
724 #define KEY_PSPEC "gwh-settings-configure-pspec"
725 #define KEY_WIDGET "gwh-settings-configure-widget"
726
727 /**
728 * gwh_settings_widget_new_full:
729 * @self: A GwhSettings object
730 * @prop_name: the name of the setting for which create a widget
731 * @setting_changed_callback: a callback to be called when the widget's value
732 * changes, or %NULL. The type of the callback
733 * is GwhSettingsWidget*Notify, depending of the
734 * setting type
735 * @user_data: user data to pass to @callback
736 * @notify_flags: notification flags
737 *
738 * Creates a widgets to configure the value of a setting @prop_name.
739 * This setting will not be changed automatically, you'll need to call
740 * gwh_settings_widget_sync() when you want to synchronize the widget's value to
741 * the setting.
742 *
743 * Returns: A #GtkWidget that displays and edit @prop_name.
744 */
745 GtkWidget *
gwh_settings_widget_new_full(GwhSettings * self,const gchar * prop_name,GCallback setting_changed_callback,gpointer user_data,GwhSettingsNotifyFlags notify_flags)746 gwh_settings_widget_new_full (GwhSettings *self,
747 const gchar *prop_name,
748 GCallback setting_changed_callback,
749 gpointer user_data,
750 GwhSettingsNotifyFlags notify_flags)
751 {
752 GtkWidget *widget = NULL;
753 GParamSpec *pspec;
754 GValue value = {0};
755 gboolean needs_label = FALSE;
756
757 g_return_val_if_fail (GWH_IS_SETTINGS (self), NULL);
758
759 pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (self), prop_name);
760 g_return_val_if_fail (pspec != NULL, NULL);
761
762 g_value_init (&value, pspec->value_type);
763 g_object_get_property (G_OBJECT (self), prop_name, &value);
764 switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (&value))) {
765 #define HANDLE_TYPE(T, t, signal) \
766 case G_TYPE_##T: \
767 widget = gwh_settings_widget_##t##_new (self, &value, pspec, \
768 &needs_label); \
769 if (setting_changed_callback) { \
770 GwhSettingsWidgetNotifyData *data = g_malloc (sizeof *data); \
771 \
772 data->settings = self; \
773 data->callback = setting_changed_callback; \
774 data->user_data = user_data; \
775 g_signal_connect_data (widget, signal, \
776 G_CALLBACK (gwh_settings_widget_##t##_notify_callback),\
777 data, (GClosureNotify)g_free, 0); \
778 if (notify_flags & GWH_SETTINGS_NOTIFY_ON_CONNEXION) { \
779 gwh_settings_widget_##t##_notify (G_OBJECT (widget), data); \
780 } \
781 } \
782 break;
783
784 HANDLE_TYPE (BOOLEAN, boolean, "toggled")
785 HANDLE_TYPE (ENUM, enum, "changed")
786 HANDLE_TYPE (INT, int, "value-changed")
787 HANDLE_TYPE (STRING, string, "notify::text")
788
789 #undef HANDLE_TYPE
790
791 default:
792 g_critical ("Unsupported property type \"%s\"",
793 G_VALUE_TYPE_NAME (&value));
794 }
795 if (widget) {
796 g_object_set_data_full (G_OBJECT (widget), KEY_PSPEC,
797 g_param_spec_ref (pspec),
798 (GDestroyNotify)g_param_spec_unref);
799 if (needs_label) {
800 GtkWidget *box;
801 gchar *label;
802
803 box = gtk_hbox_new (FALSE, 6);
804 label = g_strdup_printf (_("%s:"), g_param_spec_get_nick (pspec));
805 gtk_box_pack_start (GTK_BOX (box), gtk_label_new (label), FALSE, TRUE, 0);
806 g_free (label);
807 gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 0);
808 g_object_set_data_full (G_OBJECT (box), KEY_WIDGET,
809 g_object_ref (widget), g_object_unref);
810 widget = box;
811 } else {
812 g_object_set_data_full (G_OBJECT (widget), KEY_WIDGET,
813 g_object_ref (widget), g_object_unref);
814 }
815 gtk_widget_set_tooltip_text (widget, g_param_spec_get_blurb (pspec));
816 }
817
818 return widget;
819 }
820
821 GtkWidget *
gwh_settings_widget_new(GwhSettings * self,const gchar * prop_name)822 gwh_settings_widget_new (GwhSettings *self,
823 const gchar *prop_name)
824 {
825 return gwh_settings_widget_new_full (self, prop_name, NULL, NULL, 0);
826 }
827
828 static gboolean
gwh_settings_widget_sync_internal(GwhSettings * self,GtkWidget * widget)829 gwh_settings_widget_sync_internal (GwhSettings *self,
830 GtkWidget *widget)
831 {
832 GParamSpec *pspec;
833 GValue value = {0};
834
835 g_return_val_if_fail (G_IS_OBJECT (widget), FALSE);
836
837 widget = g_object_get_data (G_OBJECT (widget), KEY_WIDGET);
838 g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
839 pspec = g_object_get_data (G_OBJECT (widget), KEY_PSPEC);
840 g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
841 g_value_init (&value, pspec->value_type);
842 g_object_get_property (G_OBJECT (self), pspec->name, &value);
843 switch (G_TYPE_FUNDAMENTAL (pspec->value_type)) {
844 #define HANDLE_TYPE(T, t) \
845 case G_TYPE_##T: \
846 gwh_settings_widget_##t##_sync (self, pspec, &value, widget); \
847 break;
848
849 HANDLE_TYPE (BOOLEAN, boolean)
850 HANDLE_TYPE (ENUM, enum)
851 HANDLE_TYPE (INT, int)
852 HANDLE_TYPE (STRING, string)
853
854 #undef HANDLE_TYPE
855
856 default:
857 g_critical ("Unsupported property type \"%s\"",
858 g_type_name (pspec->value_type));
859 }
860 g_value_unset (&value);
861
862 return TRUE;
863 }
864
865 /**
866 * gwh_settings_widget_sync_v:
867 * @self: A #GwhSettings object
868 * @...: a %NULL-terminated list of widgets to sync
869 *
870 * Same as gwh_settings_widget_sync() but emits notifications only after all
871 * widgets got synchronized.
872 */
873 void
gwh_settings_widget_sync_v(GwhSettings * self,...)874 gwh_settings_widget_sync_v (GwhSettings *self,
875 ...)
876 {
877 GtkWidget *widget;
878 va_list ap;
879
880 g_return_if_fail (GWH_IS_SETTINGS (self));
881
882 g_object_freeze_notify (G_OBJECT (self));
883 va_start (ap, self);
884 while ((widget = va_arg (ap, GtkWidget*))) {
885 if (! gwh_settings_widget_sync_internal (self, widget)) {
886 break;
887 }
888 }
889 va_end (ap);
890 g_object_thaw_notify (G_OBJECT (self));
891 }
892
893 void
gwh_settings_widget_sync(GwhSettings * self,GtkWidget * widget)894 gwh_settings_widget_sync (GwhSettings *self,
895 GtkWidget *widget)
896 {
897 g_return_if_fail (GWH_IS_SETTINGS (self));
898
899 gwh_settings_widget_sync_internal (self, widget);
900 }
901