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 <string>
16 #include <map>
17 #include <vector>
18 #include <climits>
19 #include <cfloat>
20 #include <glibmm/ustring.h>
21 #include "xml/xml-forward.h"
22 #include "xml/repr.h"
24 class SPCSSAttr;
26 namespace Inkscape {
28 /**
29 * @brief Preference storage class
30 *
31 * This is a singleton that allows one to access the user preferences stored in
32 * the preferences.xml file. The preferences are stored in a file system-like
33 * hierarchy. They are generally typeless - it's up to the programmer to ensure
34 * that a given preference is always accessed as the correct type. The backend
35 * is not guaranteed to be tolerant to type mismatches.
36 *
37 * Preferences are identified by paths similar to file system paths. Components
38 * of the path are separated by a slash (/). As an additional requirement,
39 * the path must start with a slash, and not contain a trailing slash.
40 * An example of a correct path would be "/options/some_group/some_option".
41 *
42 * All preferences are loaded when the first singleton pointer is requested,
43 * or when the static load() method is called. Before loading, the static
44 * variable @c use_gui should be set accordingly. To save the preferences,
45 * the method save() or the static function unload() can be used.
46 *
47 * In future, this will be a virtual base from which specific backends
48 * derive (e.g. GConf, flat XML file...)
49 */
50 class Preferences {
51 public:
52 // #############################
53 // ## inner class definitions ##
54 // #############################
56 class Entry;
57 class Observer;
59 /**
60 * @brief Base class for preference observers
61 *
62 * If you want to watch for changes in the preferences, you'll have to
63 * derive a class from this one and override the notify() method.
64 */
65 class Observer {
66 friend class Preferences;
67 public:
69 /**
70 * @brief Constructor.
71 *
72 * Since each Observer is assigned to a single path, the base
73 * constructor takes this path as an argument. This prevents one from
74 * adding a single observer to multiple paths, but this is intentional
75 * to simplify the implementation of observers and notifications.
76 *
77 * After you add the object with Preferences::addObserver(), you will
78 * receive notifications for everything below the attachment point.
79 * You can also specify a single preference as the watch point.
80 * For example, watching the directory "/foo" will give you notifications
81 * about "/foo/some_pref" as well as "/foo/some_dir/other_pref".
82 * Watching the preference "/options/some_group/some_option" will only
83 * generate notifications when this single preference changes.
84 *
85 * @param path Preference path the observer should watch
86 */
87 Observer(Glib::ustring const &path);
88 virtual ~Observer();
90 /**
91 * @brief Notification about a preference change
92 * @param new_val Entry object containing information about
93 * the modified preference
94 */
95 virtual void notify(Preferences::Entry const &new_val) = 0;
97 Glib::ustring const observed_path; ///< Path which the observer watches
98 private:
99 void *_data; ///< additional data used by the implementation while the observer is active
100 };
103 /**
104 * @brief Data type representing a typeless value of a preference
105 *
106 * This is passed to the observer in the notify() method.
107 * To retrieve useful data from it, use its member functions. Setting
108 * any preference using the Preferences class invalidates this object,
109 * so use its get methods before doing so.
110 */
111 class Entry {
112 friend class Preferences; // Preferences class has to access _value
113 public:
114 ~Entry() {}
115 Entry() : _pref_path(""), _value(NULL) {} // needed to enable use in maps
116 Entry(Entry const &other) : _pref_path(other._pref_path), _value(other._value) {}
118 /**
119 * @brief Check whether the received entry is valid.
120 * @return If false, the default value will be returned by the getters.
121 */
122 bool isValid() const { return _value != NULL; }
124 /**
125 * @brief Interpret the preference as a Boolean value.
126 * @param def Default value if the preference is not set
127 */
128 inline bool getBool(bool def=false) const;
130 /**
131 * @brief Interpret the preference as an integer.
132 * @param def Default value if the preference is not set
133 */
134 inline int getInt(int def=0) const;
136 /**
137 * @brief Interpret the preference as a limited integer.
138 *
139 * This method will return the default value if the interpreted value is
140 * larger than @c max or smaller than @c min. Do not use to store
141 * Boolean values as integers.
142 *
143 * @param def Default value if the preference is not set
144 * @param min Minimum value allowed to return
145 * @param max Maximum value allowed to return
146 */
147 inline int getIntLimited(int def=0, int min=INT_MIN, int max=INT_MAX) const;
149 /**
150 * @brief Interpret the preference as a floating point value.
151 * @param def Default value if the preference is not set
152 */
153 inline double getDouble(double def=0.0) const;
155 /**
156 * @brief Interpret the preference as a limited floating point value.
157 *
158 * This method will return the default value if the interpreted value is
159 * larger than @c max or smaller than @c min.
160 *
161 * @param def Default value if the preference is not set
162 * @param min Minimum value allowed to return
163 * @param max Maximum value allowed to return
164 */
165 inline double getDoubleLimited(double def=0.0, double min=DBL_MIN, double max=DBL_MAX) const;
167 /**
168 * @brief Interpret the preference as an UTF-8 string.
169 *
170 * To store a filename, convert it using Glib::filename_to_utf8().
171 */
172 inline Glib::ustring getString() const;
174 /**
175 * @brief Interpret the preference as a CSS style.
176 * @return A CSS style that has to be unrefed when no longer necessary. Never NULL.
177 */
178 inline SPCSSAttr *getStyle() const;
180 /**
181 * @brief Interpret the preference as a CSS style with directory-based
182 * inheritance
183 *
184 * This function will look up the preferences with the same entry name
185 * in ancestor directories and return the inherited CSS style.
186 *
187 * @return Inherited CSS style that has to be unrefed after use. Never NULL.
188 */
189 inline SPCSSAttr *getInheritedStyle() const;
191 /**
192 * @brief Get the full path of the preference described by this Entry.
193 */
194 Glib::ustring const &getPath() const { return _pref_path; }
196 /**
197 * @brief Get the last component of the preference's path
198 *
199 * E.g. For "/options/some_group/some_option" it will return "some_option".
200 */
201 Glib::ustring getEntryName() const;
202 private:
203 Entry(Glib::ustring const &path, void const *v) : _pref_path(path), _value(v) {}
205 Glib::ustring _pref_path;
206 void const *_value;
207 };
209 // utility methods
211 /**
212 * @name Load stored preferences and save them to the disk.
213 * @{
214 */
216 /**
217 * @brief Load the preferences from the default location.
218 *
219 * Loads the stored user preferences and enables saving them. If there's
220 * no preferences file in the expected location, it creates it. Any changes
221 * made to the preferences before loading will be overridden by the stored
222 * prefs. Not calling load() is sometimes useful, e.g. for testing.
223 *
224 * @param use_gui Whether to use dialogs to notify about errors when
225 * loading the preferences. Set to false in console mode.
226 * @param quiet Whether to output any messages about preference loading.
227 * If this is true, the use_gui parameter is ignored.
228 */
229 void load(bool use_gui=true, bool quiet=false);
231 /**
232 * @brief Save all preferences to the hard disk.
233 *
234 * For some backends, the preferences may be saved as they are modified.
235 * Not calling this method doesn't guarantee the preferences are unmodified
236 * the next time Inkscape runs.
237 */
238 void save();
240 /**
241 * @brief Check whether saving the preferences will have any effect.
242 */
243 bool isWritable() { return _writable; }
244 /*@}*/
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 */
423 /**
424 * @brief Access the singleton Preferences object.
425 */
426 static Preferences *get() {
427 if (!_instance) _instance = new Preferences();
428 return _instance;
429 }
431 /**
432 * @brief Unload all preferences
433 * @param save Whether to save the preferences; defaults to true
434 *
435 * This deletes the singleton object. Calling get() after this function
436 * will reinstate it, so you shouldn't. Pass false as the parameter
437 * to suppress automatic saving.
438 */
439 static void unload(bool save=true);
440 /*@}*/
442 protected:
443 /* helper methods used by Entry
444 * This will enable using the same Entry class with different backends.
445 * For now, however, those methods are not virtual. These methods assume
446 * that v._value is not NULL
447 */
448 bool _extractBool(Entry const &v);
449 int _extractInt(Entry const &v);
450 double _extractDouble(Entry const &v);
451 Glib::ustring _extractString(Entry const &v);
452 SPCSSAttr *_extractStyle(Entry const &v);
453 SPCSSAttr *_extractInheritedStyle(Entry const &v);
455 private:
456 Preferences();
457 ~Preferences();
458 void _loadDefaults();
459 void _getRawValue(Glib::ustring const &path, gchar const *&result);
460 void _setRawValue(Glib::ustring const &path, gchar const *value);
461 void _errorDialog(Glib::ustring const &, Glib::ustring const &);
462 void _keySplit(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key);
463 XML::Node *_getNode(Glib::ustring const &pref_path, bool create=false);
464 XML::Node *_findObserverNode(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key, bool create);
466 // disable copying
467 Preferences(Preferences const &);
468 Preferences operator=(Preferences const &);
470 std::string _prefs_basename; ///< Basename of the prefs file
471 std::string _prefs_dir; ///< Directory in which to look for the prefs file
472 std::string _prefs_filename; ///< Full filename (with directory) of the prefs file
473 XML::Document *_prefs_doc; ///< XML document storing all the preferences
474 bool _use_gui; ///< Use GUI error notifications?
475 bool _quiet; ///< Display any messages about loading?
476 bool _loaded; ///< Was a load attempt made?
477 bool _writable; ///< Will the preferences be saved at exit?
479 /// Wrapper class for XML node observers
480 class PrefNodeObserver;
482 typedef std::map<Observer *, PrefNodeObserver *> _ObsMap;
483 /// Map that keeps track of wrappers assigned to PrefObservers
484 _ObsMap _observer_map;
486 // privilege escalation methods for PrefNodeObserver
487 static Entry const _create_pref_value(Glib::ustring const &, void const *ptr);
488 static void *_get_pref_observer_data(Observer &o) { return o._data; }
490 static Preferences *_instance;
492 friend class PrefNodeObserver;
493 friend class Entry;
494 };
496 /* Trivial inline Preferences::Entry functions.
497 * In fact only the _extract* methods do something, the rest is delegation
498 * to avoid duplication of code. There should be no performance hit if
499 * compiled with -finline-functions.
500 */
502 inline bool Preferences::Entry::getBool(bool def) const
503 {
504 if (!this->isValid()) return def;
505 return Inkscape::Preferences::get()->_extractBool(*this);
506 }
508 inline int Preferences::Entry::getInt(int def) const
509 {
510 if (!this->isValid()) return def;
511 return Inkscape::Preferences::get()->_extractInt(*this);
512 }
514 inline int Preferences::Entry::getIntLimited(int def, int min, int max) const
515 {
516 if (!this->isValid()) return def;
517 int val = Inkscape::Preferences::get()->_extractInt(*this);
518 return ( val >= min && val <= max ? val : def );
519 }
521 inline double Preferences::Entry::getDouble(double def) const
522 {
523 if (!this->isValid()) return def;
524 return Inkscape::Preferences::get()->_extractDouble(*this);
525 }
527 inline double Preferences::Entry::getDoubleLimited(double def, double min, double max) const
528 {
529 if (!this->isValid()) return def;
530 double val = Inkscape::Preferences::get()->_extractDouble(*this);
531 return ( val >= min && val <= max ? val : def );
532 }
534 inline Glib::ustring Preferences::Entry::getString() const
535 {
536 if (!this->isValid()) return "";
537 return Inkscape::Preferences::get()->_extractString(*this);
538 }
540 inline SPCSSAttr *Preferences::Entry::getStyle() const
541 {
542 if (!this->isValid()) return sp_repr_css_attr_new();
543 return Inkscape::Preferences::get()->_extractStyle(*this);
544 }
546 inline SPCSSAttr *Preferences::Entry::getInheritedStyle() const
547 {
548 if (!this->isValid()) return sp_repr_css_attr_new();
549 return Inkscape::Preferences::get()->_extractInheritedStyle(*this);
550 }
552 inline Glib::ustring Preferences::Entry::getEntryName() const
553 {
554 Glib::ustring path_base = _pref_path;
555 path_base.erase(0, path_base.rfind('/') + 1);
556 return path_base;
557 }
559 } // namespace Inkscape
561 #endif // INKSCAPE_PREFSTORE_H
563 /*
564 Local Variables:
565 mode:c++
566 c-file-style:"stroustrup"
567 c-file-offsets:((innamespace . 0)(inline-open . 0))
568 indent-tabs-mode:nil
569 fill-column:75
570 End:
571 */
572 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :