Preference Panel Plug-in Reference Manual
This document describes how to incoporate a panel for setting
Grail preferences into the Grail browser.
Note that preference panels do not create preferences, they
are for controlling established ones. To incorporate new preferences
into the system, you must create an entry in grail-defaults
preferences file, and hook them up with the relevant Grail mechanisms
using the Grail preferences object interface. See the
Preferences infrastructure document for
background on the Grail preferences mechanisms, and the section on
the GUI framework for details on
the underlying preference panel support mechanisms, in particular.
For a representative example of a preference panel, see
GeneralPanel.py
in the Grail source's
prefspanels
subdirectory.
Preference Panel Modules
Specific preference panels are defined in modules with a particular
name and location:
- The panel modules should be placed in a subdirectory named
"prefpanels", either in the grail source directory for system panels,
or in ".grail" subdirectory of the user's home directory. The user's
prefpanel subdir has precedence.
- The module name should consist of the preference category, eg
"General", followed by the suffix "Panel.py": "GeneralPanel.py". "_"
underscores can be used to separate the two parts. Any underscores
embedded in the category name will be interpreted as spaces for the
name of the panel.
Preference Panel Class
To create a preference panel, define a class in your panel module
which:
- is named named by appending "Panel" to the category part of the
module name. For example, for "GeneralPanel.py" the class should be
named "GeneralPanel". (Embedded underscores in the category part of
the module name should simply be omitted in the class name.)
- inherits from PrefsPanels.Framework.
- has no init; or, if necessary, has an init which takes the same
arguments as the Framework.__init__(), and passes those args to the
Framework.__init__().
- defines a CreateLayout() method. This is where your panel GUI
code should go. Your CreateLayout() method should:
- take two parameters, name and frame.
- build the panel within the frame passed as the frame
parameter.
- couple the GUI widgets for setting preference values with the
preference entries, themselves, using the class method
self.RegisterUI()
and self.widget_set_func()
(see the RegisterUI section, below).
- defines a HELP_URL class var, pointing to a help document for the
panel. (System pref-panel developers should write a doc, similar to
the ones in "htdocs/help/prefs", and add a link to the doc at the
bottom of "index.html" in that dir. index.html is the default, and
provides a general description of the buttons and bindings.)
- optionally, defines an UpdateLayout() method. This routine will be
called whenever the panel settings are changed via the Revert or
Factory Defaults buttons.
- optionally, defines a Dismiss() method. This routine will be
called whenever the panel is hidden, e.g. when the "Ok" or "Cancel"
buttons are pressed, the reload command is invoked, or the window
manager dismisses the panel. The base class Dismiss() method is
a noop.
In addition to the low-level self.RegisterUI() mechanism, the
framework provides a few routines for creating widgets, such as
entries and buttons, which are regularly proportioned and which take
care of the preference coupling. See
the preferences-widgets section,
below.
The panel framework has a few useful instance variables:
- self.app
- The Grail application object
- self.browser
- The browser from which the panel was last posted. It's possible
that the browser has been discarded since the panel was posted, so the
panel should check the value of self.browser.valid() to verify that
the browser is still around, and use any browser in self.app.browsers
if it is not.
There are also a few conveniences for preference panel developers.
- Alt-Control-r is bound in the panel to reload_panel_cmd(). Invoking
it will dismiss the panel and reload the module from which it is
defined, so you can use it to progressively develop and debug your
panel without restarting grail.
- Setting the preference 'preferences--panel-debugging' to 1 (in
your personal grail-preferences file) enables two additional buttons
at the bottom of each preference panel, "Reload Panel" and "Reload
Preferences".
- Additionally, hitting Alt-Control-d inside the panel toggles
activation of these buttons (but does not change the preference).
Using self.RegisterUI() to Couple Widgets
With Preferences
RegisterUI takes the following arguments:
- Group - the group name of the target preference.
- Component - the component name of the target preference.
- Type - the preference value type; one of 'string', 'int', 'float',
or 'Boolean'.
- Getter routine - an argumentless routine used to get the current
setting registered in the GUI widget.
- Setter routine - a routine taking a single argument, used to
change the setting of the GUI widget to the value of that argument.
The self.RegisterUI() method associates the preference, identified by
the group and component, with the setter and getter routines. The
Framework then uses the setter and getter routines to transfer the
values between the widget and the preference.
For example, when the panel "Done" button is pushed, the getter
routines are used to obtain the current values registered in the panel
widgets and apply them to the associated preferences. Or when the
revert button is pushed, the setter routines are used to register the
current preference settings in their associated widgets, and so
forth.
The framework also provides a utility for use with self.RegisterUI(),
self.widget_set_func(widget). It takes any widget which uses
the textvariable
config option for controlling the widget
setting, and returns a routine which can be used as the Getter routine
for RegisterUI()
. For things like button widgets, you
can use the var.Set() methods of their Tkinter variables directly.
Framework Preferences Widgets
The framework provides a number of routines with two features:
- They take care of the linkage with the preference you specify, and
- They make it easy to specify a left-side label for the widget that
takes up a consistent amount of space, allowing you to line up your
widgets at a consistent column.
Currently, the repertoire consists of:
- self.PrefsWidgetLabel(frame, text, label_width=25)
- Mainly for use by the other prefs widgets. PrefsWidgetLabel
packs frame with a text label so it takes up
label_width space but has the text right-justified. This
means the text will end just before the label_width
column. By packing widgets in the frame to the right of this label,
they all can line up at the same column. (This depends on the
text fitting in - embed \n newlines for text
longer than label_width chars.)
- self.PrefsCheckButton(frame, general-text, specific-text, group,
component, label_width=25):
- Produce a check button with general-text left-side
label (see above) and coupled with the Boolean preference specified by
group/component.
- self.PrefsRadioButton(frame, general-text, specific-text, group,
component, type_name, label_width=25):
- Produce a frame of radio buttons with general-text
left-side label (see above) and coupled with the preference of
specified type.
- self.PrefsEntry(parent, label, group, component, type_name,
label_width=25, entry_height=1, entry_width=None, composite=0):
- Produce an entry widget, or a text widget with vertical scrolling
if entry_height is greater than one, coupled with the
preference of specified type.
You can pack multiple instances of these entry widgets in the
together, eg to make a
"Width: #### Height: #####"
widget, composed of two such entry widgets, by setting the
composite keyword argument. This will override the
label-lineup feature, so the combination can be used together with a
left-side self.PrefsWidgetLabel() to make a composite
widget line for the panel:
"Browser geometry: Width: #### Height: #####"
For examples using these widgets, see GeneralPanel.py
in
the grail distribution prefspanels directory.