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