/*
* JFLAP - Formal Languages and Automata Package
*
*
* Susan H. Rodger
* Computer Science Department
* Duke University
* August 27, 2009
* Copyright (c) 2002-2009
* All rights reserved.
* JFLAP is open source software. Please see the LICENSE for terms.
*
*/
package gui.tree;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.geom.*;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.tree.*;
import java.util.*;
/**
* The DefaultTreeDrawer
object is used to draw a TreeModel
* in a given space with some built in basic functionality.
*
* @author Thomas Finley
*/
public class DefaultTreeDrawer implements TreeDrawer, TreeModelListener {
/**
* Instantiates a DefaultTreeDrawer
for a given tree.
*
* @param tree
* the tree to draw, assumed to have all nodes as javax.swing.tree.TreeNode
* objects.
* @see javax.swing.tree.TreeNode
*/
public DefaultTreeDrawer(TreeModel tree) {
this.tree = tree;
tree.addTreeModelListener(this);
}
/**
* This method returns the color for a particular node.
*
* @param node
* the node to color
* @return the color for this node, which by default is always yellow
*/
protected Color getNodeColor(TreeNode node) {
return Color.yellow;
}
/**
* Draws the tree in the indicated amount of space.
*
* @param g
* the graphics object to draw upon
* @param size
* the bounds for the space the tree has to draw itself in in the
* current graphics, assumed to be a rectangle with a corner at
* 0,0.
*/
public void draw(Graphics2D g, Dimension2D size) {
if (!valid)
revalidate();
g = (Graphics2D) g.create();
// Draw the nodes.
g.setFont(g.getFont().deriveFont(10.0f));
g.setColor(Color.black);
draw(g, (TreeNode) tree.getRoot(), size, null);
g.dispose();
}
/**
* Given a point and a size, returns the point scaled to the size.
*
* @param point
* a point in ([0,1], [0,1])
* @param size
* some dimensions
* @return a point scaled to the size, or POSSIBLY null if an error occurred
* (which it never should)
*/
private Point2D scalePoint(Point2D point, Dimension2D size) {
Point2D scale = null;
try {
scale = (Point2D) point.getClass().newInstance();
if (inverted)
scale.setLocation(point.getX() * size.getWidth(), (1.0 - point
.getY())
* size.getHeight());
else
scale.setLocation(point.getX() * size.getWidth(), point.getY()
* size.getHeight());
} catch (Throwable e) {
System.err.println("BADNESS SCALING THE POINT IN TREEDRAWER!");
System.err.println(e);
}
return scale;
}
/**
* Draws the given node, as well as its subnodes.
*
* @param g
* the graphics object to draw upon
* @param node
* the node to draw
* @param size
* the dimension of the area the graph is drawn in
* @param parent
* the point that the parent was drawn at, or null
* if this is the root node
*/
protected void draw(Graphics2D g, TreeNode node, Dimension2D size,
Point2D parent) {
boolean visible = isVisible(node);
Point2D p = scalePoint((Point2D) nodeToPoint.get(node), size);
// Draw the tree-connections in preorder.
if (visible && parent != null)
g.drawLine((int) parent.getX(), (int) parent.getY(),
(int) p.getX(), (int) p.getY());
// Recurse on the children.
TreeNode[] c = Trees.children(node);
for (int i = 0; i < c.length; i++)
draw(g, c[i], size, visible ? p : null);
// Draw the node in postorder.
if (!visible)
return;
Graphics2D g2 = (Graphics2D) g.create();
g2.translate(p.getX(), p.getY());
g2.setColor(getNodeColor(node));
nodeDrawer.draw(g2, node);
g2.dispose();
}
/**
* Returns the TreeModel
that this TreeDrawer
* draws.
*
* @return the tree model this drawer draws
*/
public TreeModel getModel() {
return tree;
}
/**
* Sets the TreeModel
that this TreeDrawer
* draws.
*
* @param model
* the new tree model
*/
public void setModel(TreeModel model) {
tree = model;
invalidate();
}
/**
* Sets all nodes to hidden.
*/
public void hideAll() {
defaultVisible = false;
visibleNodes.clear();
}
/**
* Sets all nodes to visible.
*/
public void showAll() {
defaultVisible = true;
visibleNodes.clear();
}
/**
* Shows a node.
*
* @param node
* the node to set as visible
*/
public void show(TreeNode node) {
if (defaultVisible)
visibleNodes.remove(node);
else
visibleNodes.put(node, null);
}
/**
* Hides a node.
*
* @param node
* the node to set as invisible
*/
public void hide(TreeNode node) {
defaultVisible = !defaultVisible;
show(node);
defaultVisible = !defaultVisible;
}
/**
* Returns if a node is visible. This function is undefined if the node in
* question is not in the tree.
*
* @param node
* the node to get visibility of
* @return the node is visible
*/
public boolean isVisible(TreeNode node) {
return defaultVisible ^ visibleNodes.containsKey(node);
}
/**
* This marks the structure as uninitialized.
*/
public void invalidate() {
valid = false;
}
/**
* This initializes whatever structures need to be reinitialized after there
* is some change in the tree.
*/
public void revalidate() {
valid = true;
nodeToPoint = nodePlacer.placeNodes(tree, nodeDrawer);
}
/**
* Returns the node at a particular point.
*
* @param point
* the point to check for the presence of a node
* @param size
* the size that the tree, if drawn, would be drawn in
*/
public TreeNode nodeAtPoint(Point2D point, Dimension2D size) {
Iterator it = nodeToPoint.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Point2D p = scalePoint((Point2D) entry.getValue(), size);
TreeNode node = (TreeNode) entry.getKey();
if (nodeDrawer.onNode(node, point.getX() - p.getX(), point.getY()
- p.getY()))
return node;
}
return null;
}
/**
* Recursively sets the points of the tree.
*
* @param node
* the current node in the tree
* @param depth
* the total depth of the tree
* @param thisDepth
* the depth of this particular node
* @param width
* the array of all widths
* @param widthSofar
* the widths sofar
*/
private void setPoints(TreeNode node, int depth, int thisDepth,
int[] width, int[] widthSofar) {
// Scale points along ([0,1], [0,1]).
float x = (float) (widthSofar[thisDepth] + 1)
/ (float) (width[thisDepth] + 1);
float y = (float) (thisDepth + 1) / (float) (depth + 2);
nodeToPoint.put(node, new Point2D.Float(x, y));
// Update the depth and width figures.
widthSofar[thisDepth++]++;
// Recurse on children.
TreeNode[] children = Trees.children(node);
for (int i = 0; i < children.length; i++)
setPoints(children[i], depth, thisDepth, width, widthSofar);
}
/**
* Invoked after nodes have changed.
*/
public void treeNodesChanged(TreeModelEvent e) {
invalidate();
}
/**
* Invoked after nodes have been inserted.
*/
public void treeNodesInserted(TreeModelEvent e) {
invalidate();
}
/**
* Invoked after nodes have been removed.
*/
public void treeNodesRemoved(TreeModelEvent e) {
invalidate();
}
/**
* Invoked after the structure of a tree has changed.
*/
public void treeStructureChanged(TreeModelEvent e) {
invalidate();
}
/**
* Sets the node placer for this drawer. By default this is set to an
* instance of DefaultNodePlacer
.
*
* @param placer
* the new node placer
* @see gui.tree.DefaultNodePlacer
*/
public void setNodePlacer(NodePlacer placer) {
this.nodePlacer = placer;
invalidate();
}
/**
* Returns the node placer for this drawer.
*
* @return the node placer for this drawer
*/
public NodePlacer getNodePlacer() {
return nodePlacer;
}
/**
* Sets the node drawer for this drawer. By default this is set to an
* instance of DefaultNodeDrawer
.
*
* @param drawer
* the new node drawer
* @see gui.tree.DefaultNodeDrawer
*/
public void setNodeDrawer(NodeDrawer drawer) {
this.nodeDrawer = drawer;
}
/**
* Returns the node drawer for this drawer.
*
* @return the node drawer for this drawer
*/
public NodeDrawer getNodeDrawer() {
return nodeDrawer;
}
/**
* Sets the display of the tree to inverted or uninverted.
*
* @param inverted
* true
if the root should be at the base, false
* if the root should be at the top
*/
public void setInverted(boolean inverted) {
this.inverted = inverted;
}
/**
* Returns if the display of the tree is inverted.
*
* @return true
if the root is at the base, false
* if the root is at the top
*/
public boolean isInverted() {
return inverted;
}
/** Is the root at the base (true) or top (false)? */
private boolean inverted = false;
/** Are our structures valid? */
private boolean valid = false;
/** The tree that this TreeDrawer
is drawing. */
private TreeModel tree;
/** The mapping of nodes to points. */
private Map nodeToPoint = new HashMap();
/**
* True if visible set denotes invisible nodes (default is visible), false
* if visible (default is invisible).
*/
private boolean defaultVisible = true;
/** The set of visible/invisible nodes. */
private WeakHashMap visibleNodes = new WeakHashMap();
/** The drawer for the nodes. */
private NodeDrawer nodeDrawer = new DefaultNodeDrawer();
/** The placer for the nodes. */
private NodePlacer nodePlacer = new DefaultNodePlacer();
}