This document provides guidance on the use of SAFARI to develop a
preference service and preference pages for SAFARI-based IDEs.
Contents
The SAFARI Preferences Model
The SAFARI Prefrences Service
Operations on the Service
Comparison to the Eclipse Preferences Service
Comparison to the JFace Preference Store
Using the SAFARI Preferences Service
Implementing the SAFARI Preferences Service
SAFARI Preference Pages
Using a SAFARI preference page
Implementing a SAFARI preference page
Alternative page designs
SAFARI 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 SAFARI Preference Field Editors
General responsibilities
Interactions of concerns
Other considerations
SAFARI Preferences Utilities
The SAFARI 'New Preference Page' Wizard
Examples
The SAFARI Preferences Model
SAFARI 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 SAFARI model of preferences is basically that of the Preferences Service. Key aspects of the SAFARI 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 SAFARI Preferences Service
The Safari Preferences Service generally implements the Safari Preferences Model. The service is defined by an interface ISafariPreferencesService (org.eclipse.uide.preferences.ISafariPreferencesService), which is implemented by the class SafariPreferencesService (org.eclipse.uide.preferences.Safa riPreferencesService).Operations on the Service
The Safari 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 Safari 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 Safari Preferences Service API. Note that all instances of the Safari 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 Safari Preferences Service adopts many of the basic concepts the
Eclipse Preferences Service (see The Safari Preferences Model)
but is more restricted in certain respects and more flexible in
others. Some of the major differences:- The Safari 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 Safari 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 Safari service don't have such parameters. The Safari 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 Safari case). With the Safari 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 Safari 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 Safari
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 Safari service thinks this borders on madness and maintains a fixed search order (from lower/more-specific to higher/more-general). However, the Safari 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 Safari 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 Safari service you can lookup a preference by level.
To reiterate, some of the general similarities between the two models (which reflect points the Safari 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 SAFARI Preferences Service
An instance of the Safari Preferences Service is made available through the language plugin for each Safari-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 Safari 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 Safari 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 SAFARI Preferences Service
Users of Safari do not need to do anything to implement the Safari Preferences Service for their language or IDE. The Service is implemented in SAFARI framework classes, and instances of the Service are made available automatically as part of the language plugins that are constucted by Safari when the New Language Wizard is run.SAFARI Preference Pages
Safari 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 Safari.The tabs are intended to display preference fields and related controls (see SAFARI 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. Safari generates the implementation for this automatically.
Because Safari 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). Safari doesn't restrict the use of tabs within tabs for that purpose, but such an approach might be awkward. Given that Safari 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.
Safari preference pages, like typical preference pages, can display error messages. However, Safari 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 Safari framework classes for preference pages, tabs, and fields. (The error mark or the approach to error marking of tabs can be readily changed.)
Safari 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 Safari framework classes for preference field types.
The Safari preference pages, tabs, and dialogs also have a standard set of buttons (which may be adapted). Buttons and their Behavior are discussed below.
Using a SAFARI preference page
Safari preference pages are used largely like any other preference page by typing in text boxes, checking checkboxes, pushing radio buttons, and so on. Safari 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 SAFARI 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 SAFARI 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 Safari-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)
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 Safari framework classes for preferences. Please feel free to contact the Safari team with questions or suggestions on this topic (as with any others).
SAFARI 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 Safari framework classes (all found in org.eclipse.uide.preferences.fields). The root of the hierarchy is the abstract type SafariFieldEditor (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:SafariFieldEditor (abstract)
In implementation these combine elements of the corresponding
FieldEditor subtypes (mainly for management of GUI controls) with
aspects relating to the use of the Safari Preferences Service (rather
than the Eclipse Preference Store), the inheritance of preference
values, and various controls and attributes used in Safari preference
pages.SafariBooleanFieldEditor
SafariComboFieldEditor
SafariRadioGroupFieldEditor
SafariStringFieldEditor
SafariRadioGroupFieldEditor
SafariStringFieldEditor
SafariIntegerFieldEditor
SafariStringButtonFieldEditor (abstract)
SafariStringButtonFieldEditor (abstract)
SafariDirectoryListFieldEditor
SafariFileFieldEditor
SafariFileFieldEditor
The set of field types supported by Safari 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 Safari Preferences Tabs are designed so that the preference fields for each tab are created in a method dedicated to that purpose:protected SafariFieldEditor[] 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.
Safari provides utility methods (currently in org.eclipse.uide.preferences.SafariPreferencesUtilities) 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 SafariStringFieldEditor makeNewStringField(
PreferencePage page,
SafariPreferencesTab tab,
ISafariPreferencesService 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 Safari 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 Safari 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 Safari.
(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 Safari, 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 Safari 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 Safari 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 Safari 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
The Safari 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.SafariPreferencesUtilities). 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 SafariStringFieldEditor field,
final Composite fieldHolder,
String text)
{ ... }
final SafariStringFieldEditor 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.Safari provides support for setting up a field toggle between a boolean field and a String field, that is, the method SafariPreferencesUtilities.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 Safari 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 SafariPreferencesUtilities 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 Safari 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.
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 Safari 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. SafariPreferencesUtilities 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(..).
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
Safari generates an empty "Preferences Initializer" class for each generated preference page. This class is an extension oforg.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(
ISafariPreferencesService.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().ISafariPreferencesService.DEFAULT_LEVEL,
PreferenceConstants.P_EMIT_MESSAGES,
getDefaultEmitMessages());
In the Safari 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).
Safari 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 Safari 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 Safari 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. Safari 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 Safari 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 Safari 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 Safari 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 Safari 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 Safari 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 Safari 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 Safari 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 Safari 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 Safari 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 Safari preferences model and Service. Since the latter are more numerous than the former, the Safari field editor types have been implemented by defining a SafariFieldEditor supertype in which many of the Safari-specific concerns can be supported and adapting GUI-related elements of JFace field editors for implementing corresponding field-editor types in the Safari field-editor type hierarchy. The number, type, and structure of the GUI controls in the Safari 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 Safari Preferences Service rather than the JFace Preference Store, and to keep track of Safari-related attributes.The Safari-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
Safari 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.SafariPreferencesUtilities. Methods for creating fields and their details links have already been mentioned.The main categories of methods and types provided by SafariPreferencesUtilities 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
Safari has a 'New Preference Page' Wizard to help in creating new Safari 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 Safari-based IDE is being defined.
This field is required.
Language: Takes the name of the language for which the Safari-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 Safari. If the packages designated in the name do not already exist then Safari 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.
Language: Takes the name of the language for which the Safari-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 Safari. If the packages designated in the name do not already exist then Safari 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()")
