HIPE Preferences
This section explains the relationship between preferences and properties in HIPE, and how you can contribute to:
- Introduce preferences and develop a GUI panel for them in your module.
- Make a bridge between preferences and existing properties.
- Use preferences in client code.
- Listen to changes in preferences to adapt your functionality dinamically.
Introduction
Preferences in HIPE respond to the request of customizing the tool in a user friendly way, from the User Vision document :
«The Main Interface (...) should provide a new pull-down menu accessible from the top bar with label “Properties” to allow user to change the configuration during the session.
This should not be done with Propgen.»
That menu option is called "Preferences" instead of "Properties", since it is a more common label in GUI applications, and a standard in Mac OS X.
The preferences dialog can be accessed in three different ways:
- By clicking on the preferences icon in the Welcome view
- By pressing the menu option Edit > Preferences
- By pressing Alt+Enter at any moment
We talk here about
user preferences, meaning those preferences affecting the session that the user may want to change in a user friendly way.
Therefore we don't consider
system preferences, which are used by developers for introducing some flexibility in the functionality they provide, but users shouldn't be concerned of. The existing properties mechanism may still suffice for them.
The preferences framework is decoupled from the old configuration properties, so that it could be developed while not being dependent on the
property roadmap.
Nevertheless, preferences can reuse properties and even be just a wrapper for them if needed, as explained later. This way, the break is not dramatic, although replacing properties by preferences is encouraged.
Categories
Preferences are organized in categories, which have a hierarchical structure.
Each category may have a parent category (at most one). A category with no parent is called a
root category.
It is like a tree, where more than one root is allowed.
Every category is identified by a string that contains its whole path, including its ancestors. This full path must be unique.
The path of a category is similar to an absolute path in Unix, with the difference that the leading '/' is omitted. For example:
General A root category
General/Appearance A child category under General
General/Appearance/Window A child category under General/Appearance
General/Appearance/Console Another child category under General/Appearance
Graphics Another root category
Preferences lie under a particular category. They are identified by a name called
key, which must be unique within that category, but can be reused in other categories.
Preferences dialog
The explained approach leads to a preferences dialog in which the user may navigate between categories, select one of them, see and change any of its associated preferences, and accept or cancel the changes.
The preferences dialog then looks like this:
How to contribute
Adding and using preferences has few simple steps.
From one side, if you want to introduce a new category, you need to write a
preferences panel, and then
register it.
Afterwards, client code would want to read those preferences, which is done through
UserPreferences class.
Probably it would also want to listen to modifications, so the user doesn't need to restart HIPE for his changes being applied.
Optionally, you may want to link a preference to an existing property that you just want to migrate to the preferences framework.
Step by step:
Develop a preferences panel
When the user clicks on a category within the tree, the corresponding panel is shown at the right side of the dialog.
Creating a panel requires to extend
PreferencesPanel -basically reproduced here- by implementing a couple of methods:
public abstract class PreferencesPanel extends JPanel {
protected PreferencesPanel() {}
protected abstract void makeContent();
protected abstract void registerHandlers();
protected final void registerHandler(String key, PreferenceHandler<?> handler) { ... }
}
In this method you call registerHandler
once per preference. This is how you specify the keys belonging to the associated category.
Each preference is handled by a PreferenceHandler, which provides the means of updating the GUI with the existing preference value and to get any change that the user introduces, and more.
You may consider to extend AbstractPreferenceHandler instead of implementing the interface directly.
Here you create and add to the panel the graphical components for showing and changing the preferences associated to this category.
Example:
public class SimplePreferencesPanel extends PreferencesPanel {
private static final long serialVersionUID = 1L;
private JTextField _fieldA; // text field associated to preference keyA
private JIntegerField _fieldB; // text field associated to preference keyB
@Override
protected void registerHandlers() {
// Preference keyA with type String and default value "text"
registerHandler("keyA", new AbstractPreferenceHandler<String>("text") {
public String getValue() { return _fieldA.getText(); }
public void setValue(String value) { _fieldA.setText(value); }
});
// Preference keyB with type Integer and default value 3
registerHandler("keyB", new AbstractPreferenceHandler<Integer>(3) {
public Integer getValue() { return _fieldB.getvalue(); }
public void setValue(Integer value) { _fieldB.setValue(value); }
});
}
@Override
protected void makeContent() {
setLayout(new GridLayout(2, 2, 5, 5)); // just for the example; you may consider to use a better layout
_fieldA = new JTextField();
add(new JLabel("Key A:"));
add(_fieldA);
_fieldB = new JIntegerField();
add(new JLabel("Key B:"));
add(_fieldB);
}
}
Register the category and the panel
If you have developed the preferences panel, you have done the hard work.
Registering the category and its associated panel is straightforward. As usual, it is done in the
Extension Registry within a
__init__.py
file.
Example:
from herschel.ia.gui.kernel import ExtensionRegistry, Extension
from herschel.ia.gui.kernel.prefs import UserPreferences
CATEGORY = UserPreferences.CATEGORY
REGISTRY = ExtensionRegistry.getInstance()
# Preferences categories
REGISTRY.register(CATEGORY, Extension(
"Some/Category",
"herschel.some.package.SimplePreferencesPanel",
None, # unused
None)) # unused
# Cleanup
del(ExtensionRegistry, Extension, UserPreferences, CATEGORY, REGISTRY)
If you want to organize your categories under a common parent category, and don't have any preference associated to that parent category, it may even be easier. Just skip the creation of the panel and write:
REGISTRY.register(CATEGORY, Extension(
"Some/Category",
None, # empty panel
None, # unused
None)) # unused
In this case, the framework creates an empty panel for you, with a message saying that preferences can be found in children categories.
Note:
Preferences are just released. The still unused fields in the extension may be used in the future, although for the time being the only foreseen (probable) change is to provide an icon to each category.
Read preferences
The next step is to use the preferences in client code.
This is very easy and just implies using
UserPreferences where you would use
Configuration.
Example:
The preferences defined in the previous example could be accessed like this:
String preferenceA = UserPreferences.get("Some/Category", "keyA");
int preferenceB = UserPreferences.getInt("Some/Category", "keyB");
Any preference can be read as a string, so:
String preferenceB = UserPreferences.get("Some/Category", "keyB");
would also be valid.
Listen to changes in preferences
Suppose the user opens an
editor component you have developed, then open the preferences dialog, changes some preference related to the presentation of your editor, and press OK.
The user would expect that these changes would be applied to the opened editor, not only to editors opened from that moment on.
To solve this situation, your editor should listen to preference changes by implementing
PreferenceListener and registering with
UserPreferences.addListener
.
Link to properties
Now, what happens with plain old properties?
There may be three different situations:
Brand new preferences
If you create new preferences that have no correspondence to existing properties, all we have seen till now should be enough for you; you don't need to worry about properties. Lucky you!
Take a property as default value
The more common situation may be that you want to migrate some existing property to the new preferences mechanism.
This means that the preference should take the value of the associated property while the user doesn't override it.
After overriden, the saved preference is used and the property is not taken into account.
If this is the case, the only thing you need to do is to add few code when registering the handler of the associated preference. Something like this:
String def = Configuration.getProperty("hcss.some.property", "text"); // default value
registerHandler("keyA", new AbstractPreferenceHandler(def) {
public String getValue() { return _fieldA.getText(); }
public void setValue(String value) { _fieldA.setText(value); }
});
Maintain the old property mechanism
Although the previous two approaches are recommended, it may be the case that you cannot use
UserPreferences in your client code, for example because your module cannot depend on
ia_gui_kernel
, but still want to provide a panel for the user in the preferences window.
For instance, you want to add a panel for letting the user to set the Versant server and a database name.
In this case, your panel should be written in a module that can depend on
ia_gui_kernel
; however,
herschel.versant.store
(in this example) can only use
Configuration for reading the preferences values.
The solution is to provide the
PreferenceHandler the name of the associated property, so the preference would override the property in
user.props
in addition to being saved as a preference.
As simple as:
registerHandler("keyA", new AbstractPreferenceHandler("text", "hcss.some.property") {
public String getValue() { return _fieldA.getText(); }
public void setValue(String value) { _fieldA.setText(value); }
});
In this case, the property would also be used as default value, if existing.
Allowed types for preferences
As seen above, you can specify the type you want for each preference. The
PreferenceHandler enforces you to provide the correct types when writing their implementations.
The valid types for preferences depend on whether their associated handler uses a property (
PreferenceHandler.getProperty()
returns not null) or not, that is, if you provide a property when constructing
AbstractPreferenceHandler or not (see examples above).
- If you don't provide a property to the handler, the valid types are
Boolean
, Integer
, Long
, Float
, Double
, String
and byte[]
.
- If you provide a property to the handler, the valid types are reduced to
Boolean
, Integer
, Double
and String
.
The reason is that
Configuration provides methods for getting the property as boolean, int, double or String, but doesn't provide methods for the other types.
--
JaimeSaiz - 23 Mar 2009