1 /** @file
2 * @brief Singleton class to access the preferences file in a convenient way.
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 #ifndef INKSCAPE_PREFSTORE_H
13 #define INKSCAPE_PREFSTORE_H
15 #include <glibmm/ustring.h>
16 #include <string>
17 #include <map>
18 #include <vector>
19 #include <climits>
20 #include <cfloat>
21 #include "xml/xml-forward.h"
23 class SPCSSAttr;
25 namespace Inkscape {
27 /**
28 * @brief Preference storage class
29 *
30 * This is a singleton that allows one to access the user preferences stored in
31 * the preferences.xml file. The preferences are stored in a file system-like
32 * hierarchy. They are generally typeless - it's up to the programmer to ensure
33 * that a given preference is always accessed as the correct type. The backend
34 * is not guaranteed to be tolerant to type mismatches.
35 *
36 * Preferences are identified by paths similar to file system paths. Components
37 * of the path are separated by a slash (/). As an additional requirement,
38 * the path must start with a slash, and not contain a trailing slash.
39 * An example of a correct path would be "/options/some_group/some_option".
40 *
41 * All preferences are loaded when the first singleton pointer is requested,
42 * or when the static load() method is called. Before loading, the static
43 * variable @c use_gui should be set accordingly. To save the preferences,
44 * the method save() or the static function unload() can be used.
45 *
46 * In future, this will be a virtual base from which specific backends
47 * derive (e.g. GConf, flat XML file...)
48 */
49 class Preferences {
50 public:
51 // #############################
52 // ## inner class definitions ##
53 // #############################
55 class Entry;
56 class Observer;
58 /**
59 * @brief Base class for preference observers
60 *
61 * If you want to watch for changes in the preferences, you'll have to
62 * derive a class from this one and override the notify() method.
63 */
64 class Observer {
65 friend class Preferences;
66 public:
67 /**
68 * @brief Constructor.
69 *
70 * Since each Observer is assigned to a single path, the base
71 * constructor takes this path as an argument. This prevents one from
72 * adding a single observer to multiple paths, but this is intentional
73 * to simplify the implementation of observers and notifications.
74 *
75 * After you add the object with Preferences::addObserver(), you will
76 * receive notifications for everything below the attachment point.
77 * You can also specify a single preference as the watch point.
78 * For example, watching the directory "/foo" will give you notifications
79 * about "/foo/some_pref" as well as "/foo/some_dir/other_pref".
80 * Watching the preference "/options/some_group/some_option" will only
81 * generate notifications when this single preference changes.
82 *
83 * @param path Preference path the observer should watch
84 */
85 Observer(Glib::ustring const &path);
86 virtual ~Observer();
87 /**
88 * @brief Notification about a preference change
89 * @param new_val Entry object containing information about
90 * the modified preference
91 */
92 virtual void notify(Preferences::Entry const &new_val) = 0;
94 Glib::ustring const observed_path; ///< Path which the observer watches
95 private:
96 void *_data; ///< additional data used by the implementation while the observer is active
97 };
100 /**
101 * @brief Data type representing a typeless value of a preference
102 *
103 * This is passed to the observer in the notify() method.
104 * To retrieve useful data from it, use its member functions. Setting
105 * any preference using the Preferences class invalidates this object,
106 * so use its get methods before doing so.
107 */
108 class Entry {
109 friend class Preferences; // Preferences class has to access _value
110 public:
111 ~Entry() {}
112 Entry(Entry const &other) : _pref_path(other._pref_path), _value(other._value) {}
113 /**
114 * @brief Check whether the received entry is valid.
115 * @return If false, the default value will be returned by the getters.
116 */
117 bool isValid() const { return _value != NULL; }
119 /**
120 * @brief Interpret the preference as a Boolean value.
121 * @param def Default value if the preference is not set
122 */
123 inline bool getBool(bool def=false) const;
124 /**
125 * @brief Interpret the preference as an integer.
126 * @param def Default value if the preference is not set
127 */
128 inline int getInt(int def=0) const;
129 /**
130 * @brief Interpret the preference as a limited integer.
131 *
132 * This method will return the default value if the interpreted value is
133 * larger than @c max or smaller than @c min. Do not use to store
134 * Boolean values as integers.
135 *
136 * @param def Default value if the preference is not set
137 * @param min Minimum value allowed to return
138 * @param max Maximum value allowed to return
139 */
140 inline int getIntLimited(int def=0, int min=INT_MIN, int max=INT_MAX) const;
141 /**
142 * @brief Interpret the preference as a floating point value.
143 * @param def Default value if the preference is not set
144 */
145 inline double getDouble(double def=0.0) const;
146 /**
147 * @brief Interpret the preference as a limited floating point value.
148 *
149 * This method will return the default value if the interpreted value is
150 * larger than @c max or smaller than @c min.
151 *
152 * @param def Default value if the preference is not set
153 * @param min Minimum value allowed to return
154 * @param max Maximum value allowed to return
155 */
156 inline double getDoubleLimited(double def=0.0, double min=DBL_MIN, double max=DBL_MAX) const;
157 /**
158 * @brief Interpret the preference as an UTF-8 string.
159 *
160 * To store a filename, convert it using Glib::filename_to_utf8().
161 */
162 inline Glib::ustring getString() const;
163 /**
164 * @brief Interpret the preference as a CSS style.
165 * @return A CSS style that has to be unrefed when no longer necessary.
166 */
167 inline SPCSSAttr *getStyle() const;
168 /**
169 * @brief Interpret the preference as a CSS style with directory-based
170 * inheritance
171 *
172 * This function will look up the preferences with the same entry name
173 * in ancestor directories and return the inherited CSS style.
174 *
175 * @return Inherited CSS style that has to be unrefed after use.
176 */
177 inline SPCSSAttr *getInheritedStyle() const;
179 /**
180 * @brief Get the full path of the preference described by this Entry.
181 */
182 Glib::ustring const &getPath() const { return _pref_path; }
183 /**
184 * @brief Get the last component of the preference's path
185 *
186 * E.g. For "/options/some_group/some_option" it will return "some_option".
187 */
188 Glib::ustring getEntryName() const;
189 private:
190 Entry(Glib::ustring const &path, void const *v) : _pref_path(path), _value(v) {}
192 Glib::ustring _pref_path;
193 void const *_value;
194 };
196 // utility methods
198 /**
199 * @name Save preferences to the disk.
200 * @{
201 */
203 /**
204 * @brief Save all preferences to the hard disk.
205 *
206 * For some backends, the preferences may be saved as they are modified.
207 * Not calling this method doesn't guarantee the preferences are unmodified
208 * the next time Inkscape runs.
209 */
210 void save();
211 /**
212 * @brief Check whether saving the preferences will have any effect.
213 */
214 bool isWritable() { return _writable; }
215 /*@}*/
217 /**
218 * @name Iterate over directories and entries.
219 * @{
220 */
222 /**
223 * @brief Get all entries from the specified directory
224 *
225 * This method will return a vector populated with preference entries
226 * from the specified directory. Subdirectories will not be represented.
227 */
228 std::vector<Entry> getAllEntries(Glib::ustring const &path);
229 /**
230 * @brief Get all subdirectories of the specified directory
231 *
232 * This will return a vector populated with full paths to the subdirectories
233 * present in the specified @c path.
234 */
235 std::vector<Glib::ustring> getAllDirs(Glib::ustring const &path);
236 /*@}*/
238 /**
239 * @name Retrieve data from the preference storage.
240 * @{
241 */
242 /**
243 * @brief Retrieve a Boolean value
244 * @param pref_path Path to the retrieved preference
245 * @param def The default value to return if the preference is not set
246 */
247 bool getBool(Glib::ustring const &pref_path, bool def=false) {
248 return getEntry(pref_path).getBool(def);
249 }
250 /**
251 * @brief Retrieve an integer
252 * @param pref_path Path to the retrieved preference
253 * @param def The default value to return if the preference is not set
254 */
255 int getInt(Glib::ustring const &pref_path, int def=0) {
256 return getEntry(pref_path).getInt(def);
257 }
258 /**
259 * @brief Retrieve a limited integer
260 *
261 * The default value is returned if the actual value is larger than @c max
262 * or smaller than @c min. Do not use to store Boolean values.
263 *
264 * @param pref_path Path to the retrieved preference
265 * @param def The default value to return if the preference is not set
266 * @param min Minimum value to return
267 * @param max Maximum value to return
268 */
269 int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX) {
270 return getEntry(pref_path).getIntLimited(def, min, max);
271 }
272 double getDouble(Glib::ustring const &pref_path, double def=0.0) {
273 return getEntry(pref_path).getDouble(def);
274 }
275 /**
276 * @brief Retrieve a limited floating point value
277 *
278 * The default value is returned if the actual value is larger than @c max
279 * or smaller than @c min.
280 *
281 * @param pref_path Path to the retrieved preference
282 * @param def The default value to return if the preference is not set
283 * @param min Minimum value to return
284 * @param max Maximum value to return
285 */
286 double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX) {
287 return getEntry(pref_path).getDoubleLimited(def, min, max);
288 }
289 /**
290 * @brief Retrieve an UTF-8 string
291 * @param pref_path Path to the retrieved preference
292 */
293 Glib::ustring getString(Glib::ustring const &pref_path) {
294 return getEntry(pref_path).getString();
295 }
296 /**
297 * @brief Retrieve a CSS style
298 * @param pref_path Path to the retrieved preference
299 * @return A CSS style that has to be unrefed after use.
300 */
301 SPCSSAttr *getStyle(Glib::ustring const &pref_path) {
302 return getEntry(pref_path).getStyle();
303 }
304 /**
305 * @brief Retrieve an inherited CSS style
306 *
307 * This method will look up preferences with the same entry name in ancestor
308 * directories and return a style obtained by inheriting properties from
309 * ancestor styles.
310 *
311 * @param pref_path Path to the retrieved preference
312 * @return An inherited CSS style that has to be unrefed after use.
313 */
314 SPCSSAttr *getInheritedStyle(Glib::ustring const &pref_path) {
315 return getEntry(pref_path).getInheritedStyle();
316 }
317 /**
318 * @brief Retrieve a preference entry without specifying its type
319 */
320 Entry const getEntry(Glib::ustring const &pref_path);
321 /*@}*/
323 /**
324 * @name Update preference values.
325 * @{
326 */
328 /**
329 * @brief Set a Boolean value
330 */
331 void setBool(Glib::ustring const &pref_path, bool value);
332 /**
333 * @brief Set an integer value
334 */
335 void setInt(Glib::ustring const &pref_path, int value);
336 /**
337 * @brief Set a floating point value
338 */
339 void setDouble(Glib::ustring const &pref_path, double value);
340 /**
341 * @brief Set an UTF-8 string value
342 */
343 void setString(Glib::ustring const &pref_path, Glib::ustring const &value);
344 /**
345 * @brief Set a CSS style
346 */
347 void setStyle(Glib::ustring const &pref_path, SPCSSAttr *style);
348 /**
349 * @brief Merge a CSS style with the current preference value
350 *
351 * This method is similar to setStyle(), except that it merges the style
352 * rather than replacing it. This means that if @c style doesn't have
353 * a property set, it is left unchanged in the style stored in
354 * the preferences.
355 */
356 void mergeStyle(Glib::ustring const &pref_path, SPCSSAttr *style);
357 /*@}*/
359 /**
360 * @name Receive notifications about preference changes.
361 * @{
362 */
363 /**
364 * @brief Register a preference observer
365 */
366 void addObserver(Observer &);
367 /**
368 * @brief Remove an observer an prevent further notifications to it.
369 */
370 void removeObserver(Observer &);
371 /*@}*/
373 /**
374 * @name Access and manipulate the Preferences object.
375 */
377 /**
378 * @brief Access the singleton Preferences object.
379 */
380 static Preferences *get() {
381 if (!_instance) _instance = new Preferences();
382 return _instance;
383 }
384 /**
385 * @brief Load the preferences.
386 *
387 * This method is automatically called from get(). It exists to supress
388 * possible compiler warnings over unused variables.
389 */
390 static void load() {
391 if (!_instance) _instance = new Preferences();
392 }
393 /**
394 * @brief Unload all preferences and store them on the hard disk.
395 *
396 * This deletes the singleton object. Calling get() after this function is
397 */
398 static void unload() {
399 if(_instance)
400 {
401 delete _instance;
402 _instance = NULL;
403 }
404 }
406 /**
407 * @brief Whether to use GUI error notifications
408 *
409 * Set this to false when running Inkscape in command-line mode.
410 * Preference-related warnings will be printed to the console.
411 */
412 static bool use_gui;
413 /*@}*/
415 protected:
416 /* helper methods used by Entry
417 * This will enable using the same Entry class with different backends.
418 * For now, however, those methods are not virtual. These methods assume
419 * that v._value is not NULL
420 */
421 bool _extractBool(Entry const &v);
422 int _extractInt(Entry const &v);
423 double _extractDouble(Entry const &v);
424 Glib::ustring _extractString(Entry const &v);
425 SPCSSAttr *_extractStyle(Entry const &v);
426 SPCSSAttr *_extractInheritedStyle(Entry const &v);
428 private:
429 Preferences();
430 ~Preferences();
431 void _load();
432 void _loadDefaults();
433 void _getRawValue(Glib::ustring const &path, gchar const *&result);
434 void _setRawValue(Glib::ustring const &path, gchar const *value);
435 void _errorDialog(Glib::ustring const &, Glib::ustring const &);
436 void _keySplit(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key);
437 XML::Node *_getNode(Glib::ustring const &pref_path, bool create=false);
438 XML::Node *_findObserverNode(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key, bool create);
440 // disable copying
441 Preferences(Preferences const &);
442 Preferences operator=(Preferences const &);
444 std::string _prefs_basename; ///< Basename of the prefs file
445 std::string _prefs_dir; ///< Directory in which to look for the prefs file
446 std::string _prefs_filename; ///< Full filename (with directory) of the prefs file
447 bool _writable; ///< Will the preferences be saved at exit?
448 XML::Document *_prefs_doc; ///< XML document storing all the preferences
450 /// Wrapper class for XML node observers
451 class PrefNodeObserver;
453 typedef std::map<Observer *, PrefNodeObserver *> _ObsMap;
454 /// Map that keeps track of wrappers assigned to PrefObservers
455 _ObsMap _observer_map;
457 // privilege escalation methods for PrefNodeObserver
458 static Entry const _create_pref_value(Glib::ustring const &, void const *ptr);
459 static void *_get_pref_observer_data(Observer &o) { return o._data; }
461 static Preferences *_instance;
463 friend class PrefNodeObserver;
464 friend class Entry;
465 };
467 /* Trivial inline Preferences::Entry functions.
468 * In fact only the _extract* methods do something, the rest is delegation
469 * to avoid duplication of code. There should be no performance hit if
470 * compiled with -finline-functions.
471 */
473 inline bool Preferences::Entry::getBool(bool def) const
474 {
475 if (!this->isValid()) return def;
476 return Inkscape::Preferences::get()->_extractBool(*this);
477 }
479 inline int Preferences::Entry::getInt(int def) const
480 {
481 if (!this->isValid()) return def;
482 return Inkscape::Preferences::get()->_extractInt(*this);
483 }
485 inline int Preferences::Entry::getIntLimited(int def, int min, int max) const
486 {
487 if (!this->isValid()) return def;
488 int val = Inkscape::Preferences::get()->_extractInt(*this);
489 return ( val >= min && val <= max ? val : def );
490 }
492 inline double Preferences::Entry::getDouble(double def) const
493 {
494 if (!this->isValid()) return def;
495 return Inkscape::Preferences::get()->_extractDouble(*this);
496 }
498 inline double Preferences::Entry::getDoubleLimited(double def, double min, double max) const
499 {
500 if (!this->isValid()) return def;
501 double val = Inkscape::Preferences::get()->_extractDouble(*this);
502 return ( val >= min && val <= max ? val : def );
503 }
505 inline Glib::ustring Preferences::Entry::getString() const
506 {
507 if (!this->isValid()) return "";
508 return Inkscape::Preferences::get()->_extractString(*this);
509 }
511 inline SPCSSAttr *Preferences::Entry::getStyle() const
512 {
513 if (!this->isValid()) return NULL;
514 return Inkscape::Preferences::get()->_extractStyle(*this);
515 }
517 inline SPCSSAttr *Preferences::Entry::getInheritedStyle() const
518 {
519 if (!this->isValid()) return NULL;
520 return Inkscape::Preferences::get()->_extractInheritedStyle(*this);
521 }
523 inline Glib::ustring Preferences::Entry::getEntryName() const
524 {
525 Glib::ustring path_base = _pref_path;
526 path_base.erase(0, path_base.rfind('/') + 1);
527 return path_base;
528 }
530 } // namespace Inkscape
532 #endif // INKSCAPE_PREFSTORE_H
534 /*
535 Local Variables:
536 mode:c++
537 c-file-style:"stroustrup"
538 c-file-offsets:((innamespace . 0)(inline-open . 0))
539 indent-tabs-mode:nil
540 fill-column:75
541 End:
542 */
543 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :