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