All implementations of ILspController are chainable. The concept of chainable controllers is also used for touch-based controllers in GXY views. 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;
}