All implementations of ILspController are chainable. The concept of chainable controllers is also used in nearly all controller implementations for GXY views. See Working with controllers in GXY views for more information.

Events come in at the top of the chain, and each controller decides whether it handles the event. If the controller does not handle the event, or handles it only partially, the event is passed on to the next controller in the chain.

Controllers decide whether to handle an event by checking their event filters. Each instance of ILspController can be configured with a filter for AWTEvents. Events that do not pass the filter are delegated directly to the next controller in the chain. Events that pass the filter are handled by the controller implementation, but may still be delegated if the controller has no use for them, based on their current state for instance. The class TLcdAWTEventFilterBuilder helps you create such complex filters.

The combination of chaining and filtering possibilities allows for the creation of complex controllers. For example, in Program: Creating a composite navigation controller, a number of navigation controllers are created, along with some event filters. Those navigation controllers are then chained together. The result is a single navigation controller that offers a wide range of navigation functions.

Program: Creating a composite navigation controller. (from samples/lightspeed/common/controller/ControllerFactory)
/*
 * The default navigation controller: fly-to, pan, zoom and rotate.
 */
public static ALspController createNavigationController() {
  // First we create the controllers we want to chain.
  ALspController zoomToController = createZoomToController();
  ALspController panController = createPanController();
  ALspController zoomController = createZoomController();
  ALspController rotateController = createRotateController();

  //Chain the controllers together, events will be offered to the first and trickle down.
  zoomToController.appendController(panController);
  zoomToController.appendController(zoomController);
  zoomToController.appendController(rotateController);

  //Set general properties on the top of the chain.
  zoomToController.setIcon(TLcdIconFactory.create(TLcdIconFactory.HAND_ICON));
  zoomToController.setShortDescription(
      "<html><p>Navigate:</p><p><b>Left mouse</b>: <ul><li>Drag: pan</li>" +
      "<li>Double click: fly to</li></ul></p><p><b>Mouse wheel</b>: zoom</p>" +
      "<p><b>Right mouse</b>: rotate</p></html>"
  );

  return zoomToController;
}

/*
 * Fly-to controller with left mouse button filter. This controller will only use double
 * click events, so in combination with the applied filter, only left mouse double
 * clicks or right mouse double clicks will trigger a fly-to.
 * Left mouse zooms in and right mouse zooms out.
 */
private static ALspController createZoomToController() {
  final NoObjectSelectedFilter noObjectSelectedFilter = new NoObjectSelectedFilter();
  TLspZoomToController zoomToController = new TLspZoomToController() {
    @Override
    public void startInteraction(ILspView aView) {
      super.startInteraction(aView);
      noObjectSelectedFilter.setView(aView);
    }

    @Override
    public void terminateInteraction(ILspView aView) {
      super.terminateInteraction(aView);
      noObjectSelectedFilter.setView(null);
    }
  };
  zoomToController.setAWTFilter(TLcdAWTEventFilterBuilder.newBuilder()
                                                         .customFilter(noObjectSelectedFilter)
                                                         .and()
                                                         .leftMouseButton()
                                                         .or()
                                                         .customFilter(noObjectSelectedFilter)
                                                         .and()
                                                         .rightMouseButton()
                                                         .build());
  return zoomToController;
}

/*
 * Panning is the backup left mouse button behaviour (if editing is not possible), as well
 * as the default action mapped to the middle mouse button.
 */
public static ALspController createPanController() {
  // Use a pan controller that consumes events during panning, e.g. mouse wheel events.
  TLspPanController panController = new GreedyPanController();
  panController.setEnableInertia(true);
  panController.setAWTFilter(
      TLcdAWTEventFilterBuilder
          .newBuilder()
          .leftMouseButton()
          .or()
          .middleMouseButton()
          .or()
          .mouseWheelFilter()
          .build());
  return panController;
}

/*
 * Zooming is the default action mapped to the mouse-wheel.
 */
public static ALspController createZoomController() {
  TLspZoomController zoomController = new TLspZoomController();
  zoomController.setAWTFilter(TLcdAWTEventFilterBuilder.newBuilder().
      mouseWheelFilter().build());
  return zoomController;
}

/*
 * Rotating is the default action mapped to the right mouse button.
 */
public static ALspController createRotateController() {
  TLspRotateController rotateController = new TLspRotateController();
  rotateController.setAWTFilter(TLcdAWTEventFilterBuilder.newBuilder().
      rightMouseButton().build());
  return rotateController;
}

For an illustration of how to create a custom controller, see the sample lightspeed.customization.controller.

Implementing a chainable controller

Keep the following pointers in mind when you implement a chainable controller:

  • Take care when you implement the handleAWTEventImpl method of the AlspController base class. Events that are handled by one controller should not be passed on to other controllers. Therefore:

    • Make the method return null if the controller handles the event.

    • In the other case, if a controller does not use an event, make the method return the event so that the next controller gets a chance to handle it.

  • Some events are tightly coupled, and should be handled by the same controller. Typical examples are mouse drag or mouse click sequences. For example, a drag sequence consists of a pressed event, followed by a couple of drag events, and finally, a released event. To let a controller handle a drag sequence, make it hold on to all pressed events, and only delegate a pressed event to the next controller if you receive a released event without having received any drag events.