Java tutorial
/* * $Id$ * Copyright 2000,2005 wingS development team. * * This file is part of wingS (http://www.j-wings.org). * * wingS is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * Please see COPYING for the complete licence. */ package org.wings; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wings.plaf.TreeCG; import org.wings.tree.SDefaultTreeSelectionModel; import org.wings.tree.STreeCellRenderer; import org.wings.tree.STreeSelectionModel; import javax.swing.event.*; import javax.swing.tree.*; import java.awt.*; import java.util.ArrayList; /** * @author <a href="mailto:haaf@mercatis.de">Armin Haaf</a> * @version $Revision$ */ public class STree extends SComponent implements LowLevelEventListener, Scrollable { /** * Tree selection model. * @see STreeSelectionModel#setSelectionMode(int) * @see TreeSelectionModel#SINGLE_TREE_SELECTION */ public static final int SINGLE_TREE_SELECTION = TreeSelectionModel.SINGLE_TREE_SELECTION; /** * Tree selection model. * @see STreeSelectionModel#setSelectionMode(int) * @see TreeSelectionModel#CONTIGUOUS_TREE_SELECTION */ public static final int CONTIGUOUS_TREE_SELECTION = TreeSelectionModel.CONTIGUOUS_TREE_SELECTION; /** * Tree selection model. * @see STreeSelectionModel#setSelectionMode(int) * @see TreeSelectionModel#DISCONTIGUOUS_TREE_SELECTION */ public static final int DISCONTIGUOUS_TREE_SELECTION = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION; private final transient static Log log = LogFactory.getLog(STree.class); /** * Indent depth in pixels */ private int nodeIndentDepth = 20; /** * Creates and returns a sample TreeModel. Used primarily for beanbuilders. * to show something interesting. * * @return the default TreeModel */ protected static TreeModel getDefaultTreeModel() { DefaultMutableTreeNode root = new DefaultMutableTreeNode("STree"); DefaultMutableTreeNode parent; parent = new DefaultMutableTreeNode("colors"); root.add(parent); parent.add(new DefaultMutableTreeNode("blue")); parent.add(new DefaultMutableTreeNode("violet")); parent.add(new DefaultMutableTreeNode("red")); parent.add(new DefaultMutableTreeNode("yellow")); parent = new DefaultMutableTreeNode("sports"); root.add(parent); parent.add(new DefaultMutableTreeNode("basketball")); parent.add(new DefaultMutableTreeNode("soccer")); parent.add(new DefaultMutableTreeNode("football")); parent.add(new DefaultMutableTreeNode("hockey")); parent = new DefaultMutableTreeNode("food"); root.add(parent); parent.add(new DefaultMutableTreeNode("hot dogs")); parent.add(new DefaultMutableTreeNode("pizza")); parent.add(new DefaultMutableTreeNode("ravioli")); parent.add(new DefaultMutableTreeNode("bananas")); return new DefaultTreeModel(root); } protected TreeModel model; transient protected TreeModelListener treeModelListener; protected STreeCellRenderer renderer; protected STreeSelectionModel selectionModel; /** * store here all delayed expansion events */ private ArrayList delayedExpansionEvents; /** * store here expansion paths that will be processed after procession the * request. */ protected final ArrayList requestedExpansionPaths = new ArrayList(); protected AbstractLayoutCache treeState = new VariableHeightLayoutCache(); /** * Implementation of the {@link Scrollable} interface. */ protected Rectangle viewport; /** @see LowLevelEventListener#isEpochCheckEnabled() */ protected boolean epochCheckEnabled = true; /** * used to forward the selection to the selection Listeners of the tree */ private final TreeSelectionListener forwardSelectionEvent = new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { fireTreeSelectionEvent(e); } }; public STree(TreeModel model) { super(); setModel(model); setRootVisible(true); setSelectionModel(new SDefaultTreeSelectionModel()); } public STree() { this(getDefaultTreeModel()); } public void addTreeSelectionListener(TreeSelectionListener tsl) { addEventListener(TreeSelectionListener.class, tsl); } public void removeTreeSelectionListener(TreeSelectionListener tsl) { removeEventListener(TreeSelectionListener.class, tsl); } protected void fireTreeSelectionEvent(TreeSelectionEvent e) { // Guaranteed to return a non-null array Object[] listeners = getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeSelectionListener.class) { ((TreeSelectionListener) listeners[i + 1]).valueChanged(e); } } reload(); } /** * Adds a listener for <code>TreeWillExpand</code> events. * * @param tel a <code>TreeWillExpandListener</code> that will be notified * when a tree node will be expanded or collapsed (a "negative * expansion") */ public void addTreeWillExpandListener(TreeWillExpandListener tel) { addEventListener(TreeWillExpandListener.class, tel); } /** * Removes a listener for <code>TreeWillExpand</code> events. * * @param tel the <code>TreeWillExpandListener</code> to remove */ public void removeTreeWillExpandListener(TreeWillExpandListener tel) { removeEventListener(TreeWillExpandListener.class, tel); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the <code>path</code> parameter. * * @param path the <code>TreePath</code> indicating the node that was * expanded * @see EventListenerList */ public void fireTreeWillExpand(TreePath path, boolean expand) throws ExpandVetoException { // Guaranteed to return a non-null array Object[] listeners = getListenerList(); TreeExpansionEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeWillExpandListener.class) { // Lazily create the event: if (e == null) e = new TreeExpansionEvent(this, path); if (expand) { ((TreeWillExpandListener) listeners[i + 1]).treeWillExpand(e); } else { ((TreeWillExpandListener) listeners[i + 1]).treeWillCollapse(e); } } } } public void addTreeExpansionListener(TreeExpansionListener tel) { addEventListener(TreeExpansionListener.class, tel); } public void removeTreeExpansionListener(TreeExpansionListener tel) { removeEventListener(TreeExpansionListener.class, tel); } private static class DelayedExpansionEvent { TreeExpansionEvent expansionEvent; boolean expansion; DelayedExpansionEvent(TreeExpansionEvent e, boolean b) { expansionEvent = e; expansion = b; } } protected void addDelayedExpansionEvent(TreeExpansionEvent e, boolean expansion) { if (delayedExpansionEvents == null) { delayedExpansionEvents = new ArrayList(); } delayedExpansionEvents.add(new DelayedExpansionEvent(e, expansion)); } protected void fireDelayedExpansionEvents() { if (delayedExpansionEvents != null && !getSelectionModel().getDelayEvents()) { for (int i = 0; i < delayedExpansionEvents.size(); i++) { DelayedExpansionEvent e = (DelayedExpansionEvent) delayedExpansionEvents.get(i); fireTreeExpansionEvent(e.expansionEvent, e.expansion); } delayedExpansionEvents.clear(); } } /** * 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 * @see EventListenerList */ protected void fireTreeExpanded(TreePath path) { fireTreeExpansionEvent(new TreeExpansionEvent(this, path), true); } protected void fireTreeExpansionEvent(TreeExpansionEvent e, boolean expansion) { if (getSelectionModel().getDelayEvents()) { addDelayedExpansionEvent(e, expansion); } else { // Guaranteed to return a non-null array Object[] listeners = getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeExpansionListener.class) { if (expansion) ((TreeExpansionListener) listeners[i + 1]).treeExpanded(e); else ((TreeExpansionListener) listeners[i + 1]).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 collapsed * @see EventListenerList */ public void fireTreeCollapsed(TreePath path) { fireTreeExpansionEvent(new TreeExpansionEvent(this, path), false); } protected void processRequestedExpansionPaths() { getSelectionModel().setDelayEvents(true); for (int i = 0; i < requestedExpansionPaths.size(); i++) { try { TreePath path = (TreePath) requestedExpansionPaths.get(i); togglePathExpansion(path); } catch (ExpandVetoException ex) { // do not expand... } } requestedExpansionPaths.clear(); getSelectionModel().setDelayEvents(false); } public void fireIntermediateEvents() { processRequestedExpansionPaths(); getSelectionModel().fireDelayedIntermediateEvents(); } public void fireFinalEvents() { super.fireFinalEvents(); fireDelayedExpansionEvents(); getSelectionModel().fireDelayedFinalEvents(); } /** @see LowLevelEventListener#isEpochCheckEnabled() */ public boolean isEpochCheckEnabled() { return epochCheckEnabled; } /** @see LowLevelEventListener#isEpochCheckEnabled() */ public void setEpochCheckEnabled(boolean epochCheckEnabled) { this.epochCheckEnabled = epochCheckEnabled; } public void setRootVisible(boolean rootVisible) { treeState.setRootVisible(rootVisible); } public boolean isRootVisible() { return treeState.isRootVisible(); } public void setModel(TreeModel m) { if (model != null && treeModelListener != null) model.removeTreeModelListener(treeModelListener); model = m; treeState.setModel(m); if (model != null) { if (treeModelListener == null) treeModelListener = createTreeModelListener(); if (treeModelListener != null) model.addTreeModelListener(treeModelListener); // Mark the root as expanded, if it isn't a leaf. if (!model.isLeaf(model.getRoot())) treeState.setExpandedState(new TreePath(model.getRoot()), true); } } public TreeModel getModel() { return model; } public int getRowCount() { return treeState.getRowCount(); } public TreePath getPathForRow(int row) { return treeState.getPathForRow(row); } protected int fillPathForAbsoluteRow(int row, Object node, ArrayList path) { // and check if it is the if (row == 0) { return 0; } // end of if () for (int i = 0; i < model.getChildCount(node); i++) { path.add(model.getChild(node, i)); row = fillPathForAbsoluteRow(row - 1, model.getChild(node, i), path); if (row == 0) { return 0; } // end of if () path.remove(path.size() - 1); } return row; } public TreePath getPathForAbsoluteRow(int row) { // fill path in this array ArrayList path = new ArrayList(10); path.add(model.getRoot()); fillPathForAbsoluteRow(row, model.getRoot(), path); return new TreePath(path.toArray()); } /** * Sets the tree's selection model. When a null value is specified * an empty electionModel is used, which does not allow selections. * * @param selectionModel the TreeSelectionModel to use, or null to * disable selections * @see TreeSelectionModel */ public void setSelectionModel(STreeSelectionModel selectionModel) { if (this.selectionModel != null) this.selectionModel.removeTreeSelectionListener(forwardSelectionEvent); if (selectionModel != null) selectionModel.addTreeSelectionListener(forwardSelectionEvent); if (selectionModel == null) this.selectionModel = SDefaultTreeSelectionModel.NO_SELECTION_MODEL; else this.selectionModel = selectionModel; } /** * 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. * * @see #setSelectionModel */ public STreeSelectionModel getSelectionModel() { return selectionModel; } /** * Returns JTreePath instances representing the path between index0 * and index1 (including index1). * * @param index0 an int specifying a display row, where 0 is the * first row in the display * @param index1 an int specifying a second display row * @return an array of TreePath objects, one for each node between * index0 and index1, inclusive */ protected TreePath[] getPathBetweenRows(int index0, int index1) { int newMinIndex = Math.min(index0, index1); int newMaxIndex = Math.max(index0, index1); TreePath[] selection = new TreePath[newMaxIndex - newMinIndex + 1]; for (int i = newMinIndex; i <= newMaxIndex; i++) { selection[i - newMinIndex] = getPathForRow(i); } return selection; } /** * 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) { 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) { getSelectionModel().setSelectionPaths(paths); } /** * Selects the node at the specified row in the display. * * @param row the row to select, where 0 is the first row in * the display */ public void setSelectionRow(int row) { int[] rows = { row }; setSelectionRows(rows); } /** * Selects the nodes corresponding to each of the specified rows * in the display. * * @param rows an array of ints specifying the rows to select, * where 0 indicates the first row in the display */ public void setSelectionRows(int[] rows) { if (rows == null) return; TreePath paths[] = new TreePath[rows.length]; for (int i = 0; i < rows.length; i++) { paths[i] = getPathForRow(i); } setSelectionPaths(paths); } /** * Adds the node identified by the specified TreePath to the current * selection. If any component of the path isn't visible, it is * made visible. * * @param path the TreePath to add */ public void addSelectionPath(TreePath 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 visible, it is * made visible. * * @param paths an array of TreePath objects that specifies the nodes * to add */ public void addSelectionPaths(TreePath[] paths) { getSelectionModel().addSelectionPaths(paths); } /** * Adds the path at the specified row to the current selection. * * @param row an int specifying the row of the node to add, * where 0 is the first row in the display */ public void addSelectionRow(int row) { int[] rows = { row }; addSelectionRows(rows); } /** * Adds the paths at each of the specified rows to the current selection. * * @param rows an array of ints specifying the rows to add, * where 0 indicates the first row in the display */ public void addSelectionRows(int[] rows) { if (rows != null) { int numRows = rows.length; TreePath[] paths = new TreePath[numRows]; for (int counter = 0; counter < numRows; counter++) paths[counter] = getPathForRow(rows[counter]); addSelectionPaths(paths); } } /** * 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 * @see TreePath#getLastPathComponent */ public Object getLastSelectedPathComponent() { Object obj = null; TreePath selPath = getSelectionModel().getSelectionPath(); if (selPath != null) { obj = selPath.getLastPathComponent(); } return obj; } /** * 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 all of the currently selected rows. * * @return an array of ints that identifies all currently selected rows * where 0 is the first row in the display */ public int[] getSelectionRows() { return getSelectionModel().getSelectionRows(); } /** * Returns the number of nodes selected. * * @return the number of nodes selected */ public int getSelectionCount() { return selectionModel.getSelectionCount(); } /** * Gets the first selected row. * * @return an int designating the first selected row, where 0 is the * first row in the display */ public int getMinSelectionRow() { return getSelectionModel().getMinSelectionRow(); } /** * Gets the last selected row. * * @return an int designating the last selected row, where 0 is the * first row in the display */ public int getMaxSelectionRow() { return getSelectionModel().getMaxSelectionRow(); } /** * Returns the row index of the last node added to the selection. * * @return an int giving the row index of the last node added to the * selection, where 0 is the first row in the display */ public int getLeadSelectionRow() { return getSelectionModel().getLeadSelectionRow(); } /** * 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 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 node identitifed by row is selected. * * @param row an int specifying a display row, where 0 is the first * row in the display * @return true if the node is selected */ public boolean isRowSelected(int row) { return getSelectionModel().isRowSelected(row); } /** * Removes the nodes between index0 and index1, inclusive, from the * selection. * * @param index0 an int specifying a display row, where 0 is the * first row in the display * @param index1 an int specifying a second display row */ public void removeSelectionInterval(int index0, int index1) { TreePath[] paths = getPathBetweenRows(index0, index1); this.getSelectionModel().removeSelectionPaths(paths); } /** * Removes the node identified by the specified path from the current * selection. * * @param path the TreePath identifying a node */ public void removeSelectionPath(TreePath path) { 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) { getSelectionModel().removeSelectionPaths(paths); } /** * Removes the path at the index <code>row</code> from the current * selection. * * @param row the row identifying the node to remove */ public void removeSelectionRow(int row) { int[] rows = { row }; removeSelectionRows(rows); } public void removeSelectionRows(int[] rows) { TreePath[] paths = new TreePath[rows.length]; for (int i = 0; i < rows.length; i++) paths[i] = getPathForRow(rows[i]); removeSelectionPaths(paths); } public int getMaximumExpandedDepth() { int max = 0; for (int i = 0; i < getRowCount(); i++) max = Math.max(max, getPathForRow(i).getPathCount()); return max; } /** * Expand this tree row. * If tree is inside a {@link SScrollPane} try to * adjust pane, so that as much as possible new * nodes are visible. * * @param p the TreePath to expand */ public void expandRow(TreePath p) { treeState.setExpandedState(p, true); /* if ( viewport != null ) { int ccount = model.getChildCount( p.getLastPathComponent() ); if ( ccount + 1 <= viewport.height ) viewport.y += ccount; else viewport.y = treeState.getRowForPath( p ); } */ fireTreeExpanded(p); reload(); } public void expandRow(int row) { expandRow(getPathForRow(row)); } public void collapseRow(TreePath p) { treeState.setExpandedState(p, false); fireTreeCollapsed(p); reload(); } public void collapseRow(int row) { collapseRow(getPathForRow(row)); } public boolean isVisible(TreePath path) { if (path != null) { TreePath parentPath = path.getParentPath(); if (parentPath != null) return isExpanded(parentPath); // Root. return true; } return false; } public boolean isExpanded(TreePath path) { return treeState.isExpanded(path); } protected void togglePathSelection(TreePath path) { if (path != null) { if (isPathSelected(path)) { removeSelectionPath(path); } else { addSelectionPath(path); } } } protected void togglePathExpansion(TreePath path) throws ExpandVetoException { if (path != null) { if (treeState.isExpanded(path)) { fireTreeWillExpand(path, false); collapseRow(path); } else { fireTreeWillExpand(path, true); expandRow(path); } } } /** * This is for plafs only! * With this parameter the tree expands the given node */ public String getExpansionParameter(int row, boolean absolute) { return (absolute ? "j" : "h") + row; } /** * This is for plafs only! * With this parameter the tree selects the given node */ public String getSelectionParameter(int row, boolean absolute) { return (absolute ? "a" : "b") + row; } public void processLowLevelEvent(String action, String[] values) { processKeyEvents(values); getSelectionModel().setDelayEvents(true); for (int i = 0; i < values.length; i++) { if (values[i].length() < 2) continue; // incorrect format int row = Integer.parseInt(values[i].substring(1)); if (row < 0) continue; // row not found... if (values[i].charAt(0) == 'b') { TreePath path = getPathForRow(row); //selection if (path != null) { togglePathSelection(path); } } else if (values[i].charAt(0) == 'h') { TreePath path = getPathForRow(row); //selection if (path != null) { requestedExpansionPaths.add(path); } } else if (values[i].charAt(0) == 'a') { TreePath path = getPathForAbsoluteRow(row); //selection if (path != null) { togglePathSelection(path); } } else if (values[i].charAt(0) == 'j') { TreePath path = getPathForAbsoluteRow(row); //selection if (path != null) { requestedExpansionPaths.add(path); } } } SForm.addArmedComponent(this); getSelectionModel().setDelayEvents(false); } /** * Set the indent depth in pixel between two nodes of a different level. * Note: only positive values apply, negative values are cut off at 0. * @param depth the depth to set */ public void setNodeIndentDepth(int depth) { if (depth < 0) { depth = 0; } nodeIndentDepth = depth; } public int getNodeIndentDepth() { return nodeIndentDepth; } public void setCellRenderer(STreeCellRenderer x) { renderer = x; } public STreeCellRenderer getCellRenderer() { return renderer; } /** * Creates an instance of TreeModelHandler. */ protected TreeModelListener createTreeModelListener() { return new TreeModelHandler(); } /** * Listens to the model and updates the expandedState accordingly * when nodes are removed, or changed. */ protected class TreeModelHandler implements TreeModelListener { public void treeNodesChanged(TreeModelEvent e) { if (e == null) return; treeState.treeNodesChanged(e); reload(); } public void treeNodesInserted(TreeModelEvent e) { if (e == null) return; treeState.treeNodesInserted(e); reload(); } public void treeStructureChanged(TreeModelEvent e) { if (e == null) return; treeState.treeStructureChanged(e); reload(); } public void treeNodesRemoved(TreeModelEvent e) { if (e == null) return; treeState.treeNodesRemoved(e); reload(); } } public void setParent(SContainer p) { super.setParent(p); if (getCellRendererPane() != null) getCellRendererPane().setParent(p); } protected void setParentFrame(SFrame f) { super.setParentFrame(f); if (getCellRendererPane() != null) getCellRendererPane().setParentFrame(f); } // do not initalize with null! private SCellRendererPane cellRendererPane = new SCellRendererPane(); public SCellRendererPane getCellRendererPane() { return cellRendererPane; } /** * Returns the maximum size of this tree. * * @return maximum size */ public Rectangle getScrollableViewportSize() { return new Rectangle(0, 0, 1, getRowCount()); } /* * Setzt den anzuzeigenden Teil */ public void setViewportSize(Rectangle d) { Rectangle oldViewport = viewport; viewport = d; if ((viewport == null && oldViewport != null) || (viewport != null && !viewport.equals(oldViewport))) reload(); } public Rectangle getViewportSize() { return viewport; } public Dimension getPreferredExtent() { return null; } public void setCG(TreeCG cg) { super.setCG(cg); } }