Grail Preferences Infrastructure

The Grail preferences infrastructure provides persistent, user-controllable settings for customizable Grail features. Things like browser geometry and personal home page are set using GUI panel dialogs, and custom settings are maintained in the user's personal preferences file. This document describes the preferences infrastructure. Refer to the Preference Panel Plug-in Reference Manual for more information.

Preferences Implementation Modules

The central preferences code consists of two modules in the ancillary subdirectory of the Grail sources.
The preferences datastructure, implemented as an object, AllPreferences.
The framework for building specific panels.

In addition, panel-specific definition modules are sought in a subdirectory named 'prefpanels' in the user's .grail directory and the Grail sources directory, in that order.

Grail respects two files dictating preference values:

The system-wide defaults file.
The user's distinct preferences. (Grail will save only those settings that differ from the system defaults in this file.)

Identifying Preferences

Each preference setting has a group name and a component name. These names are constrained like python variable identifiers, except that they are allowed to include any number of embedded, non-adjacent '-' dash characters. (The first and last characters must not be dashes, however.) The names are not case sensitive.

The preferences API routines all take the group/component pair of names as a pair of arguments. In preference files, preferences names are a concatenation of the group name followed by a "--" and then the component name.

The Preferences Object

A running Grail shares a single instance of the preferences datastructure, AllPreferences, in the variable app.prefs. The instance reads the preferences files on initialization. It provides methods for programmatic access to preference settings:

Obtaining Current Settings

Four methods each return current preferences settings. They all take the preference group and component name, plus an additional optional value which, when true, indicates that the system default setting should be returned. The last three methods constrain the type of the setting as indicated by the method names, and they return a value of that type. The first one, prefs.Get(), does not constrain the type and returns a string.

Changing Current Preference Settings

The method: assigns the value to the preference in group with name.

Saving Preference Settings

The method:

commits any settings changes to file. All settings that differ from the system default settings, and only those settings, are saved in the user's preferences file.

Any callback routines associated with the group of changed settings are invoked when the settings are saved to file. No callback is invoked more than once during a save, even if it has distinct associations with multiple groups that have changed.

Associating a Callback Routine With a Preference Group

There are two methods for controlling the association of a callback routine with a preference group:
prefs.AddGroupCallback(group, callback)
associates the callback with the group, so the callback will be invoked when changed settings in that group are being saved. The callback is invoked with zero arguments. It is invoked only once for each save, just before the first changed setting in that group is written to file.

prefs.RemoveGroupCallback(group, callback)
Removes the association between the callback and the group. Removal of non-existent group/callback associations is silently ignored.
Here is an example of a callback which handles changes to the 'browser'/'load-images' setting:

    def load_images_vis_prefs(app=app): 
        app.load_images = app.prefs.GetBoolean('browser', 'load-images')
and here is how it is registered so it will be invoked when a change to any preferences in the 'browser' group is saved:

    app.prefs.AddGroupCallback('browser', load_images_vis_prefs)
The load_images_vis_prefs routine could be more elaborate - it could check for changes in the preference of concern, before taking any action, for instance - but such elaboration is not necessary for this setting.

Preferences Files

The system ("$GRAILROOT/grail-defaults") and user ("~/.grail/grail-preferences") files have the same format, consisting of lines containing name/value pairs, delimited like RFC 822 (email message) header fields. The preference group and component are encoded a single name that corresponds to the RFC 822 header-field name, by concatenating them together with a "--" pair of dashes, and appending a ':' to delimit the name from the value. Typically, whitespace follows and then the value for that preference. There are also continuation lines, which begin with whitespace but also must contain non-whitespace. These lines continue the value for the most recent line with a preference name. Comment lines, which are lines with a "#" hash character in column 0, are ignored. The preference names are case insensitive.

Note that comments are not preserved when the file is written by the preferences interface. (Thus, the system defaults file should never be written by the interface, and the user preferences file should not be expected to retain comments.)

Here is an example fragment of a system preferences file:

# Pref ('landmarks', 'home-page') with empty value:
# Pref with value on continuation line:
browser--default-height:	40

The Preference Panels Framework

The file ancillary/ implements a framework for creating Tkinter GUI panels for the viewing and setting of Grail preference options. contains two components which are used externally:

The Framework Object

The PrefsPanels class Framework provides a framework for individual panels. It is used, via inheritance, for the following features:

Refer to Preference Panel Plug-in Reference Manual for specifics.