8bac7cf9d808380b9bd6b8f5fa9e31eabb38dfb9
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);
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 not_saved = _("Inkscape will run with default settings, "
75 "and new settings will not be saved. ");
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 }
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 }
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 }
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 }
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 }
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;
152 }
154 /**
155 * @brief Flush all pref changes to the XML file
156 */
157 void Preferences::save()
158 {
159 if (!_writable) return; // no-op if the prefs file is not writable
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());
169 }
171 void Preferences::addPrefsObserver(Inkscape::XML::NodeObserver *observer)
172 {
173 _prefs_doc->addSubtreeObserver(*observer);
174 }
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)
188 {
189 return _getNode(pref_key) != NULL;
190 }
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)
200 {
201 Inkscape::XML::Node *node = _getNode(pref_key);
202 return ( node ? node->childCount() : 0 );
203 }
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)
212 {
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 "";
224 }
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)
236 {
237 if (_getNode(pref_key)) return false;
238 _getNode(pref_key, true);
239 return true;
240 }
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)
252 {
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;
259 // This is to handle legacy preferences using ints as booleans
260 if (str == "true" || str == "1") return true;
261 return false;
262 }
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)
273 {
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());
283 }
285 int Preferences::getIntLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, int def, int min, int max)
286 {
287 int value = getInt(pref_key, attr, def);
288 return ( value >= min && value <= max ? value : def);
289 }
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)
299 {
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);
305 }
307 double Preferences::getDoubleLimited(Glib::ustring const &pref_key, Glib::ustring const &attr, double def, double min, double max)
308 {
309 double value = getDouble(pref_key, attr, def);
310 return ( value >= min && value <= max ? value : def);
311 }
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)
321 {
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);
327 }
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)
339 {
340 Inkscape::XML::Node *node = _getNode(pref_key, true);
341 node->setAttribute(attr.data(), ( value ? "true" : "false" ));
342 }
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)
351 {
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);
356 }
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)
365 {
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 */
373 }
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)
382 {
383 Inkscape::XML::Node *node = _getNode(pref_key, true);
384 node->setAttribute(attr.data(), value.data());
385 }
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)
397 {
398 Inkscape::XML::Node *node = _prefs_doc->root(), *child = NULL;
399 gchar **splits = g_strsplit(pref_key.data(), ".", 0);
400 int part_i = 0;
402 while(splits[part_i]) {
403 for (child = node->firstChild(); child; child = child->next())
404 if (!strcmp(splits[part_i], child->attribute("id"))) break;
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);
416 ++part_i;
417 node = child;
418 }
419 g_strfreev(splits);
420 return node;
421 } else {
422 return NULL;
423 }
424 }
426 ++part_i;
427 node = child;
428 }
429 g_strfreev(splits);
430 return node;
431 }
434 void Preferences::_errorDialog(Glib::ustring const &msg, Glib::ustring const &secondary)
435 {
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 }
445 }
447 bool Preferences::use_gui = true;
448 Preferences *Preferences::_instance = NULL;
451 } // namespace Inkscape
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 :