All Downloads are FREE. Search and download functionalities are using the official Maven repository.

echopointng.Tree Maven / Gradle / Ivy

Go to download

Echo2 bundled with Echo2_Extras, Echo2_FileTransfer and echopointing and various improvements/bugfixes

There is a newer version: 2.0.4
Show newest version
package echopointng;

/* 
 * This file is part of the Echo Point Project.  This project is a collection
 * of Components that have extended the Echo Web Application Framework.
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 */

/*
 * The design paradigm and class name used within have been taken directly from
 * the java.swing package has been retro-fitted to work with the NextApp Echo web framework.
 *
 * This file was made part of the EchoPoint project on the 25/07/2002.
 *
 */
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.WeakHashMap;

import nextapp.echo2.app.Component;
import nextapp.echo2.app.Label;
import nextapp.echo2.app.Style;
import nextapp.echo2.app.event.ActionEvent;
import nextapp.echo2.app.event.ActionListener;
import echopointng.tree.DefaultMutableTreeNode;
import echopointng.tree.DefaultTreeCellRenderer;
import echopointng.tree.DefaultTreeIcons;
import echopointng.tree.DefaultTreeModel;
import echopointng.tree.DefaultTreeSelectionModel;
import echopointng.tree.EmptyTreeSelectionModel;
import echopointng.tree.ExpandVetoException;
import echopointng.tree.RowMapper;
import echopointng.tree.TreeCellRenderer;
import echopointng.tree.TreeExpansionEvent;
import echopointng.tree.TreeExpansionListener;
import echopointng.tree.TreeIcons;
import echopointng.tree.TreeModel;
import echopointng.tree.TreeModelEvent;
import echopointng.tree.TreeModelListener;
import echopointng.tree.TreeNode;
import echopointng.tree.TreePath;
import echopointng.tree.TreeSelectionEvent;
import echopointng.tree.TreeSelectionListener;
import echopointng.tree.TreeSelectionModel;
import echopointng.tree.TreeWillExpandListener;

/**
 * The Tree component displays data in a hierarchial manner. The
 * API for this Component has based heavily on the Swing JTree class.
 * 

* A Tree only renders the visible nodes and an event is * generated to expand or collapse nodes as well as when the user clicks on a * node. This allows the application to insert more nodes as necessary or on a * "just in time" basis. *

* Tree also supports node selection via its TreeSelectionModel. *

* The Tree component supports "bubbling up" actions. *

* If the objects placed in the Tree implement TreeNode then an * action command can be identified with each node. When a node is pressed, the * associated action command will be raised with any * ActionListeners of the Tree. If no action command is * associated with a TreeNode then the path to the ancestors will * be followed to look for an action command. If no action command can be found, * then the action command of the tree itself will be used. *

* If you call setNullActionCommandsRaiseEvents(false) then * action events will not be raised for TreeNodes that have null action * commands. It is TRUE by default. *

* * @see echopointng.tree.TreeModel * @see echopointng.tree.DefaultTreeModel * @see echopointng.tree.TreeCellRenderer * @see echopointng.tree.DefaultTreeCellRenderer * @see echopointng.tree.TreeNode * @see echopointng.tree.MutableTreeNode * @see echopointng.tree.DefaultMutableTreeNode */ public class Tree extends AbleComponent { /** * Used as an INPUT name to indicate that a node is to be toggled, ie * expanded or contracted */ public static final String INPUT_TOGGLE = "toggle"; /** * Used as an INPUT name to indicate that a node is to be selected */ public static final String INPUT_SELECT = "select"; public final static String PROPERTY_ACTION_COMMAND = "actionCommand"; public final static String PROPERTY_CELL_RENDERER = "cellRenderer"; public final static String PROPERTY_LINES_DRAWN = "linesDrawn"; public final static String PROPERTY_NULL_ACTION_COMMANDS_RAISE_EVENTS = "nullActionCommandsRaiseEvents"; public final static String PROPERTY_ROOT_VISIBLE = "rootVisible"; public static final String PROPERTY_ROW_HEIGHT = "rowHeight"; public final static String PROPERTY_SELECTION_MODEL = "selectionModel"; public final static String PROPERTY_SHOWS_ROOT_HANDLES = "showsRootHandles"; public final static String PROPERTY_TREE_ICONS = "treeIcons"; public final static String PROPERTY_MODEL = "model"; public final static String PROPERTY_ROOT_AUTO_EXPANDED = "rootAutoExpanded"; public final static String PROPERTY_PARTIAL_UPDATE_SUPPORT = "partialUpdateSupport"; public final static String PROPERTY_CELL_WIDTH_CONTRAINED = "cellWidthContrained"; public final static String PROPERTY_SELECTION_INCLUDES_ICON = "selectionIncludesIcon"; public final static String PROPERTY_SCROLL_INTO_VIEW_USED = "scrollIntoViewUsed"; public static final String NODE_CHANGED_PROPERTY = "nodeChanged"; public static final String MODEL_STRUCTURE_CHANGED_PROPERTY = "modelStructureChanged"; /** * The TreeExpansionHandler is a private handler for * listening to our own expansion events. */ private class TreeExpansionHandler implements Serializable, TreeExpansionListener { /** * @see echopointng.tree.TreeExpansionListener#treeCollapsed(TreeExpansionEvent) */ public void treeCollapsed(TreeExpansionEvent e) { markPathDirty(e.getPath(), false); } /** * @see echopointng.tree.TreeExpansionListener#treeExpanded(TreeExpansionEvent) */ public void treeExpanded(TreeExpansionEvent e) { markPathDirty(e.getPath(), false); lastExpandedPaths.add(e.getPath()); } } /** * Listens to the model and updates the expandedState accordingly when nodes * are removed, or changed. */ protected class TreeModelHandler implements TreeModelListener, Serializable { public void treeNodesChanged(TreeModelEvent e) { markPathsDirty(e.getTreePath(), e.getChildren(), false); } public void treeNodesInserted(TreeModelEvent e) { markPathsDirty(e.getTreePath(), e.getChildren(), true); } public void treeNodesRemoved(TreeModelEvent e) { if (e == null) { return; } TreePath parent = e.getTreePath(); Object[] children = e.getChildren(); if (children == null) { return; } TreePath rPath; Vector toRemove = new Vector(Math.max(1, children.length)); for (int counter = children.length - 1; counter >= 0; counter--) { rPath = parent.pathByAddingChild(children[counter]); if (expandedState.get(rPath) != null) { toRemove.addElement(rPath); } } if (toRemove.size() > 0) { removeDescendantToggledPaths(toRemove.elements()); } TreeModel model = getModel(); if (model == null || model.isLeaf(parent.getLastPathComponent())) { expandedState.remove(parent); } // Don't need to mark the path as dirty - partial render removes all // deleted nodes invalidate(); firePropertyChange(NODE_CHANGED_PROPERTY, null, null); // Forces // partial // redraw } public void treeStructureChanged(TreeModelEvent e) { if (e == null) { return; } TreePath parent = e.getTreePath(); if (parent == null) { return; } if (parent.getPathCount() == 1) { clearToggledPaths(); TreeModel model = getModel(); if (model != null && !model.isLeaf(model.getRoot())) { expandedState.put(parent, Boolean.TRUE); } } else if (expandedState.get(parent) != null) { Vector toRemove = new Vector(1); boolean isExpanded = isExpanded(parent); toRemove.addElement(parent); removeDescendantToggledPaths(toRemove.elements()); if (isExpanded) { TreeModel model = getModel(); if (model == null || model.isLeaf(parent.getLastPathComponent())) collapsePath(parent); else expandedState.put(parent, Boolean.TRUE); } } invalidate(); firePropertyChange(MODEL_STRUCTURE_CHANGED_PROPERTY, null, null); // Forces // full // redraw } } /** * Handles the RowMapper interface for the tree *

*/ protected class TreeRowMapper implements Serializable, RowMapper { /** * @see echopointng.tree.RowMapper#getRowsForPaths(echopointng.tree.TreePath[]) */ public int[] getRowsForPaths(TreePath[] paths) { return Tree.this.getRowsForPaths(paths); } } /** * Handles creating a new TreeSelectionEvent with the Tree as the source and * passing it off to all the listeners. *

*/ protected class TreeSelectionForwarder implements Serializable, TreeSelectionListener { /** * Invoked by the TreeSelectionModel when the selection changes. * * @param e * the TreeSelectionEvent generated by the TreeSelectionModel */ public void valueChanged(TreeSelectionEvent e) { // Forces redraw of selected nodes TreePath[] paths = e.getPaths(); for (int i = 0; i < paths.length; i++) { markPathDirty(paths[i], false); } TreeSelectionEvent newE; newE = (TreeSelectionEvent) e.cloneWithSource(Tree.this); fireValueChanged(newE); } } /* Max number of stacks to keep around. */ private static int TEMP_STACK_SIZE = 15; /* a HashMap of TreeNodes --> Component mappings */ private transient Map componentMap = new WeakHashMap(); /* a bit bucket for components */ // private DevNull devNull; /* Used when setExpandedState is invoked, will be a Stack of Stacks. */ private transient Stack expandedStack; /* Maps from TreePath to Boolean indicating whether a path is expanded. */ private transient Hashtable expandedState; /* Records the last tree path that was expanded */ private transient Collection lastExpandedPaths = new ArrayList(); /* Internal watch for expansion events */ private TreeExpansionHandler expansionForwarder; /* Creates a new event and passed it off the selectionListeners. */ private TreeSelectionForwarder selectionForwarder; private TreeModelListener treeModelListener; /* List of nodes which have changed since the last render */ private ArrayList dirtyPaths = new ArrayList(); /* indicates whether the Tree is valid or not */ private boolean valid; public static final Style DEFAULT_STYLE; static { MutableStyleEx style = new MutableStyleEx(); style.setProperty(PROPERTY_ROOT_VISIBLE, true); style.setProperty(PROPERTY_LINES_DRAWN, true); style.setProperty(PROPERTY_SHOWS_ROOT_HANDLES, true); style.setProperty(PROPERTY_SCROLL_INTO_VIEW_USED, true); style.setProperty(PROPERTY_NULL_ACTION_COMMANDS_RAISE_EVENTS, true); DEFAULT_STYLE = style; } /** * Returns a Tree with a DefaultMutableTreeNode * as its root, which displays the root node. */ public Tree() { this(new DefaultMutableTreeNode("root"), false); } /** * Returns a Tree with the specified TreeNode * as its root, which displays the root node. */ public Tree(TreeNode root) { this(root, false); } /** * Returns a Tree with the specified TreeNode * as its root, which displays the root node and which decides whether a * node is a leaf node in the specified manner. * */ public Tree(TreeNode root, boolean asksAllowsChildren) { this(new DefaultTreeModel(root, asksAllowsChildren)); } /** * Returns an instance of a Tree which displays the root node * and is created using the specified data model. * */ public Tree(TreeModel newModel) { super(); expandedStack = new Stack(); expandedState = new Hashtable(); DefaultTreeSelectionModel tempSelectionModel = new DefaultTreeSelectionModel(); tempSelectionModel.setRowMapper(new TreeRowMapper()); setSelectionModel(tempSelectionModel); expansionForwarder = new TreeExpansionHandler(); addTreeExpansionListener(expansionForwarder); setCellRenderer(new DefaultTreeCellRenderer()); setTreeIcons(new DefaultTreeIcons()); setModel(newModel); } /** * @see nextapp.echo2.app.Component#processInput(java.lang.String, * java.lang.Object) */ public void processInput(String name, Object value) { super.processInput(name, value); if (getModel() == null) { return; } TreePath path = (TreePath) value; if (name.startsWith(INPUT_TOGGLE)) { // and flip it if (isExpanded(path)) { collapsePath(path); } else { expandPath(path); } } else if (name.startsWith(INPUT_SELECT)) { TreeSelectionModel selectionModel = getSelectionModel(); if (selectionModel != null) { if (selectionModel.isPathSelected(path)) { selectionModel.removeSelectionPath(path); } else { selectionModel.addSelectionPath(path); } } Object[] nodes = path.getPath(); if (nodes.length > 0 && nodes[nodes.length - 1] instanceof TreeNode) { TreeNode node = (TreeNode) nodes[nodes.length - 1]; String actionCommand = node.getActionCommand(); while (actionCommand == null) { if (node.getParent() != null) { node = node.getParent(); actionCommand = node.getActionCommand(); } else { break; } } if (actionCommand == null) { actionCommand = getActionCommand(); } ActionEvent e = new ActionEvent(this, actionCommand); fireActionPerformed(e); } } invalidate(); } /** * Adds an ActionListener. * * @param l * The ActionListener to be added. */ public void addActionListener(ActionListener l) { getEventListenerList().addListener(ActionListener.class, l); } /** * Adds the node identified by the specified TreePath to the current * selection. If any component of the path isn't viewable, it is made * viewable. * * @param path * the TreePath to add */ public void addSelectionPath(TreePath path) { makeVisible(path); getSelectionModel().addSelectionPath(path); } /** * Adds each path in the array of paths to the current selection. If any * component of any of the paths isn't viewable, it is made viewable. * * @param paths * an array of TreePath objects that specifies the nodes to add */ public void addSelectionPaths(TreePath[] paths) { if (paths != null) { for (int counter = paths.length - 1; counter >= 0; counter--) makeVisible(paths[counter]); } getSelectionModel().addSelectionPaths(paths); } /** * Adds a listener for TreeExpansion events. * * @param tel * a TreeExpansionListener that will be notified when a tree node * is expanded or collapsed (a "negative expansion") */ public void addTreeExpansionListener(TreeExpansionListener tel) { getEventListenerList().addListener(TreeExpansionListener.class, tel); } /** * Adds a listener for TreeSelection events. * * @param tsl * the TreeSelectionListener that will be notified when a node is * selected or deselected (a "negative selection") */ public void addTreeSelectionListener(TreeSelectionListener tsl) { getEventListenerList().addListener(TreeSelectionListener.class, tsl); if (getEventListenerList().getListenerCount(TreeSelectionListener.class) != 0 && selectionForwarder == null) { selectionForwarder = new TreeSelectionForwarder(); getSelectionModel().addTreeSelectionListener(selectionForwarder); } } /** * Adds a listener for TreeWillExpand events. * * @param tel * a TreeWillExpandListener that will be notified when a tree * node will be expanded or collapsed (a "negative expansion") */ public void addTreeWillExpandListener(TreeWillExpandListener tel) { getEventListenerList().addListener(TreeWillExpandListener.class, tel); } /** * Ensures that all the nodes in the tree are collapsed */ public void collapseAll() { TreeModel model = getModel(); if (model != null) { toggleAllNodes(new TreePath(model.getRoot()), false); } } /** * Ensures that the node identified by the specified path is collapsed and * viewable. * * @param path * the TreePath identifying a node */ public void collapsePath(TreePath path) { setExpandedState(path, false); } /** * Ensures that all the nodes are expanded and viewable. */ public void expandAll() { TreeModel model = getModel(); if (model != null) { toggleAllNodes(new TreePath(model.getRoot()), true); } } /** * Ensures that the node identified by the specified path is expanded and * viewable. * * @param path * the TreePath identifying a node */ public void expandPath(TreePath path) { TreeModel model = getModel(); if (path != null && model != null && !model.isLeaf(path.getLastPathComponent())) { setExpandedState(path, true); } } /** * Notifies all listeners that have registered for this event type. * * @param e * The ActionEvent to send. */ public void fireActionPerformed(ActionEvent e) { EventListener[] listeners = getEventListenerList().getListeners(ActionListener.class); for (int index = 0; index < listeners.length; ++index) { ((ActionListener) listeners[index]).actionPerformed(e); } } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param path * the TreePath indicating the node that was collapsed */ public void fireTreeCollapsed(TreePath path) { Object[] listeners = getEventListenerList().getListeners(TreeExpansionListener.class); TreeExpansionEvent e = null; for (int index = 0; index < listeners.length; ++index) { if (e == null) e = new TreeExpansionEvent(this, path); ((TreeExpansionListener) listeners[index]).treeCollapsed(e); } } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param path * the TreePath indicating the node that was expanded */ public void fireTreeExpanded(TreePath path) { Object[] listeners = getEventListenerList().getListeners(TreeExpansionListener.class); TreeExpansionEvent e = null; for (int index = 0; index < listeners.length; ++index) { if (e == null) e = new TreeExpansionEvent(this, path); ((TreeExpansionListener) listeners[index]).treeExpanded(e); } } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param path * the TreePath indicating the node that was expanded */ public void fireTreeWillCollapse(TreePath path) throws ExpandVetoException { Object[] listeners = getEventListenerList().getListeners(TreeWillExpandListener.class); TreeExpansionEvent e = null; for (int index = 0; index < listeners.length; ++index) { if (e == null) e = new TreeExpansionEvent(this, path); ((TreeWillExpandListener) listeners[index]).treeWillCollapse(e); } } /** * Notify all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param path * the TreePath indicating the node that was expanded */ public void fireTreeWillExpand(TreePath path) throws ExpandVetoException { Object[] listeners = getEventListenerList().getListeners(TreeWillExpandListener.class); TreeExpansionEvent e = null; for (int index = 0; index < listeners.length; ++index) { if (e == null) e = new TreeExpansionEvent(this, path); ((TreeWillExpandListener) listeners[index]).treeWillExpand(e); } } /** * Returns the action command string of the Tree * * @return java.lang.String */ public String getActionCommand() { return (String) getProperty(PROPERTY_ACTION_COMMAND); } /** * Returns the current TreeCellRenderer that is rendering each cell. * * @return the TreeCellRenderer that is rendering each cell * @see echopointng.tree.TreeCellRenderer */ public TreeCellRenderer getCellRenderer() { return (TreeCellRenderer) getProperty(PROPERTY_CELL_RENDERER); } /** * Returns the Component that will be used to render the provided tree node. * If this method returns null, then no Component has been associated with a * specified tree node and the TreeCellRenderer.getTreeCellRendererText() * method will be used to render the Tree cell. *

* This method is primarily for use by the Tree UI peer rendering code. You * should call the "invalidate" and then "validate" methods first before * calling this method to ensure that all child Components are known and are * valid. * * @param treeNode - * the tree node in question * @return - a component for the node or null * * @see TreeCellRenderer#getTreeCellRendererText(Tree, Object, boolean, * boolean, boolean) */ public Component getComponent(Object treeNode) { WeakReference wr = (WeakReference) componentMap.get(treeNode); return wr != null ? (Component) wr.get() : null; } /** * Returns an Enumeration of the descendants of path that are * currently expanded. If path is not currently expanded, * this will return null. If you expand/collapse nodes while iterating over * the returned Enumeration this may not return all the expanded paths, or * may return paths that are no longer expanded. */ public Enumeration getExpandedDescendants(TreePath parent) { if (!isExpanded(parent)) return null; Enumeration toggledPaths = expandedState.keys(); Vector elements = new Vector(); TreePath path; Object value; if (toggledPaths != null) { while (toggledPaths.hasMoreElements()) { path = (TreePath) toggledPaths.nextElement(); value = expandedState.get(path); // Add the path if it is expanded, a descendant of parent, // and it is visible (all parents expanded). if (value != null && ((Boolean) value).booleanValue() && parent.isDescendant(path) && isVisible(path)) { elements.addElement(path); } } } return elements.elements(); } /** * Returns the last path component in the first node of the current * selection. * * @return the last Object in the first selected node's TreePath, or null if * nothing is selected */ public Object getLastSelectedPathComponent() { TreePath selPath = getSelectionModel().getSelectionPath(); if (selPath != null) return selPath.getLastPathComponent(); return null; } /** * Returns the path of the last node added to the selection. * * @return the TreePath of the last node added to the selection. */ public TreePath getLeadSelectionPath() { return getSelectionModel().getLeadSelectionPath(); } /** * Returns the TreeModel that is providing the data. * * @return the TreeModel that is providing the data */ public TreeModel getModel() { return (TreeModel) getProperty(PROPERTY_MODEL); } /** * This will return a collection of TresePath that have expanded within the * Tree. This will be automatically cleared each time a Tree is rendered so * dont rely on it. * * @return a collection of TresePath that have expanded within the Tree. */ public Collection getLastExpandedPaths() { return lastExpandedPaths; } /** * Returns the row that the TreePath path is being displayed * at. If the TreePath in path is not valid it will be set to * -1. */ public int getRowForPath(TreePath path) { Object[] nodes = null; if (getModel() != null) { nodes = path.getPath(); } // // is it being displayed at all if (nodes.length == 0 || !isExpanded(path)) return -1; TreePath rootPath = new TreePath(getModel().getRoot()); Enumeration enumeration = getDescendantToggledPaths(rootPath); Vector pathsVec = orderPathEnumeration(rootPath, enumeration); for (int i = 0; i < pathsVec.size(); i++) { TreePath testPath = (TreePath) pathsVec.get(i); if (testPath.equals(path)) return i; } return -1; } /** * Returns the rows that the TreePath instances in path are * being displayed at. The returned array is an array of the same length as * that passed in, and if one of the TreePaths in path is not * valid its entry in the array should be set to -1. */ public int[] getRowsForPaths(TreePath[] paths) { if (paths == null) return null; int numPaths = paths.length; int[] rows = new int[numPaths]; for (int counter = 0; counter < numPaths; counter++) rows[counter] = getRowForPath(paths[counter]); return rows; } /** * Returns the row height to be used for each row within the Tree. If this * value is 0 or less, then no row height will be used. * * @return int */ public int getRowHeight() { return getProperty(PROPERTY_ROW_HEIGHT, -1); } /** * Returns the number of nodes selected. * * @return the number of nodes selected */ public int getSelectionCount() { return getSelectionModel().getSelectionCount(); } /** * Returns the model for selections. This should always return a non-null * value. If you don't want to allow anything to be selected set the * selection model to null, which forces an empty selection model to be * used. * */ public TreeSelectionModel getSelectionModel() { return (TreeSelectionModel) getProperty(PROPERTY_SELECTION_MODEL); } /** * Returns the path to the first selected node. * * @return the TreePath for the first selected node, or null if nothing is * currently selected */ public TreePath getSelectionPath() { return getSelectionModel().getSelectionPath(); } /** * Returns the paths of all selected values. * * @return an array of TreePath objects indicating the selected nodes, or * null if nothing is currently selected. */ public TreePath[] getSelectionPaths() { return getSelectionModel().getSelectionPaths(); } /** * Returns true if handles for the root nodes are displayed. * */ public boolean getShowsRootHandles() { return getProperty(PROPERTY_SHOWS_ROOT_HANDLES, true); } /** * Returns the TreeIcons being used by the Tree * * @return the TreeIcons being used by the Tree * @see echopointng.tree.TreeIcons */ public TreeIcons getTreeIcons() { return (TreeIcons) getProperty(PROPERTY_TREE_ICONS); } /** * Returns true if the node identified by the path has ever been expanded. * * @param path = * The TreePath in question */ public boolean hasEverBeenExpanded(TreePath path) { return (path != null && expandedState.get(path) != null); } /** * Marks the Tree as needing to be re-rendered. */ public void invalidate() { valid = false; // need this to convince Echo2 that the DOM has changed somehow // firePropertyChange(null, null, null); } /** * Returns true if the value identified by path is currently collapsed, this * will return false if any of the values in path are currently not being * displayed. * * @param path * the TreePath to check * @return true if any of the nodes in the node's path are collapsed, false * if all nodes in the path are expanded */ public boolean isCollapsed(TreePath path) { return !isExpanded(path); } /** * Returns true if the node identified by the path is currently expanded, * * @param path * the TreePath specifying the node to check * @return false if any of the nodes in the node's path are collapsed, true * if all nodes in the path are expanded */ public boolean isExpanded(TreePath path) { if (path == null) return false; // Is this node expanded? Object value = expandedState.get(path); if (value == null || !((Boolean) value).booleanValue()) return false; // It is, make sure its parent is also expanded. TreePath parentPath = path.getParentPath(); if (parentPath != null) return isExpanded(parentPath); return true; } /** * Returns the path for the specified row. If row is not * visible, null is returned. * * @param row * an integer specifying a row * @return the TreePath to the specified node, * null if row < 0 or * row >= getRowCount() */ public TreePath getPathForRow(final int row) { if (row < 0) { return null; } // a simply call back implementation that stops at the given row. TreePathNavigationListener pathNavigationListener = new TreePathNavigationListener() { TreePath targetPath = null; public boolean onTreePath(TreePath path, int pathRow) { if (pathRow == row) { targetPath = path; return false; } return true; } public TreePath getLastEncounteredPath() { return targetPath; } }; // call the method with the path listener to find the last encountered // path findAllVisibleRows(pathNavigationListener); return pathNavigationListener.getLastEncounteredPath(); } /** * Returns the number of rows that are currently being displayed in the * Tree. * * @return the number of rows that are being displayed in the Tree */ public int getRowCount() { int rowCount = 0; if (getModel().getRoot() == null) { return 0; } TreePath rootPath = new TreePath(getModel().getRoot()); if (!isExpanded(rootPath)) { return 1; } rowCount = findAllVisibleRows(null); return rowCount; } /** * TreePathListener is a simply callback interface that is * callde when a TreePath is encountered during a model navigation. */ private interface TreePathNavigationListener { /** * @return false if the navigation is to be stopped at the current * TreePath. */ boolean onTreePath(TreePath path, int pathRow); /** * @return the TreePath that as last encountered during navigation. */ TreePath getLastEncounteredPath(); } /** * This will traverse the Tree in a breath first manner and count all the * visible rows. If the pathNavigationListener object is non null then it * will inform it each time it encounters a new TreePath. *

* If the pathNavigationListener tells it to stop, then -1 is returned as * the rowCount * * @param pathNavigationListener - * an optional callback interface for indicating when a TreePath * has been encountered. * * @return a count of all the visible TreePaths or -1 if it is told to stop */ private int findAllVisibleRows(TreePathNavigationListener pathNavigationListener) { TreeModel model = getModel(); Object rootNode = model.getRoot(); TreePath rootPath = new TreePath(rootNode); int rowCount = 0; if (isRootVisible()) { rowCount++; if (pathNavigationListener != null) { if (!pathNavigationListener.onTreePath(rootPath, rowCount - 1)) { return -1; } } } if (!model.isLeaf(model.getRoot()) && isExpanded(rootPath)) { int childRowCount = findAllVisibleRows(rootNode, rootPath, pathNavigationListener, rowCount); if (childRowCount == -1) { return -1; } rowCount = childRowCount; } return rowCount; } /** * This will navigate down the TreeModel until it is told not to or it finds * all visible nodes. If the pathNavigationListener tells it to stop, then * -1 is returned as the rowCount * * @see Tree#findAllVisibleRows(TreePathNavigationListener) */ private int findAllVisibleRows(Object parentNode, TreePath parentPath, TreePathNavigationListener pathNavigationListener, int currentRowCount) { TreeModel model = getModel(); int rowCount = currentRowCount; int cc = model.getChildCount(parentNode); for (int i = 0; i < cc; i++) { Object childNode = model.getChild(parentNode, i); TreePath childPath = new TreePath(parentPath, childNode); rowCount++; if (pathNavigationListener != null) { if (!pathNavigationListener.onTreePath(childPath, rowCount - 1)) { return -1; } } if (!model.isLeaf(childNode) && isExpanded(childPath)) { int childRowCount = findAllVisibleRows(childNode, childPath, pathNavigationListener, rowCount); if (childRowCount == -1) { return -1; } rowCount = childRowCount; } } return rowCount; } /** * Returns true if the node at the specified display row is currently * expanded. * * @param row * the row to check, where 0 is the first row in the display * @return true if the node is currently expanded, otherwise false */ public boolean isExpanded(int row) { TreePath path = getPathForRow(row); if (path != null) { Boolean value = (Boolean) expandedState.get(path); return (value != null && value.booleanValue()); } return false; } /** * Returns true if the node at the specified display row is collapsed. * * @param row * the row to check, where 0 is the first row in the display * @return true if the node is currently collapsed, otherwise false */ public boolean isCollapsed(int row) { return !isExpanded(row); } /** * Ensures that the node in the specified row is expanded and viewable. *

* If row is < 0 or >=getRowCount this will * have no effect. * * @param row * an integer specifying a display row, where 0 is the first row * in the display */ public void expandRow(int row) { expandPath(getPathForRow(row)); } /** * Ensures that the node in the specified row is collapsed. *

* If row is < 0 or >=getRowCount this will * have no effect. * * @param row * an integer specifying a display row, where 0 is the first row * in the display */ public void collapseRow(int row) { collapsePath(getPathForRow(row)); } /** * Returns true if lines are drawn between tree nodes. * * @return boolean */ public boolean isLinesDrawn() { return getProperty(PROPERTY_LINES_DRAWN, true); } /** * @return true if the last expanded node will be scroll into view */ public boolean isScrollIntoViewUsed() { return getProperty(PROPERTY_SCROLL_INTO_VIEW_USED, true); } /** * Tree has the ability to "scroll" a peer or child into view when a node is * expanded. This provides a more pleasant user experience. If this flag is * ste to true, the Tree will "scroll" the peer of an expanded node into * view and if it has not peer then it will "scroll" the last child of the * expanded node into view. *

* If you child node counts are quite high, you may not want this behaviour * and hence you should set it to false. * * @param newValue * a new boolean flag value */ public void setScrollIntoViewUsed(boolean newValue) { setProperty(PROPERTY_SCROLL_INTO_VIEW_USED, newValue); } /** * Returns TRUE of TreeNode's with 'null' action commands will still raise * events by bubbling up the Tree. * * @return boolean */ public boolean isNullActionCommandsRaiseEvents() { return getProperty(PROPERTY_NULL_ACTION_COMMANDS_RAISE_EVENTS, true); } /** * Returns true if the item identified by the path is currently selected. * * @param path * a TreePath identifying a node * @return true if the node is selected */ public boolean isPathSelected(TreePath path) { return getSelectionModel().isPathSelected(path); } /** * Returns true if the root node of the tree is displayed. * */ public boolean isRootVisible() { return getProperty(PROPERTY_ROOT_VISIBLE, true); } /** * Returns true if the root node of the tree is expanded when the TreeModel * is first set. * */ public boolean isRootAutoExpanded() { return getProperty(PROPERTY_ROOT_AUTO_EXPANDED, true); } /** * Returns true if the selection is currently empty. * * @return true if the selection is currently empty */ public boolean isSelectionEmpty() { return getSelectionModel().isSelectionEmpty(); } /** * Returns true if the Tree cells use as much UI width as possible or false * if the cells try to contrain their UI width. * * @return true if the Tree cells use as much UI width as possible or false * if the cells try to contrain their UI width. */ public boolean isCellWidthConstrained() { return getProperty(PROPERTY_CELL_WIDTH_CONTRAINED, false); } /** * Set to true if the Tree cells use as much UI width as possible or false * if the cells try to contrain their UI width. * * @param newValue - * true if the Tree cells use as much UI width as possible or * false if the cells try to contrain their UI width. */ public void setCellWidthConstrained(boolean newValue) { setProperty(PROPERTY_CELL_WIDTH_CONTRAINED, newValue); } /** * @return true if the selection of a tree node includes the area around the * node icon or false if it does not. */ public boolean isSelectionIncludesIcon() { return getProperty(PROPERTY_SELECTION_INCLUDES_ICON, true); } /** * Set to true if the selection of a tree node includes the area around the * node icon or false if it does not. * * @param newValue - * true if the selection of a tree node includes the area around * the node icon or false if it does not. */ public void setSelectionIncludesIcon(boolean newValue) { setProperty(PROPERTY_SELECTION_INCLUDES_ICON, newValue); } /** * Returns true if the value identified by path is currently viewable, which * means it is either the root or all of its parents are exapnded , * Otherwise, this method returns false. * * @return true if the node is viewable, otherwise false */ public boolean isVisible(TreePath path) { if (path != null) { TreePath parentPath = path.getParentPath(); if (parentPath != null) return isExpanded(parentPath); return true; } return false; } /** * Ensures that the node identified by path is currently viewable. * * @param path * the TreePath to make visible */ public void makeVisible(TreePath path) { if (path != null) { TreePath parentPath = path.getParentPath(); if (parentPath != null) { expandPath(parentPath); } } } /** * Removes an ActionListener. * * @param l * The ActionListener to be removed. */ public void removeActionListener(ActionListener l) { getEventListenerList().removeListener(ActionListener.class, l); } /** * Removes any descendants of the TreePaths in toRemove that * have been expanded. */ protected void removeDescendantToggledPaths(Enumeration toRemove) { if (toRemove != null) { while (toRemove.hasMoreElements()) { Enumeration descendants = getDescendantToggledPaths((TreePath) toRemove.nextElement()); if (descendants != null) { while (descendants.hasMoreElements()) { expandedState.remove(descendants.nextElement()); } } } } } /** * Removes the node identified by the specified path from the current * selection. * * @param path * the TreePath identifying a node */ public void removeSelectionPath(TreePath path) { this.getSelectionModel().removeSelectionPath(path); } /** * Removes the nodes identified by the specified paths from the current * selection. * * @param paths * an array of TreePath objects that specifies the nodes to * remove */ public void removeSelectionPaths(TreePath[] paths) { this.getSelectionModel().removeSelectionPaths(paths); } /** * Removes a listener for TreeExpansion events. * * @param tel * the TreeExpansionListener to remove */ public void removeTreeExpansionListener(TreeExpansionListener tel) { getEventListenerList().removeListener(TreeExpansionListener.class, tel); } /** * Removes a TreeSelection listener. * * @param tsl * the TreeSelectionListener to remove */ public void removeTreeSelectionListener(TreeSelectionListener tsl) { getEventListenerList().removeListener(TreeSelectionListener.class, tsl); if (getEventListenerList().getListenerCount(TreeSelectionListener.class) == 0 && selectionForwarder != null) { getSelectionModel().removeTreeSelectionListener(selectionForwarder); selectionForwarder = null; } } /** * Removes a listener for TreeWillExpand events. * * @param tel * the TreeWillExpandListener to remove */ public void removeTreeWillExpandListener(TreeWillExpandListener tel) { getEventListenerList().removeListener(TreeWillExpandListener.class, tel); } /** * Sets the action command string of the Tree. Note that action commands * bubble up from the TreeNode, via their parents to the * Tree itself. * * @param newActionCommand * String */ public void setActionCommand(String newActionCommand) { setProperty(PROPERTY_ACTION_COMMAND, newActionCommand); } /** * Sets the TreeCellRenderer that will be used to draw each cell. *

* NOTE : Some TreeCellRender implementations such as * DefaultTreeeCellRenderer are implemented as a Component. While this is * perfectly valid, please note that the implementation is not added a a * child of Tree and hence inheritied styles from the component hierarchy do * not apply. Not adding the implementation as a child component allows for * "static" implementations of TreeCellRender that are derived from * Component. * * @param newCellRenderer * the TreeCellRenderer that is to render each cell * @see echopointng.tree.TreeCellRenderer */ public void setCellRenderer(TreeCellRenderer newCellRenderer) { if (newCellRenderer == null) { throw new IllegalArgumentException("The TreeCellRenderer must not be null"); } if (!newCellRenderer.equals(getCellRenderer())) { setProperty(PROPERTY_CELL_RENDERER, newCellRenderer); componentMap.clear(); // must clear this as new Components could // be generated invalidate(); } } /** * @see nextapp.echo2.app.Component#setEnabled(boolean) */ public void setEnabled(boolean newValue) { if (newValue != isEnabled()) { super.setEnabled(newValue); invalidate(); } } /** * Sets whether lines are drawn between tree nodes * * @param newDrawLines * boolean */ public void setLinesDrawn(boolean newDrawLines) { setProperty(PROPERTY_LINES_DRAWN, Boolean.valueOf(newDrawLines)); } /** * If this is set to true, the Tree rendering code is given a hint on * whether it can perform partial updates to speed up visual performance. By * default this is true. Partial update support costs more in terms of * server memory because the old state must be tracked. But it can result in * better visual performance. * * @param newValue - * a true value cause partial updates to be used by the rendering */ public void setPartialUpdateSupport(boolean newValue) { setProperty(PROPERTY_PARTIAL_UPDATE_SUPPORT, Boolean.valueOf(newValue)); } /** * Returns true if the partial update support is currently enabled. *

* If this is set to true, the Tree rendering code is given a hint on * whether it can perform partial updates to speed up visual performance. By * default this is true. Partial update support costs more in terms of * server memory because the old state must be tracked. But it can result in * better visual performance. * * @return true if the partial update support is currently enabled. */ public boolean getPartialUpdateSupport() { return getProperty(PROPERTY_PARTIAL_UPDATE_SUPPORT, true); } /** * Sets the TreeModel that will provide the data. * * @param newTreeModel - * the TreeModel that is to provide the data */ public void setModel(TreeModel newTreeModel) { TreeModel oldModel = getModel(); if (newTreeModel == null || !newTreeModel.equals(oldModel)) { if (oldModel != null && treeModelListener != null) { oldModel.removeTreeModelListener(treeModelListener); } setProperty(PROPERTY_MODEL, newTreeModel); clearToggledPaths(); if (newTreeModel != null) { if (treeModelListener == null) { treeModelListener = createTreeModelListener(); } if (treeModelListener != null) { newTreeModel.addTreeModelListener(treeModelListener); } if (newTreeModel.getRoot() != null) { if (isRootAutoExpanded() && !newTreeModel.isLeaf(newTreeModel.getRoot())) { expandedState.put(new TreePath(newTreeModel.getRoot()), Boolean.TRUE); } } } invalidate(); } } /** * Sets whether TreeNode null action commands will cause an event to be * raised. * * @param newValue * boolean */ public void setNullActionCommandsRaiseEvents(boolean newValue) { setProperty(PROPERTY_NULL_ACTION_COMMANDS_RAISE_EVENTS, newValue); } /** * Determines whether or not the root node from the TreeModel is visible. * */ public void setRootVisible(boolean rootVisible) { if (rootVisible != isRootVisible()) { setProperty(PROPERTY_ROOT_VISIBLE, rootVisible); invalidate(); } } /** * Determines whether or not the root node from the TreeModel is * automatically expanded when the model is set into the Tree. *

* By default it is and hence the 2nd child level of the TreeModel will be * accessed and shown. *

* If this is false, then only the root node will be accessed and displayed * when a TreeModel is first set and the Tree is rendered. * */ public void setRootAutoExpanded(boolean rootExpanded) { if (rootExpanded != isRootAutoExpanded()) { setProperty(PROPERTY_ROOT_AUTO_EXPANDED, rootExpanded); invalidate(); } } /** * Sets the row height to be used for each row within the Tree. If this * value is 0 or less, then no row height will be used. * * @param newRowHeight * int */ public void setRowHeight(int newRowHeight) { setProperty(PROPERTY_ROW_HEIGHT, newRowHeight); } /** * Sets the tree's selection model. When a null value is specified an empty * electionModel is used, which does not allow selections. * */ public void setSelectionModel(TreeSelectionModel newSelectionModel) { if (newSelectionModel == null) { newSelectionModel = EmptyTreeSelectionModel.getInstance(); } TreeSelectionModel oldValue = getSelectionModel(); if (!newSelectionModel.equals(oldValue)) { if (oldValue != null && selectionForwarder != null) { oldValue.removeTreeSelectionListener(selectionForwarder); } setProperty(PROPERTY_SELECTION_MODEL, newSelectionModel); if (selectionForwarder == null) { selectionForwarder = new TreeSelectionForwarder(); } newSelectionModel.addTreeSelectionListener(selectionForwarder); invalidate(); } } /** * Selects the node identified by the specified path. If any component of * the path is hidden (under a collapsed node), it is exposed (made * viewable). * * @param path * the TreePath specifying the node to select */ public void setSelectionPath(TreePath path) { makeVisible(path); getSelectionModel().setSelectionPath(path); } /** * Selects the nodes identified by the specified array of paths. If any * component in any of the paths is hidden (under a collapsed node), it is * exposed (made viewable). * * @param paths * an array of TreePath objects that specifies the nodes to * select */ public void setSelectionPaths(TreePath[] paths) { if (paths != null) { for (int counter = paths.length - 1; counter >= 0; counter--) makeVisible(paths[counter]); } getSelectionModel().setSelectionPaths(paths); } /** * Determines whether the node handles are to be displayed. * */ public void setShowsRootHandles(boolean newValue) { setProperty(PROPERTY_SHOWS_ROOT_HANDLES, newValue); TreeModel model = getModel(); if (model != null) { if (model.getRoot() != null) { expandPath(new TreePath(model.getRoot())); } } } /** * Sets the TreeIcons being used by the Tree * * @param newTreeIcons - * the new icons to use * @see TreeIcons */ public void setTreeIcons(TreeIcons newTreeIcons) { if (newTreeIcons == null) { throw new IllegalArgumentException("Non null TreeIcons required"); } if (!newTreeIcons.equals(getTreeIcons())) { setProperty(PROPERTY_TREE_ICONS, newTreeIcons); invalidate(); } } /** * @see nextapp.echo2.app.Component#setVisible(boolean) */ public void setVisible(boolean newValue) { if (newValue != isVisible()) { super.setVisible(newValue); invalidate(); } } /** * Toggles all nodes as expanded or collapsed depending on expand * starting from parentPath. */ public void toggleAllNodes(TreePath parentPath, boolean expand) { Object parent = parentPath.getLastPathComponent(); toggleAllNodesImpl(parent, parentPath, expand); } /* * And implementation that keeps track of the TreePaths so we dont have to * call getPathToRoot() on the model. */ private void toggleAllNodesImpl(Object parent, TreePath parentPath, boolean expand) { TreeModel model = getModel(); if (model == null) { return; } if (!model.isLeaf(parent)) { int cc = model.getChildCount(parent); for (int i = 0; i < cc; i++) { Object node = model.getChild(parent, i); TreePath childPath = new TreePath(parentPath, node); if (!model.isLeaf(node)) { toggleAllNodesImpl(node, childPath, expand); } else { // must be a tip of the trunk so expand/collapse it setExpandedState(childPath, expand); } } // If we are collapsing all then we need to collapse each branch // node rather than just the leaves if (!expand) { setExpandedState(parentPath, false); } } } /** * Creates and returns an instance of TreeModelListener. The returned object * is responsible for updating the expanded state when the TreeModel * changes. */ protected TreeModelListener createTreeModelListener() { return new TreeModelHandler(); } /** * Clears the cache of toggled tree paths. This does NOT send out any * TreeExpansionListener events. */ protected void clearToggledPaths() { if (expandedState != null) expandedState.clear(); } /** * Notify all listeners that have registered interest for notification on * this event type. * * @param e * the TreeSelectionEvent generated by the TreeSelectionModel * when a node is selected or deselected */ protected void fireValueChanged(TreeSelectionEvent e) { Object[] listeners = getEventListenerList().getListeners(TreeSelectionListener.class); for (int i = 0; i < listeners.length; i++) { ((TreeSelectionListener) listeners[i]).valueChanged(e); } } /** * Returns an Enumeration of TreePaths that have been expanded that are * descendants of parent. */ protected Enumeration getDescendantToggledPaths(TreePath parent) { if (parent == null) return null; Vector descendants = new Vector(); Enumeration nodes = expandedState.keys(); TreePath path; while (nodes.hasMoreElements()) { path = (TreePath) nodes.nextElement(); if (parent.isDescendant(path)) descendants.addElement(path); } return descendants.elements(); } /** * Sets the expanded state of the path. If state is true, all * parents of path as well as path are marked * as expanded. *

* If state is false, all parents of path are * marked EXPANDED, but path itself is marked collapsed. *

* This will fail if a TreeWillExpandListener vetos it. */ protected void setExpandedState(TreePath path, boolean state) { if (path != null) { // Make sure all parents of path are expanded. Stack stack; TreePath parentPath = path.getParentPath(); if (expandedStack.size() == 0) { stack = new Stack(); } else { stack = (Stack) expandedStack.pop(); } try { while (parentPath != null) { if (isExpanded(parentPath)) { parentPath = null; } else { stack.push(parentPath); parentPath = parentPath.getParentPath(); } } for (int counter = stack.size() - 1; counter >= 0; counter--) { parentPath = (TreePath) stack.pop(); if (!isExpanded(parentPath)) { try { fireTreeWillExpand(parentPath); } catch (ExpandVetoException eve) { return; } expandedState.put(parentPath, Boolean.TRUE); fireTreeExpanded(parentPath); } } } finally { if (expandedStack.size() < TEMP_STACK_SIZE) { stack.removeAllElements(); expandedStack.push(stack); } } if (!state) { // collapse last path. Object cValue = expandedState.get(path); if (cValue != null && ((Boolean) cValue).booleanValue()) { try { fireTreeWillCollapse(path); } catch (ExpandVetoException eve) { return; } expandedState.put(path, Boolean.FALSE); fireTreeCollapsed(path); } } else { // Expand last path. Object cValue = expandedState.get(path); if (cValue == null || !((Boolean) cValue).booleanValue()) { try { fireTreeWillExpand(path); } catch (ExpandVetoException eve) { return; } expandedState.put(path, Boolean.TRUE); fireTreeExpanded(path); } } } } /** * Returns true if the hash table contains the nominated path or equivalent. */ private boolean orderContainsTreePath(Hashtable hash, TreePath path) { for (Enumeration enumeration = hash.elements(); enumeration.hasMoreElements();) { TreePath testPath = (TreePath) enumeration.nextElement(); if (testPath.equals(path)) return true; } return false; } /** * Orders an enumeration of paths for use within the RowMapper. It makes a * breadth first descent of the tree and finds each node in the Enumeration * and its it in an Vector, ordered from the top down starting from parent * path. */ private Vector orderPathEnumeration(TreePath parentPath, Enumeration enumeration) { Hashtable unsortedSet = new Hashtable(); Vector sortedSet = new Vector(); if (getModel() == null) return sortedSet; for (; enumeration.hasMoreElements();) { TreePath path = (TreePath) enumeration.nextElement(); unsortedSet.put(path.toString(), path); } // down throught the tree please if (orderContainsTreePath(unsortedSet, parentPath)) sortedSet.add(parentPath); orderPathSet(parentPath, unsortedSet, sortedSet); return sortedSet; } /** * Recursive function to travel down the tree and find tree paths within the * unsorted set and put them in breathfirst order with the sorted set. */ private void orderPathSet(TreePath parentPath, Hashtable unsortedSet, Vector sortedSet) { // now run down the whole tree, starting at parent, and // add any elements we find as we find them. Object node = parentPath.getLastPathComponent(); if (!getModel().isLeaf(node) && isExpanded(parentPath)) { int cc = getModel().getChildCount(node); for (int i = 0; i < cc; i++) { Object child = getModel().getChild(node, i); TreePath targetPath = parentPath.pathByAddingChild(child); if (orderContainsTreePath(unsortedSet, targetPath)) sortedSet.add(targetPath); if (!getModel().isLeaf(child)) { // recurse orderPathSet(targetPath, unsortedSet, sortedSet); } } } } /** * Checks the given node to see if it contains a Component and adds it as a * child of the Tree. If the cell renderer returns text, then the component * call will not be made. This ensures that the TreeCellrenderer contract is * kept. */ private void doComponentCheck(TreePath path, TreeModel model, Map referenceMap) { Object node = path.getLastPathComponent(); // Do not get a new component every time if it has already been created, // unless the path is dirty Component cellComponent = getComponent(node); if (cellComponent != null) { if (dirtyPaths.contains(path)) { // Remove Component if being used to render this node componentMap.remove(node); remove(cellComponent); } else { referenceMap.put(cellComponent, cellComponent); return; } } boolean isExpanded = isExpanded(path); boolean isSelected = isPathSelected(path); boolean isLeaf = model.isLeaf(node); Label l = getCellRenderer().getTreeCellRendererText(this, node, isSelected, isExpanded, isLeaf); if (l == null) { cellComponent = getCellRenderer().getTreeCellRendererComponent(this, node, isSelected, isExpanded, isLeaf); if (cellComponent != null) { if (!this.isAncestorOf(cellComponent)) { this.add(cellComponent); } componentMap.put(node, new WeakReference(cellComponent)); if (referenceMap != null) { referenceMap.put(cellComponent, cellComponent); } } } } /** * Recursive call to validate a TreePath. All reference components are * placed in the componentMap key by the Tree node and all referenced * components are also placed in the referenceMap keyed by the component. * * The referenceMap may be null in which case we dont use it. */ private void doPathValidation(TreePath path, Map referenceMap) { if (path == null) return; TreeModel model = getModel(); // // check if the node has a component that needs to placed in the // hierarchy // doComponentCheck(path, model, referenceMap); // // find out if the current node is NOT a leaf and hence // we can go down to it. boolean isExpanded = isExpanded(path); Object node = path.getLastPathComponent(); if (!model.isLeaf(node) && isExpanded) { int cc = model.getChildCount(node); for (int i = 0; i < cc; i++) { Object nodeChild = model.getChild(node, i); TreePath childPath = new TreePath(path, nodeChild); doComponentCheck(childPath, model, referenceMap); // // if we have a server side tree, then we dont want to invoke // the cell // renderer any more than we have to. Therefore we only go down // the // tree path if the path is visible and hence child components // might be // needed. We alway descend for a client side tree // isExpanded = isExpanded(childPath); if (isExpanded) { doPathValidation(childPath, referenceMap); } } } } /** * Called just before the tree is rendered. We run through the tree nodes * and try and find any Components that may be rendered by the tree. These * will be the Components that are returned by the * TreeCellRenderer#getTreeCellRendererComponent(..) method. *

* We then add them as children of the tree (if they are not already * ancestors) so that a component rendering peer is created properly for * each cell. * */ public void validate() { super.validate(); if (!valid) { valid = true; TreeModel model = getModel(); if (model == null) { return; } Object root = model.getRoot(); if (root == null) { return; } TreeCellRenderer renderer = getCellRenderer(); if (renderer == null) { throw new IllegalStateException("The Tree has no TreeCellRenderer"); } HashMap referenceMap = new HashMap(); // // validate the tree path // TreePath path = new TreePath(root); doPathValidation(path, referenceMap); // // if we have child components that are no longer referenced in the // model then we need to get rid of them. We use the referenceMap so // that can find components keyed by themselves. // // componentMap contains WeakReferences to components, keyed by // Nodes. // If the components are no longer in the referenceMap, remove them // from the componentMap Iterator iter = componentMap.values().iterator(); while (iter.hasNext()) { WeakReference wr = (WeakReference) iter.next(); Component component = (Component) wr.get(); if (!referenceMap.containsKey(component)) { iter.remove(); this.remove(component); } } } } /** * @return the collection of pending changed paths (needed for partial * updates when rendering) */ public Collection getDirtyPaths() { return dirtyPaths; } /** * Marks an array of nodes as being dirty - ie requiring re-rendering. * Should be called whenever the state of a node is changed or a node is * moved within the tree. * * @param parentPath - * current path to the parent of the nodes * @param nodes - * array of nodes to be marked dirty * @param markChildren - * whether to mark child nodes as dirty too (true when moving a * node) */ private void markPathsDirty(TreePath parentPath, Object[] nodes, boolean markChildren) { for (int i = nodes.length - 1; i >= 0; i--) { TreePath path = parentPath.pathByAddingChild(nodes[i]); markPathDirty(path, markChildren); } } /** * Registers a node as being dirty - ie requiring re-rendering. Should be * called whenever the state of a node is changed or a node is moved within * the tree. * * @param path - * current path to the node * @param markChildren - * whether to mark child nodes as dirty too (true when moving a * node) */ private void markPathDirty(TreePath path, boolean markChildren) { invalidate(); if (!dirtyPaths.contains(path)) { dirtyPaths.add(path); firePropertyChange(NODE_CHANGED_PROPERTY, null, null); // Forces // partial // redraw } Object node = path.getLastPathComponent(); // Recursively mark child paths as dirty if requested TreeModel model = getModel(); if (markChildren && !model.isLeaf(node)) { // && isExpanded(path) int cc = model.getChildCount(node); for (int i = 0; i < cc; i++) { Object nodeChild = model.getChild(node, i); markPathDirty(new TreePath(path, nodeChild), true); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy