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