PolyTreeTable - A Java/Swing Polyhierarchy View

PolyTreeTable is a Swing Component that can display a graph where nodes have multiple parents (polyhierarchy). There is a focus node, a parent tree above, a child tree below, parents expand upwards, and are right aligned (configurable). PolyTreeTable is a JTable derivate and offers multiple columns.

This package is open source, published under Lesser GNU Public License (LGPL).


SourceForge.net Logo
Author: Ritzberger Fritz, released April 2004




View Concept

A view of a graph with multiple parents can only be an aspect. The aspect is chosen by selecting a focus node. Only for the focus node both parents and children are shown. Above the focus node there is a parent hierarchy, every parent showing its parents, below the focus node there is a child hierarchy, every child showing its children. To separate parents and children, parents are right aligned, and children are left aligned. Parents expand upwards, children expand downwards.

Such a view is a compromise between a ony-way hierarchy and a tree that always shows the focus node and only its first-level parents and children (both as lists that can not be expanded). It improves the naivigability of the view. Default action (double click, ENTER key) changes the focus node.

The screenshot below shows PolyTreeTable filled with the java language grammar. You can realize which constructs need the "block" construct, and which constructs are needed by the "block" construct. This is the main advantage of the PolyTreeTable view.

Screenshot


Programming Concept

PolyTreeTable shows a graph with nodes that can have multiple parents (cycles). That means, a node can be the child of different parents, and so it will be shown. Deleting this node can make it disappear from one, more or all its parents. Inserting a node will make it the child of one parent, but maybe this node ought to be connected to other parents, like its siblings.

A node could be shown more than once in this view, as it could belong to more than one parent. So the visible node is a just a view to the real node, shown in different contexts. Therefore PolyTreeTable uses visible nodes as views to "user objects", which incorporate the real model. The user object is similar to javax.swing.tree.DefaultMutableTreeNode's user object, but it is cached in a class cache that holds a unique map for every user object class.  The interface PolyTreeTableUserObject describes the responsibilities of an user object. Every visble node can have just one user object, but a user object can belong to more than one visible node. The user object implementation needs to implement hashCode() and equals() to be identifyable within the user object cache!

PolyTreeTable's delete-method requires a parent as argument, to delete it from more or all parents you must implement a loop.

It offers no logic for connecting an inserted node to other parents, this must be solved by the application programmer. But PolyTreeTable provides the creation of cached user objects, see below.

Feel free to implement common editing logic, or a better node caching. Let me know about your solution.

Constructing a PolyTreeTable

You can construct PolyTreeTable using different model levels. Most comfortable is to construct it with an PolyTreeTableUserObject implementation, which plays the same role as DefaultMutableTreeNode's user object, but is an interface. The advantage of implementing PolyTreeTableUserObject is that you don't need to care about node caching. Node caching is essential in a graph where an object can appear more than once.
PolyTreeTableUserObject userObject = new XXXPolyTreeTableUserObject(data);
PolyTreeTable polytreetable = new PolyTreeTable(userObject);
getContentPane().add(new JScrollPane(
polytreetable), BorderLayout.CENTER);
If you decide to do your own caching, you can use DefaultPolyTreeNode as base class.
PolyTreeNode startNode = new XXXPolyTreeNode(data);
PolyTreeTable polytreetable = new PolyTreeTable(startNode);
getContentPane().add(new JScrollPane(polytreetable), BorderLayout.CENTER);
The lowest level is to implement your own PolyTreeTableModel.
PolyTreeTableModel model = new XXXPolyTreeTableModel(data);
PolyTreeTable polytreetable = new PolyTreeTable(model);
getContentPane().add(new JScrollPane(polytreetable), BorderLayout.CENTER);

I would recommend to do the first variant, as caching errors can lead to strange effects in polytreetable. These are the responsibilities of a  PolyTreeTableUserObject that will be cached automatically by using the first constructor:
public interface PolyTreeTableUserObject
{
    /** Returns true if passed column is editable. */
    public boolean isColumnEditable(int column);

    /** Returns the Object for given column from this userObject. */
    public Object getColumnObject(int column);

    /** Sets the the given column Object into this userObject.
        @return new PolyTreeTableUserObject if it had to be changed completely, else "this". */
    public Object setColumnObject(int column, Object aValue);

    /** Returns the number of columns for this userObject. */
    public int getColumnCount();

    /** Returns the class for given column. */
    public Class getColumnClass(int column);

    /** Returns the name for given column. */
    public String getColumnName(int column);

    /** Returns the number of children of this userObject. */
    public int getChildCount();

    /** Returns the child object at given index of this userObject. */
    public Object getChildAt(int index);

    /** Returns true if the receiver allows children, i.e. it is a "child folder". */
    public boolean getAllowsChildren();

    /** Returns the number of parents of this userObject. */
    public int getParentCount();

    /** Returns the parent object at given index of this userObject. */
    public Object getParentAt(int index);

    /** Returns true if the receiver allows parents, i.e. it is a "parent folder". */
    public boolean getAllowsParents();
}

Accessing the model

When you need to acces the model, mind that getModel() will return an adapter object. Use getPolyTreeTableModel() to retrieve the model.

Management of polyhierarchies is complicated. The model offers simple insert and remove calls. When using PolyTreeTableUserObject the node is removed from one given parent, and is inserted only into one given parent. Its the programmers responsibility to add more logic. Override polytreetable.createDefaultModel() to force a new model class.

Getting the selected lead node:
ListSelectionModel sm = polytreetable.getSelectionModel();    // is a DefaultListSelectionModel subclass
int lead = sm.getLeadSelectionIndex();
PolyTreeNode selected = (PolyTreeNode) polytreetable.getTree().getNodeForViewRow(lead);
Creating and inserting a new node into selected node:
PolyTreeNode newNode = DefaultCachedPolyTreeNode.create(new XXXPolyTreeTableUserObject(data));
DefaultPolyTreeTableModel model = (DefaultPolyTreeTableModel) table.getPolyTreeTableModel();
model.insertChildIntoNode(newNode, selected, 0);
Removing a node from selected node:
DefaultPolyTreeTableModel model = (DefaultPolyTreeTableModel) table.getPolyTreeTableModel();
int parentIndex  = 0;    // remove from first parent
MutablePolyTreeNode parent = (MutablePolyTreeNode) model.getParent(selected, parentIndex);
model.removeChildFromNode(selected, parent);
Changing the name of a node will trigger the setColumnObject method of PolyTreeTableUserObject.


Catching events

Following listeners can be added to PolyTreeTable:JavaDoc
PolyTreeWillExpandListener    // provides ExpandVetoException
PolyTreeExpansionListener
If you want to catch focus node changes ("lead term"), you will receive this in your
treeWillStructureChange(PolyTreeExpansionEvent event)
treeStructureChanged(PolyTreeExpansionEvent event)
listener callbacks.

Of course you can listen to model events:
PolyTreeTableModelListener
This gives you all change events within the model.


If you don't find a service, try the PolyTreeView API. This is the most complex class, it manages the layout of PolyTreeTable. You can get it by calling
PolyTreeView polyTreeView = polytreetable.getTree();
It is not meant to be the "facade" class, but some functionality has not been made available in the PolyTreeTable (facade).




Please read API reference for further information.





Author: Ritzberger Fritz, released April 2004