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).
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.
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.