Difference between revisions of "Preferences subsystem"

From Inkscape Wiki
Jump to navigation Jump to search
m (→‎Where preferences are stored: updating location of preferences file to also cover more recent Windows version (Vista / 7))
 
(15 intermediate revisions by 5 users not shown)
Line 1: Line 1:
While reading this file, it's advisable to consult the [[Doxygen documentation]] for the Inkscape::Preferences class.
== Where preferences are stored ==
== Where preferences are stored ==


Preferences are stored in an XML file called  
Preferences are currently stored in an XML file called  


  ~/.inkscape/preferences.xml  
  ~/.inkscape/preferences.xml         (0.46 and earlier)
~/.config/Inkscape/preferences.xml  (0.47 and later)


on Linux or
on Linux or


  inkscape/preferences.xml
  %APPDATA%\inkscape\preferences.xml
 
on Windows. (See [http://en.wikipedia.org/wiki/Environment_variable#Default_Values_on_Microsoft_Windows Default Values on Microsoft Windows] for the location of %APPDATA%)
<!-- original text additionally had this note:
 
Note that the Windows path is supposed to be relative to Inkscape program directory.
 
which AFAIU needs to be verified if it still holds true. Edit is in reply to complaints on irc about incorrect path for Windows 7, 2012-01-04 ~suv -->


on Windows. Note that the Windows path is supposed to be relative to Inkscape program directory. (FIXME: can we use $HOME on Windows too?)
This file stores the hierarchy of values, much like a GConf database or the Windows Registry. In this file, element nodes correspond to keys (folders) and attributes to entries.


This file stores the hierarchy of values. Each value is in an attribute belonging to one of the elements, and elements with attributes are usually grouped together by top-level elements. Note that all elements have unique <tt>id</tt> attributes.
In future, there will be no guarantee that preferences are stored in an XML file.


== Find a place for your value ==
== Find a place for your value ==


When creating a new preference value, start by examining this file and finding a logical place for the new value. For example, if I want to add a value for some option related to the selector tool, I find the <tt>group</tt> element with <tt>id="tools"</tt> and inside I find <tt><eventcontext id="select"/></tt>. Now this element is empty, but I can store my value in a new attribute. If you have added a new object to the program, such as a new tool or dialog, add a new element in an appropriate top-level element. If you want to store something entirely new and not yet taken care of, you can even add a new top-level element.
When creating a new preference value, start by examining this file and finding a logical place for the new value. For example, if I want to add a value for some option related to the Select tool, I find the <tt>group</tt> element with <tt>id="tools"</tt> and inside I find <tt><eventcontext id="select"/></tt>. Now this element is empty, but I can store my value in a new attribute. If you have added a new object to the program, such as a new tool or dialog, add a new element in an appropriate top-level element. If you want to store something entirely new and not yet taken care of, you can even add a new top-level element.
 
Here is a positive example:
 
<group id="tools">
  <eventcontext id="select"
        foo="1"
        bar="1.0" />
</group>


This is what I did when I decided to store a value of the nudge distance (the distance of one arrow-key jump). This value is not local to any single tool; it is used in selector and node editor and will likely be used in other tools as well. So I created a new top-level element for all such global options and put my value there like so:
Do NOT do something like this:


   <group id="options">
   <group id="options">
Line 23: Line 41:
   </group>
   </group>


Here, 2.8346457 is the distance in points that is equal to 1 mm. Note:
This is because if you think about element nodes as folders and attributes as entries, you would get a path like "/options/nudgedistance/value", which is redundant. It's better to place your preference as an attribute of the options node. However, it would be ideal if you found a better place for your preference than the generic "options" hierarchy.
 
Some points of interest:


* Element names actually do not matter. What matters for finding the value is the <tt>id</tt> attribute values. So you can use <tt>group</tt> elements or any others, whatever seems more logical.
* Element names are irrelevant. What matters for finding the value is the <tt>id</tt> attribute values. So you can use <tt>group</tt> elements or any others, whatever seems more logical.


* Values are stored in attributes. No text within elements is allowed (except whitespace).
* Values are stored in attributes. No text within elements is allowed (except whitespace).
Line 33: Line 53:
When no preferences file exists, a new one is created based on <tt>src/preferences-skeleton.h</tt>. Moreover, if the preferences file lacks some values, the defaults are taken from the same file. So, I edit this file adding
When no preferences file exists, a new one is created based on <tt>src/preferences-skeleton.h</tt>. Moreover, if the preferences file lacks some values, the defaults are taken from the same file. So, I edit this file adding


  " <group id=\"options\">"
  "<group id=\"tools\">\n"
  "   <group id=\"nudgedistance\" value=\"2.8346457\"/>"
  " <eventcontext id=\"select\"\n"
  " </group>"
"      foo=\"1\"\n"
"      bar=\"1.0\" />\n"
  "</group>\n"


before the closing  
before the closing  
Line 49: Line 71:
Now for the interesting part. If you want to access your value in the program, start by adding
Now for the interesting part. If you want to access your value in the program, start by adding


  #include "prefs-utils.h"
  #include "preferences.h"


to the header. Now, create a variable:
This header includes a singleton class that you can use to access the preferences. To get at the value of the preference, use its members methods. To receive an instance of this class, call the static function get(). Always name the pointer to this object "prefs", for consistency.


  double nudge;
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
  double val = prefs->getDouble("/tools/select/bar", 1.0);


and right before it is going to be used, update it from the preferences like so:
What this call does is it reads the value from the memory representation of <tt>preferences.xml</tt>. This representation was created on program start, may be used or modified by changing any values in it, and will be written back into <tt>preferences.xml</tt> when the program exits. Preferences are also saved in the crash handler, so changes to them should be preserved even if Inkscape crashes.


nudge = prefs_get_double_attribute ("options.nudgedistance", "value", 2.8346457);
To pinpoint the value we need, you pass to this function a text string in a syntax that is vaguely reminiscent of XPath, except that it uses <tt>id</tt> attribute values instead of element names. That is, <tt>/tools/select/bar</tt> means, find top-level element with <tt>id="tools"</tt>, within it, an element with <tt>id="select"</tt>, and finally get the value from the <tt>bar</tt> attribute of the <tt>select</tt> node. The second argument is the default value which will be returned if no such value is stored in the preferences.  


What this call does is reads the value from the memory representation of <tt>preferences.xml</tt>. This representation was created on program start, may be used or modified by changing any values in it, and will be written back into <tt>preferences.xml</tt> when the program exits. (FIXME: do we need a menu command for saving preferences?)
It is important that this <tt>prefs->getDouble</tt> call is done before each use of the value, and not just once upon program launch, because the preferences may be edited by the user via a dialog. If your preference is directly tied to some user interface element, it's best to use observers to update it (more on them later).


To pinpoint the value we need, you pass to this function a text string in a syntax that is vaguely reminiscent of XPath, except that it uses <tt>id</tt> attribute values instead of element names and a dot instead of / as a separator. That is, <tt>options.nudgedistance</tt> means, find top-level element with <tt>id="options"</tt> and within it, an element with <tt>id="nudgedistance"</tt>. The second argument to the <tt>prefs_get_double_attribute</tt> is the name of the attribute that we are interested in. The third argument is the default value which will be returned if no such value is stored in the preferences.
Similarly, to write a new value back into the preferences' memory representation, use


It is important that this <tt>prefs_get_double_attribute</tt> call is done before each use of the value, and not just once upon program launch, because the preferences may be edited by the user via a dialog (see below for more on that) and we want the interface to respond to these changes immediately.
prefs->setDouble("/tools/select/bar", new_bar);


Similarly, to write a new value back into the preferences' memory representation, use
In addition to <tt>double</tt> values, you can also store and retrieve <tt>Glib::ustring</tt>s, <tt>int</tt>s, <tt>bool</tt>s, CSS styles and a few more (check up-to-date Doxygen documentation for a list). Always choose the right type for your value.


prefs_set_double_attribute ("options.nudgedistance", "value", new_nudge);
== Preference observers ==


Instead of <tt>double</tt> values, you can store and retrieve <tt>int</tt>s (more precisely, <tt>gint</tt>s) by
If your preference is directly tied to something and you don't want to retrieve its value every time, you can use observers. You do this by deriving from the <tt>Inkscape::Preferences::Observer</tt> inner class. Here is an example on how to do it:


  gint prefs_get_int_attribute (gchar *path, gchar *attr, default);
  class MyObserver: public Inkscape::Preferences::Observer {
void prefs_set_int_attribute (gchar *path, gchar *attr, new_value);  
public:
    MyObserver() : Inkscape::Preferences::Observer("/tools/select/bar") {}
    virtual void notify(Inkscape::Preferences::Entry const &value)
    {
        double new_value = value.getDouble();
        // ...
        // do something with you value here
        // ...
    }
};


or strings by
You can set observers on any point of the hierarchy - both keys and individual entries. This way, the <tt>notify</tt> method will be called every time your preference is updated. If you want to change the preference from its handler, be sure to guard against infinite recursion (or ideally don't do this, because it's not a good practice). <tt>Inkscape::Preferences::Entry</tt> has more public methods that essentially mirror those of the <tt>Preferences</tt> class, but don't take a path parameter. To get the preference's path, use <tt>getPath()</tt>. To get the preference's base name (i.e. the last element of its path), use <tt>getEntryName()</tt>.


gchar const *prefs_get_string_attribute (gchar const *path, gchar const *attr);
NOTE: In future, this mechanism may be obsoleted in favor of sigc++ signals.
void prefs_set_string_attribute (gchar const *path, gchar const *attr, gchar const *value);


== Guard against screw-ups ==
== Guard against screw-ups ==


Sometimes there may be very weird values stored in the preferences file, which may cause trouble to the program. It is wise to guard against them by providing min and max bounds for your value. Here's how:
You should not assume that the values in the prefs file were written there by your code - they may have been very well directly tweaked by the user. If your code will crash when an out-of-bounds value is returned, you should use the methods getIntLimited and getDoubleLimited. Do not use these to store boolean preferences as integers limited to 0 and 1 - use getBool and setBool for that.
 
gint prefs_get_int_attribute_limited (gchar *path, gchar *attr, gint def, gint min, gint max);
double prefs_get_double_attribute_limited (gchar *path, gchar *attr, double def, double min, double max);
 
These functions will return <tt>def</tt> if the value stored in the preferences is less than min or greater than max. Be generous in setting limits, but be sure to test that with the value set to any of the limits program does not crash.
 
 


== Add UI ==
In ui/dialogs/inkscape-preferences.cpp is the code that attaches a GUI to any given preference.  If your option doesn't already have a logical place to go, add it under the "Misc" section. For example, to add an integer-selecting spin-button (sb) you could add to inkscape-preferences.h the line


    PrefSpinButton  _steps_arrow,


------
And add to inkscape-preferences.cpp the lines:


The next section of this tutorial will be devoted to how a preference value can be edited in an options dialog. See related page: PreferencesDialog.
    _steps_arrow.init ( "/tools/select/bar", 0.0, 3000.0, 0.01, 1.0, 2.0, false, false);
    _page_steps.add_line( false, _("Arrow keys move by:"), _steps_arrow, _("px"),
                          _("Pressing an arrow key moves selected object(s) or node(s) by this distance (in px units)"), false);


------
The next section of this tutorial will be devoted to how a preference value can be edited in an options dialog. See related page: [[PreferencesDialog]].


''Discuss below:''
''Discuss below:''
Line 109: Line 138:


Mental and I were discussing GConf, and he brought up the issue of Win32 compatibility.  It would be nice if we could abstract a preferences interface that would work with both the windows registry and GConf.  I think they have similar interfaces - so it shouldn't be impossible --ted
Mental and I were discussing GConf, and he brought up the issue of Win32 compatibility.  It would be nice if we could abstract a preferences interface that would work with both the windows registry and GConf.  I think they have similar interfaces - so it shouldn't be impossible --ted
:: Please do not use the Windows registry. The beauty of having the preferences file is that Inkscape does not need an install or admin rights, and does not litter the OS (at least, i think that is what the registry does). It is very easy for a user that has problems to delete his preferences.xml and download a new one; much easier than fiddling in the registry. - Johan


If this is going to be done, why not also look into implementing Apple's .plist support, it's a simple xml format defined: http://www.apple.com/DTDs/PropertyList-1.0.dtd
If this is going to be done, why not also look into implementing Apple's .plist support, it's a simple xml format defined: http://www.apple.com/DTDs/PropertyList-1.0.dtd
Line 117: Line 148:
* [http://developer.java.sun.com/developer/technicalArticles/releases/preferences/ A nice Sun article about them]
* [http://developer.java.sun.com/developer/technicalArticles/releases/preferences/ A nice Sun article about them]
* [http://www-106.ibm.com/developerworks/java/library/j-mer1002/ An article by IBM about them]
* [http://www-106.ibm.com/developerworks/java/library/j-mer1002/ An article by IBM about them]
The prefs have been extensively refactored, so much of the above discussion is no longer relevant. Coding in plist support should be easier now if desired. GConf is also possible as a backend but it would be a pain for users that need to switch between KDE and Gnome, so on the mailing list we decided against it. On top of that, it seems like GConf will be obsoleted in favor of the (hopefully desktop-neutral) dconf in some near future. -- Krzysztof Kosiński (tweenk)
[[Category:Developer Documentation]]

Latest revision as of 18:19, 4 January 2012

While reading this file, it's advisable to consult the Doxygen documentation for the Inkscape::Preferences class.

Where preferences are stored

Preferences are currently stored in an XML file called

~/.inkscape/preferences.xml          (0.46 and earlier)
~/.config/Inkscape/preferences.xml   (0.47 and later)

on Linux or

%APPDATA%\inkscape\preferences.xml

on Windows. (See Default Values on Microsoft Windows for the location of %APPDATA%)

This file stores the hierarchy of values, much like a GConf database or the Windows Registry. In this file, element nodes correspond to keys (folders) and attributes to entries.

In future, there will be no guarantee that preferences are stored in an XML file.

Find a place for your value

When creating a new preference value, start by examining this file and finding a logical place for the new value. For example, if I want to add a value for some option related to the Select tool, I find the group element with id="tools" and inside I find <eventcontext id="select"/>. Now this element is empty, but I can store my value in a new attribute. If you have added a new object to the program, such as a new tool or dialog, add a new element in an appropriate top-level element. If you want to store something entirely new and not yet taken care of, you can even add a new top-level element.

Here is a positive example:

<group id="tools">
  <eventcontext id="select"
       foo="1"
       bar="1.0" />
</group>

Do NOT do something like this:

 <group id="options">
   <group id="nudgedistance" value="2.8346457"/>
 </group>

This is because if you think about element nodes as folders and attributes as entries, you would get a path like "/options/nudgedistance/value", which is redundant. It's better to place your preference as an attribute of the options node. However, it would be ideal if you found a better place for your preference than the generic "options" hierarchy.

Some points of interest:

  • Element names are irrelevant. What matters for finding the value is the id attribute values. So you can use group elements or any others, whatever seems more logical.
  • Values are stored in attributes. No text within elements is allowed (except whitespace).

Add the value to the skeleton

When no preferences file exists, a new one is created based on src/preferences-skeleton.h. Moreover, if the preferences file lacks some values, the defaults are taken from the same file. So, I edit this file adding

"<group id=\"tools\">\n"
"  <eventcontext id=\"select\"\n"
"       foo=\"1\"\n"
"       bar=\"1.0\" />\n"
"</group>\n"

before the closing

"</inkscape>";

Don't forget to escape quotes. The value given in this file is the default; if the user has changed it, his/her value in preferences.xml takes precedence over src/preferences-skeleton.h.

Now when you recompile, run, and exit Inkscape, the new elements with the default values will be added to your local preferences.xml (without affecting other values there).

Access the value in the program

Now for the interesting part. If you want to access your value in the program, start by adding

#include "preferences.h"

This header includes a singleton class that you can use to access the preferences. To get at the value of the preference, use its members methods. To receive an instance of this class, call the static function get(). Always name the pointer to this object "prefs", for consistency.

Inkscape::Preferences *prefs = Inkscape::Preferences::get();
double val = prefs->getDouble("/tools/select/bar", 1.0);

What this call does is it reads the value from the memory representation of preferences.xml. This representation was created on program start, may be used or modified by changing any values in it, and will be written back into preferences.xml when the program exits. Preferences are also saved in the crash handler, so changes to them should be preserved even if Inkscape crashes.

To pinpoint the value we need, you pass to this function a text string in a syntax that is vaguely reminiscent of XPath, except that it uses id attribute values instead of element names. That is, /tools/select/bar means, find top-level element with id="tools", within it, an element with id="select", and finally get the value from the bar attribute of the select node. The second argument is the default value which will be returned if no such value is stored in the preferences.

It is important that this prefs->getDouble call is done before each use of the value, and not just once upon program launch, because the preferences may be edited by the user via a dialog. If your preference is directly tied to some user interface element, it's best to use observers to update it (more on them later).

Similarly, to write a new value back into the preferences' memory representation, use

prefs->setDouble("/tools/select/bar", new_bar); 

In addition to double values, you can also store and retrieve Glib::ustrings, ints, bools, CSS styles and a few more (check up-to-date Doxygen documentation for a list). Always choose the right type for your value.

Preference observers

If your preference is directly tied to something and you don't want to retrieve its value every time, you can use observers. You do this by deriving from the Inkscape::Preferences::Observer inner class. Here is an example on how to do it:

class MyObserver: public Inkscape::Preferences::Observer {
public:
    MyObserver() : Inkscape::Preferences::Observer("/tools/select/bar") {}
    virtual void notify(Inkscape::Preferences::Entry const &value)
    {
        double new_value = value.getDouble();
        // ...
        // do something with you value here
        // ...
    }
};

You can set observers on any point of the hierarchy - both keys and individual entries. This way, the notify method will be called every time your preference is updated. If you want to change the preference from its handler, be sure to guard against infinite recursion (or ideally don't do this, because it's not a good practice). Inkscape::Preferences::Entry has more public methods that essentially mirror those of the Preferences class, but don't take a path parameter. To get the preference's path, use getPath(). To get the preference's base name (i.e. the last element of its path), use getEntryName().

NOTE: In future, this mechanism may be obsoleted in favor of sigc++ signals.

Guard against screw-ups

You should not assume that the values in the prefs file were written there by your code - they may have been very well directly tweaked by the user. If your code will crash when an out-of-bounds value is returned, you should use the methods getIntLimited and getDoubleLimited. Do not use these to store boolean preferences as integers limited to 0 and 1 - use getBool and setBool for that.

Add UI

In ui/dialogs/inkscape-preferences.cpp is the code that attaches a GUI to any given preference. If your option doesn't already have a logical place to go, add it under the "Misc" section. For example, to add an integer-selecting spin-button (sb) you could add to inkscape-preferences.h the line

   PrefSpinButton  _steps_arrow,

And add to inkscape-preferences.cpp the lines:

   _steps_arrow.init ( "/tools/select/bar", 0.0, 3000.0, 0.01, 1.0, 2.0, false, false);
   _page_steps.add_line( false, _("Arrow keys move by:"), _steps_arrow, _("px"), 
                         _("Pressing an arrow key moves selected object(s) or node(s) by this distance (in px units)"), false);

The next section of this tutorial will be devoted to how a preference value can be edited in an options dialog. See related page: PreferencesDialog.

Discuss below:

I also think we should consider having a global preferences file in /etc/inkscape/preferences.xml, and similarly with markers.svg, that are installed during program installation, and merged with or overwritten by the user's settings. This is standard UNIX config file strategy. - bryce

Njh wonders whether you would be better off using gconf?

Maybe, but I'm not in a position to decide. Right now I'm interested in how I can use the existing preferences system to store and retrieve a value. -- bb

Looking further, it appears that gconf is indeed a similar XML system, so at some point it should be easy enough to switch over. --njh

Mental and I were discussing GConf, and he brought up the issue of Win32 compatibility. It would be nice if we could abstract a preferences interface that would work with both the windows registry and GConf. I think they have similar interfaces - so it shouldn't be impossible --ted

Please do not use the Windows registry. The beauty of having the preferences file is that Inkscape does not need an install or admin rights, and does not litter the OS (at least, i think that is what the registry does). It is very easy for a user that has problems to delete his preferences.xml and download a new one; much easier than fiddling in the registry. - Johan

If this is going to be done, why not also look into implementing Apple's .plist support, it's a simple xml format defined: http://www.apple.com/DTDs/PropertyList-1.0.dtd These files are stored in ~/Library/Preferences/tld.<company>.<product>.plist - tom

Well, if we're looking around at existing approaches, the discussion probably wouldn't be complete without taking a look at the new Java preferences API added in 1.4. They currently store to a dir/dir/xml tree on Linux, and to the registry on Windows, so they do that exact abstraction we're looking at. (funny thing is, for some apps, I wrote a layer to abstract that for running in Java VM's prior to 1.4 - double indirection). --jon

The prefs have been extensively refactored, so much of the above discussion is no longer relevant. Coding in plist support should be easier now if desired. GConf is also possible as a backend but it would be a pain for users that need to switch between KDE and Gnome, so on the mailing list we decided against it. On top of that, it seems like GConf will be obsoleted in favor of the (hopefully desktop-neutral) dconf in some near future. -- Krzysztof Kosiński (tweenk)