54ab79448b4bda1752401d14d0b0d0ba8b15c85c
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;
182 }
184 /**
185 * @brief Flush all pref changes to the XML file
186 */
187 void Preferences::save()
188 {
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());
199 }
201 void Preferences::addPrefsObserver(Inkscape::XML::NodeObserver *observer)
202 {
203 _prefs_doc->addSubtreeObserver(*observer);
204 }
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)
218 {
219 return _getNode(pref_key) != NULL;
220 }
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)
230 {
231 Inkscape::XML::Node *node = _getNode(pref_key);
232 return ( node ? node->childCount() : 0 );
233 }
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)
242 {
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 "";
254 }
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)
266 {
267 if (_getNode(pref_key)) return false;
268 _getNode(pref_key, true);
269 return true;
270 }
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)
282 {
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;
292 }
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)
303 {
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());
313 }
315 int Preferences::getIntLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, int def, int min, int max)
316 {
317 int value = getInt(pref_key, attr, def);
318 return ( value >= min && value <= max ? value : def);
319 }
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)
329 {
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);
335 }
337 double Preferences::getDoubleLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, double def, double min, double max)
338 {
339 double value = getDouble(pref_key, attr, def);
340 return ( value >= min && value <= max ? value : def);
341 }
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)
351 {
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);
357 }
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)
369 {
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" ));
375 }
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)
384 {
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);
389 }
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)
398 {
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 */
406 }
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)
415 {
416 Inkscape::XML::Node *node = _getNode(pref_key, true);
417 node->setAttribute(attr.data(), value.data());
418 }
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)
430 {
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;
464 }
467 void Preferences::_errorDialog(Glib::ustring const &msg, Glib::ustring const &secondary)
468 {
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 }
478 }
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 :