Development Notes: Interface Shapes

Updated for release version: 1.0.7-alpha

This page discusses interface shapes (ShapeInts) in modelGUI (classes which inherit InterfaceShape). It provides a summary of how ShapeInts are conceptually designed, and discusses implementation details which are important to know when developing with, or extending, ShapeInt functionality.

Introduction

ShapeInts provide an interface for shapes (instances of Shape). They provide functionality for:

  • Attributes
  • Tree Nodes
  • Shape Events
  • Geometry details (center point, bounding box)
  • XML I/O

ShapeInts are instances of InterfaceShape. The two main subclasses of this abstract class are Shape3DInt and Shape2DInt.

What follows is a description of the implementation and API details for various aspects of ShapeInts.

Attributes

Shape attributes are specified in an AttributeList (main article). The ShapeInt registers as an AttributeListener on the AttributeList, so that it can respond to attribute changes via the attributeUpdated method. AttibuteListener is an interface allowing objects to respond to changes to their attributes. The interface specifies one method: attributeUpdated(AttributeEvent e).

The current implementation of attributeUpdated for Shape3DInt is:

/******************************
 * Responds to an update in one of this shape's attributes.
 * 
 * <p>Subclasses should override and call this super method AFTER handling the
 * attribute change appropriately. Subclasses should also override needsRedraw() 
 * to indicate whether the scene node should be regenerated.
 * 
 * @param e an <code>AttributeEvent</code> specifying which attribute has been changed
 */
public void attributeUpdated(AttributeEvent e){
    if (!notifyListeners) return;
 
    Attribute attribute = e.getAttribute();
 
    if (attribute.getName().equals("DataMin") ||
            attribute.getName().equals("DataMax"))
        return;
 
    if (attribute.getName() == "ShowBounds3D" ||
            attribute.getName() == "BoundsColour")
        setBoundBoxNode();
 
    if (needsRedraw(attribute) && scene3DObject != null)
        setScene3DObject();
 
    if (needsRedraw(attribute))
        fireShapeListeners(new ShapeEvent(this, ShapeEvent.EventType.AttributeModified));
 
    updateChildren2D(attribute);        
}

As explained in the API doc comments, if you need to override this method to perform special handling of attribute change events, this should be done prior to calling the super method. The super method does a number of things. Firstly, if the attribute updates the bound box, it calls setBoundBoxNode to update the Java3D node. Next, if the attribute change necessitates a redrawing of the node (as determined by the needsRedraw method), the setScene3DObject method is called, and this Shape3DInt's shape listeners are notified with an AttributeModified event (see Shape Listeners section, below). Finally, the updateChildren2D method is called, which redraws any 2D children which have changed.

In practice, you may only need to override the needsRedraw method to specify which nodes need to be redrawn; only in the case where special handling is required should you override attributeUpdated.

The current implementation of attributeUpdated for Shape2DInt is:

/**********************************************
 * Responds to a change in a specific attribute by notifying this ShapeInt's shape listeners with an
 * <code>AttributeModified</code> shape event. If overriding, this super method should be called
 * AFTER the special handling has been performed. 
 * 
 */
@Override
public void attributeUpdated(AttributeEvent e){
 
    if (needsRedraw(e.getAttribute())){
        modified_attribute = e.getAttribute();
        fireShapeListeners(new ShapeEvent(this, ShapeEvent.EventType.AttributeModified));
        modified_attribute = null;
        }
 
}

This method is simpler than that for Shape3DInt. Its only function is to determine whether the Shape2DInt should be redrawn, and if so, notifying its listeners with an AttributeModified shape event. Since any InterfaceGraphic2D window containing this shape should be registered as a listener, this effectively informs the window to redraw itself to reflect the attribute change (see Shape Listeners section, below).

Tree Nodes

Tree nodes (main article) allow a ShapeInt to be represented in a hierarchical tree structure; i.e., in an InterfaceTree, which is a subclass of Swing's JTree component. This representation is quite powerful, as it allows both an intuitive representation of the object model, as well as a useful means of querying and updating a ShapeInt's attributes.

InterfaceShape inherits its tree node functionality from AbstractInterfaceObject. This functionality is extended in the package mgui.interfaces.shapes.trees. All ShapeInts are represented with a specialized subclass of InterfaceTreeNode called ShapeTreeNode; this is further subclassed into Shape2DTreeNode and Shape3DTreeNode. Accordingly, the getTreeNode method (which essentially acts as a server for new tree nodes) creates instances of ShapeTreeNode and sets them using the setTreeNode method.

NB. future releases will likely remove the unnecessary division of ShapeTreeNode into Shape2DTreeNode and Shape3DTreeNode, treating all instances of InterfaceShape generically.

For Shape3DInt the getTreeNode method looks like:

/***************************************
 * Issues a new tree node for this ShapeInt. Creates the node and then calls {@link setTreeNode}
 * to construct it.
 * 
 * @return a new <code>InterfaceTreeNode</code>, which is an instance of <code>Shape3DTreeNode</code>. 
 */
@Override
public InterfaceTreeNode getTreeNode(){
    Shape3DTreeNode treeNode = new Shape3DTreeNode(this);
    setTreeNode(treeNode);
    treeNodes.add(treeNode);
    return treeNode;
}

ArrayList<InterfaceTreeNode> treeNodes is a member of AbstractInterfaceObject, which keeps track of the tree nodes which have been issued by this shape, updates them when necessary, and removes them when they have been destroyed. This method calls the setTreeNode method, which updates an existing tree node. This scheme allows existing tree nodes to be updated without having to destroy existing nodes and issuing new ones.

The implementation of setTreeNode from InterfaceShape looks like:

/**************************************************
 * Constructs a tree node from this shape. Adds an {@link AttributeTreeNode} via the <code>super</code>
 * method, and also adds a node to display the vertex-wise data columns associated with this ShapeInt.
 *
 * <p>If overriding this method, subclass implementations should first call this super method to initialize
 * the node and provide a basic construction.
 * 
 * @param treeNode the tree node to construct
 */
@Override
public void setTreeNode(InterfaceTreeNode treeNode){
    super.setTreeNode(treeNode);
    if (!hasData()) return;
    DefaultMutableTreeNode node = new DefaultMutableTreeNode("Data");
    ArrayList<String> list = getDataColumns();
    if (list == null) return;
 
    for (int i = 0; i < list.size(); i++)
        node.add(new DefaultMutableTreeNode(list.get(i)));
    treeNode.add(node);
}

Note that the first line of this method calls the super method from AbstractInterfaceObject. As stated in the API doc comments, if a subclass overrides this method for some reason, this super method should always be called prior to constructing the node; it takes care of all the ugly details and provides a basic construction that should be common to all shape tree nodes, including adding an AttributeTreeNode to display the shape's attributes and an additional node to display its data columns.

A ShapeTreeNode registers itself as a shape listener on the ShapeInt which issues it. It therefore can also respond to shape events on the ShapeInt. At present, this method only responds to updates on a ShapeSet.

Shape Listeners

Instantiation

Shape Rendering

Java3D nodes

Intersecting with a plane: 2D children

Updating the ShapeInt

Geometry changes

Attribute changes

Destruction

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License