Code

8bac7cf9d808380b9bd6b8f5fa9e31eabb38dfb9
[inkscape.git] / src / preferences.cpp
1 /** @file
2  * @brief  Singleton class to access the preferences file - implementation
3  *
4  * Authors:
5  *   Krzysztof KosiƄski <tweenk.pl@gmail.com>
6  *
7  * Copyright (C) 2008 Authors
8  *
9  * Released under GNU GPL.  Read the file 'COPYING' for more information.
10  */
12 #include "preferences.h"
13 #include "preferences-skeleton.h"
14 #include "inkscape.h"
15 #include "xml/repr.h"
16 #include "xml/node-observer.h"
17 #include <glibmm/fileutils.h>
18 #include <glibmm/i18n.h>
19 #include <glib.h>
20 #include <glib/gstdio.h>
21 #include <gtkmm/messagedialog.h>
23 #define PREFERENCES_FILE_NAME "preferences.xml"
25 namespace Inkscape {
27 Preferences::Preferences() :
28     _prefs_basename(PREFERENCES_FILE_NAME),
29     _prefs_dir(""),
30     _prefs_filename(""),
31     _writable(false),
32     _prefs_doc(NULL)
33 {
34     // profile_path essentailly returns the argument prefixed by the profile directory.
35     gchar *path = profile_path(NULL);
36     _prefs_dir = path;
37     g_free(path);
38     
39     path = profile_path(_prefs_basename.data());
40     _prefs_filename = path;
41     g_free(path);
42     
43     _load();
44 }
46 Preferences::~Preferences()
47 {
48     // when the preferences are unloaded, save them
49     save();
50     Inkscape::GC::release(_prefs_doc);
51 }
53 /**
54  * @brief Load internal defaults
55  *
56  * In the future this will try to load the system-wide file before falling
57  * back to the internal defaults.
58  */
59 void Preferences::_loadDefaults()
60 {
61     _prefs_doc = sp_repr_read_mem(preferences_skeleton, PREFERENCES_SKELETON_SIZE, NULL);
62 }
64 /**
65  * @brief Load the user's customized preferences
66  *
67  * Tries to load the user's preferences.xml file. If there is none, creates it.
68  * Displays dialog boxes on any errors.
69  */
70 void Preferences::_load()
71 {
72     _loadDefaults();
73     
74     Glib::ustring not_saved = _("Inkscape will run with default settings, "
75                                 "and new settings will not be saved. ");
76     
77     // 1. Does the file exist?
78     if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_EXISTS)) {
79         // No - we need to create one.
80         // Does the profile directory exist?
81         if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_EXISTS)) {
82             // No - create the profile directory
83             if (g_mkdir(_prefs_dir.data(), 0755)) {
84                 // the creation failed
85                 _errorDialog(Glib::ustring::compose(_("Cannot create profile directory %1."),
86                     Glib::filename_to_utf8(_prefs_dir)), not_saved);
87                 return;
88             }
89             // create some subdirectories for user stuff
90             char const *user_dirs[] = {"keys", "templates", "icons", "extensions", "palettes", NULL};
91             for(int i=0; user_dirs[i]; ++i) {
92                 char *dir = profile_path(user_dirs[i]);
93                 g_mkdir(dir, 0755);
94                 g_free(dir);
95             }
96             
97         } else if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_IS_DIR)) {
98             // The profile dir is not actually a directory
99             _errorDialog(Glib::ustring::compose(_("%1 is not a valid directory."),
100                 Glib::filename_to_utf8(_prefs_dir)), not_saved);
101             return;
102         }
103         // The profile dir exists and is valid.
104         if (!g_file_set_contents(_prefs_filename.data(), preferences_skeleton, PREFERENCES_SKELETON_SIZE, NULL)) {
105             // The write failed.
106             _errorDialog(Glib::ustring::compose(_("Failed to create the preferences file %1."),
107                 Glib::filename_to_utf8(_prefs_filename)), not_saved);
108             return;
109         }
110         
111         // The prefs file was just created.
112         // We can return now and skip the rest of the load process.
113         _writable = true;
114         return;
115     }
116     
117     // Yes, the pref file exists.
118     // 2. Is it a regular file?
119     if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_IS_REGULAR)) {
120         _errorDialog(Glib::ustring::compose(_("The preferences file %1 is not a regular file."),
121             Glib::filename_to_utf8(_prefs_filename)), not_saved);
122         return;
123     }
124     
125     // 3. Is the file readable?
126     gchar *prefs_xml = NULL; gsize len = 0;
127     if (!g_file_get_contents(_prefs_filename.data(), &prefs_xml, &len, NULL)) {
128         _errorDialog(Glib::ustring::compose(_("The preferences file %1 could not be read."),
129             Glib::filename_to_utf8(_prefs_filename)), not_saved);
130         return;
131     }
132     // 4. Is it valid XML?
133     Inkscape::XML::Document *prefs_read = sp_repr_read_mem(prefs_xml, len, NULL);
134     g_free(prefs_xml);
135     if (!prefs_read) {
136         _errorDialog(Glib::ustring::compose(_("The preferences file %1 is not a valid XML document."),
137             Glib::filename_to_utf8(_prefs_filename)), not_saved);
138         return;
139     }
140     // 5. Basic sanity check: does the root element have a correct name?
141     if (strcmp(prefs_read->root()->name(), "inkscape")) {
142         _errorDialog(Glib::ustring::compose(_("The file %1 is not a valid Inkscape preferences file."),
143             Glib::filename_to_utf8(_prefs_filename)), not_saved);
144         Inkscape::GC::release(prefs_read);
145         return;
146     }
147     
148     // Merge the loaded prefs with defaults.
149     _prefs_doc->root()->mergeFrom(prefs_read->root(), "id");
150     Inkscape::GC::release(prefs_read);
151     _writable = true;
154 /**
155  * @brief Flush all pref changes to the XML file
156  */
157 void Preferences::save()
159     if (!_writable) return; // no-op if the prefs file is not writable
160     
161     // sp_repr_save_file uses utf-8 instead of the glib filename encoding.
162     // I don't know why filenames are kept in utf-8 in Inkscape and then
163     // converted to filename encoding when necessary through sepcial functions
164     // - wouldn't it be easier to keep things in the encoding they are supposed
165     // to be in?
166     Glib::ustring utf8name = Glib::filename_from_utf8(_prefs_filename);
167     if (utf8name.empty()) return;
168     sp_repr_save_file(_prefs_doc, utf8name.data());
171 void Preferences::addPrefsObserver(Inkscape::XML::NodeObserver *observer)
173     _prefs_doc->addSubtreeObserver(*observer);
178 // Now for the meat.
179 // Most of the logic is similar to former prefs-utils.cpp
182 /**
183  * @brief Check for the existence of a given pref key
184  * @param pref_key Preference key to check
185  * @return True if the key exists, false otherwise
186  */
187 bool Preferences::exists(Glib::ustring const &pref_key)
189     return _getNode(pref_key) != NULL;
192 /**
193  * @brief Get the number of sub-preferences of a given pref
194  * @param pref_key Preference key to check
195  * @return Number of sub-preferences
196  *
197  * Note: This does not count attributes, only child preferences.
198  */
199 unsigned int Preferences::childCount(Glib::ustring const &pref_key)
201     Inkscape::XML::Node *node = _getNode(pref_key);
202     return ( node ? node->childCount() : 0 );
205 /**
206  * @brief Get the key of the n-th sub-preference of the specified pref
207  * @param father_key Parent key
208  * @param n The zero-based index of the pref key to retrieve
209  * @return The key of the n-th sub-preference
210  */
211 Glib::ustring Preferences::getNthChild(Glib::ustring const &father_key, unsigned int n)
213     Inkscape::XML::Node *node = _getNode(father_key), *child;
214     if (!node) return "";
215     child = node->nthChild(n);
216     if (!child) return "";
217     if (child->attribute("id")) {
218         Glib::ustring child_key = father_key;
219         child_key += '.';
220         child_key += child->attribute("id");
221         return child_key;
222     }
223     return "";
227 /**
228  * @brief Create the preference with the specified key
229  * @return True if the node was created, false if it already existed
230  *
231  * This method is redundant, because the setters automatically create prefs
232  * if they don't already exist. It is only left to accomodate some legacy code
233  * which manipulates the DOM of the preferences file directly.
234  */
235 bool Preferences::create(Glib::ustring const &pref_key)
237     if (_getNode(pref_key)) return false;
238     _getNode(pref_key, true);
239     return true;
242 // getter methods
244 /**
245  * @brief Get a boolean attribute of a preference
246  * @param pref_key Key of he preference to retrieve
247  * @param attr Attribute to retrieve
248  * @param def The default value to return if the preference is not set
249  * @return The retrieved value
250  */
251 bool Preferences::getBool(Glib::ustring const &pref_key, Glib::ustring const &attr, bool def)
253     Inkscape::XML::Node *node = _getNode(pref_key);
254     if (!node) return def;
255     gchar const *rawstr = node->attribute(attr.data());
256     if(!rawstr || !rawstr[0]) return def;
257     Glib::ustring str = rawstr;
258     
259     // This is to handle legacy preferences using ints as booleans
260     if (str == "true" || str == "1") return true;
261     return false;
265 /**
266  * @brief Get an integer attribute of a preference
267  * @param pref_key Key of he preference to retrieve
268  * @param attr Attribute to retrieve
269  * @param def The default value to return if the preference is not set
270  * @return The retrieved value
271  */
272 int Preferences::getInt(Glib::ustring const &pref_key, Glib::ustring const &attr, int def)
274     Inkscape::XML::Node *node = _getNode(pref_key);
275     if (!node) return def;
276     gchar const *rawstr = node->attribute(attr.data());
277     if (!rawstr || !rawstr[0]) return def;
278     Glib::ustring str = rawstr;
279     // Protection against leftover getInt calls when the value is in fact a boolean
280     if (str == "true") return 1;
281     if (str == "false") return 0;
282     return atoi(str.data());
285 int Preferences::getIntLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, int def, int min, int max)
287     int value = getInt(pref_key, attr, def);
288     return ( value >= min && value <= max ? value : def);
291 /**
292  * @brief Get a floating point attribute of a preference
293  * @param pref_key Key of he preference to retrieve
294  * @param attr Attribute to retrieve
295  * @param def The default value to return if the preference is not set
296  * @return The retrieved value
297  */
298 double Preferences::getDouble(Glib::ustring const &pref_key, Glib::ustring const &attr, double def)
300     Inkscape::XML::Node *node = _getNode(pref_key);
301     if (!node) return def;
302     gchar const *str = node->attribute(attr.data());
303     if (!str) return def;
304     return g_ascii_strtod(str, NULL);
307 double Preferences::getDoubleLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, double def, double min, double max)
309     double value = getDouble(pref_key, attr, def);
310     return ( value >= min && value <= max ? value : def);
313 /**
314  * @brief Get a string attribute of a preference
315  * @param pref_key Key of he preference to retrieve
316  * @param attr Attribute to retrieve
317  * @param def The default value to return if the preference is not set
318  * @return The retrieved value
319  */
320 Glib::ustring Preferences::getString(Glib::ustring const &pref_key, Glib::ustring const &attr)
322     Inkscape::XML::Node *node = _getNode(pref_key);
323     if (!node) return "";
324     gchar const *str = node->attribute(attr.data());
325     if (!str) return "";
326     return Glib::ustring(str);
330 // setter methods
332 /**
333  * @brief Set a boolean attribute of a preference
334  * @param pref_key Key of the preference to modify
335  * @param attr Attribute to set
336  * @param value The new value of the pref attribute
337  */
338 void Preferences::setBool(Glib::ustring const &pref_key, Glib::ustring const &attr, bool value)
340     Inkscape::XML::Node *node = _getNode(pref_key, true);
341     node->setAttribute(attr.data(), ( value ? "true" : "false" ));
344 /**
345  * @brief Set an integer attribute of a preference
346  * @param pref_key Key of the preference to modify
347  * @param attr Attribute to set
348  * @param value The new value of the pref attribute
349  */
350 void Preferences::setInt(Glib::ustring const &pref_key, Glib::ustring const &attr, int value)
352     Inkscape::XML::Node *node = _getNode(pref_key, true);
353     gchar intstr[32];
354     g_snprintf(intstr, 32, "%d", value);
355     node->setAttribute(attr.data(), intstr);
358 /**
359  * @brief Set a floating point attribute of a preference
360  * @param pref_key Key of the preference to modify
361  * @param attr Attribute to set
362  * @param value The new value of the pref attribute
363  */
364 void Preferences::setDouble(Glib::ustring const &pref_key, Glib::ustring const &attr, double value)
366     Inkscape::XML::Node *node = _getNode(pref_key, true);
367     sp_repr_set_svg_double(node, attr.data(), value);
368     /*
369     gchar dblstr[32];
370     g_snprintf(dblstr, 32, "%g", value);
371     node->setAttribute(attr, dblstr);
372     */
375 /**
376  * @brief Set a string attribute of a preference
377  * @param pref_key Key of the preference to modify
378  * @param attr Attribute to set
379  * @param value The new value of the pref attribute
380  */
381 void Preferences::setString(Glib::ustring const &pref_key, Glib::ustring const &attr, Glib::ustring const &value)
383     Inkscape::XML::Node *node = _getNode(pref_key, true);
384     node->setAttribute(attr.data(), value.data());
387 /**
388  * @brief Get the XML node corresponding to the given pref key
389  * @param pref_key Preference key (path) to get
390  * @param create Whether to create the corresponding node if it doesn't exist
391  * @return XML node corresponding to the specified key
392  *
393  * The separator for key components is '.' (a dot). Derived from former
394  * inkscape_get_repr().
395  */
396 Inkscape::XML::Node *Preferences::_getNode(Glib::ustring const &pref_key, bool create)
398     Inkscape::XML::Node *node = _prefs_doc->root(), *child = NULL;
399     gchar **splits = g_strsplit(pref_key.data(), ".", 0);
400     int part_i = 0;
401     
402     while(splits[part_i]) {
403         for (child = node->firstChild(); child; child = child->next())
404             if (!strcmp(splits[part_i], child->attribute("id"))) break;
405         
406         // If the previous loop found a matching key, child now contains the node
407         // matching the processed key part. If no node was found then it is NULL.
408         if (!child) {
409             if (create) {
410                 // create the rest of the key
411                 while(splits[part_i]) {
412                     child = node->document()->createElement("group");
413                     child->setAttribute("id", splits[part_i]);
414                     node->appendChild(child);
415                     
416                     ++part_i;
417                     node = child;
418                 }
419                 g_strfreev(splits);
420                 return node;
421             } else {
422                 return NULL;
423             }
424         }
425         
426         ++part_i;
427         node = child;
428     }
429     g_strfreev(splits);
430     return node;
434 void Preferences::_errorDialog(Glib::ustring const &msg, Glib::ustring const &secondary)
436     if (Preferences::use_gui) {
437         Gtk::MessageDialog err(
438             msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
439         err.set_secondary_text(secondary);
440         err.run();
441     } else {
442         g_message("%s", msg.data());
443         g_message("%s", secondary.data());
444     }
447 bool Preferences::use_gui = true;
448 Preferences *Preferences::_instance = NULL;
451 } // namespace Inkscape
452  
453 /*
454   Local Variables:
455   mode:c++
456   c-file-style:"stroustrup"
457   c-file-offsets:((innamespace . 0)(inline-open . 0))
458   indent-tabs-mode:nil
459   fill-column:75
460   End:
461 */
462 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :