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