]> Pileus Git - ~andy/gtk/blob - gtk/gtkfilechoosersettings.c
dcd769220b5539ba2171b0ab95febbafe3544683
[~andy/gtk] / gtk / gtkfilechoosersettings.c
1 /* GTK - The GIMP Toolkit
2  * gtkfilechoosersettings.c: Internal settings for the GtkFileChooser widget
3  * Copyright (C) 2006, Novell, Inc.
4  *
5  * Authors: Federico Mena-Quintero <federico@novell.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 /* TODO:
24  *
25  * - Persist these:
26  *   - hpaned position
27  *   - browse_for_other_folders?
28  *
29  * - Do we want lockdown?
30  */
31
32 #include <config.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <glib/gi18n-lib.h>
36 #include "gtkfilechoosersettings.h"
37 #include "gtkalias.h"
38
39 /* Increment this every time you change the configuration format */
40 #define CONFIG_VERSION 0
41
42 #define ELEMENT_TOPLEVEL        "gtkfilechooser"
43 #define ELEMENT_LOCATION        "location"
44 #define ELEMENT_SHOW_HIDDEN     "show_hidden"
45 #define ATTRIBUTE_VERSION       "version"
46 #define ATTRIBUTE_MODE          "mode"
47 #define ATTRIBUTE_VALUE         "value"
48 #define MODE_PATH_BAR           "path-bar"
49 #define MODE_FILENAME_ENTRY     "filename-entry"
50 #define VALUE_TRUE              "true"
51 #define VALUE_FALSE             "false"
52
53 #define EQ(a, b) (g_ascii_strcasecmp ((a), (b)) == 0)
54
55 static char *
56 get_config_dirname (void)
57 {
58   return g_build_filename (g_get_user_config_dir (), "gtk-2.0", NULL);
59 }
60
61 static char *
62 get_config_filename (void)
63 {
64   return g_build_filename (g_get_user_config_dir (), "gtk-2.0", "gtkfilechooser", NULL);
65 }
66
67 static void
68 set_defaults (GtkFileChooserSettings *settings)
69 {
70   settings->location_mode = LOCATION_MODE_PATH_BAR;
71   settings->show_hidden = FALSE;
72 }
73
74 typedef enum {
75   STATE_START,
76   STATE_END,
77   STATE_ERROR,
78   STATE_IN_TOPLEVEL,
79   STATE_IN_LOCATION,
80   STATE_IN_SHOW_HIDDEN
81 } State;
82
83 struct parse_state {
84   GtkFileChooserSettings *settings;
85   int version;
86   State state;
87 };
88
89 static const char *
90 get_attribute_value (const char **attribute_names,
91                      const char **attribute_values,
92                      const char *attribute)
93 {
94   const char **name;
95   const char **value;
96
97   name = attribute_names;
98   value = attribute_values;
99
100   while (*name)
101     {
102       if (EQ (*name, attribute))
103         return *value;
104
105       name++;
106       value++;
107     }
108
109   return NULL;
110 }
111
112 static void
113 set_missing_attribute_error (struct parse_state *state,
114                              int line,
115                              int col,
116                              const char *attribute,
117                              GError **error)
118 {
119   state->state = STATE_ERROR;
120   g_set_error (error,
121                G_MARKUP_ERROR,
122                G_MARKUP_ERROR_INVALID_CONTENT,
123                _("Line %d, column %d: missing attribute \"%s\""),
124                line,
125                col,
126                attribute);
127 }
128
129 static void
130 set_unexpected_element_error (struct parse_state *state,
131                               int line,
132                               int col,
133                               const char *element,
134                               GError **error)
135 {
136   state->state = STATE_ERROR;
137   g_set_error (error,
138                G_MARKUP_ERROR,
139                G_MARKUP_ERROR_UNKNOWN_ELEMENT,
140                _("Line %d, column %d: unexpected element \"%s\""),
141                line,
142                col,
143                element);
144 }
145
146 static void
147 set_unexpected_element_end_error (struct parse_state *state,
148                                   int line,
149                                   int col,
150                                   const char *expected_element,
151                                   const char *unexpected_element,
152                                   GError **error)
153 {
154   state->state = STATE_ERROR;
155   g_set_error (error,
156                G_MARKUP_ERROR,
157                G_MARKUP_ERROR_UNKNOWN_ELEMENT,
158                _("Line %d, column %d: expected end of element \"%s\", but found end for element \"%s\" instead"),
159                line,
160                col,
161                expected_element,
162                unexpected_element);
163 }
164
165
166 static void
167 parse_start_element_cb (GMarkupParseContext *context,
168                         const char *element_name,
169                         const char **attribute_names,
170                         const char **attribute_values,
171                         gpointer data,
172                         GError **error)
173 {
174   struct parse_state *state;
175   int line, col;
176
177   state = data;
178   g_markup_parse_context_get_position (context, &line, &col);
179
180   switch (state->state)
181     {
182     case STATE_START:
183       if (EQ (element_name, ELEMENT_TOPLEVEL))
184         {
185           const char *version_str;
186
187           state->state = STATE_IN_TOPLEVEL;
188
189           version_str = get_attribute_value (attribute_names, attribute_values, ATTRIBUTE_VERSION);
190           if (!version_str)
191             state->version = -1;
192           else
193             if (sscanf (version_str, "%d", &state->version) != 1 || state->version < 0)
194               state->version = -1;
195         }
196       else
197         {
198           state->state = STATE_ERROR;
199           g_set_error (error,
200                        G_MARKUP_ERROR,
201                        G_MARKUP_ERROR_UNKNOWN_ELEMENT,
202                        _("Line %d, column %d: expected \"%s\" at the toplevel, but found \"%s\" instead"),
203                        line,
204                        col,
205                        ELEMENT_TOPLEVEL,
206                        element_name);
207         }
208       break;
209
210     case STATE_END:
211       g_assert_not_reached ();
212       break;
213
214     case STATE_ERROR:
215       g_assert_not_reached ();
216       break;
217
218     case STATE_IN_TOPLEVEL:
219       if (EQ (element_name, ELEMENT_LOCATION))
220         {
221           const char *location_mode_str;
222
223           state->state = STATE_IN_LOCATION;
224
225           location_mode_str = get_attribute_value (attribute_names, attribute_values, ATTRIBUTE_MODE);
226           if (!location_mode_str)
227             set_missing_attribute_error (state, line, col, ATTRIBUTE_MODE, error);
228           else if (EQ (location_mode_str, MODE_PATH_BAR))
229             state->settings->location_mode = LOCATION_MODE_PATH_BAR;
230           else if (EQ (location_mode_str, MODE_FILENAME_ENTRY))
231             state->settings->location_mode = LOCATION_MODE_FILENAME_ENTRY;
232           else
233             {
234               state->state = STATE_ERROR;
235               g_set_error (error,
236                            G_MARKUP_ERROR,
237                            G_MARKUP_ERROR_INVALID_CONTENT,
238                            _("Line %d, column %d: expected \"%s\" or \"%s\", but found \"%s\" instead"),
239                            line,
240                            col,
241                            MODE_PATH_BAR,
242                            MODE_FILENAME_ENTRY,
243                            location_mode_str);
244             }
245         }
246       else if (EQ (element_name, ELEMENT_SHOW_HIDDEN))
247         {
248           const char *value_str;
249
250           state->state = STATE_IN_SHOW_HIDDEN;
251
252           value_str = get_attribute_value (attribute_names, attribute_values, ATTRIBUTE_VALUE);
253
254           if (!value_str)
255             set_missing_attribute_error (state, line, col, ATTRIBUTE_VALUE, error);
256           else if (EQ (value_str, VALUE_TRUE))
257             state->settings->show_hidden = TRUE;
258           else if (EQ (value_str, VALUE_FALSE))
259             state->settings->show_hidden = FALSE;
260           else
261             {
262               state->state = STATE_ERROR;
263               g_set_error (error,
264                            G_MARKUP_ERROR,
265                            G_MARKUP_ERROR_INVALID_CONTENT,
266                            _("Line %d, column %d: expected \"%s\" or \"%s\", but found \"%s\" instead"),
267                            line,
268                            col,
269                            VALUE_FALSE,
270                            VALUE_TRUE,
271                            value_str);
272             }
273         }
274       else
275         set_unexpected_element_error (state, line, col, element_name, error);
276
277       break;
278
279     case STATE_IN_LOCATION:
280     case STATE_IN_SHOW_HIDDEN:
281       set_unexpected_element_error (state, line, col, element_name, error);
282       break;
283
284     default:
285       g_assert_not_reached ();
286     }
287 }
288
289 static void
290 parse_end_element_cb (GMarkupParseContext *context,
291                       const char *element_name,
292                       gpointer data,
293                       GError **error)
294 {
295   struct parse_state *state;
296   int line, col;
297
298   state = data;
299   g_markup_parse_context_get_position (context, &line, &col);
300
301   switch (state->state)
302     {
303     case STATE_START:
304       g_assert_not_reached ();
305       break;
306
307     case STATE_END:
308       g_assert_not_reached ();
309       break;
310
311     case STATE_ERROR:
312       g_assert_not_reached ();
313       break;
314
315     case STATE_IN_TOPLEVEL:
316       if (EQ (element_name, ELEMENT_TOPLEVEL))
317         state->state = STATE_END;
318       else
319         set_unexpected_element_end_error (state, line, col, ELEMENT_TOPLEVEL, element_name, error);
320
321       break;
322
323     case STATE_IN_LOCATION:
324       if (EQ (element_name, ELEMENT_LOCATION))
325         state->state = STATE_IN_TOPLEVEL;
326       else
327         set_unexpected_element_end_error (state, line, col, ELEMENT_LOCATION, element_name, error);
328
329       break;
330
331     case STATE_IN_SHOW_HIDDEN:
332       if (EQ (element_name, ELEMENT_SHOW_HIDDEN))
333         state->state = STATE_IN_TOPLEVEL;
334       else
335         set_unexpected_element_end_error (state, line, col, ELEMENT_SHOW_HIDDEN, element_name, error);
336
337       break;
338
339     default:
340       g_assert_not_reached ();
341     }
342 }
343
344 static gboolean
345 parse_config (GtkFileChooserSettings *settings,
346               const char *contents,
347               GError **error)
348 {
349   GMarkupParser parser = { 0, };
350   GMarkupParseContext *context;
351   struct parse_state state;
352   gboolean retval;
353
354   parser.start_element = parse_start_element_cb;
355   parser.end_element = parse_end_element_cb;
356
357   state.settings = settings;
358   state.version = -1;
359   state.state = STATE_START;
360
361   context = g_markup_parse_context_new (&parser,
362                                         0,
363                                         &state,
364                                         NULL);
365
366   retval = g_markup_parse_context_parse (context, contents, -1, error);
367   g_markup_parse_context_free (context);
368
369   return retval;
370 }
371
372 static gboolean
373 read_config (GtkFileChooserSettings *settings,
374              GError **error)
375 {
376   char *filename;
377   char *contents;
378   gsize contents_len;
379   gboolean success;
380
381   filename = get_config_filename ();
382
383   success = g_file_get_contents (filename, &contents, &contents_len, error);
384   g_free (filename);
385
386   if (!success)
387     {
388       set_defaults (settings);
389       return FALSE;
390     }
391
392   success = parse_config (settings, contents, error);
393
394   g_free (contents);
395
396   return success;
397 }
398
399 static void
400 ensure_settings_read (GtkFileChooserSettings *settings)
401 {
402   if (settings->settings_read)
403     return;
404
405   /* NULL GError */
406   read_config (settings, NULL);
407
408   settings->settings_read = TRUE;
409 }
410
411 G_DEFINE_TYPE (GtkFileChooserSettings, _gtk_file_chooser_settings, G_TYPE_OBJECT)
412
413 static void
414 _gtk_file_chooser_settings_class_init (GtkFileChooserSettingsClass *class)
415 {
416 }
417
418 static void
419 _gtk_file_chooser_settings_init (GtkFileChooserSettings *settings)
420 {
421 }
422
423 GtkFileChooserSettings *
424 _gtk_file_chooser_settings_new (void)
425 {
426   return g_object_new (GTK_FILE_CHOOSER_SETTINGS_TYPE, NULL);
427 }
428
429 LocationMode
430 _gtk_file_chooser_settings_get_location_mode (GtkFileChooserSettings *settings)
431 {
432   ensure_settings_read (settings);
433   return settings->location_mode;
434 }
435
436 void
437 _gtk_file_chooser_settings_set_location_mode (GtkFileChooserSettings *settings,
438                                               LocationMode location_mode)
439 {
440   settings->location_mode = location_mode;
441 }
442
443 gboolean
444 _gtk_file_chooser_settings_get_show_hidden (GtkFileChooserSettings *settings)
445 {
446   ensure_settings_read (settings);
447   return settings->show_hidden;
448 }
449
450 void
451 _gtk_file_chooser_settings_set_show_hidden (GtkFileChooserSettings *settings,
452                                             gboolean show_hidden)
453 {
454   settings->show_hidden = show_hidden ? TRUE : FALSE;
455 }
456
457 static char *
458 settings_to_markup (GtkFileChooserSettings *settings)
459 {
460   const char *location_mode_str;
461   const char *show_hidden_str;
462
463   if (settings->location_mode == LOCATION_MODE_PATH_BAR)
464     location_mode_str = MODE_PATH_BAR;
465   else if (settings->location_mode == LOCATION_MODE_FILENAME_ENTRY)
466     location_mode_str = MODE_FILENAME_ENTRY;
467   else
468     {
469       g_assert_not_reached ();
470       return NULL;
471     }
472
473   show_hidden_str = settings->show_hidden ? VALUE_TRUE : VALUE_FALSE;
474
475   return g_strdup_printf
476     ("<" ELEMENT_TOPLEVEL ">\n"                                         /* <gtkfilechooser>               */
477      "  <" ELEMENT_LOCATION " " ATTRIBUTE_MODE "=\"%s\"/>\n"            /*   <location mode="path-bar"/>  */
478      "  <" ELEMENT_SHOW_HIDDEN " " ATTRIBUTE_VALUE "=\"%s\"/>\n"        /*   <show_hidden value="false"/> */
479      "</" ELEMENT_TOPLEVEL ">\n",                                       /* </gtkfilechooser>              */
480      location_mode_str,
481      show_hidden_str);
482 }
483
484 gboolean
485 _gtk_file_chooser_settings_save (GtkFileChooserSettings *settings,
486                                  GError                **error)
487 {
488   char *contents;
489   char *filename;
490   char *dirname;
491   gboolean retval;
492
493   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
494
495   contents = settings_to_markup (settings);
496
497   filename = get_config_filename ();
498   dirname = NULL;
499
500   retval = FALSE;
501
502   if (!g_file_set_contents (filename, contents, -1, NULL))
503     {
504       char *dirname;
505       int saved_errno;
506
507       /* Directory is not there? */
508
509       dirname = get_config_dirname ();
510       if (g_mkdir_with_parents (dirname, 0700) != 0) /* 0700 per the XDG basedir spec */
511         {
512           saved_errno = errno;
513           g_set_error (error,
514                        G_FILE_ERROR,
515                        g_file_error_from_errno (saved_errno),
516                        _("Could not create directory: %s"),
517                        dirname);
518           goto out;
519         }
520
521       if (!g_file_set_contents (filename, contents, -1, error))
522         goto out;
523     }
524
525   retval = TRUE;
526
527  out:
528
529   g_free (contents);
530   g_free (dirname);
531   g_free (filename);
532
533   return retval;
534 }