HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

A Tree for All Occasions
The Java Foundation Class, also known as Swing, in addition to augmenting, enhancing and generally implementing platform-independent replacements of AWT components, added the JTree class to its repertoire of new GUI components. Swing's JTree supports a Windows Explorer-style (outliner-style) tree that makes it easy to graphically render data with hierarchical relationships. A limited number of graphical attributes as supplied by the default look-and-feel provided with Swing - e.g., the icon representing the nodes - is also readily configurable.

This article describes an implementation similar to JTree that can be used in Java 1.0.2 or when using Swing may not be an option. This implementation also adds features not found in the default JTree. These include the option of rendering a tree vertically or horizontally, and of aligning it left, center or right. In addition, subtrees can be interactively moved from one branch to another using drag-and-drop. I also describe how some of these extra features can be migrated to Swing when it becomes more popular in the future. The source code in this article requires the Callbackable, CallbackList, Widget, PositionableGridConstraints and PositionableGridLayout classes introduced in previous issues of JDJ.

Creating a Tree - The TreeNode Class
A tree is essentially a set of hierarchically related nodes. In this implementation, each node is an instance of the TreeNode class. A user object may be stored in a TreeNode, including any AWT or composite AWT Component. While TreeNode doesn't contain any rendering code, the TreeViewer class stores the icon for each node in its representative TreeNode instance. Thus each TreeNode can be represented with a unique icon if necessary.

An instance of the TreeNode class stores references to its parent and children. A null reference for the parent means that this node is the root of the entire tree. A null reference for its children means that the node is a leaf. Listing 1 shows the implementation of a TreeNode class.

The following code fragment shows how to create the tree shown in Figure 1:

TreeNode root = new TreeNode( null, null, null, "Root", null );
new TreeNode( root, null, null,
"Level 1.1", null );
new TreeNode ( root, null, null,
"Level 1.2", null );
new TreeNode ( root, null, null,
"Level 1.3", null );
new TreeNode ( root.getChild(1),
null, null, "Level 2.1", null );
new TreeNode ( root.getChild(1),
null, null, "Level 2.2", null );
new TreeNode ( root.getChild(1),
null, null, "Level 2.3", null );

Figure 1
Figure 1:

The first line creates the root of the tree; note the null passed in the first parameter, which indicates no parent. Lines 2 to 4 create the second level; note that root is passed as the first parameter in this case. Lines 5 to 7 create the third level; note that the child of the root with index 1 is used for the first parameter.

Walking the Tree - The TreeWalker Class
While a collection of hierarchically linked TreeNode instances implements the structure of a tree, the TreeWalker implements an abstract class for traversing the tree. In other words, given a tree, an extension of the TreeWalker will traverse it in either a breadth or depth first approach, performing an action on each node as it goes. Listing 2 shows the code of the TreeWalker class. When creating an instance of a TreeNode, an instance of a TreeWalker extension is usually passed as the last parameter of the TreeNode constructor. Listing 3 shows the TreePrinter class, which is an extension of TreeWalker for printing each node of a tree.

A sample printout of the tree in Figure 1 using the TreePrinter will yield the console output seen in Listing 4.

Rendering the Tree - The TreeViewer Class
TreeViewer is an extension of the TreeWalker class used specifically for providing an interactive graphical front-end to a tree. The TreeViewer class takes a tree, traverses it and renders each node on any AWT display surface - e.g., Canvas or Panel. As noted before, the AWT Component stored in each TreeNode instance is used to render the node. For example, if a Button is to be displayed for a specific node in a tree, then an instance of the Button should be added to the TreeViewer, giving its location in the tree with respect to a parent node. The TreeViewer also manages the relocation of subtrees when the user initiates a drag-and-drop action.

TreeViewer contains the TreeViewerPanel class that uses the custom LayoutManager introduced in the article "Implementing a Grid Layout Manager with Positionable Components" (JDJ Vol. 3, Issue 12) to lay out the tree. The depth of a node in a tree hierarchy determines its vertical position in the grid. To determine its width, the TreeViewer recursively walks the tree (using the walkDepth method) to determine the grid position of each node relative to its neighbor on the left. After adjusting for alignment and orientation to determine its exact location on the grid, the node, as well as the lines leading to it from its parent, is drawn.

Actions on the nodes are left to the AWT Components representing the nodes themselves to handle. However, Tree relies on the Widget class introduced in the Widget-izing AWT Components to implement drop-and-drag. Because some AWT Components (notably the Button) recognize only the action event in some implementation of Java 1.0.2, the drag-and-drop capability is also implemented in the TreeViewerPanel. In general, a node or a subtree may be dragged by holding the mouse button just outside of the Button representing the node or the root of the subtree, and then moving the mouse. If the particular AWT implementation supports dragging inside the Component (e.g., Button), dragging may be accomplished by holding the mouse button down inside the Component itself and then moving the mouse. When the user moves a subtree, the callback determines the drop site, removes the subtree from the original location and attaches it to the new parent (MouseDragCallback). The complete TreeViewer implementation is shown in Listing 5.

Using the Tree Class to Display a Tree
Creating a visual representation of a tree using the TreeViewer class parallels that of creating a tree structure using the TreeNode class. Listing 6 shows an application that creates an interactive tree, which is center aligned and vertically oriented. The application requires two command line parameters: the first to specify whether the tree is to be displayed "horizontally" or "vertically"; the second to determine whether it is to be displayed "left," "center" or "right" align. The root will be created by default. Click on a button to create a child for it. To move a node, hold down the mouse button just outside the Button representing the node and drag it to the node that will become the new parent of the subtree. An entire subtree may also be moved this way.


Figure 2: This figure shows a vertically oriented, center-aligned tree using widgetized Buttons as nodes. Also shown is a red rectangle indicating that the "1" Button is being dragged. Dragging is accomplished by holding down the mouse button-represented here by the white cursor-near the Button to be dragged. The red rectangle will trace out the drag path while the mouse button is continued to be held down. Releasing the mouse button on top of any other Buttons moves the entire subtree rooted at "1" to the next location.

TreeViewer and JTree
TreeViewer and TreeNode are analogs of Swing's JTree and TreeNode. Both TreeViewer and JTree are the respective renderers and event handlers in each implementation.

By default, JTree provides only a Windows Explorer-style tree. In reality, JTree delegates most of its look-and-feel decisions to an instance of the BasicUI, so it is possible to customize the renderer by extending the supplied BasicTreeUI class. As Swing gradually becomes the GUI toolkit of choice for Java, the code use in this implementation of a general tree viewer may be migrated.

Conclusion
In this article we introduced a TreeViewer implementation with extra features that are not in Java's Swing-supplied implementation. In general, TreeViewer could be a powerful tool for visualizing hierarchically organized structures. A future article will explore the use of TreeViewer in viewing the organization of an AWT graphical user interface design - a step that will take us one step closer to implementing a GUI layout editor.

Download Source Code
The program in this article requires the callback and widget code from the Implementing Callback and Widget-izing AWT articles published in the April and June 1998 issues of JDJ (Vol. 3, Issues 4 and 5). It also requires the positionable grid layout code from the December issue of JDJ (Vol. 3, Issue 12). Full source code for this article (including a Java 1.1 version) can be downloaded free from www.wigitek.com. A version that supports the collapsing of subtrees for Java 1.0 and 1.1 is also available from Wigitek Corporation at the same Web site.

About the Author
Daniel Dee has two MS degrees and is currently president of Wigitek Corporation. He has more than 10 years' working experience in the development of GUI software toolkits, using X versions 10 and 11 and then Java since its inception. Daniel can be reached at [email protected]

	

Listing 1.
 
 * Description: 
   Package contains common classes for the ViviGraphics Widget 
   Toolkit - a callback-based toolkit. 
   Originally, part of the Eva Toolkit - the prototype 
   implementation. 
 */ 
package com.wigitek.vivigraphics.widget.common; 

/** 
 * This class implements a one-level tree, which can be chained 
   together with other one-level trees to form a multi-level tree. 
   It maintains information about its branches and its parent. 
   It can also store the object and associated data about the 
   root of this tree. It cannot walk or display the tree. 
   That capability is left to the TreeWalker class to implement. 
   This allows us to separate the view from the tree model. 
   Note: This class should be enhanced to support multiple walker, 
   possibly to support multiple viewer. 
 * @version $Revision: 2.1 $ 
 * @author Originally written by Daniel Dee, 3/18/97 
 * @author Last updated by $Author$, $Date$ 
 * @see TreeWalker 
 */ 
public class TreeNode extends Object 
{ 
    /** 
     * Constructs a one-level Tree, i.e. a TreeNode with references 
       to its parent and its children. 
     * @param parent  the parent of this tree if any; 
use null if root of multi-level tree 
     * @param object  the object stored at the root of this 
one-level tree 
     * @param data    additional data stored at the root of this 
one-level tree 
     * @param name    the name for the root of this one-level tree 
     * @param walker  the instance of the TreeWalker class that provides
the capability 
to walk the multi-level tree for which this tree is a part 
     */ 
    public TreeNode( TreeNode parent, Object object, Object data, 
String name, TreeWalker walker ) 
    { 
        this.parent = parent; 
        this.object = object; 
        this.data   = data; 
        this.name   = name; 
        this.walker = walker; 
        if( parent != null ) 
            parent.add( this ); 
    } 

    /** 
     * Makes the subtree one of its children. 
     * @param subtree the subtree 
     */ 
    protected void add( TreeNode subtree ) 
    { 
        // If the children (subtrees) array is exceeded, expand it 
        // and copies existing children to the new array. 
        if( numberOfChildren >= maxNumberOfChildren ) 
        { 
            TreeNode[] newChildren; 

            maxNumberOfChildren = 2 * maxNumberOfChildren; 
            newChildren = new TreeNode[maxNumberOfChildren]; 
            for( int i=0; i < numberOfChildren; i++ ) 
                newChildren[i] = children[i]; 
            children = newChildren; 
            newChildren = null; 
        } 

        // Add the new children (subtree). 
        children[numberOfChildren++] = subtree; 

        // Tell the walker that the tree has been updated. 
        if( walker != null ) 
            walker.treeUpdate(this); 
    } 

    /** 
     * Removes this leaf from the parent. 
     */ 
    public void remove() 
    { 
        if( parent != null ) 
            parent.remove(this); 
        else if( walker != null ) 
            walker.treeRemove(this); 
    } 

    /** 
     * Removes the subtree from the list of children. 
     * @param the specified subtree 
     */ 
    public void remove( TreeNode subtree ) 
    { 
        if( walker != null ) 
            walker.treeRemove(subtree); 

        for( int i=0; i < numberOfChildren; i++ ) 
            if( children[i] == subtree ) 
            { 
                for( int j=i; j < numberOfChildren-1; j++ ) 
                    children[j] = children[j+1]; 
                numberOfChildren--; 
                break; 
            } 

        if( walker != null ) 
            walker.treeUpdate(this); 
    } 

    /** 
     * Detach the subtree from the parent. 
       Does not destroy the subtree. 
     * @param the specified subtree 
     */ 
    protected void detach( TreeNode subtree ) 
    { 
        for( int i=0; i < numberOfChildren; i++ ) 
            if( children[i] == subtree ) 
            { 
                for( int j=i; j < numberOfChildren-1; j++ ) 
                    children[j] = children[j+1]; 
                numberOfChildren--; 
                break; 
            } 
    } 

    /** 
     * Detach this leaf from its original parent and attach to the 
new parent. 
     * @param newparent new parent 
     */ 
    public void setParent( TreeNode newparent ) 
    { 
        parent.detach(this); 
        parent = newparent; 
        parent.add(this); 
    } 

    /** 
     * Gets the parent. 
     */ 
    public TreeNode getParent() 
    { 
        return parent; 
    } 

    /** 
     * Gets the "width" of the subtree rooted at this TreeNode 
       without considering the grandchildren. It returns the 
       number of children of this TreeNode unless it is 0. 
       If it is 0, then getWidth returns 1. 
     */ 
    public int getWidth() 
    { 
        return numberOfChildren <= 1 ? 1 : numberOfChildren; 
    } 

    /** 
     * Gets the object stored at the root of the Tree. 
     */ 
    public Object getObject() 
    { 
        return object; 
    } 

    /** 
     * Gets the number of children in the tree. 
     */ 
    public int getNumberOfChildren() 
    { 
        return numberOfChildren; 
    } 

    /** 
     * Gets the 0-based i-th child. 
     * @param i the index of the child. 
     */ 
    public TreeNode getChild( int i ) 
    { 
        return children[i]; 
    } 

    /** 
     * Gets the data stored at the root of the tree. 
     */ 
    public Object getData() 
    { 
        return data; 
    } 

    /** 
     * Gets the name of the button. 
       Implements getName() method of the Widget interface. 
     */ 
    public String getName() 
    { 
        return name; 
    } 

    /** 
     * Returns a String that represents the value of this Object. 
       Overrides the method in java.lang.Object. 
     * @return a String 
     * @see Object 
     */ 
    public String toString() 
    { 
        String string = super.toString(); 
        int length = string.length(); 
        return string.substring( 0, length-1 ) + ",name=" + name + "]"; 
    } 
  

    // The name of an instance of this class 
    private String name; 

    // Initial array allocation to store Tree children 
    private int maxNumberOfChildren = 50;         // initial array size 
    private TreeNode[] children = new TreeNode[maxNumberOfChildren]; 
    private int numberOfChildren = 0; 

    private TreeNode parent;         // Tree parent 
    private TreeWalker walker;   // Instance of TreeWalker class that can 
walk this tree from the root 
    private Object object;       // Object stored at the root of the Tree 
    private Object data;         // Other information stored at the 
root of the Tree 
} 

Listing 2.
 
/** 
 * Copyright (c) 1997 Daniel Dee 
 * Description: 
   Package contains common classes for the Vivigraphics widget 
   Toolkit - a callback-based toolkit. 
   Originally, part of the Eva Toolkit - the prototype 
   implementation. 
 */ 
package com.wigitek.vivigraphics.widget.common; 

/** 
 * This class implements the skeleton tree walking capability 
   for the Tree class. 
 * Bugs: BREADTH_FIRST traversal not yet implemented. 
 * @version $Revision$ 
 * @author Originally written by Daniel Dee, 4/11/97 
 * @author Last updated by $Author$, $Date$ 
 * @see Tree 
 */ 
public abstract class TreeWalker extends Object 
{ 
    /** 
     * Constructs a new TreeWalker. 
     */ 
    public TreeWalker() 
    { 
        currentTree = null; 
    } 

    /** 
     * Updates the tree to which the subtree belong. 
     * @param subtree the subtree belonging to the tree to be updated. 
     */ 
    public void treeUpdate( TreeNode subtree ) 
    { 
        if( subtree != null ) 
            walk( currentTree = getRoot(subtree), DEPTH_FIRST ); 
    } 

    /** 
     * Updates the last updated tree. 
     */ 
    public void treeUpdate() 
    { 
        if( currentTree != null ) 
            walk(currentTree, DEPTH_FIRST); 
    } 

    /** 
     * Gets the root of the entire tree to which the specified subtree
belongs. 
     * @param subtree the specified subtree 
     */ 
    public TreeNode getRoot( TreeNode subtree ) 
    { 
        TreeNode parent; 

        while( (parent = subtree.getParent()) != null ) 
            subtree = parent; 
        return subtree; 
    } 

    /** 
     * Removes the subtree. 
     * @param subtree the subtree to be removed. 
     */ 
    public abstract void treeRemove( TreeNode subtree ); 

    /** 
     * Walks the tree starting at tree. 
     * @param tree     the starting root of the tree to walk 
     * @param walkType DEPTH_FIRST or BREADTH_FIRST 
     */ 
    protected abstract void walk( TreeNode tree, int walkType ); 

    /** 
     * Walks the tree depth-first starting at tree. Since the 
       method may be used recursively, parameter level 
       allows walkDepth to count the number of levels it has 
       recursed. 
     * @param tree the specified tree 
     * @param level the recursion level; must be supplied with default 
     */ 
    protected abstract void walkDepth( TreeNode tree, int level ); 
  

    protected TreeNode currentTree; // the last tree walked 

    public final static int DEPTH_FIRST = 0;   // depth-first walk 
    public final static int BREADTH_FIRST = 1; // breadth-first walk 
} 

Listing 3.
 
class TreePrinter extends TreeWalker 
{ 
    public void treeRemove( Tree subtree ) 
    { 
    } 

    protected void walk( Tree tree, int walkType ) 
    { 
        walkDepth( tree, 0 ); 
    } 

    protected void walkDepth( Tree tree, int level ) 
    { 
        for( int i=0; i < level; i++ ) 
            System.out.print( " " ); 
        System.out.println( tree.getName() ); 

        int numberOfChildren = tree.getNumberOfChildren(); 
        for( int i=0; i < numberOfChildren; i++ ) 
            walkDepth( tree.getChild(i), level+1 ); 
    } 
} 

Listing 4.
 
Root 
Level 1.1 
Root 
Level 1.1 
Level 1.2 
Root 
Level 1.1 
Level 1.2 
Level 1.3 
Root 
Level 1.1 
Level 1.2 
Level 2.1 
Level 1.3 
Root 
Level 1.1 
Level 1.2 
Level 2.1 
Level 2.2 
Level 1.3 
Root 
Level 1.1 
Level 1.2 
Level 2.1 
Level 2.2 
Level 2.3 
Level 1.3 

Listing 5.
 
/** 
 * Copyright (c) 1997 Daniel Dee 
 * Description: 
   Package contains common classes for the Vivigraphics widget 
   Toolkit - a callback-based toolkit. 
   Originally, part of the Eva Toolkit - the prototype 
   implementation. 
 */ 
package com.wigitek.vivigraphics.widget.gui; 

import java.awt.Color; 
import java.awt.Component; 
import java.awt.Container; 
import java.awt.Dimension; 
import java.awt.Event; 
import java.awt.Frame; 
import java.awt.Image; 
import java.awt.Insets; 
import java.awt.Point; 
import java.awt.Graphics; 
import java.util.Hashtable; 
import java.util.Enumeration; 

import com.wigitek.vivigraphics.widget.gui.Button; 
import com.wigitek.vivigraphics.widget.gui.Panel; 
import com.wigitek.vivigraphics.widget.common.Callbackable; 
import com.wigitek.vivigraphics.widget.common.Alignment; 
import com.wigitek.vivigraphics.widget.common.Orientation; 
import com.wigitek.vivigraphics.widget.common.Widget; 
import com.wigitek.vivigraphics.widget.common.TreeNode; 
import com.wigitek.vivigraphics.widget.common.TreeWalker; 
import com.wigitek.vivigraphics.widget.common.PositionableGridConstraints; 
import com.wigitek.vivigraphics.widget.gui.PositionableGridLayout; 

/** 
 * This class extends TreeWalker to implement a Tree viewer 
   for the Tree class. TreeViewer uses GridLayout to layout 
   the leaves in the tree. 
 * Restrictions: 
   (1) Collapsing and expanding of subtree not implemented 
       in this version. 
   (2) BREADTH_FIRST traversal not yet implemented. 
   (3) One Viewer can have view only one Tree. . 
 * @version $Revision$ 
 * @author Originally written by Daniel Dee, 4/11/97 
 * @author Last updated by $Author$, $Date$ 
 * @see Tree 
 * @see TreeWalker 
 */ 
public class TreeViewer extends TreeWalker 
{ 
    /** 
     * Constructs a "unnamed" TreeViewer with VERTICAL orientation. 
     */ 
    public TreeViewer() 
    { 
        this( "Unnamed", Orientation.VERTICAL ); 
    } 

    /** 
     * Constructs a TreeViewer with given name and orientation. 
     * @param name   the specified name 
     * @param orient the specified orientation 
     */ 
    public TreeViewer( String name, Orientation 
orient ) 
    { 
        super(); 
        this.orient = orient; 
        panel = new TreeViewerPanel(name,this); 
        layout = new PositionableGridLayout(); 
        panel.setLayout( layout ); 
        comptable = new Hashtable(); // TRADEOFF 
    } 

    /** 
     * Constructs a fixed-grid TreeViewer with given name and orientation. 
     * @param name   the specified name 
     * @param width  the fixed width 
     * @param height the fixed height 
     * @param orient the specified orientation 
     */ 
    public TreeViewer( String name, int width, int height, Orientation orient ) 
    { 
        super(); 
        this.orient = orient; 
        panel = new TreeViewerPanel(name,this); 
        layout = new PositionableGridLayout(width, height,new 
Insets(2,2,2,2), PositionableGridLayout.NO_ADJUST); 
        panel.setLayout( layout ); 
        comptable = new Hashtable(); // TRADEOFF 
    } 

    /** 
     * Sets the display alignment for the Tree. 
     * @param align the display alignment 
     */ 
    public void setAlignment( Alignment align ) 
    { 
        this.align = align; 
        treeUpdate(); 
    } 

    /** 
     * Sets the display orientation for the Tree. 
     * @param orient the display orientation 
     */ 
    public void setOrientation( Orientation orient ) 
    { 
        this.orient = orient; 
        treeUpdate(); 
    } 

    /** 
     * Sets the layout constraints for the specified component. 
       TreeViewer uses GridLayout for layout 
       and the associated GridConstraints for the GridLayout constraints. 
       Note that this GridLayout is a completely different implementation 
       from AWTís GridLayout. 
     * @param comp        the component 
     * @param constraints the componentís layout constraints 
     */ 
    protected void setConstraints( Component comp, 
PositionableGridConstraints constraints ) 
    { 
        layout.setConstraints(comp, constraints); 
    } 

    /** 
     * Adds a Widget as a child of the specified parent in a Tree. 
     * @param comp    the specified Widget 
     * @param parent  the specified parent 
     */ 
    public void add( Widget comp, Widget parent ) // TRADEOFF 
    { 
setConstraints
( 
(Component)comp, new PositionableGridConstraints(0,0,PositionableGridConstraints.PREFERRED_SIZE,
PositionableGridConstraints.PREFERRED_SIZE,PositionableGridConstraints.NORTHWEST) 
); 
// set the Widget to the origin initially 
        panel.add((Component)comp) // add it to the panel which displays the tree 

        if( comp.isDraggable() ) 
         comp.addCallback( Button.DRAG_CALLBACK, new 
TreeViewerMouseDragCallback(comp,this,panel) ); 

        TreeNode tree = new TreeNode((parent != null) ? (TreeNode)
comptable.get(parent): null, 
                            comp, new TreeViewerData(new Point(0,0)), 
comp.getName(), this); // now actually create the Tree for this Widget 
        comptable.put( comp, tree ); // adds tree to the hashtable with comp as key 
    } 

    /** 
     * Removes the specified Widget from the Tree. 
     * @param comp the specified Widget 
     */ 
    public void remove( Widget comp ) 
    { 
        ((TreeNode)comptable.get(comp)).remove(); 
    } 

   /** 
    * Gets the current grid dimensions of the associated layout 
      (i.e., number of cells in each dimension). 
    */ 
    public Dimension getLayoutDimension() 
    { 
        return layout.getLayoutDimension(); 
    } 

    /** 
     * Returns the preferred dimensions for this layout given the components 
       in the specified parent. 
     * @param parent the parent which needs to be laid out 
     * @see #minimumLayoutSize 
     */ 
    public Dimension getPreferredLayoutSize(Container parent) 
    { 
        Insets insets = parent.insets(); 
        Dimension size = layout.preferredLayoutSize(panel); 
        Dimension gridSize = layout.getGridSize(); 

        // Add extra 1-grid-element margin as preferredSize. 
        size.width += gridSize.width; 
        size.height += gridSize.height; 
        Dimension dim = new Dimension( size.width + insets.left + insets.right, 
                                       size.height + insets.top + insets.bottom ); 
        return dim; 
    } 

    /** 
     * Returns the minimum dimensions needed to layout the components 
       contained in the specified parent. 
     * @param parent the parent which needs to be laid out 
     * @see #preferredLayoutSize 
     */ 
    public Dimension getMinimumLayoutSize(Container parent) 
    { 
        Insets insets = parent.insets(); 
        Dimension size = layout.minimumLayoutSize(panel); 
        Dimension dim = new Dimension( size.width + insets.left + insets.right, 
                                       size.height + insets.top + insets.bottom ); 
        return dim; 
    } 

    /** 
     * Gets the Panel in which this Tree is drawn. 
     */ 
    public Panel getPanel() 
    { 
        return panel; 
    } 

    /** 
     * Clears the Panel and updates all components and branches 
       in the subtree in the TreeViewer. 
     * @param subtree the specified subtree 
     */ 
    public void treeUpdate( TreeNode subtree ) 
    { 
        clearTree(); 
        super.treeUpdate(subtree); 
    } 

    /** 
     * Clears the Panel and updates all components and branches 
       in the last tree drawn in the TreeViewer. 
     */ 
    public void treeUpdate() 
    { 
        clearTree(); 
        super.treeUpdate(); 
    } 

    /** 
     * Clears the Panel the Tree is drawn in, removes the subtree 
       from its parent, and updates the TreeViewer. 
     * @param subtree the subtree to remove 
     */ 
    public void treeRemove( TreeNode subtree ) 
    { 
        clearTree(); 
        walkRemove(subtree); 
    } 

    /** 
     * Clears the Panel the Tree is drawn in. 
     */ 
    protected void clearTree() 
    { 
        Dimension dim = panel.size(); 
        Graphics g = panel.getGraphics(); 
        if( g != null ) 
            g.clearRect(0,0,dim.width,dim.height); 
    } 

    /** 
     * Walks the subtree and remove all components from the 
       Panel. 
     * @param subtree the specified subtree 
     */ 
    protected void walkRemove( TreeNode subtree ) 
    { 
        panel.remove( (Component)subtree.getObject() ); 
        int numberOfChildren = subtree.getNumberOfChildren(); 
        for( int i=0; i < numberOfChildren; i++ ) 
            walkRemove( subtree.getChild(i) ); 
    } 

 /** 
  * Finds the Widget at the x and y pixel position. 
  * @param x the x pixel position 
  * @param y the y pixel position 
  * @return the widget at the x and y pixel position 
  */ 
    public Widget findWidget( int x, int y ) 
    { 
        Enumeration widgets = comptable.keys(); 
        while( widgets.hasMoreElements() ) 
        { 
            Component w = (Component)widgets.nextElement(); 
            Point loc = w.location(); 
            Dimension size = w.size(); 
            if( x > loc.x && x < loc.x + size.width && 
                y > loc.y && y < loc.y + size.height ) 
                return (Widget)w; 
        } 
        return null; 
    } 

 /** 
  * Finds the Widget at the x and y pixel position 
    within a tolerance of e. 
  * @param x the x pixel position 
  * @param y the y pixel position 
  * @param e the tolerance 
  * @return the widget at the x and y pixel position 
  */ 
    public Widget findWidget( int x, int y, int e ) 
    { 
        Enumeration widgets = comptable.keys(); 
        while( widgets.hasMoreElements() ) 
        { 
            Component w = (Component)widgets.nextElement(); 
            Point loc = w.location(); 
            Dimension size = w.size(); 
            if( x > loc.x-e && x < loc.x+size.width+e && 
                y > loc.y-e && y < loc.y+size.height+e ) 
                return (Widget)w; 
        } 
        return null; 
    } 

 /** 
  * Finds the TreeNode that is the root of the subtree 
    located at the x and y pixel position. 
  * @param x the x pixel position 
  * @param y the y pixel position 
  * @return the TreeNode at the x and y pixel position 
  */ 
    public TreeNode findSubtree( int x, int y ) 
    { 
        Enumeration widgets = comptable.keys(); 
        while( widgets.hasMoreElements() ) 
        { 
            Component w = (Component)widgets.nextElement(); 
            Point loc = w.location(); 
            Dimension size = w.size(); 
            if( x > loc.x && x < loc.x + size.width && 
                y > loc.y && y < loc.y + size.height ) 
            { 
                TreeNode subtree = (TreeNode)comptable.get(w); 
                return subtree; 
            } 
        } 
        return null; 
    } 

 /** 
  * Finds the TreeNode that is the root of the subtree 
    located at the x and y pixel position within a tolerance 
    of e. 
  * @param x the x pixel position 
  * @param y the y pixel position 
  * @param e the tolerance 
  * @return the TreeNode at the x and y pixel position 
  */ 
    public TreeNode findSubtree( int x, int y, int e ) 
    { 
        Enumeration widgets = comptable.keys(); 
        while( widgets.hasMoreElements() ) 
        { 
            Component w = (Component)widgets.nextElement(); 
            Point loc = w.location(); 
            Dimension size = w.size(); 
            if( x > loc.x-e && x < loc.x+size.width+e && 
                y > loc.y-e && y < loc.y+size.height+e ) 
            { 
                TreeNode subtree = (TreeNode)comptable.get(w); 
                return subtree; 
            } 
        } 
        return null; 
    } 

 /** 
  * Finds the TreeNode counterpart of the Widget w. 
  * @param w the specified Widget 
  * @return the TreeNode 
  */ 
    public TreeNode findSubtree( Widget w ) 
    { 
        return (TreeNode)comptable.get(w); 
    } 

    /** 
     * Draws all components of the tree in the TreeViewer. 
     * @param tree the specified tree; cannot be null 
     * @param walkType DEPTH_FIRST or BREADTH_FIRST 
     */ 
    protected void walk( TreeNode tree, int walkType ) 
    { 
        width = -1; 
        depth = 0; 

        walkDepth( tree, 0 ); 
        layout.layoutContainer(panel); 
    } 

    /** 
  * Draws the plus sign enclosed in a box to represent 
    the icon that will expand a subtree. 
  * @param g the Graphics context of the TreeViewerPanel 
             on which the tree is being drawn 
  * @param x the x pixel position 
  * @param y the y pixel position 
  */ 
    protected void drawUnfoldIcon( Graphics g, int x, int y ) 
    { 
        g.setColor(Color.white); 
        g.fillRect( x-3, y-3, 6, 6 ); 
        g.setColor(Color.black); 
        g.drawRect( x-3, y-3, 6, 6 ); 
        g.drawLine( x-1, y, x+1, y ); 
        g.drawLine( x, y-1, x, y+1 ); 
    } 

    /** 
  * Draws the minus sign enclosed in a box to represent 
    the icon that will collapse a subtree. 
  * @param g the Graphics context of the TreeViewerPanel 
             on which the tree is being drawn 
  * @param x the x pixel position 
  * @param y the y pixel position 
  */ 
    protected void drawFoldIcon( Graphics g, int x, int y ) 
    { 
        g.setColor(Color.white); 
        g.fillRect( x-3, y-3, 6, 6 ); 
        g.setColor(Color.black); 
        g.drawRect( x-3, y-3, 6, 6 ); 
        g.drawLine( x-1, y, x+1, y ); 
    } 

 /** 
  * Gets the hierarchy level of the subtree rooted at the 
    specified TreeNode. 
  * @param subtree the TreeNode that is the root of the subtree 
                   for which the hierarchy level is to be 
                   determined 
  * @return the hierarchy level of the subtree 
  */ 
    public int getLevel( TreeNode subtree ) 
    { 
        TreeViewerData td = null; 
        if( subtree != null ) 
            td = (TreeViewerData)subtree.getData(); 
        return (td != null) ? td.getLevel() : 0; 
    } 

    /** 
     * Draws all components of the tree in the TreeViewer. 
       Traversal is depth-first. Since the 
       method is used recursively, parameter level 
       allows walkDepth to count the number of levels it has 
       recursed. This method is always called from walk(). 
     * @param tree the specified tree; cannot be null 
     * @param level the recursion level; must be supplied with default 
     */ 
    protected void walkDepth( TreeNode tree, int level ) 
    { 
        TreeNode child; 
Point treePos = (Point)((TreeViewerData)tree.getData()).getPoint(); 
Point foldPos = (Point)((TreeViewerData)tree.getData()).getFoldPoint(); 
        Point firstPos, lastPos; 
        PositionableGridConstraints constraints =
		 layout.lookupConstraints((Component)tree.getObject()); 

        ((TreeViewerData)tree.getData()).setLevel(level); 

        // There is always a one-line spacing between levels. 
        // In other words, level 0 will be displayed on grid line 0, 
        // and level 1 will displayed on grid line 2, and so on. 
        treePos.y = level * 2; 

        // Count the depth of the tree. Always 1-based. 
        if( level+1 > depth ) 
            depth = level+1; 

        // If this subtree has no children, then it is a leaf node. 
        // Do some minor processing, and return immediately. 

        int numberOfChildren = tree.getNumberOfChildren(); 

        if( numberOfChildren == 0 ) 
        { 
            // Same as levels, there is also always a one-line spacing 
            // between adjacent nodes. 
            width += 2; 
            treePos.x = width - 1; 

            // Invert the grid x and y coordiates if HORIZONTAL. 
            // Otherwise, just save the calculated x and y position. 
            if( orient == Orientation.VERTICAL ) 
            { 
                constraints.gridx = treePos.x; 
                constraints.gridy = treePos.y; 
            } 
            else 
            { 
                constraints.gridx = treePos.y; 
                constraints.gridy = treePos.x; 
            } 

            return; 
        } 

        // If not a leaf node, recursively find leaf nodes on al 
        // its branches. 
        for( int i=0; i < numberOfChildren; i++ ) 
        { 
            child = tree.getChild(i); 
            { 
                Component widget = (Component)child.getObject(); 
                widget.show(); 
                walkDepth( child, level+1 ); 
            } 
        } 

        // Calculate the horizontal position of the current node 
        // which should between the leftmost and rightmost of its 
        // children. Invert these values if HORIZONTAL. 
        firstPos = (Point)((TreeViewerData)tree.getChild(0).getData()).getPoint(); 
        lastPos  =
 (Point)((TreeViewerData)tree.getChild(numberOfChildren-1).getData()).getPoint(); 

        { 
            if( align == Alignment.LEFT ) 
                treePos.x = firstPos.x; 
            else if( align == Alignment.RIGHT ) 
                treePos.x = lastPos.x; 
            else // Alignment.CENTER 
                treePos.x = (firstPos.x + lastPos.x) / 2; 
        } 

        if( orient == Orientation.VERTICAL ) 
        { 
            constraints.gridx = treePos.x; 
            constraints.gridy = treePos.y; 
        } 
        else 
        { 
            constraints.gridx = treePos.y; 
            constraints.gridy = treePos.x; 
        } 

        // Draw the branches. 
        Graphics graphics = panel.getGraphics(); 
        if( graphics != null ) 
        { 
            if( orient == Orientation.VERTICAL ) 
            { 
                Point treeCoord = layout.coordinates(treePos.x,treePos.y); 
                Point firstCoord = layout.coordinates(firstPos.x,firstPos.y); 
                Point lastCoord = layout.coordinates(lastPos.x,lastPos.y); 
                // Use objectís size instead of grid size so fold icon will 
                // not be fall underneath the object. 
                Dimension dim = ((Component)tree.getObject()).size(); 
                int x = treeCoord.x + dim.width/2; 
                int y  = (treeCoord.y + dim.height + firstCoord.y) / 2; 
                int xfirst = firstCoord.x + dim.width/2; 
                int xlast  = lastCoord.x + dim.width/2; 
                graphics.drawLine( x, treeCoord.y+layout.gridInsets().top, x, y ); 

                { 
                    graphics.drawLine( xfirst, y, xlast, y ); 
                    graphics.drawLine( xfirst, y, xfirst, firstCoord.y ); 
                    graphics.drawLine( xlast, y, xlast, lastCoord.y ); 
                } 

                for( int i=1; i < numberOfChildren-1; i++ ) 
                { 
                    Point childPos =
					 (Point)((TreeViewerData)tree.getChild(i).getData()).getPoint(); 
                    Point childCoord = layout.coordinates(childPos.x,childPos.y); 
                    x = childCoord.x + dim.width/2; 
                    graphics.drawLine( x, y, x, childCoord.y ); 
                } 
                foldPos.x = treeCoord.x + dim.width/2; 
                foldPos.y = (treeCoord.y + dim.height + firstCoord.y) / 2; 
                 drawFoldIcon( graphics, treeCoord.x + dim.width/2, (treeCoord.y +
				  dim.height + firstCoord.y) / 2 ); // <<< 
            } 
            else 
            { 
                Point treeCoord = layout.coordinates(treePos.y,treePos.x); 
                Point firstCoord = layout.coordinates(firstPos.y,firstPos.x); 
                Point lastCoord = layout.coordinates(lastPos.y,lastPos.x); 
                // Use objectís size instead of grid size so fold icon will 
                // not be fall underneath the object. 
                Dimension dim = ((Component)tree.getObject()).size(); 
                int x = (treeCoord.x + dim.width + firstCoord.x) / 2; 
                int y  = treeCoord.y + dim.height/2; 
                int yfirst = firstCoord.y + dim.height/2; 
                int ylast  = lastCoord.y + dim.height/2; 
                graphics.drawLine( treeCoord.x+layout.gridInsets().left, y, x, y ); 

                { 
                    graphics.drawLine( x, yfirst, x, ylast ); 
                    graphics.drawLine( x, yfirst, firstCoord.x, yfirst ); 
                    graphics.drawLine( x, ylast, lastCoord.x, ylast); 
                } 

                for( int i=1; i < numberOfChildren-1; i++ ) 
                { 
                    Point childPos =
	(Point)((TreeViewerData)tree.getChild(i).getData()).getPoint(); 
	Point childCoord = layout.coordinates(childPos.y,childPos.x); 
                    y = childCoord.y + dim.height/2; 
                    graphics.drawLine( x, y, childCoord.x, y ); 
                } 
                foldPos.x = (treeCoord.x + dim.width + firstCoord.x) / 2; 
                foldPos.y = treeCoord.y + dim.height/2; 
  
                 drawFoldIcon( graphics, (treeCoord.x + dim.width + firstCoord.x) / 2,
				  treeCoord.y + dim.height/2 ); // <<< 
            } 
        } 
    } 
  

    private TreeViewerPanel panel; // Panel on which the Tree is drawn 
    private PositionableGridLayout layout;     // GridLayout style for Panel 
    private int width;             // GridLayout width 
    private int depth;             // Depth of Tree 
    private Orientation orient;    // Orientation of Tree drawn 
    private Alignment align;       // Alignment of Tree drawn 
    private Hashtable comptable;   // Widget-to-Tree hashtable 
} 

/** 
 * Subclass of Panel Widget to force Tree Update. 
 */ 
class TreeViewerPanel extends Panel 
{ 
    /** 
     * Constructs a TreeViewerPanel. 
     * @param name   the name of the TreeViewerPanel 
     * @param viewer the viewer that has to be updated when paint is called 
     */ 
    public TreeViewerPanel( String name, TreeViewer viewer ) 
    { 
        super(name); 
        this.viewer = viewer; 
        addCallback( Panel.DRAG_CALLBACK, dcb = new TreeViewerPanelDragCallbackable(viewer,this) ); 
    } 

    /** 
     * Extends Panel.paint to call treeUpdate() first. 
     * @param g the Graphics context 
     */ 
    public void paint(Graphics g) 
    { 
        viewer.treeUpdate(); 
        super.paint(g); 
    } 
  
    public void setDragMode( boolean dragState ) 
    { 
        dragMode = dragState; 
    } 

    private TreeViewer viewer; 
    private TreeViewerPanelDragCallbackable dcb; 
    private boolean dragMode = false; 
} 

class TreeViewerPanelDragCallbackable extends Callbackable 
{ 
    public TreeViewerPanelDragCallbackable( TreeViewer viewer, TreeViewerPanel panel ) 
    { 
        this.viewer = viewer; 
        this.panel = panel; 
    } 

    public boolean activate( Event evt ) 
    { 
     if( comp == null ) 
      if( (comp = viewer.findWidget( evt.x, evt.y, 5 )) == null ) 
       return true; 
        Point loc = ((Component)comp).location(); 
        Dimension size = ((Component)comp).size(); 
        Graphics g = ((Component)panel).getGraphics(); 

  if( evt.id == Event.MOUSE_UP ) 
        { 
            g.setXORMode(Color.white); 
            if( compImage != null ) 
                g.drawImage(compImage,prevPoint.x-offset.x,prevPoint.y-offset.y,panel); 
            compImage = null; 
            compGraphics = null; 
            panel.setDragMode(false); 



  

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.