5. Control

The control defines the mapping from user events to methods of the graph- and selection model and the view. It is implemented in a platform-dependent way in the UI-delegate, and basically deals with in-place editing, cell handling, and updating the display. As the only object that is exchanged when the look and feel changes, the UI-delegate also specifies the look and feel specific part of the rendering.

5.1. UI-Delegate

The GraphUI abstract class and its default implementation, the BasicGraphUI constitute JGraph's UI-delegate. Aside from the event listeners to track user input, the UI-delegate is also used to paint the graph, and update the display when the graph or the selection changes. When a graph is painted, the UI-delegate loops through the views and paints them by painting their respective renderers.

5.1.1. GraphUI Interface

The GraphUI abstract class defines the methods for an object that may be used as a UI-delegate for JGraph, whereas the BasicGraphUI provides default implementations for these methods. The BasicGraphUI in turn may be subclassed to implement the UI for a specific look and feel, but the default implementation already contains some look and feel specific coloring.

5.1.2. GraphUI Default Implementation

The BasicGraphUI class maintains a reference to a RootHandle, which is used to react to mouse events. The mouse listener, which calls this handler, is an inner-class of the UI-delegate. It defines the basic behavior of JGraph with regard to mouse events, and implements the two main functionalities: selection and editing. Selection includes single-cell and marquee selection. Editing can either be based on the CellEditor interface, for in-place editing, or on the CellHandle interface, for cell handling.

The BasicGraphUI creates the root handle, whereas the cell handles are created by the cell views. The root handle is responsible to move the selection, whereas its children are used to change the shape of the cells. A set of default keyboard bindings is supported by the UI-delegate, and listed in Table 1. (The number of clicks that trigger in-place editing can be changed using the setEditClickCount method of JGraph.)

Table 1. JGraph's default keyboard bindings

Double-click / F2Starts editing current cell
Shift-ClickForces marquee selection
Shift-SelectExtends selection
Control-SelectToggles selection
Control-DragClones selection
Shift-DragConstrained drag

5.2. Renderers

Renderers do not paint cells; they paint objects that implement the CellView interface. For each cell in the model, there exists exactly one cell view in each graph view, which has its own internal representation of the graph model. The renderers are instantiated and referenced by the cell views.

Renderers are based on the idea of the TreeCellRenderer class [bib-View] from Swing, and on the Flyweight design pattern (see [bib-GOF], page 195 ff). The basic idea behind this pattern is to "use sharing to support large numbers of fine-grained objects efficiently." [bib-GOF]

Because having a separate component for each CellView-instance would be prohibitively expensive, the component is shared among all cell views of a certain class. A cell view therefore only stores the state of the component (such as the color, size etc.), whereas the renderer holds the component's overhead, and the painting code (for example a JLabel instance).

The CellViews are painted by configuring the renderer, and painting the latter to a CellRendererPane [bib-Java1.4], which may be used to paint components without the need to add them, as in the case of a container. Configuring a renderer means to fetch the state from the cell view, and apply it to the renderer. For example in the case of a vertex, which uses a JLabel instance as its renderer, the cell's label is retrieved using graph.convertValueToString, and set using setText on the renderer, together with other supported attributes that make up the state of a vertex.

The renderers in JGraph are used in analogy to the renderer in JTree, just that JGraph provides more than one renderer, namely one for each type of cell. Thus, JGraph provides a renderer for vertices, one for edges, and one for ports. For each subtype of the CellView interface, overriding the getRenderer method may associate a new renderer. The renderer should be static to allow it to be shared among multiple instances of a class.

The renderer itself is typically an instance of the JComponent class, with an overridden paint method that paints the cell, based on the attributes of the passed-in cell view. The renderer also implements the CellViewRenderer interface, which provides the getRendererComponent method to configure the renderer.

5.2.1. CellViewRenderer Interface

This interface provides two methods: one to configure and return the renderer, the other to check if an attribute is supported. The first method's arguments are used to specify the cell view and its state, that is, if it is selected, if it has focus, and if it is to be painted as a preview or in highlighted state.

The preview argument is true if the renderer is used in the context of live-preview, which requires faster painting. In this case, the renderer may decide not to paint the cell in full quality, based on the cost of this painting. The arguments passed to this method can not be deduced from the cell view; they are determined by the UI-delegate, the selection-model or from the context in which the renderer is used.

5.2.2. CellViewRenderer Default Implementations

Figure 37. CellViewRenderer default implementations

CellViewRenderer default implementations

The three default implementations of the CellViewRenderer interface are the VertexRenderer, EdgeRenderer and PortRenderer classes. The VertexRenderer is an extension of JLabel, which offers the code to paint the label, the icon, the border and background of a vertex. The paint method is only overridden to paint the selection border.

The other renderers are subclasses of the JComponent class, because they perform special rendering, which is not offered by any of the JComponent extensions. The EdgeRenderer uses the Graphics2D class and the Shape interface to paint the label, edge, and decorations. The PortRenderer provides the code to paint a small cross, or a highlighted port in the case where the highlight argument is true.

The preview flag is ignored in the context of PortRenderers because in live-previews, ports are never painted, and therefore, the flag is never true when used in a PortRenderer.

5.3. Editors

In-place editing is accomplished through a set of methods that the UI-delegate provides. Internally, the object that is used as an editor is retrieved from the cell view, just like the renderer is. In contrast to the renderer, however, there is only one GraphCellEditor for all cells, even if the design allows different editors to be specified for each view type, even for each view instance.

As in the case of the CellViewRenderer interface, the GraphCellEditor interface provides a method that is used to return a configured editor for the specified cell. The argument to this method is not a CellView, as in the case of CellViewRenderer; it is a cell instead, which is of type Object. This underlines the fact that the label is typically stored in the model, not in the view.

The editor is placed inside the graph by the UI-delegate, which is also responsible to remove it from there, and dispatch the change to the model. Thus, for customized editing, it might be necessary to provide a custom UI-delegate, overriding the protected startEditing and completeEditing methods of the BasicGraphUI class.

5.4. Cell Handles

Cell handles are a new concept that is not used elsewhere in Swing. This is because in Swing, the only method to edit cells is by means of in-place editing. It is not possible to change the bounds of a cell, or to move the cell to an arbitrary location. Rather, the UI-delegate and the state of the component determine the location of a cell. In JGraph instead, the position and size are stored in the attributes, and do not depend on the state of the graph. Therefore, a way must be provided to change the attributes in a transaction-oriented way.

Handles are based on the Composite pattern, where a root object provides access to children based on a common interface, namely the CellHandle interface. The UI-delegate creates a RootHandle upon a selection change. The root handle, in turn, uses the CellView's getHandle method to create its child handles.

While the root handle is active, the UI-delegate's internal mouse handler messages its methods, and the root handle in turn delegates control to the correct subhandle, or absorbs the events in order to move the selection.

5.4.1. Live-Preview

Figure 38. Live-preview

Live-preview

During a change to the graph, the new attributes are displayed by use of temporary cell views, which have previously been created using the GraphContext object. This is referred to as the live-preview, meaning that the graph is overlaid with the change, so that the user can see what the graph will look like when the mouse is released. (The change may be cancelled by pressing the Escape key.)

Figure 39. Live-preview uses the applyMap method

Live-preview uses the applyMap method

If the model is an attribute store, then the change is executed on the model. Otherwise, the change is executed on the view. In both cases, the applyMap method is used, which is provided by the GraphConstants class.

5.4.2. CellHandle Interface

The CellHandle interface is very similar to a MouseAdapter, providing all the methods that are used to handle mouse events. In addition, the interface defines two methods to paint the handle, namely the paint method, which is used to draw the static part (the actual handles), and the overlay method, which is used to draw the dynamic part (the live-preview), using fast XOR'ed-painting.

5.4.3. CellHandle Default Implementations

Figure 40. CellHandle default implementations

CellHandle default implementations

The default implementations of the CellHandle interface are the SizeHandle, EdgeHandle, and RootHandle classes. The root handle is responsible to move cells, the size handle is used to resize cells, and the edge handle allows to connect and disconnect edges, and to add, modify or remove individual points.

5.5. GraphTransferable

The UI-delegate provides an implementation of the TransferHandler class, which in turn is responsible to create the Transferablebased on the current selection.

Figure 41. The GraphTransferable class

The GraphTransferable class

The transferable is represented by the GraphTransferable class, which in turn has a reference to the ConnectionSet, the cells and their corresponding view's attributes. The ConnectionSet is created using the static factory method that was outlined before. The data may be returned as serialized objects, or as plain text or HTML. In the latter two cases the label of the selected cell is returned, or an empty string, if the transferable contains more than one cell.

5.6. Marquee Selection

Marquee selection is the ability to select a rectangular region by use of the mouse, which is not provided by Swing. The BasicMarqueeHandler class is used to implement this type of selection. From an architectural point of view, the marquee handler is analogous to the transfer handler, because it is a "high-level" listener that is called by low-level listeners, such as the mouse listener, which is installed in the UI-delegate.

With regard to its methods, the marquee handler is more similar to the cell handle, because the marquee handler deals with mouse event, and allows additional painting and overlaying of the marquee. (The marquee is a rectangle that constitutes the to-be selected region.)

5.7. Event Model

In JGraph, the graph model, selection model and graph view may dispatch events. The events dispatched by the model may be categorized into

  • Change notification

  • History support

Figure 42. JGraph's event model

JGraph's event model

The GraphModelListener interface is used to update the view and repaint the graph, and the UndoableEditListener interface to update the history. These listeners may be registered or removed using the respective methods on the model.

The selection model dispatches GraphSelectionEvents to the GraphSelectionListeners that have been registered with it, for example to update the display with the current selection. The view's event model is based on the Observer and Observable class to repaint the graph, and also provides undo-support, which uses the graph model's implementation.

An additional notification mechanism between the model and the view enables a single model to have multiple views. Note that the view does not itself implement the GraphModelListener interface, it is messaged by the model listener that is part of the UI-delegate, passing along the change information as an argument.

Each change is either executed on the model, or on a specific view, or it is a combination of both. No changes exist that affect multiple views at a time, except implicitly, through a change of the model. This leads to a separate notification between the local view and its UI to be repainted. The GraphLayoutCache class implements the Observable interface, and the BasicGraphUIprovides an Observerinstance, which may be registered with the graph view, and is in charge of repainting the graph upon a view-only change.

5.7.1. Change Notification

Upon insertion, removal or modification of cells in the model, the model constructs an object that executes and describes the change. Because the execution and the description of such a change are closely related, these are stored in the same object. (Upon a change, the factory methods of the ParentMap and ConnectionSet classes are used to construct the respective objects that describe the change.)

The object is then sent to the model listeners such that they may repaint the affected region of the graph, and remove, add or update the cell views for the changed cells. The listeners might also want to ensure other properties, like for auto sizing, in which case the preferred size is recomputed when a cell has changed.

In contrast to the TreeModelListener interface, which provides three methods to be messaged, namely one for insertion, one for removal, and one if cells are changed, the GraphModelListener interface only provides one method to be informed on any change. (The latest release of the TreeModelListener even has an additional method, called treeStructureChanged.)

This has a subtle consequence when it comes to composite changes, which can insert, remove and change cells at the same time. In JTree listeners, these changes are handled by different parts of the handler code, resulting in up to four separate updates for one composite change, whereas in JGraph, the whole change is handled in one method, resulting in one single update, which is more efficient, even if such changes are rare.

Model listeners are notified using objects that implement the GraphModelEvent interface, which in turn returns an object that implements the GraphChange interface. The GraphChange interface is used to describe the change, that is, it returns the cells that were added, removed, or changed. An inner class of the model, which also contains the code to execute and undo the change, implements the GraphChange interface.

5.7.2. Undo-support

Undo-support, that is, the storage of the changes that were executed so far, is typically implemented on the application level. This means, JGraph itself does not provide a running history, it only provides the classes and methods to support it on the application level. This is because the history requires memory space, depending on how many steps it stores (which is an application parameter). Also, history cannot always be implemented, and is not always desirable.

The GraphChange object is sent to the UndoableEditListeners that have been registered with the model. The object therefore implements the GraphChange interface and the UndoableEdit interface. The latter is used to implement undo-support, as it provides an undo and a redo method. (The code to execute, undo and redo the change is stored within the object, and travels along to the listeners.)

5.7.3. Undo-support Relay

Aside from the model, the graph view also uses the code that the model provides to notify its undo listeners of an undoable change. This can be done because each view typically has a reference to the model, whereas the model does not have references to its views. (The GraphModel interface allows relaying UndoableEdits by use of the fourth argument to the edit method.)

The GraphLayoutCache class uses the model's undo-support to pass the UndoableEdits that it creates to the UndoableEditListeners that have been registered with the model. Again, the objects that travel to the listeners contain the code to execute the change on the view, and also the code to undo and redo the given change.

This has the advantage that the GraphUndoManager must only be attached to the model, instead of the model and each view.

5.7.4. GraphUndoManager

Separate geometries, which are stored independently of the model, lead to changes that are possibly only visible in one view (view-only), not affecting the other views, or the model. The other views are unaware of the change, and if one of them calls undo, this has to be taken into account.

An extension of Swing's UndoManager in the form of GraphUndoManager is available to undo or redo such changes in the context of multiple views. GraphUndoManager overrides methods of Swing's UndoManager with an additional argument, which allows specifying the calling view as a context for the undo/redo operation.

This context is used to determine the last or next relevant transaction with respect to the calling view. Relevant in this context means visible, that is, all transactions that are not visible in the calling view are undone or redone implicitly, until the next or last visible transaction is found for the context.

As an example consider the following situation: Two views share the same model, which is not an attribute store. This means, each view can change independently, and if the model changes, both views are updated accordingly. The model notifies its views if cells are added or removed, or if the connectivity of the graph is modified, meaning that either the source or target port of one or more edges have changed. All other cases are view-only transactions, as for example if cells are moved, resized, or if points are added, modified or removed for an edge. All views but the source view are unaware of such view-only transactions, because such transactions are only visible in the source view.

Figure 43. Command history and multiple views

Command history and multiple views

In the above figure, the state of the command history is shown after a cell insertion into the model, move by the second view, and subsequent move by the first view. After insertion of the cell into the model, the cell's position is the same in all views, namely the position that was passed to the insertcall. The arrows illustrate the edits to be undone by the GraphUndoManager for the respective views. In the case of view 3, which only sees the insert, all edits are undone.

As mentioned above, even if there are possibly many sources which notify the GraphUndoManager, the implementation of this undo-support exists only once, namely in the graph's model. Thus, the GraphUndoManager must only be added to one global entry point, which is the GraphModel object.