Code

54ab79448b4bda1752401d14d0b0d0ba8b15c85c
[inkscape.git] / src / preferences.cpp
1 /** @file
2  * @brief  Singleton class to access the preferences file - implementation
3  */
4 /* Authors:
5  *   Krzysztof Kosinski <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);
39     path = profile_path(_prefs_basename.data());
40     _prefs_filename = path;
41     g_free(path);
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();
74     Glib::ustring const not_saved = _("Inkscape will run with default settings, "
75                                 "and new settings will not be saved. ");
77     // NOTE: After we upgrade to Glib 2.16, use Glib::ustring::compose
79     // 1. Does the file exist?
80     if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_EXISTS)) {
81         // No - we need to create one.
82         // Does the profile directory exist?
83         if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_EXISTS)) {
84             // No - create the profile directory
85             if (g_mkdir(_prefs_dir.data(), 0755)) {
86                 // the creation failed
87                 //_errorDialog(Glib::ustring::compose(_("Cannot create profile directory %1."),
88                 //    Glib::filename_to_utf8(_prefs_dir)), not_saved);
89                 gchar *msg = g_strdup_printf(_("Cannot create profile directory %s."),
90                     Glib::filename_to_utf8(_prefs_dir).data());
91                 _errorDialog(msg, not_saved);
92                 g_free(msg);
93                 return;
94             }
95             // create some subdirectories for user stuff
96             char const *user_dirs[] = {"keys", "templates", "icons", "extensions", "palettes", NULL};
97             for(int i=0; user_dirs[i]; ++i) {
98                 char *dir = profile_path(user_dirs[i]);
99                 g_mkdir(dir, 0755);
100                 g_free(dir);
101             }
103         } else if (!g_file_test(_prefs_dir.data(), G_FILE_TEST_IS_DIR)) {
104             // The profile dir is not actually a directory
105             //_errorDialog(Glib::ustring::compose(_("%1 is not a valid directory."),
106             //    Glib::filename_to_utf8(_prefs_dir)), not_saved);
107             gchar *msg = g_strdup_printf(_("%s is not a valid directory."),
108                 Glib::filename_to_utf8(_prefs_dir).data());
109             _errorDialog(msg, not_saved);
110             g_free(msg);
111             return;
112         }
113         // The profile dir exists and is valid.
114         if (!g_file_set_contents(_prefs_filename.data(), preferences_skeleton, PREFERENCES_SKELETON_SIZE, NULL)) {
115             // The write failed.
116             //_errorDialog(Glib::ustring::compose(_("Failed to create the preferences file %1."),
117             //    Glib::filename_to_utf8(_prefs_filename)), not_saved);
118             gchar *msg = g_strdup_printf(_("Failed to create the preferences file %s."),
119                 Glib::filename_to_utf8(_prefs_filename).data());
120             _errorDialog(msg, not_saved);
121             g_free(msg);
122             return;
123         }
125         // The prefs file was just created.
126         // We can return now and skip the rest of the load process.
127         _writable = true;
128         return;
129     }
131     // Yes, the pref file exists.
132     // 2. Is it a regular file?
133     if (!g_file_test(_prefs_filename.data(), G_FILE_TEST_IS_REGULAR)) {
134         //_errorDialog(Glib::ustring::compose(_("The preferences file %1 is not a regular file."),
135         //    Glib::filename_to_utf8(_prefs_filename)), not_saved);
136         gchar *msg = g_strdup_printf(_("The preferences file %s is not a regular file."),
137             Glib::filename_to_utf8(_prefs_filename).data());
138         _errorDialog(msg, not_saved);
139         g_free(msg);
140         return;
141     }
143     // 3. Is the file readable?
144     gchar *prefs_xml = NULL; gsize len = 0;
145     if (!g_file_get_contents(_prefs_filename.data(), &prefs_xml, &len, NULL)) {
146         //_errorDialog(Glib::ustring::compose(_("The preferences file %1 could not be read."),
147         //    Glib::filename_to_utf8(_prefs_filename)), not_saved);
148         gchar *msg = g_strdup_printf(_("The preferences file %s could not be read."),
149             Glib::filename_to_utf8(_prefs_filename).data());
150         _errorDialog(msg, not_saved);
151         g_free(msg);
152         return;
153     }
154     // 4. Is it valid XML?
155     Inkscape::XML::Document *prefs_read = sp_repr_read_mem(prefs_xml, len, NULL);
156     g_free(prefs_xml);
157     if (!prefs_read) {
158         //_errorDialog(Glib::ustring::compose(_("The preferences file %1 is not a valid XML document."),
159         //    Glib::filename_to_utf8(_prefs_filename)), not_saved);
160         gchar *msg = g_strdup_printf(_("The preferences file %s is not a valid XML document."),
161             Glib::filename_to_utf8(_prefs_filename).data());
162         _errorDialog(msg, not_saved);
163         g_free(msg);
164         return;
165     }
166     // 5. Basic sanity check: does the root element have a correct name?
167     if (strcmp(prefs_read->root()->name(), "inkscape")) {
168         //_errorDialog(Glib::ustring::compose(_("The file %1 is not a valid Inkscape preferences file."),
169         //    Glib::filename_to_utf8(_prefs_filename)), not_saved);
170         gchar *msg = g_strdup_printf(_("The file %s is not a valid Inkscape preferences file."),
171             Glib::filename_to_utf8(_prefs_filename).data());
172         _errorDialog(msg, not_saved);
173         g_free(msg);
174         Inkscape::GC::release(prefs_read);
175         return;
176     }
178     // Merge the loaded prefs with defaults.
179     _prefs_doc->root()->mergeFrom(prefs_read->root(), "id");
180     Inkscape::GC::release(prefs_read);
181     _writable = true;
184 /**
185  * @brief Flush all pref changes to the XML file
186  */
187 void Preferences::save()
189     if (!_writable) return; // no-op if the prefs file is not writable
191     // sp_repr_save_file uses utf-8 instead of the glib filename encoding.
192     // I don't know why filenames are kept in utf-8 in Inkscape and then
193     // converted to filename encoding when necessary through sepcial functions
194     // - wouldn't it be easier to keep things in the encoding they are supposed
195     // to be in?
196     Glib::ustring utf8name = Glib::filename_from_utf8(_prefs_filename);
197     if (utf8name.empty()) return;
198     sp_repr_save_file(_prefs_doc, utf8name.data());
201 void Preferences::addPrefsObserver(Inkscape::XML::NodeObserver *observer)
203     _prefs_doc->addSubtreeObserver(*observer);
208 // Now for the meat.
209 // Most of the logic is similar to former prefs-utils.cpp
212 /**
213  * @brief Check for the existence of a given pref key
214  * @param pref_key Preference key to check
215  * @return True if the key exists, false otherwise
216  */
217 bool Preferences::exists(Glib::ustring const &pref_key)
219     return _getNode(pref_key) != NULL;
222 /**
223  * @brief Get the number of sub-preferences of a given pref
224  * @param pref_key Preference key to check
225  * @return Number of sub-preferences
226  *
227  * Note: This does not count attributes, only child preferences.
228  */
229 unsigned int Preferences::childCount(Glib::ustring const &pref_key)
231     Inkscape::XML::Node *node = _getNode(pref_key);
232     return ( node ? node->childCount() : 0 );
235 /**
236  * @brief Get the key of the n-th sub-preference of the specified pref
237  * @param father_key Parent key
238  * @param n The zero-based index of the pref key to retrieve
239  * @return The key of the n-th sub-preference
240  */
241 Glib::ustring Preferences::getNthChild(Glib::ustring const &father_key, unsigned int n)
243     Inkscape::XML::Node *node = _getNode(father_key), *child;
244     if (!node) return "";
245     child = node->nthChild(n);
246     if (!child) return "";
247     if (child->attribute("id")) {
248         Glib::ustring child_key = father_key;
249         child_key += '.';
250         child_key += child->attribute("id");
251         return child_key;
252     }
253     return "";
257 /**
258  * @brief Create the preference with the specified key
259  * @return True if the node was created, false if it already existed
260  *
261  * This method is redundant, because the setters automatically create prefs
262  * if they don't already exist. It is only left to accomodate some legacy code
263  * which manipulates the DOM of the preferences file directly.
264  */
265 bool Preferences::create(Glib::ustring const &pref_key)
267     if (_getNode(pref_key)) return false;
268     _getNode(pref_key, true);
269     return true;
272 // getter methods
274 /**
275  * @brief Get a boolean attribute of a preference
276  * @param pref_key Key of he preference to retrieve
277  * @param attr Attribute to retrieve
278  * @param def The default value to return if the preference is not set
279  * @return The retrieved value
280  */
281 bool Preferences::getBool(Glib::ustring const &pref_key, Glib::ustring const &attr, bool def)
283     Inkscape::XML::Node *node = _getNode(pref_key);
284     if (!node) return def;
285     gchar const *rawstr = node->attribute(attr.data());
286     if(!rawstr || !rawstr[0]) return def;
287     Glib::ustring str = rawstr;
289     // This is to handle legacy preferences using ints as booleans
290     if (str == "true" || str == "1") return true;
291     return false;
295 /**
296  * @brief Get an integer attribute of a preference
297  * @param pref_key Key of he preference to retrieve
298  * @param attr Attribute to retrieve
299  * @param def The default value to return if the preference is not set
300  * @return The retrieved value
301  */
302 int Preferences::getInt(Glib::ustring const &pref_key, Glib::ustring const &attr, int def)
304     Inkscape::XML::Node *node = _getNode(pref_key);
305     if (!node) return def;
306     gchar const *rawstr = node->attribute(attr.data());
307     if (!rawstr || !rawstr[0]) return def;
308     Glib::ustring str = rawstr;
309     // Protection against leftover getInt calls when the value is in fact a boolean
310     if (str == "true") return 1;
311     if (str == "false") return 0;
312     return atoi(str.data());
315 int Preferences::getIntLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, int def, int min, int max)
317     int value = getInt(pref_key, attr, def);
318     return ( value >= min && value <= max ? value : def);
321 /**
322  * @brief Get a floating point attribute of a preference
323  * @param pref_key Key of he preference to retrieve
324  * @param attr Attribute to retrieve
325  * @param def The default value to return if the preference is not set
326  * @return The retrieved value
327  */
328 double Preferences::getDouble(Glib::ustring const &pref_key, Glib::ustring const &attr, double def)
330     Inkscape::XML::Node *node = _getNode(pref_key);
331     if (!node) return def;
332     gchar const *str = node->attribute(attr.data());
333     if (!str) return def;
334     return g_ascii_strtod(str, NULL);
337 double Preferences::getDoubleLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, double def, double min, double max)
339     double value = getDouble(pref_key, attr, def);
340     return ( value >= min && value <= max ? value : def);
343 /**
344  * @brief Get a string attribute of a preference
345  * @param pref_key Key of he preference to retrieve
346  * @param attr Attribute to retrieve
347  * @param def The default value to return if the preference is not set
348  * @return The retrieved value
349  */
350 Glib::ustring Preferences::getString(Glib::ustring const &pref_key, Glib::ustring const &attr)
352     Inkscape::XML::Node *node = _getNode(pref_key);
353     if (!node) return "";
354     gchar const *str = node->attribute(attr.data());
355     if (!str) return "";
356     return Glib::ustring(str);
360 // setter methods
362 /**
363  * @brief Set a boolean attribute of a preference
364  * @param pref_key Key of the preference to modify
365  * @param attr Attribute to set
366  * @param value The new value of the pref attribute
367  */
368 void Preferences::setBool(Glib::ustring const &pref_key, Glib::ustring const &attr, bool value)
370     /// @todo Boolean values should be stored as "true" and "false",
371     /// but this is not possible ude to an interaction with event contexts.
372     /// Investigate this in depth.
373     Inkscape::XML::Node *node = _getNode(pref_key, true);
374     node->setAttribute(attr.data(), ( value ? "1" : "0" ));
377 /**
378  * @brief Set an integer attribute of a preference
379  * @param pref_key Key of the preference to modify
380  * @param attr Attribute to set
381  * @param value The new value of the pref attribute
382  */
383 void Preferences::setInt(Glib::ustring const &pref_key, Glib::ustring const &attr, int value)
385     Inkscape::XML::Node *node = _getNode(pref_key, true);
386     gchar intstr[32];
387     g_snprintf(intstr, 32, "%d", value);
388     node->setAttribute(attr.data(), intstr);
391 /**
392  * @brief Set a floating point attribute of a preference
393  * @param pref_key Key of the preference to modify
394  * @param attr Attribute to set
395  * @param value The new value of the pref attribute
396  */
397 void Preferences::setDouble(Glib::ustring const &pref_key, Glib::ustring const &attr, double value)
399     Inkscape::XML::Node *node = _getNode(pref_key, true);
400     sp_repr_set_svg_double(node, attr.data(), value);
401     /*
402     gchar dblstr[32];
403     g_snprintf(dblstr, 32, "%g", value);
404     node->setAttribute(attr, dblstr);
405     */
408 /**
409  * @brief Set a string attribute of a preference
410  * @param pref_key Key of the preference to modify
411  * @param attr Attribute to set
412  * @param value The new value of the pref attribute
413  */
414 void Preferences::setString(Glib::ustring const &pref_key, Glib::ustring const &attr, Glib::ustring const &value)
416     Inkscape::XML::Node *node = _getNode(pref_key, true);
417     node->setAttribute(attr.data(), value.data());
420 /**
421  * @brief Get the XML node corresponding to the given pref key
422  * @param pref_key Preference key (path) to get
423  * @param create Whether to create the corresponding node if it doesn't exist
424  * @return XML node corresponding to the specified key
425  *
426  * The separator for key components is '.' (a dot). Derived from former
427  * inkscape_get_repr().
428  */
429 Inkscape::XML::Node *Preferences::_getNode(Glib::ustring const &pref_key, bool create)
431     Inkscape::XML::Node *node = _prefs_doc->root(), *child = NULL;
432     gchar **splits = g_strsplit(pref_key.data(), ".", 0);
433     int part_i = 0;
435     while(splits[part_i]) {
436         for (child = node->firstChild(); child; child = child->next())
437             if (!strcmp(splits[part_i], child->attribute("id"))) break;
439         // If the previous loop found a matching key, child now contains the node
440         // matching the processed key part. If no node was found then it is NULL.
441         if (!child) {
442             if (create) {
443                 // create the rest of the key
444                 while(splits[part_i]) {
445                     child = node->document()->createElement("group");
446                     child->setAttribute("id", splits[part_i]);
447                     node->appendChild(child);
449                     ++part_i;
450                     node = child;
451                 }
452                 g_strfreev(splits);
453                 return node;
454             } else {
455                 return NULL;
456             }
457         }
459         ++part_i;
460         node = child;
461     }
462     g_strfreev(splits);
463     return node;
467 void Preferences::_errorDialog(Glib::ustring const &msg, Glib::ustring const &secondary)
469     if (Preferences::use_gui) {
470         Gtk::MessageDialog err(
471             msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
472         err.set_secondary_text(secondary);
473         err.run();
474     } else {
475         g_message("%s", msg.data());
476         g_message("%s", secondary.data());
477     }
480 bool Preferences::use_gui = true;
481 Preferences *Preferences::_instance = NULL;
484 } // namespace Inkscape
486 /*
487   Local Variables:
488   mode:c++
489   c-file-style:"stroustrup"
490   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
491   indent-tabs-mode:nil
492   fill-column:99
493   End:
494 */
495 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :