1 /*
2  *
3  *  GeanyGenDoc, a Geany plugin to ease generation of source code documentation
4  *  Copyright (C) 2010-2011  Colomban Wendling <ban@herbesfolles.org>
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 # include "config.h" /* for the gettext domain */
23 #endif
24 
25 #include "ggd-options.h"
26 
27 #include <glib.h>
28 #include <glib/gi18n-lib.h>
29 #include <gtk/gtk.h>
30 
31 
32 /* This module is strongly inspired from Geany's Stash module with some design
33  * changes and a complete reimplementation.
34  * The major difference is the way proxies are managed. */
35 
36 
37 /* stolen from Geany */
38 #define foreach_array(array, type, item)                         \
39   for ((item) = ((type*)(gpointer)(array)->data);                \
40        (item) < &((type*)(gpointer)(array)->data)[(array)->len]; \
41        (item)++)
42 
43 
44 /*
45  * GgdOptEntry:
46  * @type: The setting's type
47  * @key: The setting's key (both its name and its key in the underlying
48  *       configuration file)
49  * @optvar: Pointer to the actual value
50  * @value_destroy: Function use to destroy the value, or %NULL
51  * @proxy: A #GObject to use as a proxy for the value
52  * @proxy_prop: Name of the proxy's property to read and write the value
53  * @destroy_hid: Signal handler identifier for the proxy's ::destroy signal, if
54  *               @proxy is a #GtkObject.
55  *
56  * The structure that represents an option.
57  */
58 struct _GgdOptEntry
59 {
60   GType           type;
61   gchar          *key;
62   gpointer        optvar;
63   GDestroyNotify  value_destroy;
64   GObject        *proxy;
65   gchar          *proxy_prop;
66   gulong          destroy_hid;
67 };
68 
69 typedef struct _GgdOptEntry GgdOptEntry;
70 
71 /* syncs an entry's proxy to the entry's value */
72 static void
ggd_opt_entry_sync_to_proxy(GgdOptEntry * entry)73 ggd_opt_entry_sync_to_proxy (GgdOptEntry *entry)
74 {
75   if (entry->proxy) {
76     /*g_debug ("Syncing proxy for %s", entry->key);*/
77     g_object_set (entry->proxy, entry->proxy_prop, *(gpointer *)entry->optvar,
78                   NULL);
79   }
80 }
81 
82 /* syncs an entry's value to the entry's proxy value */
83 static void
ggd_opt_entry_sync_from_proxy(GgdOptEntry * entry)84 ggd_opt_entry_sync_from_proxy (GgdOptEntry *entry)
85 {
86   if (entry->proxy) {
87     if (entry->value_destroy) entry->value_destroy (*(gpointer *)entry->optvar);
88     g_object_get (entry->proxy, entry->proxy_prop, entry->optvar, NULL);
89   }
90 }
91 
92 /*
93  * ggd_opt_entry_set_proxy:
94  * @entry: A #GgdOptEntry
95  * @proxy: The proxy object
96  * @prop: The name of the proxy's property to sync with
97  *
98  * Sets and syncs the proxy of a #GgdOptEntry.
99  */
100 static void
ggd_opt_entry_set_proxy(GgdOptEntry * entry,GObject * proxy,const gchar * prop)101 ggd_opt_entry_set_proxy (GgdOptEntry *entry,
102                          GObject     *proxy,
103                          const gchar *prop)
104 {
105   if (entry->proxy) {
106     if (entry->destroy_hid > 0l) {
107       g_signal_handler_disconnect (entry->proxy, entry->destroy_hid);
108     }
109     g_object_unref (entry->proxy);
110   }
111   g_free (entry->proxy_prop);
112   entry->proxy = (proxy) ? g_object_ref (proxy) : proxy;
113   entry->proxy_prop = g_strdup (prop);
114   entry->destroy_hid = 0l;
115   /* sync the proxy with the setting's current state */
116   ggd_opt_entry_sync_to_proxy (entry);
117 }
118 
119 /* Frees an entry's allocated data */
120 static void
ggd_opt_entry_free_data(GgdOptEntry * entry,gboolean free_opt)121 ggd_opt_entry_free_data (GgdOptEntry *entry,
122                          gboolean     free_opt)
123 {
124   if (entry) {
125     ggd_opt_entry_set_proxy (entry, NULL, NULL);
126     /* don't free the value to let it in a usable state, and it is consistent
127      * since the user allocated it */
128     if (free_opt && entry->value_destroy) {
129       entry->value_destroy (*(gpointer *)entry->optvar);
130       *(gpointer *)entry->optvar = NULL;
131     }
132     g_free (entry->key);
133   }
134 }
135 
136 
137 struct _GgdOptGroup
138 {
139   gchar      *name;
140   GArray     *prefs;
141 };
142 
143 /**
144  * ggd_opt_group_new:
145  * @section: The name of the section for which this group is for
146  *
147  * Creates a new #GgdOptGroup.
148  *
149  * Returns: The newly created #GgdOptGroup. Free with ggd_opt_group_free().
150  */
151 GgdOptGroup *
ggd_opt_group_new(const gchar * section)152 ggd_opt_group_new (const gchar *section)
153 {
154   GgdOptGroup *group;
155 
156   group = g_slice_alloc (sizeof *group);
157   group->name = g_strdup (section);
158   group->prefs = g_array_new (FALSE, FALSE, sizeof (GgdOptEntry));
159 
160   return group;
161 }
162 
163 /**
164  * ggd_opt_group_free:
165  * @group: A #GgdoptGroup
166  * @free_opts: Whether to free the allocated options or not
167  *
168  * Frees a #GgdOptGroup.
169  */
170 void
ggd_opt_group_free(GgdOptGroup * group,gboolean free_opts)171 ggd_opt_group_free (GgdOptGroup  *group,
172                     gboolean      free_opts)
173 {
174   if (group) {
175     GgdOptEntry *entry;
176 
177     foreach_array (group->prefs, GgdOptEntry, entry) {
178       ggd_opt_entry_free_data (entry, free_opts);
179     }
180     g_array_free (group->prefs, TRUE);
181     g_free (group->name);
182     g_slice_free1 (sizeof *group, group);
183   }
184 }
185 
186 /* adds an entry to a group */
187 static GgdOptEntry *
ggd_opt_group_add_entry(GgdOptGroup * group,GType type,const gchar * key,gpointer optvar,GDestroyNotify value_destroy)188 ggd_opt_group_add_entry (GgdOptGroup   *group,
189                          GType          type,
190                          const gchar   *key,
191                          gpointer       optvar,
192                          GDestroyNotify value_destroy)
193 {
194   GgdOptEntry entry;
195 
196   entry.type          = type;
197   entry.key           = g_strdup (key);
198   entry.optvar        = optvar;
199   entry.value_destroy = value_destroy;
200   entry.proxy         = NULL;
201   entry.proxy_prop    = NULL;
202 
203   g_array_append_val (group->prefs, entry);
204 
205   return &g_array_index (group->prefs, GgdOptEntry, group->prefs->len -1);
206 }
207 
208 /* looks up for an entry in a group */
209 static GgdOptEntry *
ggd_opt_group_lookup_entry(GgdOptGroup * group,gpointer optvar)210 ggd_opt_group_lookup_entry (GgdOptGroup  *group,
211                             gpointer      optvar)
212 {
213   GgdOptEntry *entry;
214 
215   foreach_array (group->prefs, GgdOptEntry, entry) {
216     if (entry->optvar == optvar) {
217       return entry;
218     }
219   }
220 
221   return NULL;
222 }
223 
224 /* looks up an entry in a group from is proxy */
225 static GgdOptEntry *
ggd_opt_group_lookup_entry_from_proxy(GgdOptGroup * group,GObject * proxy)226 ggd_opt_group_lookup_entry_from_proxy (GgdOptGroup *group,
227                                        GObject     *proxy)
228 {
229   GgdOptEntry *entry;
230 
231   foreach_array (group->prefs, GgdOptEntry, entry) {
232     if (entry->proxy == proxy) {
233       return entry;
234     }
235   }
236 
237   return NULL;
238 }
239 
240 /**
241  * ggd_opt_group_add_boolean:
242  * @group: A #GgdOptGroup
243  * @optvar: Pointer to setting's variable. It must be already set to a valid
244  *          value.
245  * @key: The key name for this setting
246  *
247  * Adds a boolean setting to a #GgdOptGroup.
248  */
249 void
ggd_opt_group_add_boolean(GgdOptGroup * group,gboolean * optvar,const gchar * key)250 ggd_opt_group_add_boolean (GgdOptGroup *group,
251                            gboolean    *optvar,
252                            const gchar *key)
253 {
254   ggd_opt_group_add_entry (group, G_TYPE_BOOLEAN, key, optvar, NULL);
255 }
256 
257 /**
258  * ggd_opt_group_add_string:
259  * @group: A #GgdOptGroup
260  * @optvar: Pointer to setting's variable. It must be already set to a valid
261  *          value allocated by the GLib's memory manager or to %NULL since it
262  *          must can be freed.
263  * @key: The key name for this setting
264  *
265  * Adds a string setting to a #GgdOptGroup.
266  */
267 void
ggd_opt_group_add_string(GgdOptGroup * group,gchar ** optvar,const gchar * key)268 ggd_opt_group_add_string (GgdOptGroup  *group,
269                           gchar       **optvar,
270                           const gchar  *key)
271 {
272   if (*optvar == NULL) {
273     *optvar = g_strdup ("");
274   }
275   ggd_opt_group_add_entry (group, G_TYPE_STRING, key, (gpointer)optvar, g_free);
276 }
277 
278 
279 /**
280  * ggd_opt_group_sync_to_proxies:
281  * @group: A #GgdOptGroup
282  *
283  * Syncs proxies' values of a #GgdOptGroup to their setting' value.
284  */
285 void
ggd_opt_group_sync_to_proxies(GgdOptGroup * group)286 ggd_opt_group_sync_to_proxies (GgdOptGroup *group)
287 {
288   GgdOptEntry *entry;
289 
290   foreach_array (group->prefs, GgdOptEntry, entry) {
291     ggd_opt_entry_sync_to_proxy (entry);
292   }
293 }
294 
295 /**
296  * ggd_opt_group_sync_from_proxies:
297  * @group: A #GgdOptGroup
298  *
299  * Syncs settings of a #GgdOptGroup to their proxies' values.
300  */
301 void
ggd_opt_group_sync_from_proxies(GgdOptGroup * group)302 ggd_opt_group_sync_from_proxies (GgdOptGroup *group)
303 {
304   GgdOptEntry *entry;
305 
306   foreach_array (group->prefs, GgdOptEntry, entry) {
307     ggd_opt_entry_sync_from_proxy (entry);
308   }
309 }
310 
311 /* set the proxy of a value
312  * see the doc of ggd_opt_group_set_proxy_full() that does the same but returns
313  * a boolean */
314 static GgdOptEntry *
ggd_opt_group_set_proxy_full_internal(GgdOptGroup * group,gpointer optvar,gboolean check_type,GType type_check,GObject * proxy,const gchar * prop)315 ggd_opt_group_set_proxy_full_internal (GgdOptGroup  *group,
316                                        gpointer      optvar,
317                                        gboolean      check_type,
318                                        GType         type_check,
319                                        GObject      *proxy,
320                                        const gchar  *prop)
321 {
322   GgdOptEntry *entry;
323 
324   entry = ggd_opt_group_lookup_entry (group, optvar);
325   if (! entry) {
326     g_warning (_("Unknown option"));
327   } else {
328     if (check_type) {
329       gboolean  success = TRUE;
330       GValue    val     = {0};
331 
332       g_value_init (&val, type_check);
333       g_object_get_property (proxy, prop, &val);
334       if (! (G_VALUE_HOLDS (&val, type_check) && entry->type == type_check)) {
335         g_critical (_("Invalid option or proxy: either the proxy's property or "
336                       "the option type is incompatible."));
337       }
338       g_value_unset (&val);
339       if (! success) {
340         return FALSE;
341       }
342     }
343     ggd_opt_entry_set_proxy (entry, proxy, prop);
344   }
345 
346   return entry;
347 }
348 
349 /**
350  * ggd_opt_group_set_proxy_full:
351  * @group: A #GgdOptGroup
352  * @optvar: The setting's pointer, as passed when adding the setting
353  * @check_type: Whether to check the consistence of the setting's type and the
354  *              proxy's property
355  * @type_check: The type that the setting and the proxy's property should have
356  *              (only used if @check_type is %TRUE)
357  * @proxy: A #GObject to use as proxy for this setting
358  * @prop: The property name of the proxy (must be readable and/or writable,
359  *        depending on whether the prosy are sunk from or to settings)
360  *
361  * This function sets the proxy object for a given setting.
362  * Proxy objects can be used to e.g. display and edit a setting.
363  * If you use a #GtkObject derivate as proxy (such as #GtkWidget<!-- -->s),
364  * consider using ggd_opt_group_set_proxy_gtkobject_full() in place of this
365  * function.
366  *
367  * <note><para>
368  *   Setting the proxy sets its value to the setting's value, no need to sync it
369  *   again with ggd_opt_group_sync_to_proxies().
370  * </para></note>
371  *
372  * Returns: %TRUE if the proxy was correctly set, or %FALSE if the setting
373  *          doesn't exists in @group.
374  */
375 gboolean
ggd_opt_group_set_proxy_full(GgdOptGroup * group,gpointer optvar,gboolean check_type,GType type_check,GObject * proxy,const gchar * prop)376 ggd_opt_group_set_proxy_full (GgdOptGroup  *group,
377                               gpointer      optvar,
378                               gboolean      check_type,
379                               GType         type_check,
380                               GObject      *proxy,
381                               const gchar  *prop)
382 {
383   return ggd_opt_group_set_proxy_full_internal (group, optvar,
384                                                 check_type, type_check,
385                                                 proxy, prop) != NULL;
386 }
387 
388 /**
389  * ggd_opt_group_remove_proxy:
390  * @group: A #GgdOptGroup
391  * @optvar: The setting's pointer, as passed when adding the setting
392  *
393  * Removes the proxy of a given setting.
394  */
395 void
ggd_opt_group_remove_proxy(GgdOptGroup * group,gpointer optvar)396 ggd_opt_group_remove_proxy (GgdOptGroup *group,
397                             gpointer     optvar)
398 {
399   ggd_opt_group_set_proxy_full_internal (group, optvar, FALSE, 0, NULL, NULL);
400 }
401 
402 /* detaches a proxy */
403 static void
ggd_opt_group_remove_proxy_from_proxy(GgdOptGroup * group,GObject * proxy)404 ggd_opt_group_remove_proxy_from_proxy (GgdOptGroup *group,
405                                        GObject     *proxy)
406 {
407   GgdOptEntry *entry;
408 
409   entry = ggd_opt_group_lookup_entry_from_proxy (group, proxy);
410   if (entry) {
411     ggd_opt_entry_set_proxy (entry, NULL, NULL);
412   }
413 }
414 
415 /**
416  * ggd_opt_group_set_proxy_gtkobject_full:
417  * @group: A #GgdOptGroup
418  * @optvar: The setting's pointer, as passed when adding the setting
419  * @check_type: Whether to check the consistence of the setting's type and the
420  *              proxy's property
421  * @type_check: The type that the setting and the proxy's property should have
422  *              (only used if @check_type is %TRUE)
423  * @proxy: A #GtkObject to use as proxy for this setting
424  * @prop: The property name of the proxy (must be readable and/or writable,
425  *        depending on whether the prosy are sunk from or to settings)
426  *
427  * This is very similar to ggd_opt_group_set_proxy_full() but adds a signal
428  * handler on object's ::destroy signal to release it when destroyed.
429  * This should be used when @proxy is a GtkObject derivate, unless you handle
430  * the case yourself (e.g. to sync when widgets gets destroyed).
431  *
432  * Returns: %TRUE if the proxy was correctly set, or %FALSE if the setting
433  *          doesn't exists in @group.
434  */
435 gboolean
ggd_opt_group_set_proxy_gtkobject_full(GgdOptGroup * group,gpointer optvar,gboolean check_type,GType type_check,GtkObject * proxy,const gchar * prop)436 ggd_opt_group_set_proxy_gtkobject_full (GgdOptGroup  *group,
437                                         gpointer      optvar,
438                                         gboolean      check_type,
439                                         GType         type_check,
440                                         GtkObject    *proxy,
441                                         const gchar  *prop)
442 {
443   GgdOptEntry *entry;
444 
445   entry = ggd_opt_group_set_proxy_full_internal (group, optvar,
446                                                  check_type, type_check,
447                                                  G_OBJECT (proxy), prop);
448   if (entry) {
449     entry->destroy_hid = g_signal_connect_swapped (
450       proxy, "destroy",
451       G_CALLBACK (ggd_opt_group_remove_proxy_from_proxy), group
452     );
453   }
454 
455   return entry != NULL;
456 }
457 
458 /*
459  * ggd_opt_group_manage_key_file:
460  * @group: A #GgdOptGroup
461  * @load: Whether to read (%TRUE) or write (%FALSE) the group to or from the
462  *        given key file
463  * @key_file: A #GKeyFile
464  *
465  * Reads or writes a #GgdOptGroup from or to a #GKeyFile
466  */
467 static void
ggd_opt_group_manage_key_file(GgdOptGroup * group,gboolean load,GKeyFile * key_file)468 ggd_opt_group_manage_key_file (GgdOptGroup  *group,
469                                gboolean      load,
470                                GKeyFile     *key_file)
471 {
472   GgdOptEntry *entry;
473 
474   foreach_array (group->prefs, GgdOptEntry, entry) {
475     GError *err = NULL;
476 
477     switch (entry->type) {
478       case G_TYPE_BOOLEAN: {
479         gboolean *setting = (gboolean *)entry->optvar;
480 
481         if (load) {
482           gboolean v;
483 
484           v = g_key_file_get_boolean (key_file, group->name, entry->key, &err);
485           if (! err) {
486             *setting = v;
487           }
488         } else {
489           g_key_file_set_boolean (key_file, group->name, entry->key, *setting);
490         }
491         break;
492       }
493 
494       case G_TYPE_STRING: {
495         gchar **setting = (gchar **)entry->optvar;
496 
497         if (load) {
498           gchar *str;
499 
500           str = g_key_file_get_string (key_file, group->name, entry->key, &err);
501           if (! err) {
502             if (entry->value_destroy) entry->value_destroy (*setting);
503             *setting = str;
504           }
505         } else {
506           g_key_file_set_string (key_file, group->name, entry->key, *setting);
507         }
508         break;
509       }
510 
511       default:
512         g_warning (_("Unknown value type for keyfile entry %s::%s"),
513                    group->name, entry->key);
514     }
515     if (err) {
516       g_warning (_("Error retrieving keyfile entry %s::%s: %s"),
517                  group->name, entry->key, err->message);
518       g_error_free (err);
519     } else {
520       if (load) {
521         ggd_opt_entry_sync_to_proxy (entry);
522       }
523     }
524   }
525 }
526 
527 /**
528  * ggd_opt_group_load_from_key_file:
529  * @group: A #GgdOptGroup
530  * @key_file: A #GKeyFile from where load values
531  *
532  * Loads values of a #GgdOptGroup from a #GKeyFile.
533  */
534 void
ggd_opt_group_load_from_key_file(GgdOptGroup * group,GKeyFile * key_file)535 ggd_opt_group_load_from_key_file (GgdOptGroup  *group,
536                                   GKeyFile     *key_file)
537 {
538   ggd_opt_group_manage_key_file (group, TRUE, key_file);
539 }
540 
541 /**
542  * ggd_opt_group_load_from_file:
543  * @group: A #GgdOptGroup
544  * @filename: Name of the file from which load values in the GLib file names
545  *            encoding
546  * @error: return location for or %NULL to ignore them
547  *
548  * Loads values of a #GgdOptGroup from a file.
549  *
550  * Returns: %TRUE on success, %FALSE otherwise
551  */
552 gboolean
ggd_opt_group_load_from_file(GgdOptGroup * group,const gchar * filename,GError ** error)553 ggd_opt_group_load_from_file (GgdOptGroup  *group,
554                               const gchar  *filename,
555                               GError      **error)
556 {
557   gboolean  success = FALSE;
558   GKeyFile *key_file;
559 
560   key_file = g_key_file_new ();
561   if (g_key_file_load_from_file (key_file, filename, 0, error)) {
562     ggd_opt_group_load_from_key_file (group, key_file);
563     success = TRUE;
564   }
565   g_key_file_free (key_file);
566 
567   return success;
568 }
569 
570 /**
571  * ggd_opt_group_write_to_key_file:
572  * @group: A #GgdOptGroup
573  * @key_file: A #GKeyFile
574  *
575  * Writes the values of a #GgdOptGroup to a #GkeyFile.
576  */
577 void
ggd_opt_group_write_to_key_file(GgdOptGroup * group,GKeyFile * key_file)578 ggd_opt_group_write_to_key_file (GgdOptGroup *group,
579                                  GKeyFile    *key_file)
580 {
581   ggd_opt_group_manage_key_file (group, FALSE, key_file);
582 }
583 
584 /**
585  * ggd_opt_group_write_to_file:
586  * @group: A #GgdOptGroup
587  * @filename: Name of the file in which save the values, in the GLib file names
588  *            encoding
589  * @error: Return location for errors or %NULL to ignore them
590  *
591  * Writes a #GgdOptGroup to a file.
592  * It keeps everything in the file, overwriting only the group managed by the
593  * given #GgdOptGroup. This means that it is perfectly safe to use this function
594  * to save a group that is not alone in a key file.
595  *
596  * <note><para>The file must be both readable and writable.</para></note>
597  *
598  * Returns: %TRUE on success, %FALSE otherwise.
599  */
600 gboolean
ggd_opt_group_write_to_file(GgdOptGroup * group,const gchar * filename,GError ** error)601 ggd_opt_group_write_to_file (GgdOptGroup *group,
602                              const gchar *filename,
603                              GError     **error)
604 {
605   gboolean  success = FALSE;
606   GKeyFile *key_file;
607   gchar    *data;
608   gsize     data_length;
609 
610   key_file = g_key_file_new ();
611   /* try to load the original file but blindly ignore errors because they are
612    * unlikely to be interesting (the file doesn't already exist, a syntax error
613    * because the file exists but is empty (yes, this throws a parse error),
614    * etc.) */
615   g_key_file_load_from_file (key_file, filename,
616                              G_KEY_FILE_KEEP_COMMENTS |
617                              G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
618   ggd_opt_group_write_to_key_file (group, key_file);
619   data = g_key_file_to_data (key_file, &data_length, error);
620   if (data) {
621     success = g_file_set_contents (filename, data, data_length, error);
622     g_free (data);
623   }
624   g_key_file_free (key_file);
625 
626   return success;
627 }
628