Composite Interfaces

Composites are compound graphical objects. Within the Tensegrity Graph Framework, they are used to visualize the fairly complex representations of nodes and edges. In order to reduce such complexity at the programming or specification levels, composites are constructed using one or more Primitive objects. In and of themselves, Primitive objects are fairly simple and straightforward geometric shapes.

To assist developers in using and creating even more complex and interactive composites, the Tensegrity Graph Framework allows you to implement and integrate user-defined composite classes which share these basic characteristics:

The following list shows a few Repository composites taken from the Tensegrity Graph Demo Application.

The following diagram shows the interface hierarchy for composites, which will be explained in subsequent sections:

Figure 2.2. Structure of the Composite

Structure of the Composite

The class CompositeView and all subclasses of BaseCompositeContainer will be discussed in detail later in this chapter, when interface CompositeGroup is introduced.

BaseComposite Interface

The BaseComposite interface is an aggregated interface that combines multiple roles and responsibilities. Apart from those roles which are inherited, this interface defines new methods that deal with the containment, management and drawing of Primitive objects to a Device.

It is very unlikely that you will create your own class that implements this abstract interface. More likely, you will use classes that already implement derived interfaces, such as Composite and CompositeLine, two concrete specifications that define the behavioral contracts for rectangular and line-based composites respectively.

This interface exists because client code does not always have to differentiate between rectangular and line composites, two types which exhibit deviant runtime behaviors.

Figure 2.3. Class Diagram BaseComposite

Class Diagram BaseComposite

A far more comfortable way to add multiple Primitive objects to this container is by using an instance of interface GeometryDescriptor. These named objects are instantiated from an XML file and can be more easily defined and assembled. Moreover, a runtime object of this type provides the configuration data for several Primitive objects anyways. So instead of writing Java code to create the Primitive objects, you can define them as a GeometryDescriptor written in an XML file, load them at system startup and apply them by calling method applyGeometry(GeometryDescriptor)

Similarly, the appearance of a BaseComposite can be set by applying a StyleDescriptor. This object specifies how a Geometry is represented on the screen in terms of line styles, area fill and more. You can invoke the following method multiple times: applyStyle(StyleDescriptor)

A BaseComposite must be representable on the screen. Therefore, it uses a Renderer and a Device object to do so, as you can see in the following draw methods:

  • void draw(Renderer, Device, Transform2D)

    This method is called from the context the BaseComposite lies within. The Transform2D given by xform_context is the transformation needed to draw within the context and is used to draw the BaseComposite.

  • void draw(Renderer, Device, int, int, int, int)

    This method is called from outside when it is not possible to create a transformation for the context the BaseComposite lies within (This occurs for instance when using a coordinate system, that is not able to create a linear transformation). In this case the bounding box of the BaseComposite has to be created from the context the BaseComposite lies within.

A BaseComposite is able to manage an InfoValue object. This value object can be used in many different ways. Therefore, the following constants are defined within the BaseComposite interface:

NameDescription
NON_INFOVALUENo InfoValue is associated with the BaseComposite
GENERAL_INFOVALUEDefines an InfoValue that is used with no special purpose
DELEGATE_INFOVALUE Defines an InfoValue for a delegate object. Although delegation is sometimes used instead of generalization, it comes with some problems because the methods of the delegation object do not override the InfoValue. If the InfoValue is marked with this constant, it is used instead of the BaseComposite instance.

Note

  1. When implementing a BaseComposite, you have to support the constants listed above.

  2. Because the composite framework is under construction, the definition of more constants is likely.

  3. The InfoValue is not deep-copied.

  • Object getInfoValue()

    Returns the InfoValue assigned to the BaseComposite or null if no InfoValue has been assigned. It is recommended to read the documentation of this interface to get a deeper understanding of how the InfoValue is used.

  • void setInfoValue(Object)

    Sets the given Object as the new InfoValue of this BaseComposite. Setting the InfoValue with this method identifies it as a GENERAL_INFOVALUE. It is recommended to read the documentation of this interface to get a deeper understanding of how a InfoValue can be used.

  • void setInfoValue(int, Object)

    Sets the given Object as the new InfoValue of this BaseComposite. The InfoValue is identified by the constant that is given by identifier. It is recommended to read the documentation of this interface to get a deeper understanding of how the InfoValue is used.

  • int getInfoValueIdentifier()

    Returns the identifier that specifies the usage of the InfoValue.

Adding Primitives

The control over Primitive object containment is exposed to clients with the following interface methods:

  • void addPrimitive(Primitive)

    Adds the given Primitive to the composites list of primitives.

  • PrimitiveIterator getPrimitiveIterator()

    Returns a PrimitiveIterator that can be used to walk through the BaseComposites list of Primitives.

  • void removeAllPrimitives()

    Removes all Primitives from the BaseComposites list of Primitives.

  • void removePrimitive(Primitive)

    Removes the given Primitive from the composites list of primitives.

Events

A BaseComposite must support the EventMediator protocol introduced by the top-level CompositeView container. The method to do this is

  • void registerEventMediator(EventMediator)

We recommend that you do not register your own EventMediator since this is done by the CompositeView, which automatically contains all BaseComposite instances. Please read the class documentation for CompositeView for more information.

The event-throwing mechanism of a BaseComposite can be turned on and off. By invoking the enableEvents method and supplying the specific event mask, you can control which events are thrown. A call to disableEvents, on the other hand, suitably turns off the specified events marked by the event mask parameter.

By default all events are turned on. To determine which events are enabled or disabled, you can invoke the areEventsEnabled method. The event masks are defined by an enumeration in this interface or in an interface that extends this one. Event mask names always start with the EVENT_MASK prefix. The methods a BaseComposite exposes to support the event mechanism are:

  • void enableEvents(int)

  • void disableEvents(int)

  • int getEventMask()

  • boolean areEventsEnabled(int)

Because the BaseComposite interface extends the AttributableOnSet interface, an instance will notify registered listeners about changes to the managed Attribute objects. As explained in the AttributableOnSet interface, notifications can be turned on and off by using the appropriate methods.

Note

When turning on or off the attribute change notification of a BaseComposite, the attribute change notification of all contained Primitive objects is updated as well.

Composite

The Composite interface supplements the BaseComposite behavior by managing its own CoordinateSystem. In doing so, the coordinates of the contained Primitive objects remain independent of any other CoordinateSystem used in a view, such as that of a parent container or the top-level CompositeView.

Moreover, a Composite object has a location and a size within its managed CoordinateSystem and is set by a BoundingBox. This box defines the top-left corner and size of all inlying Primitive objects managed by the Composite container.

Taken together, the CoordinateSystem and BoundingBox of a Composite uniquely determine the transformation that is used when drawing the contained Primitive objects.

CoordinateSystem

Every object that implements Composite contains a separate CoordinateSystem. This member can be accessed using the following methods:

  • CoordinateSystem getCoordinateSystem()

    Returns the CoordianteSystem the composite uses.

  • void setCoordinateSystem(CoordinateSystem)

    Sets the CoordinateSystem used by this Composite to the given one. If you need a mutable version you can set an instance of MutableCoordinateSystem.

Note

The CoordinateSystem of a Composite that is defined in the elements.xml file will be pooled and is therefore immutable. See section Coordinate Systems of this chapter for more information on tis topic.

Size and Location

The following methods deal with the BoundingBox of a Composite:

  • void addToLocation(Size)

    Adds the extensions given by sizes to the Composites current location. These extensions should be given in the coordinate system the Composite lies within. Adding the extensions to the current location of the Composite creates a MoveEvent if the extensions are not zero. Notice that a call to this method has no effect if the Composite is not movable.

  • void setBoundingBox(int, int, int, int)

    Sets the composites bounding box to the values given by left, right, top and bottom. These values should be given in the coordinate system the Composite lies within. Setting the bounding box of a Composite creates a MoveEvent if the new location of the bounding box is different from the current location and a ResizeEvent if the new size of the bounding box is different from the current size. Notice that a call to this method has no effect to the position when the Composite is not movable and no effect to the size when the Composite is not resizable.

  • Coordinate getLocation()

    Returns the current location of the Composite. The components of the returned Coordinate are given in the coordinate system the Composite lies within.

  • void setLocation(Coordinate)

    Sets the locations of the Composite to the Coordinate given by coordinate. The components of the given Coordinate should be given in the coordinate system the Composite lies within. Setting the location of a Composite creates a MoveEvent if the location is different from the current location. Notice that a call to this method has no effect if the Composite is not movable.

  • Size getMinimumSize()

    Returns the minimum size for the Composite. While user interaction takes place the size of the Composite is compared against the minimum size and adjusted to this size if necessary.

  • void setMinimumSize(Size)

    Sets the minimum size of the Composite to the value given by size. While user interaction takes place the size of the Composite is compared against the minimum size and adjusted to this size if necessary. Note: Currently the size of the Composite is only compared against the minimum size while user interaction. Hence it is possible to set a smaller size through the interface methods.

  • Size getSize()

    Returns the current size of the Composite. The extensions of the returned Size are given in the coordinate system the Composite lies within.

  • void setSize(Size)

    Sets the size of the Composite to the extensions given by size. The extensions of the Size should be given in the coordinate system the Composite lies within. Setting the size of a Composite creates a ResizeEvent if the size is different from the current size of the Composite. Notice that a call to this methods has no effect if the Composite is not resizable.

  • void layout(int, int, int, int)

    Sets the Composites bounding box to the values given by left, top, right and bottom. These values should be given in the coordinate system the Composite lies within. Notice that a call to layout creates no events and does not take care of the movable and resizable settings for the Composite but takes the setting for the minimum size into account.

Transformations

The CoordinateSystem and BoundingBox of a Composite uniquely determine the transformation that is used to draw the inlying Primitive objects. You can access this Transform2D using the following methods:

  • Transform2D getCoordinateSytemTransform(int)

    Returns a Tranform2D that can be used to calculate the transformation from or the transformation to this Composite depending on the given type. The transformation is definite determined by the CoordinateSystem and the BoundingBox of this Composite.

  • Transform2D getTransform()

    Returns the transformation the composite currently is transformed by.

  • void setTransform(Transform2D)

    Sets the transform the composite is transformed by.

Note

If you ever have a need to use these methods, please refer to section Composite Hierarchies in chapter Composite Utilities of this manual.

Listeners

A Composite object fires events for every change that can happen to it. In order to be notified about these events, you have to programmatically register an object that implements the CompositeListener interface. For more information about this interface, please take a closer look at the documentation for CompositeListener.

Some of the events generated by a Composite are vetoable, which means that the processing of an event can be programmatically stopped. In order to veto an event, you have to register a VetoableCompositeListener. For more information about this interface, please take a closer look at the documentation for VetoableCompositeListener.

For more information about the event mechanism in the tensegrity frameworks, please see interface EventListenerTag and class EventMultiplexer.

The methods a Composite provides to support the event mechanism are:

  • void addCompositeListener(CompositeListener)

  • void removeCompositeListener(CompositeListener)

  • void addVetoableCompositeListener(VetoableCompositeListener)

  • void removeVetoableCompositeListener(VetoableCompositeListener)

CompositeLine

The CompositeLine interface is used whenever a line is drawn or two composites are connected with a link. This connecting edge is made up of a CompositeLine - a Composite that holds a line and behaves differently than normal composites.

The CompositeLine interface supplements the BaseComposite behavior by managing a set of Coordinate objects that are used by a parent CoordinateSystem.

Figure 2.4. Class Diagram CompositeLine

Class Diagram CompositeLine

To modify the coordinates of a CompositeLine, please use the following methods:

  • void addCoordinate(Coordinate)

    Adds the Coordinate given by coordinate to the end of the CompositeLines actual coordinates.

  • Coordinate getCoordinateAt(int)

    Returns the Coordinate at the index given by index from the CompositeLines current coordinates. If the index is less than zero or larger than the actual count of Coordinates an exception will be thrown.

  • int getCoordinateCount()

    Returns the current number of Coordinates the CompositeLine consist of.

  • Coordinate getStartingLocation()

    Returns the starting Coordinate of the CompositeLine. The ending coordinate is the last Coordinate the CompositeLine consists of (respectively the CompositeLine starts at).

  • void setStartingLocation(Coordinate)

    Sets the Coordinate given by coordinate as the new starting coordinate of the CompositeLine. The starting coordinate is the first Coordinate the CompositeLine consists of respectively the CompositeLine starts.

  • void insertCoordinate(int, Coordinate)

    Inserts the Coordinate given by coordinate at the index given by index. If the first index is less than zero it is adjusted to zero. If the second index is larger than the actual count of Coordinates it is adjusted to the actual count of Coordinates

  • void removeCoordinate(int)

    Removes the Coordinate at the index given by index from the CompositeLines coordinates. If the index is less than zero or larger than the actual count of Coordinates an exception will be thrown.

CompositeView

This interface represents the top-level BaseCompositeContainer in a tree of like-kind containers. Clients will reference this interface type explicitly because it declares methods needed at the very top of the hierarchy. Moreover, a CompositeView merges several basic interfaces and offers special support for handling objects of type BaseComposite. This interface is a mighty specification: the classes which implement it are basically responsible for performing all drawing, event-handling, selection and notification tasks necessary for a top-level view container.

Figure 2.5. Class Diagram CompositeView

Class Diagram CompositeView

It should come as no surprise, then, that this interface is merged into the interface specification for VisualGraphView. This allows clients to speak to a top-level VisualGraph as if it were a CompositeView by nature.

A CompositeView will draw the following objects in the order specified:

  1. Contained Primitive instances

  2. Contained BaseComposite instances

  3. Selected BaseComposite instances

  4. Contained BaseComposite indicators

  • void draw(Renderer, Device, Transform2D)

    Draws the Primitives and Composites added to the CompositeView with the Renderer given by renderer onto the Device given by device. This method draws everything that is contained in this CompositeView. If drawing of a combination of particular items inside this instance is required use the method draw(Renderer, Device, Transform2D, int) and specify a bitmask composed of the desired drawing flags.

  • void draw(Renderer, Device, Transform2D, int)

    Draws the Primitives and Composites added to the CompositeView with the Renderer given by renderer onto the Device given by device.

A CompositeView supports user interaction by extending the InteractionProvider interface. In order to plug additional interaction behavior into an instance, you must register a CompositeInteractionHandler that is invoked by the CompositeView whenever some form of user interaction takes place. The table below gives information about how method calls are delegated.

Method of class CompositeViewInvoked method of class CompositeInteractionHandler
startActionAtstartActionAt
stopActionAtstopActionAt
actAtpreActAt
actAtactAt

For more information, please refer to the JavaDoc of interface CompositeInteractionHandler.

Because it is possible that the view area of CompositeView is much larger than the drawing area, scrollbars are usually needed to make the entire view area visible to the user. Since scrollbars cannot be attached to a CompositeView, the relevant scroll information can nevertheless be provided to an object that requires it. These are the methods to call:

  • int getHorizontalScrollbarValues()

    Calculates and returns the values needed to setup a horizontal scrollbar. the values are as follows:

    • minimum value

    • maximum value

    • current value

    • current extend

    The indices of the values are defined by the constants listed below.

  • int getVerticalScrollbarValues()

    Calculates and returns the values needed to setup a vertical scrollbar. The values are as follows:

    • minimum value

    • maximum value

    • current value

    • current extend

    The indices of the values are defined by the constants listed below.

Additionally, this interface specifies that Primitive objects can be used to draw a title and other background graphics, objects which should not respond to mouse events. In other words, these primitives should only be used for the views static adornment. The methods to manage these Primitive objects are:

  • void addPrimitive(Primitive)

    Adds the given Primitive to the elements of the CompositeView>.

  • void removePrimitive(Primitive)

    Removes the given Primitive from the elements of the CompositeView.

Two methods deal with the layered positioning of contained BaseComposite instances:

  • void toBack(BaseComposite)

    Sends the given composite to the back of the drawing order.

  • void toFront(BaseComposite)

    Sends the given composite to the front of the drawing order.

A CompositeView has both a CoordinateSystem and its own BoundingBox which allows it to scale and position all child composites contained below it. The relevant methods are:

  • Boundary getBoundingBox()

    Returns the current bounding box of the CompositeView.

  • void setBoundingBox(int, int, int, int)

    Sets the bounding box of the CompositeView to the values given by left, top, right and bottom,

In addition to its own CoordinateSystem, a CompositeView uses a second instance, which it calls the "reference coordinate system." This CoordinateSystem is used to define how much of the view area should be visible within the viewport of the CompositeView. The following method is used to retrieve this CoordinateSystem.

  • CoordinateSystem getReferenceCoordinateSystem()

    Returns the CoordinateSystem the CompositeView currently uses as reference for the viewport and such.

A CompositeView also supports different modes which can be externally configured.

  • int MODE_MASK_ALL

    Indicating that all modes are set.

  • int MODE_MASK_CONTEXT_MENU_ALLOWED

    Indicating that the context menu is enabled / disabled.

  • int MODE_MASK_EDITING_ALLOWED

    Indicating that the editing of inlying BaseComposite objects is allowed.

  • int MODE_MASK_NONE

    Indicating that no mode is set.

  • int MODE_MASK_SELECTION_ALLOWED

    Indicating that selections of inlying Composite respectively Primitive objects are allowed.

  • int MODE_MASK_SELECTIONBOX

    Indicating the mode mask for rectangular selections.

These modes can be manipulated using the following methods:

  • boolean areEventsEnabled(int)

    Returns true, if events of the given eventmask are enabled.

  • void disableEvents(int)

    Disables all of the events specified in the given eventmask.

  • void enableEvents(int)

    Enables all of the events specified in the given eventmask.

  • void enableMode(int)

    Enables the mode specified by the given modemask

  • int getEventmask()

    Returns the current eventmask.

  • int getModemask()

    Returns the current setting for the mode the CompositeView work in. The returned value will be in the following list:

    • MODE_MASK_EDITING_ALLOWED

    • MODE_MASK_CONTEXT_MENU_ALLOWED

    • MODE_MASK_SELECTION_ALLOWED

  • boolean isModeEnabled(int)

    Returns a boolean that indicates whether the mode specified by modemask is enabled or not.

As a container of BaseComposite objects, a CompositeView will mediate all occurring events propagated by objects of type Composite, CompositeLine, CompositeGroup and, of course, its own type (CompositeView).

To be informed about these events, one must add an EventMediatorListener or a VetoableEventMediatorListener to a CompositeView instance.

The following interfaces can provide you with more information about the event mediator mechanism: EventMediator, EventMediatorListener, VetoableEventMediator, VetoableEventMediatorListener.

The relevant event mediation methods are:

  • void addEventMediatorListener(EventMediatorListener)

  • void removeEventMediatorListener(EventMediatorListener)

  • void addVetoableEventMediatorListener(VetoableEventMediatorListener)

  • void removeVetoableEventMediatorListener(VetoableEventMediatorListener)

Events are delivered in the following order:

  1. VetoableCompositeLineListener, VetoableCompositeListener, VetoableCompositeGroupListener

  2. VetoableEventMediatorListener

  3. CompositeLineListener, CompositeListener, CompositeGroupListener

  4. EventMediatorListener

A CompositeView object fires events for every conceivable change that can happen to it. In order to be notified about the occurrence of these events, an object which implements the CompositeViewListener interface must be registered with an instance of this class.

Some of the events fired by a CompositeView may be vetoed, which means that the processing of an event can be stopped. To veto an event, one must register a VetoableCompositeViewListener with an instance of this class.

The event-throwing mechanism of a CompositeView can be turned on and off. By invoking the enableEvents method and supplying the specific event mask, you can control which events are thrown. A call to disableEvents, on the other hand, suitably turns off the specified events marked by the event mask parameter.

By default all events are turned on. To determine which events are enabled or disabled, you can invoke the areEventsEnabled method. The event masks are defined by an enumeration in this interface or in an interface that extends this one. Event mask names always start with the EVENT_MASK prefix. The methods a CompositeView exposes to support the event mechanism are:

  • void addCompositeViewListener(CompositeViewListener)

    Adds the given CompositeViewListener to the CompositeViews internal structure, such that the listener is informed about every event that occurs.

  • void addVetoableCompositeViewListener(VetoableCompositeViewListener)

    Adds the given VetoableCompositeViewListener to the CompositeViews internal structure, such that the listener is informed about every event that occurs.

  • void removeCompositeViewListener(CompositeViewListener)

    Removes the given CompositeViewListener from the CompositeViews internal structure, such that the listener is no longer informed about occurring events.

  • void removeVetoableCompositeViewListener(VetoableCompositeViewListener)

    Removes the given VetoableCompositeViewListener from the CompositeViews internal structure, such that the listener is no longer informed about occurring events.

An example arrangement

Composite objects hold their own CoordinateSystem, which is nested within the CoordinateSystem of a parent CompositeGroup or CompositeView. The BoundingBox of a Composite, moreover, defines its size and location and should be specified using the CoordinateSystem of its parent view.

Imagine a scenario with the following components:

  • A view with an x-axis going from 0 to 7000 and a y-axis of the same length.

  • Composite one:

    • CoordinateSystem: x-Axis from 0 to 2000, y-Axis from 0 to 2000

    • BoundingBox: left-top: (1000, 1000), right-bottom: (2000, 4000)

  • Composite two:

    • CoordinateSystem: x-Axis from 0 to 2000, y-Axis from 0 to 2000

    • BoundingBox: left-top: (5000, 5000), right-bottom: (6500, 6500)

A visualization of this arrangement looks as follows:

Figure 2.6. An arrangement of Composites in a Composite View

An arrangement of Composites in a Composite View

Note

Note the CoordinateSystem of the second Composite. Although it has the same maximum values as the first, the Composite does not have the same size in the view because its size is determined by its BoundingBox. The layout of Primitive objects in a Composite, however, is done by using the CoordinateSystem of a Composite.

Creating a Simple Composite

Now let us see how to create a Composite programmatically. First of all, we need to create a CompositeView:

// Get a CompositeFactory instance
CompositeFactory factory= CompositeFactory.newInstance();
//  Create a view with the given CoordinateSystem Composite
CompositeView compositeView= factory.newCompositeView();

As you can see, we have chosen to use MutableScale and MutableCoordinateSystem objects so that we can change the minimum and maximum values later.

Now lets create a new Composite. In the following examples we will create two of them:

CoordinateSystem coordSystem1= 
    new MutableCoordinateSystem(
        new DefaultMutableScaleLinear(0, 2000),
        new DefaultMutableScaleLinear(0, 2000));
Composite composite1= factory.newComposite(coordSystem1);
composite1.setBoundingBox(1000, 4000, 1000, 3500);

The creation of the second Composite is equivalent:

CoordinateSystem coordSystem2= 
    new MutableCoordinateSystem(
        new DefaultMutableScaleLinear(0, 2000),
        new DefaultMutableScaleLinear(0, 2000));
Composite composite2= factory.newComposite(coordSystem2);
composite2.setBoundingBox(5000, 6500, 5000, 6500);

Now we can add the two newly-created Composite instances to the view by simply calling its addComposite method:

compositeView.addComposite(composite1);
compositeView.addComposite(composite2);

Although the above code is not ready for use, it should give you a basic understanding how to deal with the CoordinateSystem and BoundingBox of a Composite. This example[4] is provided to get you used to the basic concepts of the CompositeAPI. We will have a look at some real world examples in later sections.

Note

Most often, you will use xml-based element definitions to specify a repository from which Composite objects will be automatically created. However, if the capabilites of these elements are not sufficient for your application, you have the option to implement your own Composite class.



[4] The complete listing can be found in Appendix “Listings”.