[Window] Fix tab ordering
[tilda-gobject.git] / tilda-config.c
1 #include "tilda-config.h"
2
3 #include "tilda.h"
4 #include "tilda-types.h"
5 #include "debug.h"
6 #include "translation.h"
7
8 #include <glib-object.h> /* for GType */
9 #include <stdlib.h> /* for exit() */
10
11 GKeyFile *config_defaults = NULL;
12 GKeyFile *config_userprefs = NULL;
13
14 /*
15  * TODO:
16  * 1) Configuration forward-migration, automatically, on startup.
17  */
18
19 /* The main idea behind this configuration system is that each type of GObject
20  * that needs to use the system implements its own lookup scheme. I tried to
21  * do this as generically as possible, by using just the GObject property names,
22  * and then looking up their type and using that to use the correct parsing
23  * function.
24  *
25  * It is basically function overloading (for example, from C++) but done in C.
26  * Rather complicated, if you ask me. Also a complete pain. But the result
27  * sure is nice.
28  */
29
30 static gchar *
31 tilda_config_lookup_version (GKeyFile *keyfile, GError **error)
32 {
33         debug_enter  ();
34
35         return g_key_file_get_string (keyfile, "Meta", "version", error);
36 }
37
38 static gboolean
39 tilda_config_parse_integer (GKeyFile    *keyfile,
40                                                         const gchar *group_name,
41                                                         const gchar *key,
42                                                         GValue      *value,
43                                                         GError     **user_error)
44 {
45         debug_enter  ();
46         debug_assert (G_IS_VALUE(value));
47
48         GError *error = NULL;
49         gint ret;
50
51         /* Try to get the value */
52         ret = g_key_file_get_integer (keyfile, group_name, key, &error);
53
54         /* Check for error */
55         if (error)
56         {
57                 g_propagate_error (user_error, error);
58                 return FALSE;
59         }
60
61         /* We successfully parsed the int, so set it in the GValue */
62         g_value_set_int (value, ret);
63
64         return TRUE;
65 }
66
67 static gboolean
68 tilda_config_parse_boolean (GKeyFile    *keyfile,
69                                                         const gchar *group_name,
70                                                         const gchar *key,
71                                                         GValue      *value,
72                                                         GError     **user_error)
73 {
74         debug_enter  ();
75         debug_assert (G_IS_VALUE(value));
76
77         GError *error = NULL;
78         gboolean ret;
79
80         /* Try to get the value */
81         ret = g_key_file_get_boolean (keyfile, group_name, key, &error);
82
83         /* Check for error */
84         if (error)
85         {
86                 g_propagate_error (user_error, error);
87                 return FALSE;
88         }
89
90         /* Success */
91         g_value_set_boolean (value, ret);
92
93         return TRUE;
94 }
95
96 static gboolean
97 tilda_config_parse_string (GKeyFile    *keyfile,
98                                                    const gchar *group_name,
99                                                    const gchar *key,
100                                                    GValue      *value,
101                                                    GError **user_error)
102 {
103         debug_enter  ();
104         debug_assert (G_IS_VALUE(value));
105
106         GError *error = NULL;
107         gchar *ret;
108
109         /* Try to get the value */
110         ret = g_key_file_get_string (keyfile, group_name, key, &error);
111
112         /* Check for error */
113         if (error)
114         {
115                 g_propagate_error (user_error, error);
116                 return FALSE;
117         }
118
119         /* Success */
120         g_value_set_string (value, ret);
121
122         return TRUE;
123 }
124
125 #define key_is(STR) (g_ascii_strcasecmp(key,(STR)) == 0)
126
127 static gboolean
128 tilda_config_parse_enum (GKeyFile    *keyfile,
129                                                  const gchar *group_name,
130                                                  const gchar *key,
131                                                  GValue      *value,
132                                                  GError     **user_error)
133 {
134         gchar *ret;
135         GError *error = NULL;
136         GEnumClass *enum_class;
137         GEnumValue *enum_value;
138
139         if (key_is("backspace-binding") || key_is("delete-binding"))
140                 enum_class = g_type_class_peek (vte_terminal_erase_binding_get_type());
141         else if (key_is("dynamic-title"))
142                 enum_class = g_type_class_peek (tilda_dynamic_title_get_type());
143         else if (key_is("exit-action"))
144                 enum_class = g_type_class_peek (tilda_child_exit_action_get_type());
145         else if (key_is("scrollbar-position"))
146                 enum_class = g_type_class_peek (tilda_scrollbar_position_get_type());
147         else if (key_is("animation-orientation") || key_is("tab-position"))
148                 enum_class = g_type_class_peek (gtk_position_type_get_type());
149         else
150         {
151                 g_critical ("FIXME: developer error -- unknown enum key used: %s\n", key);
152                 exit (1);
153                 return FALSE;
154         }
155
156         /* Get the value from the config as a string */
157         ret = g_key_file_get_string (keyfile, group_name, key, &error);
158
159         if (error)
160         {
161                 g_propagate_error (user_error, error);
162                 return FALSE;
163         }
164
165         enum_value = g_enum_get_value_by_nick (enum_class, ret);
166
167         if (!enum_value)
168         {
169                 /* This is exactly the same error that is returned by g_key_file_get_integer()
170                  * when it cannot correctly parse the value. */
171                 g_set_error (user_error,
172                                          G_KEY_FILE_ERROR,
173                                          G_KEY_FILE_ERROR_INVALID_VALUE,
174                                          _("Key file contains key '%s' in group '%s' which has value that cannot be interpreted."), key, group_name);
175                 return FALSE;
176         }
177
178         /* All was successful, let's set it */
179         g_value_set_enum (value, enum_value->value);
180         return TRUE;
181 }
182
183 gboolean
184 tilda_controller_set_property_from_config (TildaController *self, const gchar *property)
185 {
186         debug_enter  ();
187         debug_assert (TILDA_IS_CONTROLLER(self));
188
189         GError *error = NULL;
190         gboolean ret;
191
192         GParamSpec *pspec;
193         GValue *value = g_new0 (GValue, 1);
194         gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
195
196         /* Get the pspec for this property */
197         pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
198
199         /* Make sure that this property exists */
200         if (pspec == NULL)
201         {
202                 g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
203                 exit (1);
204         }
205
206         /* Initialize the GValue that is going to hold the returned value */
207         g_value_init (value, pspec->value_type);
208
209         /* Set the correct function to do the parsing */
210         if      (g_type_is_a (pspec->value_type, G_TYPE_INT))
211                 parse_func = tilda_config_parse_integer;
212         else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
213                 parse_func = tilda_config_parse_boolean;
214         else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
215                 parse_func = tilda_config_parse_string;
216         else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
217                 parse_func = tilda_config_parse_enum;
218         else
219         {
220                 g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
221                 exit(1);
222         }
223
224         /* Do the [Controller] lookup */
225         ret = parse_func (config_userprefs, "Controller", property, value, &error);
226
227         if (error)
228         {
229                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
230                         g_warning (error->message);
231
232                 g_clear_error (&error);
233         }
234         else
235                 goto success;
236
237         /* Do the [Global] lookup */
238         ret = parse_func (config_userprefs, "Global", property, value, &error);
239
240         if (error)
241         {
242                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
243                         g_warning (error->message);
244
245                 g_clear_error (&error);
246         }
247         else
248                 goto success;
249
250         /* Do the [controller-defaults] lookup */
251         ret = parse_func (config_defaults, "controller-defaults", property, value, &error);
252
253         if (error)
254         {
255                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
256                         g_warning (error->message);
257
258                 g_clear_error (&error);
259
260                 /* This is somewhat of a nasty hack, but I really do want to just set the property to
261                  * NULL if there is no default, but ONLY for strings. */
262                 if (parse_func == tilda_config_parse_string)
263                 {
264                         g_value_set_string (value, NULL);
265                         goto success;
266                 }
267         }
268         else
269                 goto success;
270
271 //failure:
272         g_critical (_("Unable to find a value for controller property: %s\n"), property);
273         g_free (value);
274         return FALSE;
275
276 success:
277         g_object_set_property (G_OBJECT(self), property, value);
278         g_value_unset (value);
279         g_free (value);
280         return TRUE;
281 }
282
283 gboolean
284 tilda_window_set_property_from_config (TildaWindow *self, const gchar *property)
285 {
286         debug_enter  ();
287         debug_assert (TILDA_IS_WINDOW(self));
288
289         gchar *group_name;
290         GError *error = NULL;
291         gboolean ret;
292
293         GParamSpec *pspec;
294         GValue *value = g_new0(GValue, 1);
295         gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
296
297         /* Get the pspec for this property */
298         pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
299
300         /* Make sure that this property exists */
301         if (pspec == NULL)
302         {
303                 g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
304                 exit (1);
305         }
306
307         /* Initialize the GValue that is going to hold the returned value */
308         g_value_init (value, pspec->value_type);
309
310         /* Set the correct function to do the parsing */
311         if      (g_type_is_a (pspec->value_type, G_TYPE_INT))
312                 parse_func = tilda_config_parse_integer;
313         else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
314                 parse_func = tilda_config_parse_boolean;
315         else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
316                 parse_func = tilda_config_parse_string;
317         else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
318                 parse_func = tilda_config_parse_enum;
319         else
320         {
321                 g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
322                 exit(1);
323         }
324
325         /* Do the [Window] lookup */
326         group_name = g_strdup_printf ("Window%d", self->number);
327         ret = parse_func (config_userprefs, group_name, property, value, &error);
328         g_free (group_name);
329
330         if (error)
331         {
332                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
333                         g_warning (error->message);
334
335                 g_clear_error (&error);
336         }
337         else
338                 goto success;
339
340         /* Do the [Global] lookup */
341         ret = parse_func (config_userprefs, "Global", property, value, &error);
342
343         if (error)
344         {
345                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
346                         g_warning (error->message);
347
348                 g_clear_error (&error);
349         }
350         else
351                 goto success;
352
353         /* Do the [window-defaults] lookup */
354         ret = parse_func (config_defaults, "window-defaults", property, value, &error);
355
356         if (error)
357         {
358                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
359                         g_warning (error->message);
360
361                 g_clear_error (&error);
362
363                 /* This is somewhat of a nasty hack, but I really do want to just set the property to
364                  * NULL if there is no default, but ONLY for strings. */
365                 if (parse_func == tilda_config_parse_string)
366                 {
367                         g_value_set_string (value, NULL);
368                         goto success;
369                 }
370         }
371         else
372                 goto success;
373
374 //failure:
375         g_critical (_("Unable to find a value for window property: %s\n"), property);
376         g_free (value);
377         return FALSE;
378
379 success:
380         g_object_set_property (G_OBJECT(self), property, value);
381         g_value_unset (value);
382         g_free (value);
383         return TRUE;
384 }
385
386 gboolean
387 tilda_terminal_set_property_from_config (TildaTerminal *self, const gchar *property)
388 {
389         debug_enter  ();
390         debug_assert (TILDA_IS_TERMINAL(self));
391
392         TildaWindow *parent_window = TILDA_WINDOW(self->parent_window);
393         gchar *group_name;
394         GError *error = NULL;
395         gboolean ret;
396
397         GParamSpec *pspec;
398         GValue *value = g_new0 (GValue, 1);
399         gboolean (*parse_func) (GKeyFile *keyfile, const gchar *group_name, const gchar *key, GValue *value, GError **error);
400
401         /* Get the pspec for this property */
402         pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self), property);
403
404         /* Make sure that this property exists */
405         if (pspec == NULL)
406         {
407                 g_critical ("FIXME: developer error -- unable to find property: %s\n", property);
408                 exit (1);
409         }
410
411         /* Initialize the GValue that is going to hold the returned value */
412         g_value_init (value, pspec->value_type);
413
414         /* Set the correct function to do the parsing */
415         if      (g_type_is_a (pspec->value_type, G_TYPE_INT))
416                 parse_func = tilda_config_parse_integer;
417         else if (g_type_is_a (pspec->value_type, G_TYPE_BOOLEAN))
418                 parse_func = tilda_config_parse_boolean;
419         else if (g_type_is_a (pspec->value_type, G_TYPE_STRING))
420                 parse_func = tilda_config_parse_string;
421         else if (g_type_is_a (pspec->value_type, G_TYPE_ENUM))
422                 parse_func = tilda_config_parse_enum;
423         else
424         {
425                 g_critical ("FIXME: developer error -- unknown property type: %s\n", g_type_name(pspec->value_type));
426                 exit(1);
427         }
428
429         /* Do the [Window/Terminal] lookup */
430         group_name = g_strdup_printf ("Window%d/Terminal%d", parent_window->number, self->number);
431         ret = parse_func (config_userprefs, group_name, property, value, &error);
432         g_free (group_name);
433
434         if (error)
435         {
436                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
437                         g_warning (error->message);
438
439                 g_clear_error (&error);
440         }
441         else
442                 goto success;
443
444         /* Do the [Window] lookup */
445         group_name = g_strdup_printf ("Window%d", parent_window->number);
446         ret = parse_func (config_userprefs, group_name, property, value, &error);
447         g_free (group_name);
448
449         if (error)
450         {
451                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
452                         g_warning (error->message);
453
454                 g_clear_error (&error);
455         }
456         else
457                 goto success;
458
459         /* Do the [Global] lookup */
460         ret = parse_func (config_userprefs, "Global", property, value, &error);
461
462         if (error)
463         {
464                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
465                         g_warning (error->message);
466
467                 g_clear_error (&error);
468         }
469         else
470                 goto success;
471
472         /* Do the [terminal-defaults] lookup */
473         ret = parse_func (config_defaults, "terminal-defaults", property, value, &error);
474
475         if (error)
476         {
477                 if (error->code == G_KEY_FILE_ERROR_INVALID_VALUE)
478                         g_warning (error->message);
479
480                 g_clear_error (&error);
481
482                 /* This is somewhat of a nasty hack, but I really do want to just set the property to
483                  * NULL if there is no default, but ONLY for strings. */
484                 if (parse_func == tilda_config_parse_string)
485                 {
486                         g_value_set_string (value, NULL);
487                         goto success;
488                 }
489         }
490         else
491                 goto success;
492
493 //failure:
494         g_critical (_("Unable to find a value for terminal property: %s\n"), property);
495         g_free (value);
496         return FALSE;
497
498 success:
499         g_object_set_property (G_OBJECT(self), property, value);
500         g_value_unset (value);
501         g_free (value);
502         return TRUE;
503 }
504
505 gboolean
506 tilda_config_init (const gchar *userprefs_filename)
507 {
508         gchar *defaults_filename;
509         GError *error = NULL;
510
511         /* Call g_type_class_ref() on all enum types that are being used */
512         g_type_class_ref (vte_terminal_erase_binding_get_type());
513         g_type_class_ref (tilda_dynamic_title_get_type());
514         g_type_class_ref (tilda_child_exit_action_get_type());
515         g_type_class_ref (tilda_scrollbar_position_get_type());
516         g_type_class_ref (gtk_position_type_get_type());
517
518         /* Create the defaults file's path */
519         defaults_filename = g_build_filename ("share-tilda.conf", NULL); // FIXME: use /usr/share/tilda
520
521         /* Create the keyfiles */
522         config_defaults = g_key_file_new ();
523         config_userprefs = g_key_file_new ();
524
525         /* Check if the defaults exist, and load them */
526         if (!g_file_test (defaults_filename, G_FILE_TEST_EXISTS))
527         {
528                 g_critical (_("No configuration defaults file found. Tilda may not work.\n"));
529         }
530         else
531         {
532                 if (!g_key_file_load_from_file (config_defaults, defaults_filename, G_KEY_FILE_NONE, &error))
533                 {
534                         g_critical (_("Error reading configuration defaults: %s\n"), error->message);
535                         g_clear_error (&error);
536                 }
537         }
538
539         /* Check if the user's config exists, and load it */
540         if (!g_file_test (userprefs_filename, G_FILE_TEST_EXISTS))
541         {
542                 g_warning (_("No user configuration file found, using defaults\n"));
543         }
544         else
545         {
546                 if (!g_key_file_load_from_file (config_userprefs, userprefs_filename, G_KEY_FILE_NONE, &error))
547                 {
548                         g_warning (_("Error reading user configuration: %s\n"), error->message);
549                         g_clear_error (&error);
550                 }
551         }
552
553         /* This is just here for the future. Currently, it is acceptable to run without
554          * a configuration, though Tilda may not work very well. */
555         return TRUE;
556 }
557
558 /**
559  * Since we DO NOT allow writing to the configuration file from a Tilda
560  * process (only from the wizard), this does nothing more than free the
561  * defaults hashtable, and free the opened keyfile.
562  */
563 gboolean
564 tilda_config_free ()
565 {
566         /* Since we never write the config file from within Tilda,
567          * we can just close the files. */
568         g_key_file_free (config_defaults);
569         g_key_file_free (config_userprefs);
570
571         config_defaults = NULL;
572         config_userprefs = NULL;
573
574         return TRUE;
575 }
576
577 /* vim: set ts=4 sts=4 sw=4 noet tw=112: */