An important feature of an application framework like Lucy is to allow its users to display and edit properties of several types of objects. To address this need, the Lucy back-end provides the necessary infrastructure to plug in GUI components. Common examples are components that allow you to modify:
-
The properties of layers, for example changing the layer name or customizing the properties of its painters
-
The properties of models, for example changing the visibility of objects inside the model
-
The properties of individual objects, for example viewing or altering the properties of an object
The infrastructure uses the interface ILcyCustomizerPanel
to represent these GUI components. Writing your own ILcyCustomizerPanel describes how you can implement this interface. Adding your ILcyCustomizerPanel to Lucy shows how to add such a customizer panel to the Lucy environment.
Writing your own ILcyCustomizerPanel
Implementing ILcyCustomizerPanel
An ILcyCustomizerPanel
is the general representation of a Lucy GUI component for viewing or editing the properties of an object. The interface defines
the necessary methods to set an object, to retrieve or alter its properties, and to inform other interested parties of any
changes through events. Because you add instances of ILcyCustomizerPanel
to the GUI, it is required for implementations to extend java.awt.Component
. For example, an ILcyCustomizerPanel
to customize an ILcdGXYLayer
could contain a panel with check boxes to toggle the visible state, editable state, and so on.
You use setObject
to set the object that you want to customize on ILcyCustomizerPanel
. If the object is accepted, the user interface is updated according to the properties of the object. If the user has made
any modifications to the object in the user interface, the modifications are applied to the object only after applyChanges
is invoked. Before that time, the modifications are pending changes. They are not yet committed to the actual object. If
cancelChanges
is called, any pending changes must be discarded and the user interface must be reverted to the current status of the object.
In addition, there are two utility methods, isChangesPending
and isChangesValid
, which are used to check if there are any pending changes and if the pending changes are valid respectively. The concept
of pending changes allows you to implement a user interface that provides apply/cancel functionality. The container holding
the ILcyCustomizerPanel
can define apply and cancel buttons for example, which make use of these methods to commit or rollback any changes.
Figure Figure 1, “Setting and configuring an object through an ILcyCustomizerPanel
” shows an activity diagram for the configuration of a layer through a layer customizer. Figure Figure 2, “Applying the modified properties” shows the activity diagram for a user that commits the modified layer properties in the customizer. Finally, figure Figure 3, “Cancelling the modified properties” shows the activity diagram for a user discarding the modified properties.

ILcyCustomizerPanel
Next to implementing the isChangesPending
and isChangesValid
methods, an ILcyCustomizerPanel
is also required to actively update any registered property change listeners. When the user modifies a property in the user
interface, only the ILcyCustomizerPanel
knows about this modification. If the container holding the ILcyCustomizerPanel
defines apply and cancel buttons, it will want to know whether there are any pending changes and whether these are valid,
without having to poll the isChangesPending
and isChangesValid
methods continuously. The container can use this information to improve the user interface, for example by disabling the
apply button when no changes are pending, or by showing a message when an incorrect modification is carried out. For those
purposes, classes can register PropertyChangeListener
instances on an ILcyCustomizerPanel
to get automatically informed about these properties. An ILcyCustomizerPanel
is therefore required to actively send out property change events as soon as one of these two properties change.


The other methods defined in ILcyCustomizerPanel
are used to store and retrieve general customizer properties — identified by the constants NAME
, SHORT_DESCRIPTION
, LONG_DESCRIPTION
, SMALL_ICON
— and to keep track of property change listeners.
Extending from ALcyCustomizerPanel
To make development easier, the Lucy API provides an abstract extension of ILcyCustomizerPanel
with an implementation for most of its methods. The following requirements apply to extensions of this class:
-
Provide a user interface. The class already extends
JPanel
for this purpose. Whenever the user made changes in the user interface of the customizer, the methodsetChangesPending
inALcyCustomizerPanel
must be invoked. -
Implement
applyChangesImpl
. This method is similar toapplyChanges
defined inILcyCustomizerPanel
, except that it must not take into account thechangesPending
property. An implementation of this method must request the active customizer object throughgetObject()
, apply the current settings configured in the customizer to the object, and notify any listeners if required. For example, if the customized object is contained within a model, it is necessary to invokeelementChanged
inILcdModel
. This way, other components know that the contents of the model have changed, and can take appropriate action, such as a view repaint. -
Implement
updateCustomizerPanelFromObject
. When this method is invoked, the user interface must be updated to reflect the current state of the set object. -
Implementations do not have to take into account the
changesPending
andchangesValid
properties. ThechangesPending
property is automatically updated upon callingsetChangesPending
,applyChanges
orcancelChanges
, and each time its value is changed through one of these methods a property change event is automatically sent out. ThechangesValid
property on the other hand is alwaystrue
in the default implementation ofALcyCustomizerPanel
, so no property change events are needed here. Implementations can however decide to use this property to indicate whether a modification in the customizer is correct. Property change events are sent automatically whenever its value changes upon callingsetChangesValid
.
An implementation example is discussed in the Implementing a customizer panel tutorial.
The activity diagrams illustrated earlier for ILcyCustomizerPanel
now become concrete in the sequence diagrams in figure Figure 4, “Setting and configuring an object through an ALcyCustomizerPanel
”, Figure 5, “Applying the modifications” and Figure 6, “Cancelling the modifications”. Figure Figure 4, “Setting and configuring an object through an ALcyCustomizerPanel
” shows the sequence diagram when an object, a layer for example, is set on a customizer and a property of the object is changed
by the user.

ALcyCustomizerPanel
Figure Figure 5, “Applying the modifications” and Figure 6, “Cancelling the modifications” show the sequence diagram when the modified properties are respectively applied to the object, or discarded by the user.


Customizer container
The application component that uses the customizer is generally referred to as the customizer container, and is typically
a separate add-on in Lucy. The customizer container decides which type of object it customizes, where the customizers are
displayed in the GUI, and so on. By default, Lucy contains multiple add-ons that make use of ILcyCustomizerPanel
instances to customize a certain type of object:
-
TLcySelectionEditorAddOn
to customize the properties of individual domain objects, such as point coordinates. It can be accessed using Map→ Object Properties. -
TLcyModelCustomizerAddOn
to customize the contents of models, for example in a table. It can be accessed using File→ New→ Table. -
TLcyMapAddOn
andTLcyLspMapAddOn
to customize the properties of layers, such as visible, selectable, line colors, …​ It can be accessed using the Layer properties button in the layer control. -
TLcyMapAddOn
andTLcyLspMapAddOn
to customize the properties of the model encoders used to save theILcdView
of aILcyGenericMapComponent
as a georeferenced image. These are used when the File→ Save As Image→ …​ menu item is selected.
For example, the TLcySelectionEditorAddOn
monitors selections on the view, searches an appropriate customizer for any selected object, and displays the customizer
in the editor panel.
A customizer implementation does not need to know any details of the surrounding customizer container that uses it, except
for one thing: the type of object that is set through setObject
. A customizer container implementation can decide for itself which type of object is used. In general, a customizer container
will use a container object that, next to the object to be customized, also contains other useful information. The customizer
containers in Lucy use the following object types:
The first three context objects provide access to the model, the layer, and the view (if they are not already the customized object). This information can be useful when an object is updated. For example, if an object inside a model changes, the model should be warned that its contents have changed.
Whenever multiple selection is appropriate, for example when multiple objects are selected on the map, or multiple layers are selected in the layer control, an array of these context objects is used.
The fourth context object, TLcyModelEncoderContext
, provides access to the ILcdModelEncoder
and, optionally, the model it is about to encode.
The last context object, TLcyLspCustomizableStyleContext
, provides access to the TLspCustomizableStyle
.
It also contains an optional layer and/or an optional view.
You can find an example implementation of a customizer container in the layerpropertyeditor
sample in the samples/lucy/layerpropertyeditor
directory. When a layer is selected in the layer control, the container will find the customizer which can show and/or modify
the properties of the layer. Program: Find and create a customizer for a TLcyLayerContext
. shows how such a customizer is found for the TLcyLayerContext
, containing the selected layer.
TLcyLayerContext
. (from samples/lucy/layerpropertyeditor/LayerPropertyEditor
)
private ILcyCustomizerPanel createCustomizerPanelForContext(Object aContext) {
// Get the composite factory from Lucy containing all registered customizer factories.
TLcyCompositeCustomizerPanelFactory customizer_factory
= new TLcyCompositeCustomizerPanelFactory(fLucyEnv);
// Check if the factory can create a customizer for the given aContext.
if (customizer_factory.canCreateCustomizerPanel(aContext)) {
// Create a customizer for the given aContext.
ILcyCustomizerPanel customizer_panel = customizer_factory.createCustomizerPanel(aContext);
// Set the object to be edited.
customizer_panel.setObject(aContext);
// Add a listener to immediately apply all changes.
customizer_panel.addPropertyChangeListener(fApplyListener);
return customizer_panel;
}
return null;
}
As you can see in the code snippet, a property change listener is added to the created customizer. This listener will immediately apply all changes, resulting in an immediate update of the layer in the map view. Program: The property listener added to the customizer. shows how this listener is implemented.
samples/lucy/layerpropertyeditor/CustomizerPanelImmediateApplyListener
)
public class CustomizerPanelImmediateApplyListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("changesPending".equals(evt.getPropertyName()) &&
Boolean.TRUE.equals(evt.getNewValue())) {
applyIfNeeded((ILcyCustomizerPanel) evt.getSource());
} else if ("changesValid".equals(evt.getPropertyName()) &&
Boolean.TRUE.equals(evt.getNewValue())) {
applyIfNeeded((ILcyCustomizerPanel) evt.getSource());
}
}
private void applyIfNeeded(ILcyCustomizerPanel aCustomizerPanel) {
// Only apply when there are changes and the changes are valid.
if (aCustomizerPanel.isChangesPending() && aCustomizerPanel.isChangesValid()) {
aCustomizerPanel.applyChanges();
}
}
}
Adding your ILcyCustomizerPanel to Lucy
After writing an ILcyCustomizerPanel
, you still need to register it with the Lucy back-end.
This section explains in more detail how this can be achieved, and how a user can re-use existing customizers in custom implementations.
Implementing ILcyCustomizerPanelFactory
The Lucy API uses TLcyUserInterfaceManager
as the main class to keep track of all pluggable components related to the user interface, such as the customizers. That
manager is available through ILcyLucyEnv
, which represents the link with the Lucy back-end. You cannot directly add a customizer to the manager, however. Instead,
the manager expects an ILcyCustomizerPanelFactory
that is capable of creating the customizers by using the abstract factory design pattern. Next to this interface, the API
also provides an abstract extension, ALcyCustomizerPanelFactory
, which provides additional functionality by allowing you to set an ILcdFilter
. This filter is automatically used in canCreateCustomizerPanel
and createCustomizerPanel
, which leaves implementations with only one method to implement, createCustomizerPanelImpl
.
Adding an ILcyCustomizerPanelFactory to Lucy
You can add an ILcyCustomizerPanelFactory
implementation to a
TLcyUserInterfaceManager
. For this purpose, the manager uses a composite factory, TLcyCompositeCustomizerPanelFactory
. Through this factory, you can add or retrieve new or existing customizer panel factories.
Defining priorities
If you have two or more customizer factories that can handle the same object, the factory that was added first is used by
default. To change this behavior, you can associate a priority with a customizer factory. By default, the composite factory
provides two priority modes, PRIORITY_NORMAL
and PRIORITY_FALLBACK
. If a customizer factory is added through addCustomizerPanelFactory(ILcyCustomizerPanelFactory)
, it receives priority PRIORITY_NORMAL
. If it is added through addFallbackCustomizerPanelFactory(ILcyCustomizerPanelFactory)
, it receives priority PRIORITY_FALLBACK
. You can also use custom priority values, through addCustomizerPanelFactory(ILcyCustomizerPanelFactory, int)
. These priorities are used to sort the registered customizer factories: customizer factories with a lower value have precedence
over customizer factories with a higher value; if two customizer factories have the same value, the one that is added first
has precedence.
For example, suppose that you need to customize a TLcd2DEditableFeaturedPoint
object, and that there are two customizer factories available. One customizer factory is capable of creating customizers
that can change only the general properties of the object defined in the ILcd2DEditablePoint
interface. The other customizer factory is designed specifically to create customizers that can change all properties of
the TLcd2DEditableFeaturedPoint
object. It is recommended to use the more specific customizer factory in this case, because it allows you to change more
properties of the object. You can obtain this behavior by registering the customizer factory for ILcd2DEditablePoint
objects as a fallback customizer factory, so that it has a lower priority than the TLcd2DEditableFeaturedPoint
customizer factory.