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() : _pref_path(""), _value(NULL) {} // needed to enable use in maps
113 Entry(Entry const &other) : _pref_path(other._pref_path), _value(other._value) {}
114 /**
115 * @brief Check whether the received entry is valid.
116 * @return If false, the default value will be returned by the getters.
117 */
118 bool isValid() const { return _value != NULL; }
120 /**
121 * @brief Interpret the preference as a Boolean value.
122 * @param def Default value if the preference is not set
123 */
124 inline bool getBool(bool def=false) const;
125 /**
126 * @brief Interpret the preference as an integer.
127 * @param def Default value if the preference is not set
128 */
129 inline int getInt(int def=0) const;
130 /**
131 * @brief Interpret the preference as a limited integer.
132 *
133 * This method will return the default value if the interpreted value is
134 * larger than @c max or smaller than @c min. Do not use to store
135 * Boolean values as integers.
136 *
137 * @param def Default value if the preference is not set
138 * @param min Minimum value allowed to return
139 * @param max Maximum value allowed to return
140 */
141 inline int getIntLimited(int def=0, int min=INT_MIN, int max=INT_MAX) const;
142 /**
143 * @brief Interpret the preference as a floating point value.
144 * @param def Default value if the preference is not set
145 */
146 inline double getDouble(double def=0.0) const;
147 /**
148 * @brief Interpret the preference as a limited floating point value.
149 *
150 * This method will return the default value if the interpreted value is
151 * larger than @c max or smaller than @c min.
152 *
153 * @param def Default value if the preference is not set
154 * @param min Minimum value allowed to return
155 * @param max Maximum value allowed to return
156 */
157 inline double getDoubleLimited(double def=0.0, double min=DBL_MIN, double max=DBL_MAX) const;
158 /**
159 * @brief Interpret the preference as an UTF-8 string.
160 *
161 * To store a filename, convert it using Glib::filename_to_utf8().
162 */
163 inline Glib::ustring getString() const;
164 /**
165 * @brief Interpret the preference as a CSS style.
166 * @return A CSS style that has to be unrefed when no longer necessary.
167 */
168 inline SPCSSAttr *getStyle() const;
169 /**
170 * @brief Interpret the preference as a CSS style with directory-based
171 * inheritance
172 *
173 * This function will look up the preferences with the same entry name
174 * in ancestor directories and return the inherited CSS style.
175 *
176 * @return Inherited CSS style that has to be unrefed after use.
177 */
178 inline SPCSSAttr *getInheritedStyle() const;
180 /**
181 * @brief Get the full path of the preference described by this Entry.
182 */
183 Glib::ustring const &getPath() const { return _pref_path; }
184 /**
185 * @brief Get the last component of the preference's path
186 *
187 * E.g. For "/options/some_group/some_option" it will return "some_option".
188 */
189 Glib::ustring getEntryName() const;
190 private:
191 Entry(Glib::ustring const &path, void const *v) : _pref_path(path), _value(v) {}
193 Glib::ustring _pref_path;
194 void const *_value;
195 };
197 // utility methods
199 /**
200 * @name Load stored preferences and save them to the disk.
201 * @{
202 */
204 /**
205 * @brief Load the preferences from the default location.
206 *
207 * Loads the stored user preferences and enables saving them. If there's
208 * no preferences file in the expected location, it creates it. Any changes
209 * made to the preferences before loading will be overridden by the stored
210 * prefs. Not calling load() is sometimes useful, e.g. for testing.
211 *
212 * @param use_gui Whether to use dialogs to notify about errors when
213 * loading the preferences. Set to false in console mode.
214 * @param quiet Whether to output any messages about preference loading.
215 * If this is true, the use_gui parameter is ignored.
216 */
217 void load(bool use_gui=true, bool quiet=false);
218 /**
219 * @brief Save all preferences to the hard disk.
220 *
221 * For some backends, the preferences may be saved as they are modified.
222 * Not calling this method doesn't guarantee the preferences are unmodified
223 * the next time Inkscape runs.
224 */
225 void save();
226 /**
227 * @brief Check whether saving the preferences will have any effect.
228 */
229 bool isWritable() { return _writable; }
230 /*@}*/
232 /**
233 * @name Iterate over directories and entries.
234 * @{
235 */
237 /**
238 * @brief Get all entries from the specified directory
239 *
240 * This method will return a vector populated with preference entries
241 * from the specified directory. Subdirectories will not be represented.
242 */
243 std::vector<Entry> getAllEntries(Glib::ustring const &path);
244 /**
245 * @brief Get all subdirectories of the specified directory
246 *
247 * This will return a vector populated with full paths to the subdirectories
248 * present in the specified @c path.
249 */
250 std::vector<Glib::ustring> getAllDirs(Glib::ustring const &path);
251 /*@}*/
253 /**
254 * @name Retrieve data from the preference storage.
255 * @{
256 */
257 /**
258 * @brief Retrieve a Boolean value
259 * @param pref_path Path to the retrieved preference
260 * @param def The default value to return if the preference is not set
261 */
262 bool getBool(Glib::ustring const &pref_path, bool def=false) {
263 return getEntry(pref_path).getBool(def);
264 }
265 /**
266 * @brief Retrieve an integer
267 * @param pref_path Path to the retrieved preference
268 * @param def The default value to return if the preference is not set
269 */
270 int getInt(Glib::ustring const &pref_path, int def=0) {
271 return getEntry(pref_path).getInt(def);
272 }
273 /**
274 * @brief Retrieve a limited integer
275 *
276 * The default value is returned if the actual value is larger than @c max
277 * or smaller than @c min. Do not use to store Boolean values.
278 *
279 * @param pref_path Path to the retrieved preference
280 * @param def The default value to return if the preference is not set
281 * @param min Minimum value to return
282 * @param max Maximum value to return
283 */
284 int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX) {
285 return getEntry(pref_path).getIntLimited(def, min, max);
286 }
287 double getDouble(Glib::ustring const &pref_path, double def=0.0) {
288 return getEntry(pref_path).getDouble(def);
289 }
290 /**
291 * @brief Retrieve a limited floating point value
292 *
293 * The default value is returned if the actual value is larger than @c max
294 * or smaller than @c min.
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 double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX) {
302 return getEntry(pref_path).getDoubleLimited(def, min, max);
303 }
304 /**
305 * @brief Retrieve an UTF-8 string
306 * @param pref_path Path to the retrieved preference
307 */
308 Glib::ustring getString(Glib::ustring const &pref_path) {
309 return getEntry(pref_path).getString();
310 }
311 /**
312 * @brief Retrieve a CSS style
313 * @param pref_path Path to the retrieved preference
314 * @return A CSS style that has to be unrefed after use.
315 */
316 SPCSSAttr *getStyle(Glib::ustring const &pref_path) {
317 return getEntry(pref_path).getStyle();
318 }
319 /**
320 * @brief Retrieve an inherited CSS style
321 *
322 * This method will look up preferences with the same entry name in ancestor
323 * directories and return a style obtained by inheriting properties from
324 * ancestor styles.
325 *
326 * @param pref_path Path to the retrieved preference
327 * @return An inherited CSS style that has to be unrefed after use.
328 */
329 SPCSSAttr *getInheritedStyle(Glib::ustring const &pref_path) {
330 return getEntry(pref_path).getInheritedStyle();
331 }
332 /**
333 * @brief Retrieve a preference entry without specifying its type
334 */
335 Entry const getEntry(Glib::ustring const &pref_path);
336 /*@}*/
338 /**
339 * @name Update preference values.
340 * @{
341 */
343 /**
344 * @brief Set a Boolean value
345 */
346 void setBool(Glib::ustring const &pref_path, bool value);
347 /**
348 * @brief Set an integer value
349 */
350 void setInt(Glib::ustring const &pref_path, int value);
351 /**
352 * @brief Set a floating point value
353 */
354 void setDouble(Glib::ustring const &pref_path, double value);
355 /**
356 * @brief Set an UTF-8 string value
357 */
358 void setString(Glib::ustring const &pref_path, Glib::ustring const &value);
359 /**
360 * @brief Set a CSS style
361 */
362 void setStyle(Glib::ustring const &pref_path, SPCSSAttr *style);
363 /**
364 * @brief Merge a CSS style with the current preference value
365 *
366 * This method is similar to setStyle(), except that it merges the style
367 * rather than replacing it. This means that if @c style doesn't have
368 * a property set, it is left unchanged in the style stored in
369 * the preferences.
370 */
371 void mergeStyle(Glib::ustring const &pref_path, SPCSSAttr *style);
372 /*@}*/
374 /**
375 * @name Receive notifications about preference changes.
376 * @{
377 */
378 /**
379 * @brief Register a preference observer
380 */
381 void addObserver(Observer &);
382 /**
383 * @brief Remove an observer an prevent further notifications to it.
384 */
385 void removeObserver(Observer &);
386 /*@}*/
388 /**
389 * @name Access and manipulate the Preferences object.
390 * @{
391 */
393 /**
394 * @brief Access the singleton Preferences object.
395 */
396 static Preferences *get() {
397 if (!_instance) _instance = new Preferences();
398 return _instance;
399 }
400 /**
401 * @brief Unload all preferences and store them on the hard disk.
402 *
403 * This deletes the singleton object. Calling get() after this function is
404 */
405 static void unload() {
406 if(_instance)
407 {
408 delete _instance;
409 _instance = NULL;
410 }
411 }
412 /*@}*/
414 protected:
415 /* helper methods used by Entry
416 * This will enable using the same Entry class with different backends.
417 * For now, however, those methods are not virtual. These methods assume
418 * that v._value is not NULL
419 */
420 bool _extractBool(Entry const &v);
421 int _extractInt(Entry const &v);
422 double _extractDouble(Entry const &v);
423 Glib::ustring _extractString(Entry const &v);
424 SPCSSAttr *_extractStyle(Entry const &v);
425 SPCSSAttr *_extractInheritedStyle(Entry const &v);
427 private:
428 Preferences();
429 ~Preferences();
430 void _loadDefaults();
431 void _getRawValue(Glib::ustring const &path, gchar const *&result);
432 void _setRawValue(Glib::ustring const &path, gchar const *value);
433 void _errorDialog(Glib::ustring const &, Glib::ustring const &);
434 void _keySplit(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key);
435 XML::Node *_getNode(Glib::ustring const &pref_path, bool create=false);
436 XML::Node *_findObserverNode(Glib::ustring const &pref_path, Glib::ustring &node_key, Glib::ustring &attr_key, bool create);
438 // disable copying
439 Preferences(Preferences const &);
440 Preferences operator=(Preferences const &);
442 std::string _prefs_basename; ///< Basename of the prefs file
443 std::string _prefs_dir; ///< Directory in which to look for the prefs file
444 std::string _prefs_filename; ///< Full filename (with directory) of the prefs file
445 XML::Document *_prefs_doc; ///< XML document storing all the preferences
446 bool _use_gui; ///< Use GUI error notifications?
447 bool _quiet; ///< Display any messages about loading?
448 bool _loaded; ///< Was a load attempt made?
449 bool _writable; ///< Will the preferences be saved at exit?
451 /// Wrapper class for XML node observers
452 class PrefNodeObserver;
454 typedef std::map<Observer *, PrefNodeObserver *> _ObsMap;
455 /// Map that keeps track of wrappers assigned to PrefObservers
456 _ObsMap _observer_map;
458 // privilege escalation methods for PrefNodeObserver
459 static Entry const _create_pref_value(Glib::ustring const &, void const *ptr);
460 static void *_get_pref_observer_data(Observer &o) { return o._data; }
462 static Preferences *_instance;
464 friend class PrefNodeObserver;
465 friend class Entry;
466 };
468 /* Trivial inline Preferences::Entry functions.
469 * In fact only the _extract* methods do something, the rest is delegation
470 * to avoid duplication of code. There should be no performance hit if
471 * compiled with -finline-functions.
472 */
474 inline bool Preferences::Entry::getBool(bool def) const
475 {
476 if (!this->isValid()) return def;
477 return Inkscape::Preferences::get()->_extractBool(*this);
478 }
480 inline int Preferences::Entry::getInt(int def) const
481 {
482 if (!this->isValid()) return def;
483 return Inkscape::Preferences::get()->_extractInt(*this);
484 }
486 inline int Preferences::Entry::getIntLimited(int def, int min, int max) const
487 {
488 if (!this->isValid()) return def;
489 int val = Inkscape::Preferences::get()->_extractInt(*this);
490 return ( val >= min && val <= max ? val : def );
491 }
493 inline double Preferences::Entry::getDouble(double def) const
494 {
495 if (!this->isValid()) return def;
496 return Inkscape::Preferences::get()->_extractDouble(*this);
497 }
499 inline double Preferences::Entry::getDoubleLimited(double def, double min, double max) const
500 {
501 if (!this->isValid()) return def;
502 double val = Inkscape::Preferences::get()->_extractDouble(*this);
503 return ( val >= min && val <= max ? val : def );
504 }
506 inline Glib::ustring Preferences::Entry::getString() const
507 {
508 if (!this->isValid()) return "";
509 return Inkscape::Preferences::get()->_extractString(*this);
510 }
512 inline SPCSSAttr *Preferences::Entry::getStyle() const
513 {
514 if (!this->isValid()) return NULL;
515 return Inkscape::Preferences::get()->_extractStyle(*this);
516 }
518 inline SPCSSAttr *Preferences::Entry::getInheritedStyle() const
519 {
520 if (!this->isValid()) return NULL;
521 return Inkscape::Preferences::get()->_extractInheritedStyle(*this);
522 }
524 inline Glib::ustring Preferences::Entry::getEntryName() const
525 {
526 Glib::ustring path_base = _pref_path;
527 path_base.erase(0, path_base.rfind('/') + 1);
528 return path_base;
529 }
531 } // namespace Inkscape
533 #endif // INKSCAPE_PREFSTORE_H
535 /*
536 Local Variables:
537 mode:c++
538 c-file-style:"stroustrup"
539 c-file-offsets:((innamespace . 0)(inline-open . 0))
540 indent-tabs-mode:nil
541 fill-column:75
542 End:
543 */
544 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :