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.

customizer ILcyCustomizerPanel setobject
Figure 1. Setting and configuring an object through an 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.

customizer ILcyCustomizerPanel applychanges
Figure 2. Applying the modified properties
customizer ILcyCustomizerPanel cancelchanges
Figure 3. Cancelling the modified properties

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 method setChangesPending in ALcyCustomizerPanel must be invoked.

  • Implement applyChangesImpl. This method is similar to applyChanges defined in ILcyCustomizerPanel, except that it must not take into account the changesPending property. An implementation of this method must request the active customizer object through getObject(), 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 invoke elementChanged in ILcdModel. 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 and changesValid properties. The changesPending property is automatically updated upon calling setChangesPending, applyChanges or cancelChanges, and each time its value is changed through one of these methods a property change event is automatically sent out. The changesValid property on the other hand is always true in the default implementation of ALcyCustomizerPanel, 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 calling setChangesValid.

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.

customizer ALcyCustomizerPanel setobject
Figure 4. Setting and configuring an object through an 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 ALcyCustomizerPanel applychanges
Figure 5. Applying the modifications
customizer ALcyCustomizerPanel cancelchanges
Figure 6. Cancelling the modifications

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 and TLcyLspMapAddOn 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 and TLcyLspMapAddOn to customize the properties of the model encoders used to save the ILcdView of a ILcyGenericMapComponent 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.

Program: Find and create a customizer for a 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.

Program: The property listener added to the customizer. (from 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.