Using IMP to Develop Preference Services and Pages
ALERT:
Parts of this document are out of date.
This document provides guidance on the use of IMP to develop a
preference service and preference pages for -based IDEs.
Contents
The IMP Preferences Model
The IMP Prefrences Service
Operations on
the Service
Comparison
to the Eclipse Preferences Service
Comparison
to the JFace Preference Store
Using
the IMP Preferences Service
Implementing
the IMP Preferences Service
IMP Preference Pages
Using a
IMP preference page
Implementing
a IMP preference page
Alternative
page designs
IMP Preference Fields
Field types
Adding
preference fields to a preferences tab
Level-specific
considerations
Details links
and dialogs
Field toggles
Layout and spacing
Special values
Implemening Other
Things for Preference Pages
Special
elements of the project tab
Preferences
initializers and constants
Buttons and their Behavior
Implementing IMP
Preference Field Editors
General
responsibilities
Interactions of
concerns
Other considerations
IMP Preferences Utilities
The IMP 'New
Preference Page' Wizard
Examples
The IMP Preferences
Model
IMP support for the development of preference pages is based on the
Eclipse Preferences Service
(org.eclipse.core.internal.preferences.PreferencesService, since
3.0). Thus, the IMP model of preferences is basically that of
the Preferences Service. Key aspects of the IMP model
(virtually all of which come from the Eclipse model) are as follows:
- It is a layered model, with four intrinsicly supported
layers. From most general to most specific, these are:
- Default
- Configuration (i.e., workspace configuration)
- Instance (i.e., workspace instance)
- Project
Preferences set at a given level are
scoped accordingly, with default preferences applying anywhere that a
more specific preference value is lacking.
- At runtime, in memory, each level is represented by a Scope
(org.eclipse.core.preferences.IScopeContext), which in turn provides
access to Nodes (org.eclipse.core.preferences.IEclipsePreferences) via
String qualifiers. The Nodes maintain the values of preferences
at runtime.
- The Preferences Service controls where preference values are
stored, which is in canonical locations for preferences on the project,
instance, and configuration levels. No values are stored at the
default level; these are set (and reset) programmatically.
- Preferences are (logically) segregated by language; when using
the service, either a default language must be set or a specific
language given in service requests.
- The space of preference values is sparse in general. That
is, values
do not need to be set for all preferences on all levels. However,
all preferences should have an appropriate value on the default level.
- Preference values can be written to and read from specific
(given) preference levels. However, since not all preferences
need to be set on all levels, the reading of a specific preference on a
specific level is not guaranteed to return a meaningful value.
- Preference values can be read without reference to a level.
This is the nominal mode of reference for application clients of the
service. In this case, the return of a meaningful value is
guaranteed, provided that the preference is defined on some level (thus
the importance of setting all preference values on the default
level). The value returned by the level-independent reading of a
preference will be the value set at the most specific preference level
at which the preference is defined. In other words, for
level-independent reads, preference values are inherited down the
preference levels, and values set for a preference at lower levels will
override values set for the preference at higher levels.
- For an application, the applicable values are those that apply on
the project level, that is, the values that are stored on or inherited
down to the project level/
- Preferences are designated by String keys
- The types of preferences supported include boolean, byte arrays,
doubles, floats, ints, longs, and Strings (all of which, significantly,
can be stored as Strings)
The IMP Preferences
Service
The IMP Preferences Service generally implements the IMP
Preferences Model. The service is defined by an interface
IIMPPreferencesService
(org.eclipse.uide.preferences.IIMPPreferencesService), which is
implemented by the class IMPPreferencesService
(org.eclipse.uide.preferences.Safa
riPreferencesService).
Operations on the Service
The IMP Preferences Service provides operations to do the following:
- Set and get the default language and project under which
the service should operate
- Set and get preferences in batches by level (assuming
the default language and project)
- Get the applicable value of a preference by type (independent
of level, assuming the default language and project)
- Get the applicable value of a preference by type, considering a
given
project at the project level (independent of level, assuming the
default language)
- Get the value of a preference by type for a given level
(assuming the default language and project))
- Get the applicable value of a preference by type for a given
project (assuming the default language)
- Get the value of a preference by type for a given language,
project, and level (assuming nothing)
- Set the value of a preference by type for a given level
(assuming the default language and project)
- Set the value of a preference by type for a given project,
level, and language (assuming nothing)
- Clearing preferences at level
- Get information about whether and where a preference is
defined
- Accessing preferences scopes and nodes
- Adding and removing ProjectSelectionListeners (as well as types
for ProjectSelectionListener and ProjectSelectionEvent); these allow
clients to monitor changes in the Set of the default project for
the service
The IMP Preferences Service is implemented using the Eclipse
Preferences Service and relies on the Eclipse Preferences Service to
manage the storage of files that contain the preference values for each
level of the preferences model. The persistent storage of these
data is not a concern that
appears in the IMP Preferences Service API. Note that all
instances of the IMP Preferences Service use the same rules for
locating the files that persistently store preference values.
Thus all instances of the Service can be considered to share a common
persistent store.
Comparison to
the Eclipse Preferences Service
The IMP Preferences Service adopts many of the basic concepts the
Eclipse Preferences Service (see
The
IMP Preferences Model)
but is more restricted in certain respects and more flexible in
others. Some of the major differences:
- The IMP service supports both the reading and writing of
preferences, whereas the Eclipse service supports just the reading of
preferences. With the Eclipse service, preferences must be
written to the Nodes (thus clients of the Eclipse preferences
mechanisms that want to both read and write preferences have to work
through two interfaces). Giving clients a single interface
through which they can similarly get and set preference values seems
like a good idea.
- The Eclipse service provides methods for importing and exporting
preferences and applying preferences to an existing hierarchy; the IMP
service doesn't provide these. If a need for such
capabilities is discovered then they can be added.
- With the Eclipse service, the methods to get preference values
all take a parameter that represents a value to be returned if the
preference is undefined; the methods to get preference values in the
IMP service don't have such parameters. The IMP approach
assumes that all preferences of interest should have values defined on
some level and that clients should generally refer only to preferences
that are defined. To support this, there is a method to let
clients test whether a preference is defined. Both approaches
impose some burden on the developer of the client (to provide a value
in the Eclipse case, to assure that a preference is defined in the IMP
case). With the IMP approach, though, the extent of the
preferences model is clearer (which seems like a good thing).
- The Eclipse service lacks level-specific methods whereas the IMP
service provides them. This is mainly for the benefit of
clients that will be managing preferences rather than just using them
in an application.
- The Eclipse service lookup methods all take an arbitrary
qualifier that represents a namespace within the scope; the IMP
service doesn't expose these general qualifiers but instead uses
langauge names internally as qualifiers to define language-specific
namespaces.
- The Eclipse service allows clients to change the default order in
which scopes (levels) are searched for a particular preference.
The IMP service thinks this borders on madness and maintains a fixed
search order (from lower/more-specific to
higher/more-general). However, the IMP service does
allow a client to request a preference value from a specific level.
- The Eclipse service provides a means to filter the preference
nodes searched, which the IMP service does not. In the Eclipse
service this may allow a preference to be selected from a specific
scope or level (or from among a subset of the available scopes and
levels); with the IMP service you can lookup a preference by level.
To reiterate, some of the general similarities between the two models
(which reflect points the IMP model has adopted from the Eclipse
model) include the use of multiple levels or scopes of preferences, the
four particular levels or scopes of preferences, the use of Eclipse
preferences nodes, inheritance of values across scopes or levels, the
use of namespaces (whether constrained or unconstrained), the basic
types of preferences, and management of preference storage by the
service,
Comparison
to the JFace Preference Store
Prior to the introduction of the Eclipse Preferences Service (and
perhaps remaining so in practice today) the principal mechanism for
storing preferences was the JFace Preference Store
(org.eclipse.jface.preference.PreferenceStore, which implements a
persistent version of org.eclipse.jface.preference.IPreferenceStore).
There are some significant differences between the Preference Store
approach and the general Preferences Service approach:
- The Preference Store approach does not embody any notions of
levels or scopes; those must be realized by clients of a store.
- Consequently, there is no notion of inheritance of values between
stores (as between levels); again, any inheritance or copying between
stores must be managed by clients.
- Each store maintains two values for a particular preference, its
current (or in-force) value and its default value. In contrast
with the layered services approach, the default value is not defined in
a separate scope but is associated with the in-force value in each
"scope" (if we take a store to represent a scope).
- Clients are responsible for controlling the location of files
that store preferences.
Using the IMP
Preferences Service
An instance of the IMP Preferences Service is made available through
the language plugin for each IMP-supported language. This
Service instance is set by default for the particular language
represented by the plugin. The preference values are initialized
automatically. (Values for the default preferences level are set
programmatically; values for other preferences levels are available
from the files used to store them persistently.) The Service
instance is not automatically specialized for any particular project.
How a client of a Service instance should accesses the instance will
depend on its purpose and on assumptions about other possible clients
of the instance that may have compatible or conflicting purposes and
assumptions.
Consider the simple case of a single client that will work with a
single language in a single project and that is interested just in
reading applicable preference values. Then the default language
and project for the Service instance can be set accordingly and the
language and project can be assumed in future calls on the
service. Similarly, the client can ignore the preference level in
making calls on the service as the service will automatically return
the preference value that is set at the lowest (most specific) level.
If the client has a need to work with multiple languages or multiple
projects, it can change the default settings in the Service instance
and continue to make service requests that do not provide these as
parameters. Alternatively, the client can make service requests
in which the project, or language and project, are specified with each
request.
If the client of a Service instance will be operating in an environment
in which there are other clients of the same Service instance that are
concerned with the same language and project, then it should be safe
for those clients to rely on the default settings for language and
project in the Service instance.
However, ff the client of a Service-instance will be operating in an
environment in which there may be other clients of the Service instance
that are concerned with differing languages or projects, then the
default settings for language and project in the Service instance
should not be relied on. In such cases, to be safe, the clients
should make the language and project explicit in their service requests.
Typical application clients of a IMP Preferences Service instance
will be concerned with reading preference values but not writing them,
and these clients will typically not be concerned with the level at
which the preference is set. On the other hand, other clients of
a Service instance, notably those concerned with managing preferences,
will both read and write preference values, and these clients will be
concerned with the level on which a value is set.
As with clients that just read preference values, clients that both
read and write preference values may operate in environments where
there may be one or more clients in operation concurrently and where
the language or project of concern may be fixed or varied within or
across clients. Depending on the conditions that may apply, the
clients may rely on the default values that are set for language and
project in the Service instance or the clients may specify the language
and project in individual service requests. This can be done for
the setting of preference values just as it is done for the getting of
preference values.
The setting of preference values necessarily occurs on a particular
level, so methods that set preference values all take a parameter that
specifies the level. (There is not notion of a "default level" in
the IMP Preferences Service.) For clients, such as preference
managers, that are concerned with preferences on specific levels, there
are methods to get a preference value on a given level as well.
(Ordinarily, these will not be used by typical application clients that
are simply reading preference values.)
Implementing the
IMP Preferences Service
Users of IMP do not need to do anything to implement the IMP
Preferences Service for their language or IDE. The Service is
implemented in IMP framework classes, and instances of the Service
are made available automatically as part of the language plugins that
are constucted by IMP when the New Language Wizard is run.
IMP Preference Pages
IMP initially supports a single design for preference pages, which
is a preference page with four tabs, one for each of the four
preference levels. The page and the four tabs (with certain
annotations and
controls) are generated automatically by IMP.
The tabs are intended to display preference fields and related controls
(see
IMP Preference Fields).
The specific
fields on a page must be added by the developer of the page. The
fields are not restricted with respect ot their number, type,
identification, layout and other aspects of display; these are up to
the page developer. However, normally the same fields will be
presented in the same way on each tab. That is, each tab is
intended to be a view of the same set of language-related preferences,
but with values appropriate to the level represented by the tab.
The project-level tab also allows a project to be selected and displays
the currently selected project. IMP generates the
implementation for this automatically.
Because IMP uses tabs at the top level within a page to represent
preferences levels, tabs can't be used at the top level of one of these
pages for some other purpose (such as grouping preferences relating to
different concerns). IMP doesn't restrict the use of tabs
within tabs for that purpose, but such an approach might be
awkward. Given that IMP has more or less
preempted the use of tabs to display preferences levels, other
mechanisms should probably be used to organize alternative groups of
preferences, such as different pages (at the top level or nested
levels) or (possibly) links to additional dialogs or pages.
IMP preference pages, like typical preference pages, can display
error messages. However, IMP preference pages, unlike most
preference pages, have multiple tabs from which errors may arise.
Each tab, in turn, may have multiple fields from which errors
arise. To accommodate these multiple sources of errors, each tab
maintains a list of error messages that arise from errors in its
fields. The page displays an error message from the current tab,
if the tab has an error, or the page title, if the current tab does not
have an error. (The alternate display of page titles on correct
pages and error messages on erroneous pages is a typical behavior for
Eclipse preference pages.) Any tab that has errors will
have its name marked to make apparent that the tab is erroneous even if
it (and its error messages) are not displayed on the page. The
current approach is to bracket the name of the tab with an "error
mark." By default the error mark is is "**"; thus the project
level tab is
currently labeled as "Project" when its fields are correct and as
"**Project**" when it has an erroneous field This handling of
error mesages is supported in IMP framework classes for preference
pages, tabs, and fields. (The error mark or the approach to
error marking of tabs can be readily changed.)
IMP preference pages are also intended to have a consistent
appearance and behavior that must be supported in part by the
preference fields. These include a
consistent approach to indicating field values that are inherited
(currenly shown with a blue background color), a consistent approach to
indicating that a field has been modified (currently shown by a '>'
at the start of the label text), and a consistent response to various
controls (e.g., remoal of the current value, or the setting of a
special value). These aspects of appearance and behavior are
supported by the set of IMP framework classes for preference field
types.
The IMP preference pages, tabs, and dialogs also have a standard set
of buttons (which may be adapted).
Buttons and their Behavior are
discussed below.
Using an IMP
preference page
IMP preference pages are used largely like any other preference page
by typing in text boxes, checking checkboxes, pushing radio buttons,
and so on. IMP preference pages also have some features that
typical preference pages do not.
One prominent feature is the tabs that represent preferences on
different levels of the preferences moel. Preferences are always
viewed and operated on with respect to some level of the preferences
model.
On the project-level tab, there is also a field for selecting the
project that is used to determine a relevant set of project-level
preferences. Preferences that are displayed on the project-level
tab, when a project is selected, are the effective or in-force
preferences relative to that project (inheriting values from higher
levels for any preferences that are not set on the project level).
Finally, each preference field may have an associated "details" link
that will lead to a dialog that may offer buttons for setting (or
unsetting) the value of a field in particular way (see Details links
and dialogs)
Implementing a
IMP preference page
A working implementation for a preference page
with buttons and tabs is generated automatically by the New Preference
Page wizard. The principal work in implementing a page is in
implementing the fields (and related elements) in the tabs
on the page.
This consists mainly of constructing and laying out
an appropriate set of preference fields, along with the associated
details links (if used as recommended) and arranging for "toggle"
relationships among fields if necessary (as when one field is enabled
of disabled by the checking of another). This is described in
more detail under
IMP
Preference Fields.
Typically a
corresponding set of fields and links will be used on the
tabs for
all levels, although parameterized somewhat differently depending on
the level. Typically the same layout will be used on the tabs for
each level. Some additional work is needed for the project-level
tab, which must be made responsive to the selection of projects.
Approaches to laying out the fields can be seen in the
Examples section and in the preference pages for JikesPG or x10.
All tabs must also provide implementations of the methods that respond
to button pushes--these simply need to take the appropriate action on
the fields actually used on the page. This is described in more
detail under
Details links and
dialogs.
Alternative page designs
The initial design of IMP-based preference pages has certain
characterisitcs:
- All preference levels are avaliable through tabs
- All flelds on each tab can be read or written
- Some metadata about each field is shown by the way the field is
displayed (e.g., blue background for inherited fields)
- Additional metadata for each field is shown in a separate
"details" dialog for the field (accessed by a link associated with the
field)
This approach makes a full set of preference-related information and
controls available, with some separation of features to simplify
particular views or tasks.
Depending on the goals for the pages, other page designs may be
appropriate. Some alternative page designs:
- A read-only page of in-force preferences, regardless of where or
how set, for users who just want to know what the preferences
are.
This would be more or less equivalent to the fields shown on a
project-level tab.
- A more comprehensive page (or tabs) for preferences managers,
combining the information and controls that are now divided between the
tabs and the details dialogs. This would amount to a
comprehensive
"control panel" for preference managers.
- A tabbed preference page more or less like the one provided but
with fewer levels. The individual tabs and dialogs might be just
as
complex as in the provided style, but there would be fewer of them.
We have not (yet) experimented with alternative page designs such as
these, but many should be possible with substantial reuse of the IMP
framework classes for preferences. Please feel free to contact
the
IMP team with questions or suggestions on this topic (as with any
others).
IMP Preference Fields
Preference values are represented on preference pages by preference
fields. There are different types of preference field
corresponding to different types of preference values and different
ways of representing the preferences on preference pages.
Field types
Preference fields are supported by a hierarchy of IMP framework
classes (all found in org.eclipse.uide.preferences.fields). The
root
of the hierarchy is the abstract type IMPFieldEditor (which extends
org.eclipse.jface.preference.FieldEditor). The hierarchy includes
several abstract and concrete types that generally correspond to
subtypes of FieldEditor. The hierarchy of provided types is as
follows:
IMPFieldEditor (abstract)
IMPBooleanFieldEditor
IMPComboFieldEditor
IMPRadioGroupFieldEditor
IMPStringFieldEditor
IMPIntegerFieldEditor
IMPStringButtonFieldEditor (abstract)
IMPDirectoryListFieldEditor
IMPFileFieldEditor
In implementation these combine elements of the corresponding
FieldEditor subtypes (mainly for management of GUI controls) with
aspects relating to the use of the IMP Preferences Service (rather
than the Eclipse Preference Store), the inheritance of preference
values, and various controls and attributes used in IMP preference
pages.
The set of field types supported by IMP is intended to be
representative and usefully broad but not exhaustive. It may grow
in the future if there is sufficient demand for additional types.
Users are welcome to develop and contribute their own field types.
Adding preference
fields to a preferences tab
The IMP Preferences Tabs are designed so that the preference fields
for each tab are created in a method dedicated to that purpose:
protected IMPFieldEditor[]
createFields(Composite composite) {...}
This method is automatically passed the Composite that represents the
tab to which the fields are added and it returns an array of the field
editors that are created to represent the fields on the tab.
This method has three parts: 1) Declare the field editors, 2)
create the field editors and related controls, and 3) put them into an
array and return it. Of course, the only nontrivial step here is
the second.
IMP provides utility methods (currently in
org.eclipse.uide.preferences.IMPPreferencesUtilities) that allow a
preference field of a particular type to be created with a single
call. The specification of the method to create a
StringFieldEditor (which is representative) is as follows:
public IMPStringFieldEditor
makeNewStringField(
PreferencePage page,
IMPPreferencesTab tab,
IIMPPreferencesService service,
String level, String key, String text,
Composite parent,
boolean isEnabled, boolean isEditable,
boolean hasSpecialValue, String specialValue,
boolean emptyValueAllowed, String emptyValue,
boolean isRemovable) { ... }
The "page", "tab", and "service" parameters should be self-explanatory;
values for these should all be avaliable in contexts where this method
would be used. The "level"parameter is the string name of the
preferences level on which the field occurs (which should be consistent
with the level represented by the tab).
The "key" parameter is the key by which the preference value will be
stored in the Preferences Service; the "text" parameter is the name of
the preference as it will appear on the label to the field.
The "parent" parameter is a graphical element that is created within
the tab folder to hold the fields and other elements that are placed on
the tab. This object is created automatically by IMP and
passed automatically to the createFields(..) method and so should be
avaliable in the context in which fields are created. The
implementor of fields on the tab must pass it along but can otherwise
ignore it.
The remaining parameters relate to attributes of the field, some of
which are inherent to the FieldEditor type, some of which are defined
and used as part of the IMP preference pages.
The "isEnabled" parameter determines whether the field is
enabled--something that applies to all FieldEditor instances. To
be enabled means, of course, that it can be used (e.g., clicked on or
typed into), whereas to be disabled means that it cannot be used.
Typically disabled GUI controls are shown as "grayed out" in some
way. That convention is followed in IMP.
(Note to field implementors: 1) The "graying out" of disabled
fields does not happen automatically, it is controlled independently of
the enabled state and must be explicitly coordinated with it.
2) All field editors actuall contain multiple GUI controls--at
least a label and an element to represent the field value, if not
others--and each of these controls within the field can be managed
independently. Thus, when a field as a whole is enabled or
disabled--or any other property of the field as a whole is set--the
effect must be propagated to the various controls within the field
editor. We have done this in a particular way for the field
editor types we have implemented for IMP, but it could reasonable be
done in other ways.)
The "isEditable" parameter is similar to the "isEnabled" parameter in
that it governs the usability of the field. In the
implementation, "isEditable" actually applies to the Text wiget used to
hold the text in the GUI representation of the field (and so this
parameter doesn't apply to field editors that do not have a text
box). Generally, isEditable and isEnabled should be set in
parallel (otherwise you can have a disabled field that you can still
edit or an enabled filed that you can't edit).
The "hasSpecialValue" parameter is a flag that indicates whether the
field has an associated "special value". The use of a special
value for a field is optional, and whether a special value is used, and
what it may signify, are dependent on field-specific semantcs.
Typical uses would be to provide a field-specific default value, to
provide an alternative representation of some value that cannot be
represented directly, or to provide a preferred value that is something
other than the default value. These cases are discussed in the
section
Special Values. The
parameter "specialValue" is used to represent the special value if the
field has one. (Thus, if "hasSpecialValue" is true,
"specialValue" should generally not be null; also, if "hasSpecialValue"
is false, the value of "specialValue" is ignored.)
The "emptyValueAllowed" parameter is used to indicate whether the field
can take on an empty value. This attribute is adopted from the
StringFieldEditor type and in IMP is generalized to other types of
fields as well. In effect the "empty" value is a special value
for which a dedicated attribute is provided. For Strings the
empty value is naturally the empty string. For other field types
that make use of Strings, an empty String might also be used for the
empty value. In any case, the parameter "emptyValue" is provided
to enable a type-specific or field-specific empty value to be
specified. Empty values may not be supported for some field types
(in which case the "emptyValueAllowed" and "emptyValue" parameters will
not be available in the utility routines or constructor methods for
creating instances of those types.)
(Note to field implementors: In general, whether a field may have
an empty value depends on three things: 1) whether it makes
sense abstractly for the type and field; 2) whether there is some
practical way to show the empty value in the GUI; and 3) whether
there is some possible way to store the value in the preferences
service.)
The final parameter is "isRemovable", which is a flag that governs
whether a value stored for the field (that is, stored on the
preferences level where the field is used) can be removed. In
general, the values of fields below the default level can be removed
whereas the values of fields on the default level cannot be. That
is because the removal of a value on one level causes the value from
the next higher level to be inherited. However, "isRemovable" can
be set to false for some field below the default level if it is
intended that the field should always have a value that is set at that
level and not inherited. (This is more or less the effect that
you get when you enable project-specific preferences in the Eclipse
JDT.)
Level-specific
considerations
On the details level, as noted, preference values should generally not
be removable, as the details level is there to assure that preferences
are defined even when values are removed from other levels.
Preferences may be disabled or not (and uneditable or not) according to
whether it should be possible to change the default value during a
single execution of the IDE. Preference values on the default
level are statically initialized when the preference page is created,
and values in those fields are not stored by the Preferences
Service. Still, it is possible (if a field is enabled and--if
applicable--editable) to change the default-level values that are
stored in the runtime preferences model and displayed on the
default-level tab. Values changed on the default-level tab may
remain in effect so long as the IDE continues to execute. Whether
default-level preferences should be changeable is up to the designers
of a particular preferences page.
On the (workspace) configuration and (workspace) instance levels, there
are no level-specific issues. These are the intermediate levels
of the preferences model with no special restrictions or issues.
On the project level, the fields should take on values--even inherited
values--only when a project is defined. Mechanisms built into the
project tab generally take care of this once a field is defined.
Note, though, that no project is defined automatically by default; in
other words, when a page is first created, the project is
undefined. Thus, when fields are created on the project level,
they should be created with "isEnabled" set to false (and similarly for
"isEditable", if present).
Details links and dialogs
The IMP versions of field editors have more behaviors and attributes
than do their JFace counterparts. Some of this is exposed in the
approach we have taken to preferences tabs, for example, showing
inherited fields with a distinctive background color and marking the
lables of fields that have been modified. However, there is
additional information about IMP ields that is not shown on the
preferences tab because we thought the tab would be too confusing for
some purposes if all the information is shown. For that reason,
we have associated a "details" link with each field: navigating
the details link for a field brings up a dialog with more complete
information about the field and with additional controls for operating
on the field.
The information that is shown in a typical details dialog for a field
includes:
- The level of the field
- The current value of the field
- The level on which the current value is set
- The empty value of the field, if any
- The special value of the field, if any
- Whether the field (that is, its value) is removable
The controls that may be available include buttons to
- Copy in an inherited value
- Set a special value
- Set the empty value
- Remove a set value
These may be variously present or enabled depending on the type of the
field and the state of its attributes. (See
Buttons
and their Behavior for more information on these.)
The IMP framework provides utility methods to create details dialogs
and links for various types of fields (like the methods to create
fields, these are currently in
org.eclipse.uide.preferences.IMPPreferencesUtilities). Some of
these, like the one for creating a link to a StringFieldEditor, work
for the specific type and subtypes:
public Link createDetailsLink(
Composite detailsHolder,
final IMPStringFieldEditor field,
final Composite fieldHolder,
String text)
{ ... }
The "parent" parameter is the same Container that was used as the
"parent" in the corresponding field.
The field for which the dialog is to provide details is provided as the
source of those details and target of operations supported by the
dialog. An set of appropriate controls will be created and
enabled automatically according to the type of field and state of its
attributes .
The "fieldHolder" parameter is the value returned by the "getParent()"
method on the principal control in the field (for example
"myStringField.getTextControl().getParent()" or
"myBooleanField.getChangeControl().getParent()"). (In our
implementations, there is usually a level of container between the
container represented by the "parent" parameter and the field itself,
as this is sometimes helpful in managing the GUI layout. In any
case,
you can always obtain the needed element from the field, more or less
as shown.)
The "text" parameter gives the label that is to be used on the created
link; we have conventionally used "Details ..."
Field toggles
It is not infrequently the case that one field is enabled according to
how another is set. The typical case is when a boolean field
controls the enabled state of some other field. In the JikesPG
preferences, for example, there is a boolean checkbox which can be
checked (or not) to indicate whether the default generator executable
file should be used (or not). There is an accompanying String
field in which the path to an alternative generator executable can be
specified. This String field should be enabled just when the
checkbox is unchecked. We refer to such a relationship between
fields as a "field toggle", although the kind of toggle just described
is actually a relatively simple case of a more general set of
field-dependence relationships.
IMP provides support for setting up a field toggle between a boolean
field and a String field, that is, the method
IMPPreferencesUtilities.createFieldToggleListener(..). This
method makes the enabled state of a String field depenent on the value
of a boolean field, either consistent with the boolean field or
complementary to it. We have generally created field-toggle
listeners in the "createFields(..)" method along with the fields and
details links.
If experience indicates a substantial need for other sorts of
dependency relationships among fields, the IMP framework can be
extended to support these as well.
Layout and spacing
For the preferences pages that we have implemented we have used a
two-column format, with fields on the left and the associated details
link on the right. The dialog to which the link leads is created
as a consequence of creating the link. If you create a details
link immediately after creating the field to which it applies then
these will appear as successive elements in the preferences tab.
(Alternatively, you can create them non-successively and explicitly
arrange them however you want to.)
In the two column format we occasionally want to "skip a line" between
fields or to fill out the end of a row where we've included notes in
just the first column. That involves using invisible elements as
placeholders to fill out cells that will appear as empty space on the
tab. The IMPPreferencesUtilities class (cited previously)
contains a routine to help with this:
public static void
fillGridPlace(Composite composite, int num) {...}
Here the "composite" parameter is the same "parent" composite that
contains the fields and details links; "num" represents the number of
cells to be skipped. This routine is not specific to a two-column
format; for example, you can call this with "num" set to two and skip
one row in two-column format or two rows in one-column format, and so
on. The element used as the placeholder is a label with its
visibility set to false; labels aren't the tallest of GUI elements, so
to skip an extensive vertical space it may be necessary to insert
multiple rows of them.
Special values
As mentioned above, a preference field may have an associated "special
value" that may play various roles depending on the semantics of the
field. Some examples of possible uses for this field are as
follows:
- To provide a field-specific default value: In preference
pages that are based on the Eclipse Preference Store, every field has a
prevailing value and a default value; the default value is stored along
with the prevailing value for the field and is always there as a
"falllback" in the event that the prevailing value must be
discarded. With the IMP Preferences Service (following from
the Eclipse Preferences Service), there is no default value stored
along with the prevailing value for a field; rather, the default value
is more properly conceived of as the value that is inherited form the
next higher preferences level, the highest of which is designated the
"default" level (which gets its default values from the initialization
code). As a result, on levels below the default level, there is
no field-specific "fallback" value that can always be used to replace a
prevailing value. The "special value" can play this role.
- To provide an alternative representation of some value that
cannot be represented directly: Some fields may not allow the
representation of values that are problematic in some way, such as
empty strings or undefined values that fall outside of the range of
legal representations. In such cases the special value may be
used to give a representable form to a value that is otherwise not
reprsentable, such as "empty" for the empty string (where those are not
allowed) or "undefined" for values that are not set yet.
(Note: Of course the type of the special value must be consistent
with the type of the field. Many fields, even those not
ostensibly of String types, nevertheless use strings to represent their
values, so Stings can be used to define special values for most field
types.)
- To provide a preferred value that is something other than the
default value: A field may have a legitimate default value that
is generally suitable in the abstract, but it may also have non-default
values that would be preferred for some applications or in some
contexts. For example, consider a combo box (pulldown list of
chioces) where the choices are various countries. The default
value for the field may be the empty (or blank) string--but nobody
lives or works in the empty or blank country. Thus, depending on
where the application is expected to be used, the name of a specific
conutry may be preferred to the default value. The special value
can be used to represent a preferred value of this sort.
Other uses for the special value may emerge.
Given that there are several roles that a special value may play, and
given that these roles are generally not exclusive, it would seem that
the special value may be overloaded and underpowered. If
sufficient need emerges, we can provide additional field attributes
that are dedicated to more specific roles.
Implementing
Other Things for Preference Pages
There are two other areas in which implementation is required to get a
fully functioning IMP preference page. These are special
elments of the project tab and preferences initializers and
constants. (Users are also free to
implement their own field-editor types,
but that won't be ncessary where the provided field-editor types can be
used.)
Special elements
of the project tab
The preferences tab for the project level has fields and (nominally)
details links just as do the tabs for the other preferences
levels.
However, the project tab has a number of additional elements that are
related to the dynamic selection of projects for which preferences are
to be displayed.
All of the tabs have a "createFields(..)" method that must be
implemented with calls to create the fields (and associated details
links). The project tab also has a method
"addressProjectSelection(..) that takes a project selection event and
the composite parent that holds the fields on the tab. The
project selection event contains the old and new project-preference
nodes from the preferences model. In principle, either of these
may be null if they represent a state in which no project is set.
The main implementation tasks needed to address project selection are
as follows. In the case that the new preferences node is not null:
- Each field needs to be set. IMPPreferencesUtilities has
"setField" methods for various types of fields that will set the fields
with stored or inherited values, as appropriate, and set associated
attributes accordingly.
- The enabled state of each field needs to be set. Generally,
each field will be enabled, unless the enabled state of one field
depends on the value or enabled state of another field, in which case
the enabled state of the dependent field will have to be set
conditionally.
- A preference-change listener needs to be added to enable each
field to listen for changes in the preferences model. There is a
method defined for this purpose:
ProjectPreferencesTab.addProjectPreferenceChangeListeners(..).
In the case that the new preferences node is null, the fields need to
be disabled. (This can't be done generically because it depends
on the type of control(s) in the field and on the methods used to
disable them.)
Instructions about these implementation steps and some supporting steps
are provided in the generated project-tab class. Several
additional actions that must be taken in response to the selection (or
deselection) of a project in the tab are addressed automatically.
Preferences
initializers and constants
IMP generates an empty "Preferences Initializer" class for each
generated preference page. This class is an extension of
org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer and
it requires an implementation of the method
initializeDefaultPreferences(). The generated implementation of
that method is empty, since it depends on the particular fields to be
initialized. Implementing initializeDefaultPreferences() simply
requires setting the initial value of each preference at the default
level in the preferences store; for example:
service.setBooleanPreference(
IIMPPreferencesService.DEFAULT_LEVEL,
PreferenceConstants.P_EMIT_MESSAGES,
getDefaultEmitMessages());
In this case the default-level value for the preference named by
PreferenceConstants.P_EMIT_MESSAGES is set to the value returned by
getDefaultEmitMessages().
In the IMP Preferences Service (as in the Eclipse Preferences
Service) the Preferences Initializer is not just the initializer of
default values but the immediate definer of those values. That is
because the initial default values are never stored by the Service but
only obtained from the Initialzer, because these are the values that
are restored when default values are restored on the default
preferences level, and because these are the only default values that
persist between invocations of the IDE. Of course, the values
provided by the Initializer may come from any source, but the
Initializer can be used to encode the default values of preferences (as
we have done for JikesPG and X10).
IMP similarly generates an empty "Preference Constants" class for
each generated preference page. This class is simply intended for
the defintiion of String constant names for preferences that can be
reused in the Preferences Initializer and elsewhere. It might be
used for the definition of other constants as well.
Buttons and their Behavior
Each IMP preferences page should have two buttons: "Cancel"
and "OK."
The Cancel button should close the preferences page without saving the
state of the page, thereby discarding any unsaved ("non-applied")
changes. The Cancel button should be enabled at all times.
The OK button should close the preferences page after first saving the
state of the page (that is, writing the field values into the
service). The OK button should be enabled whehever there are no
errors on the page (that is, no errors on any tab on the page).
Each IMP preferences tab should have two additional buttons:
"Restore Defaults" and "Apply."
The Restore Defaults button should restore the default value of each
field on the tab. Note that, unlike the preference-store
approach, in which (in usual practice) each field has its own local
default value, in the preferences-service approach there is no locally
stored default value for each field. Rather, in the service
approach, "default" represents the most global level of
preferences. In order to support the Restore Defaults action,
some notion of the default value for levels other than the default
level needs to be defined. IMP takes the approach that the
default value for a lower level is the value that is in force at the
next higher level. So, for example, the default values at the
project level are the values in force on the (workspace) instance
level. This rule holds for the project, instance, and
configuration levels. At the default level there is no higher
level, but the default level has initial values that are set
programmatically and that can be taken as default values (or "default
default" values). So, the behavior of the Restore Defaults button
on any level that is not the default level is to remove any preferences
that are stored at that level (allowing values to be inherited
from the next higher level), and the behavior on the default level is
to
reinitialize the preferences model with the programmed values.
These operations should be applied to each field of the tab, even if it
may already display the appropriate default value; as a side effect in
the fields, each field in the tab should end up marked as modified.
The Apply button should store any locally set field values on the tab
into Preferences Service; values that are inherited should not be
stored. (The store method for each field type should actually
only store values that are set in the field and not inherited; thus,
the implementation of the action for the Apply button can simply
"store" each field on the tab and safely assume that inherited values
will be ignored.) As a side effect in the fields, any modified
marks on the labels of field should be removed. As another side
effect in the fields, some fields that were shown as inherited may end
up shown as locally set, if the Apply entails the storing of a locally
set value that replaces a previously inherited value. (The change
in field appearance from "inherited" to "locally set" occurs only when
the field is finally stored, not when it is first updated.)
As mentioned above, additional buttons occur on the dialogs reached
through the "details" links that may be associated with a field.
These include some subset of "Copy In", "Set Special", "Set Empty",
"Remove" and "OK."
The Copy In button has the effect of copying an inherited value into
the local field. On levels other than the default level the
button should be disabled whenever the value of the field is not
inherited (so pressing the button should have the effect of disabling
it). On the defalut level there can be no inherited values, so
the button should be omitted (preferrably) or permanently disabled.
The Set Special button has the effect of setting a "special" value into
the local field, if one is defined for the field. (The IMP
framework field types support the definition of a special value, but
the use of these values is optional. See the discussion of
special values.) The button should be enabled for any field
for which a special value is defined and it should be absent
(preferrably) or permanently disabled for any field for which no
special value is defined.
The Set Empty button has the effect of seting an "empty" value into the
local field, if one is defined for the field. (The IMP
framework field types support the definition of an empty value, but the
use of these values is optional. See the discussion of empty
values.) The button should be enabled for any field for
which a special value is
defined and it should be absent (preferrably) or permanently disabled
for any field for which no special value is defined.
The Remove button should have the effect of removing a value that is
locally stored in a field, thereby allowing an inherited value to come
into effect. Values of fields on the default level can never be
removed, as there is no higher level from which another value may be
inherited. Fields on other levels can be designated as removable
or not, although cases in which fields would not be removable are
expected to be rare (this would require that once a value is set
locally it must always be set locally). On levels below the
default level, the Remove button should be enable whenever the field is
removable and contains a locally set value that can be removed (and
disabled otherwise). On the default level, the Remove button
should be absent (preferrably) or permanently disabled.
The OK button just closes the dialog.
The buttons listed above are all supported by IMP framework
classes. These buttons have proven useful and
sufficient in our early experience, but users should feel free to add
further buttons as their applications may require.
Implementing
SAFARI Preference Field Editors
There is more that might be said about the implementation of a field
editor for IMP than can reasonably be fit into a user's guide of
this sort. Here we will just point out some of the general
responsibilities and concerns that must be addressed by the implementor
of a field editor. For the real details, the SAFARI field editors
themselves should be studied.
General responsibilities
The main responsibilities of a SAFARI field editor include:
- Creating and managing the GUI wigets
- Accepting and processing changes to the field from its GUI
- Accepting and processing changes to the field from its API
- Storing values into the corresponding node of the Preferences
Service
- Loading values from the conrresponding node of the Preferences
Service
- Inheriting displalyed values from the corresponding field at
higher levels of the preferences model when there is no field stored
locally
- Evaluating the validity of the field
- Maintaining various attributes reflecting the state of the field
- Supporting the enabling and disabling of the field
- Signaling modifications of the field
- Signaling errors in the field
- Updating aspects of the display to reflect the state of the field
Interactions of concerns
The complexity of field editor types arises not just from the variety
of concerns that they must address but from the interaction of these
concerns. Some facets of this interaction:
Mutual consistency of field and model:
The value of a preference is represented both in a field and in the
preferences model (as represented by the preferences service).
Generally these representations should be consistent with one another,
but their mutual consistency is subject to a number of possible
perturbations: The value in the field may be changed through the
field's GUI controls or through the field's API, and the value in the
model may be changed through its API. Typically a change of one
will entail a corresponding change in the other. This issue is
further linked to the issue of value inhteritance.
Accommodation of value inheritance:
The value shown in a field may not be stored on the corresponding level
of the preferences model but obtained from some higher level.
Thus a new value stored into a field may replace a previously inherited
one, or a stored value removed from a field may require replacement by
an inherited one. Additionally, when a value is newly added,
modified, or removed in a field on one level, the effect of that change
must be propagated down to fields that may inherit from the changed
field.
Validity of values: The
different types of preference field are characterized by different
types of validity concerns, for example, whether a string field may be
empty or whether an integer field should be restricted to a
subrange. Validity conditions may be fixed for the type or
parameterized for specific fields of the type. Each field type is
responsible for checking the relevant validity conditions when a
field's value changes, whatever the source of the change.
Validity conditions may be imposed and checked at any level of the
field-type hierarchy. As the IMP field types are implemented,
each type is responsible for asserting or retracting its own error
messages. Higher-level types must coordinate checks by their
subtypes and integrate the results of multiple checks on multiple
levels, generally (in the event of failure) to produce a single error
message for the field (and retracting any previously set
messages). Changes in the validity status of a field must be
communicated to the containing tab (so that the tab can assess its own
validity, enable or disable its controls accordingly, and communicate
its validity to the containing page).
As the IMP field types are implemented, the validity of a field is
not directly reflected in the presentation of the field. This
could have been done, but effects on the display of the field have been
used to convey other information instead. The validity of a field
is reflected in error messages that the field passes to the tab and
that are displayed by the page when the tab is visible on the
page. (A page can display one message, and each invalid field on
a tab will generate its own message, so, when there are multple invalid
fields, some will not have their error messages displayed.
However, the error message of each invalid field will be displayed at
some point.)
Effects on GUI presentation:
There are a number of attributes of fields that might be reflected in
the way they are presented on the preferences page. As IMP
field editors are implemented, they reflect two particular attributes.
One of these attributes is whether the value shown in the field is set
on the
level of the field or inherited from a higher level. This is done
by setting a distinct background color in inherited fields (which, by
default, is blue), whereas locally set fields are shown with the
usual background color (white). This approach was chosen because
all field editors (at least those encountered so far) have a background
that can be given a distinctive color fairly easily, and this is likely
to be easily noticed in the display (at least for those without color
blindness). The use of distinctive fonts to indicate inherited
values is harder to program and probably less noticable in the display;
the tagging of inherited values with distinctive characters isn't
consistent with some value types (i.e., those that are not
logically strings). The use of a separate field to indicate
inheritance would avoid many of the problems with other approaches but
would clutter up the displaly. The modifications of field labels
could serve this purpose and would avoid the clutter problem but the
modification of field lables is used for another purpose, as described
next.
The other attribute reflected directly in the way the field is
displayed is whether the field has been modified. As the IMP
fields are implemented, modification is taken to mean not just a change
in the field value but a change in the origin of the value. So,
if a field has a particular inherited value, and that value is copied
into the field, that is considered a modification, even though
application clients of the preferences service would not notice any
change. The significance of a modification in a case like this is
that it implies a need to modify the underlying preferences model (in
this example to store a value locally where there was none
before). Modified fields are indicated by prepending a "modified
mark" to the beginning of the name of the field as it appears in the
label text. (By default this mark is a right angle bracket,
'>'.) This approch is easy to implement, since all field
editors (at
least those encountered so far) have text labels, and it has an effect
that is relatively noticable.
Other considerations
The IMP field editor types reflect two main sources of
concern: concerns related to the wigets and other GUI-related
elements of the editors (which are addressed in JFace), and concerns
arising from the IMP preferences model and Service. Since the
latter are more numerous than the former, the IMP field editor types
have been implemented by defining a IMPFieldEditor supertype in
which many of the IMP-specific concerns can be supported and
adapting GUI-related elements of JFace field editors for implementing
corresponding field-editor types in the IMP field-editor type
hierarchy. The number, type, and structure of the GUI controls in
the IMP field-editor types is the same as in the corresponding JFace
field editor types, but the methods that manipulate the controls have
been modified in various ways, for example, to work with the IMP
Preferences Service rather than the JFace Preference Store, and to keep
track of IMP-related attributes.
The IMP-specific part of the interface for field-editor types is
largely systematic; that is, many of the same operations apply across
many (if not all) field-editor types. The JFace part of the
interface to the field-editor types is somewhat less systematic, which
can require some additional acclimitzation on the part of a
field-editor implementor. The lack of regularity is due in part
to the variable number and types of controls within different types of
field editor, but it also arises from the use of different names for
conceptually similar operations (notably, updating the field) and to
the requirement with some field-editor types but not others that the
containing composite be provided as a parameter for certain operations.
SAFARI Preferences
Utilities
IMP provides a utility class with routines that simplify some of the
work that implementors of preference pages may need to perform.
That class is
org.eclipse.uide.preferences.IMPPreferencesUtilities. Methods
for creating fields and their details links have already been mentioned.
The main categories of methods and types provided by
IMPPreferencesUtilities are as follows:
- Methods to create fields of particular types (these create the
field, initialize its value, initialize its properties and attributes,
and manage level-specific concerns)
- Methods do create "details" links for different field types
- Methods to set fields of particular types (these keep track of
level-specific concerns, set pertintent attributes, and update the
presentation of the field
- Preference-change listeners for different types of preference and
field (for monitoring changes in the preferences model)
- Field toggle listeners (for monitoring changes in boolean fields
that may entail changes to the enabled state of another field)
- Miscellaneous methods, such as to create the Default and Apply
buttons on a tab, or to add empty elements to a tab
The SAFARI 'New
Preference Page' Wizard
IMP has a 'New Preference Page' Wizard to help in creating new
IMP preference pages. The wizard can be found and invoked
through the File -> New menu, under the name IDE Language
Support/Core Services/Preferences Dialog.
The dialog has six fields, as follows:
Project: Takes the name of the
plugin project in which the IMP-based IDE is being defined.
This field is required.
Language: Takes the name of the language for which the IMP-base
IDE is being defined. The language and project must
be consistent in that the project must contain a plugin for the
language (although the names of the project and language do not have to
be the same). This field is required.
Id: Should be a unique identifier for the preference page that is
suitable for use within Eclipse. Its role is to differentiate
multiple preference pages for the same language or IDE and to allow the
extension for one page to refer to that for another. (Such
references are used, for example, to organize nesting of perference
pages in the main Eclipse preferences dialog.) While the use of
an id is not strictly required (for instance, it would not be needed
where there will only ever be one preference page for a language), the
provision of an id is strongly encouraged.
Name: Should be a (probably) unique identifier for the preference
page that is suitable for use by people. As with the id, the main
purpose of the name is to help differentiate and organize multiple
pages for the same language, and its use is not strictly required but
is strongly encouraged.
Class: Takes the qualified name of the class that will implement
the preference page; this name will be given to the class that is
generated by IMP. If the packages designated in the name do
not already exist then IMP will create them. This field is
required.
Category: Represents the item in the Eclipse preferences menu, if
any, under which the new page should be listed. To have the new
page listed under an existing page, provide the id of the existing
page. To have the new page listed at the top level of the menu,
leave the field blank.
When you run the 'New Preference Page' wizard, seven classes are
generated:
- A class for the preference page
- One class for each of the four tabs
- A "preferences constants" class (which is empty but is intended
for definitions of String constants for preference-field names)
- A "preferences initializer" class (this class extends
org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer and
requires an implementation of the method
"initializeDefaultPreferences()")
Examples
As you can see, there are not yet any examples here. The best
examples are the templates for the preference page and tabs that are
found in org.eclipse.uide/templates and the implementations of
preference pages and tabs for JikesPG
(org.eclipse.uide.jikespg.preferences) and for X10
(com.ibm.watson.safari.x10.preferences). The IMP framework
classes relating to preference pages are found in
org.eclipse.uide.runtime.preferences and its subpackages.