0001: /*
0002: * Copyright 2000,2005 wingS development team.
0003: *
0004: * This file is part of wingS (http://wingsframework.org).
0005: *
0006: * wingS is free software; you can redistribute it and/or modify
0007: * it under the terms of the GNU Lesser General Public License
0008: * as published by the Free Software Foundation; either version 2.1
0009: * of the License, or (at your option) any later version.
0010: *
0011: * Please see COPYING for the complete licence.
0012: */
0013: package org.wings;
0014:
0015: import java.awt.Rectangle;
0016: import java.util.ArrayList;
0017: import java.util.List;
0018:
0019: import javax.swing.event.EventListenerList;
0020: import javax.swing.event.TreeExpansionEvent;
0021: import javax.swing.event.TreeExpansionListener;
0022: import javax.swing.event.TreeModelEvent;
0023: import javax.swing.event.TreeModelListener;
0024: import javax.swing.event.TreeSelectionEvent;
0025: import javax.swing.event.TreeSelectionListener;
0026: import javax.swing.event.TreeWillExpandListener;
0027: import javax.swing.tree.AbstractLayoutCache;
0028: import javax.swing.tree.DefaultMutableTreeNode;
0029: import javax.swing.tree.DefaultTreeModel;
0030: import javax.swing.tree.ExpandVetoException;
0031: import javax.swing.tree.TreeModel;
0032: import javax.swing.tree.TreePath;
0033: import javax.swing.tree.TreeSelectionModel;
0034: import javax.swing.tree.VariableHeightLayoutCache;
0035:
0036: import org.wings.event.SMouseEvent;
0037: import org.wings.event.SMouseListener;
0038: import org.wings.event.SViewportChangeEvent;
0039: import org.wings.event.SViewportChangeListener;
0040: import org.wings.plaf.TreeCG;
0041: import org.wings.tree.SDefaultTreeSelectionModel;
0042: import org.wings.tree.STreeCellRenderer;
0043: import org.wings.tree.STreeSelectionModel;
0044:
0045: /**
0046: * Swing-like tree widget.
0047: *
0048: * @author <a href="mailto:haaf@mercatis.de">Armin Haaf</a>
0049: */
0050: public class STree extends SComponent implements Scrollable,
0051: LowLevelEventListener {
0052: /**
0053: * Tree selection model.
0054: * @see STreeSelectionModel#setSelectionMode(int)
0055: * @see TreeSelectionModel#SINGLE_TREE_SELECTION
0056: */
0057: public static final int SINGLE_TREE_SELECTION = TreeSelectionModel.SINGLE_TREE_SELECTION;
0058:
0059: /**
0060: * Tree selection model.
0061: * @see STreeSelectionModel#setSelectionMode(int)
0062: * @see TreeSelectionModel#CONTIGUOUS_TREE_SELECTION
0063: */
0064: public static final int CONTIGUOUS_TREE_SELECTION = TreeSelectionModel.CONTIGUOUS_TREE_SELECTION;
0065:
0066: /**
0067: * Tree selection model.
0068: * @see STreeSelectionModel#setSelectionMode(int)
0069: * @see TreeSelectionModel#DISCONTIGUOUS_TREE_SELECTION
0070: */
0071: public static final int DISCONTIGUOUS_TREE_SELECTION = TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
0072:
0073: /**
0074: * Indent depth in pixels
0075: */
0076: private int nodeIndentDepth = 20;
0077: private String[] lowLevelEvents;
0078:
0079: /**
0080: * Creates and returns a sample TreeModel. Used primarily for beanbuilders.
0081: * to show something interesting.
0082: *
0083: * @return the default TreeModel
0084: */
0085: protected static TreeModel getDefaultTreeModel() {
0086: DefaultMutableTreeNode root = new DefaultMutableTreeNode(
0087: "STree");
0088: DefaultMutableTreeNode parent;
0089:
0090: parent = new DefaultMutableTreeNode("colors");
0091: root.add(parent);
0092: parent.add(new DefaultMutableTreeNode("blue"));
0093: parent.add(new DefaultMutableTreeNode("violet"));
0094: parent.add(new DefaultMutableTreeNode("red"));
0095: parent.add(new DefaultMutableTreeNode("yellow"));
0096:
0097: parent = new DefaultMutableTreeNode("sports");
0098: root.add(parent);
0099: parent.add(new DefaultMutableTreeNode("basketball"));
0100: parent.add(new DefaultMutableTreeNode("soccer"));
0101: parent.add(new DefaultMutableTreeNode("football"));
0102: parent.add(new DefaultMutableTreeNode("hockey"));
0103:
0104: parent = new DefaultMutableTreeNode("food");
0105: root.add(parent);
0106: parent.add(new DefaultMutableTreeNode("hot dogs"));
0107: parent.add(new DefaultMutableTreeNode("pizza"));
0108: parent.add(new DefaultMutableTreeNode("ravioli"));
0109: parent.add(new DefaultMutableTreeNode("bananas"));
0110: return new DefaultTreeModel(root);
0111: }
0112:
0113: protected TreeModel model;
0114:
0115: transient protected TreeModelListener treeModelListener;
0116:
0117: protected STreeCellRenderer renderer;
0118:
0119: protected STreeSelectionModel selectionModel;
0120:
0121: /**
0122: * store here all delayed expansion events
0123: */
0124: private ArrayList delayedExpansionEvents;
0125:
0126: /**
0127: * store here expansion paths that will be processed after procession the
0128: * request.
0129: */
0130: protected final ArrayList requestedExpansionPaths = new ArrayList();
0131:
0132: protected transient AbstractLayoutCache treeState = new VariableHeightLayoutCache();
0133:
0134: /**
0135: * Implementation of the {@link Scrollable} interface.
0136: */
0137: protected Rectangle viewport;
0138:
0139: /** @see LowLevelEventListener#isEpochCheckEnabled() */
0140: protected boolean epochCheckEnabled = true;
0141:
0142: /**
0143: * used to forward selection events to selection listeners of the tree
0144: */
0145: private final TreeSelectionListener fwdSelectionEvents = new TreeSelectionListener() {
0146:
0147: public void valueChanged(TreeSelectionEvent e) {
0148: fireTreeSelectionEvent(e);
0149:
0150: if (isUpdatePossible()
0151: && STree.class.isAssignableFrom(STree.this
0152: .getClass())) {
0153: TreePath[] affectedPaths = e.getPaths();
0154: List deselectedRows = new ArrayList();
0155: List selectedRows = new ArrayList();
0156: for (int i = 0; i < affectedPaths.length; ++i) {
0157: int row = treeState.getRowForPath(affectedPaths[i]);
0158: if (row == -1)
0159: continue;
0160: int visibleRow = row;
0161: if (getViewportSize() != null) {
0162: visibleRow = row - getViewportSize().y;
0163: if (visibleRow < 0
0164: || visibleRow >= getViewportSize().height)
0165: continue;
0166: }
0167: if (e.isAddedPath(affectedPaths[i])) {
0168: selectedRows.add(new Integer(visibleRow));
0169: } else {
0170: deselectedRows.add(new Integer(visibleRow));
0171: }
0172: }
0173: update(((TreeCG) getCG()).getSelectionUpdate(
0174: STree.this , deselectedRows, selectedRows));
0175: } else {
0176: reload();
0177: }
0178: }
0179: };
0180:
0181: public STree(TreeModel model) {
0182: super ();
0183: setModel(model);
0184: setRootVisible(true);
0185: setSelectionModel(new SDefaultTreeSelectionModel());
0186: }
0187:
0188: public STree() {
0189: this (getDefaultTreeModel());
0190: }
0191:
0192: public void addTreeSelectionListener(TreeSelectionListener tsl) {
0193: addEventListener(TreeSelectionListener.class, tsl);
0194: }
0195:
0196: public void removeTreeSelectionListener(TreeSelectionListener tsl) {
0197: removeEventListener(TreeSelectionListener.class, tsl);
0198: }
0199:
0200: protected void fireTreeSelectionEvent(TreeSelectionEvent e) {
0201: // Guaranteed to return a non-null array
0202: Object[] listeners = getListenerList();
0203: // Process the listeners last to first, notifying
0204: // those that are interested in this event
0205: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0206: if (listeners[i] == TreeSelectionListener.class) {
0207: ((TreeSelectionListener) listeners[i + 1])
0208: .valueChanged(e);
0209: }
0210: }
0211: }
0212:
0213: /**
0214: * Adds a listener for <code>TreeWillExpand</code> events.
0215: *
0216: * @param tel a <code>TreeWillExpandListener</code> that will be notified
0217: * when a tree node will be expanded or collapsed (a "negative
0218: * expansion")
0219: */
0220: public void addTreeWillExpandListener(TreeWillExpandListener tel) {
0221: addEventListener(TreeWillExpandListener.class, tel);
0222: }
0223:
0224: /**
0225: * Removes a listener for <code>TreeWillExpand</code> events.
0226: *
0227: * @param tel the <code>TreeWillExpandListener</code> to remove
0228: */
0229: public void removeTreeWillExpandListener(TreeWillExpandListener tel) {
0230: removeEventListener(TreeWillExpandListener.class, tel);
0231: }
0232:
0233: /**
0234: * Notifies all listeners that have registered interest for
0235: * notification on this event type. The event instance
0236: * is lazily created using the <code>path</code> parameter.
0237: *
0238: * @param path the <code>TreePath</code> indicating the node that was
0239: * expanded
0240: * @see EventListenerList
0241: */
0242: public void fireTreeWillExpand(TreePath path, boolean expand)
0243: throws ExpandVetoException {
0244:
0245: // Guaranteed to return a non-null array
0246: Object[] listeners = getListenerList();
0247: TreeExpansionEvent e = null;
0248: // Process the listeners last to first, notifying
0249: // those that are interested in this event
0250: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0251: if (listeners[i] == TreeWillExpandListener.class) {
0252: // Lazily create the event:
0253: if (e == null)
0254: e = new TreeExpansionEvent(this , path);
0255:
0256: if (expand) {
0257: ((TreeWillExpandListener) listeners[i + 1])
0258: .treeWillExpand(e);
0259: } else {
0260: ((TreeWillExpandListener) listeners[i + 1])
0261: .treeWillCollapse(e);
0262: }
0263: }
0264: }
0265: }
0266:
0267: public void addTreeExpansionListener(TreeExpansionListener tel) {
0268: addEventListener(TreeExpansionListener.class, tel);
0269: }
0270:
0271: public void removeTreeExpansionListener(TreeExpansionListener tel) {
0272: removeEventListener(TreeExpansionListener.class, tel);
0273: }
0274:
0275: private static class DelayedExpansionEvent {
0276: TreeExpansionEvent expansionEvent;
0277: boolean expansion;
0278:
0279: DelayedExpansionEvent(TreeExpansionEvent e, boolean b) {
0280: expansionEvent = e;
0281: expansion = b;
0282: }
0283:
0284: }
0285:
0286: protected void addDelayedExpansionEvent(TreeExpansionEvent e,
0287: boolean expansion) {
0288: if (delayedExpansionEvents == null) {
0289: delayedExpansionEvents = new ArrayList();
0290: }
0291:
0292: delayedExpansionEvents.add(new DelayedExpansionEvent(e,
0293: expansion));
0294: }
0295:
0296: protected void fireDelayedExpansionEvents() {
0297: if (delayedExpansionEvents != null
0298: && !getSelectionModel().getDelayEvents()) {
0299: for (int i = 0; i < delayedExpansionEvents.size(); i++) {
0300: DelayedExpansionEvent e = (DelayedExpansionEvent) delayedExpansionEvents
0301: .get(i);
0302:
0303: fireTreeExpansionEvent(e.expansionEvent, e.expansion);
0304: }
0305: delayedExpansionEvents.clear();
0306: }
0307: }
0308:
0309: /**
0310: * Notify all listeners that have registered interest for
0311: * notification on this event type. The event instance
0312: * is lazily created using the parameters passed into
0313: * the fire method.
0314: *
0315: * @param path the TreePath indicating the node that was expanded
0316: * @see EventListenerList
0317: */
0318: public void fireTreeExpanded(TreePath path) {
0319: fireTreeExpansionEvent(new TreeExpansionEvent(this , path), true);
0320: }
0321:
0322: protected void fireTreeExpansionEvent(TreeExpansionEvent e,
0323: boolean expansion) {
0324: if (getSelectionModel().getDelayEvents()) {
0325: addDelayedExpansionEvent(e, expansion);
0326: } else {
0327: // Guaranteed to return a non-null array
0328: Object[] listeners = getListenerList();
0329: // Process the listeners last to first, notifying
0330: // those that are interested in this event
0331: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0332: if (listeners[i] == TreeExpansionListener.class) {
0333: if (expansion)
0334: ((TreeExpansionListener) listeners[i + 1])
0335: .treeExpanded(e);
0336: else
0337: ((TreeExpansionListener) listeners[i + 1])
0338: .treeCollapsed(e);
0339: }
0340: }
0341: fireViewportChanged(false);
0342: }
0343: }
0344:
0345: /**
0346: * Notify all listeners that have registered interest for
0347: * notification on this event type. The event instance
0348: * is lazily created using the parameters passed into
0349: * the fire method.
0350: *
0351: * @param path the TreePath indicating the node that was collapsed
0352: * @see EventListenerList
0353: */
0354: public void fireTreeCollapsed(TreePath path) {
0355: fireTreeExpansionEvent(new TreeExpansionEvent(this , path),
0356: false);
0357: }
0358:
0359: /**
0360: * Adds the specified mouse listener to receive mouse events from
0361: * this component.
0362: * If l is null, no exception is thrown and no action is performed.
0363: *
0364: * @param l the component listener.
0365: * @see org.wings.event.SMouseEvent
0366: * @see org.wings.event.SMouseListener
0367: * @see org.wings.STable#removeMouseListener
0368: */
0369: public final void addMouseListener(SMouseListener l) {
0370: addEventListener(SMouseListener.class, l);
0371: }
0372:
0373: /**
0374: * Removes the specified mouse listener so that it no longer
0375: * receives mouse events from this component. This method performs
0376: * no function, nor does it throw an exception, if the listener
0377: * specified by the argument was not previously added to this component.
0378: * If l is null, no exception is thrown and no action is performed.
0379: *
0380: * @param l the component listener.
0381: * @see org.wings.event.SMouseEvent
0382: * @see org.wings.event.SMouseListener
0383: * @see org.wings.STable#addMouseListener
0384: */
0385: public final void removeMouseListener(SMouseListener l) {
0386: removeEventListener(SMouseListener.class, l);
0387: }
0388:
0389: /**
0390: * Reports a mouse click event.
0391: *
0392: * @param event report this event to all listeners
0393: * @see org.wings.event.SMouseListener
0394: */
0395: protected void fireMouseClickedEvent(SMouseEvent event) {
0396: Object[] listeners = getListenerList();
0397: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0398: if (listeners[i] == SMouseListener.class) {
0399: ((SMouseListener) listeners[i + 1]).mouseClicked(event);
0400: }
0401: }
0402: }
0403:
0404: protected void processRequestedExpansionPaths() {
0405: getSelectionModel().setDelayEvents(true);
0406:
0407: for (int i = 0; i < requestedExpansionPaths.size(); i++) {
0408: try {
0409: TreePath path = (TreePath) requestedExpansionPaths
0410: .get(i);
0411: togglePathExpansion(path);
0412: } catch (ExpandVetoException ex) {
0413: // do not expand...
0414: }
0415: }
0416: requestedExpansionPaths.clear();
0417: getSelectionModel().setDelayEvents(false);
0418: }
0419:
0420: public void fireIntermediateEvents() {
0421: getSelectionModel().setDelayEvents(true);
0422: for (int i = 0; i < lowLevelEvents.length; i++) {
0423: String value = lowLevelEvents[i];
0424: if (value.length() < 2)
0425: continue; // incorrect format
0426:
0427: SPoint point = new SPoint(value.substring(1));
0428: int row = getRowForLocation(point);
0429: if (row < 0)
0430: continue; // row not found...
0431:
0432: switch (value.charAt(0)) {
0433: case 'b':
0434: SMouseEvent event = new SMouseEvent(this , 0, point);
0435: fireMouseClickedEvent(event);
0436: if (event.isConsumed())
0437: continue;
0438:
0439: TreePath path = getPathForRow(row);
0440: if (path != null) {
0441: togglePathSelection(path);
0442: }
0443: break;
0444: case 'a':
0445: event = new SMouseEvent(this , 0, point);
0446: fireMouseClickedEvent(event);
0447: if (event.isConsumed())
0448: continue;
0449:
0450: path = getPathForAbsoluteRow(row);
0451: if (path != null) {
0452: togglePathSelection(path);
0453: }
0454: break;
0455: case 'h':
0456: path = getPathForRow(row);
0457: if (path != null) {
0458: requestedExpansionPaths.add(path);
0459: }
0460: break;
0461: case 'j':
0462: path = getPathForAbsoluteRow(row);
0463: //selection
0464: if (path != null) {
0465: requestedExpansionPaths.add(path);
0466: }
0467: break;
0468: }
0469: }
0470: getSelectionModel().setDelayEvents(false);
0471:
0472: processRequestedExpansionPaths();
0473: getSelectionModel().fireDelayedIntermediateEvents();
0474: }
0475:
0476: /**
0477: * Returns the table row for the passed <code>SPoint</code>
0478: * instance received via {@link #addMouseListener(org.wings.event.SMouseListener)}.
0479: * @param point The pointed retuned by the mouse event.
0480: * @return The row index
0481: */
0482: public int rowAtPoint(SPoint point) {
0483: return getRowForLocation(point);
0484: }
0485:
0486: /**
0487: * Returns the tree row for the passed <code>SPoint</code>.
0488: * instance received via {@link #addMouseListener(org.wings.event.SMouseListener)}.
0489: * @param point The pointed returned by the mouse event.
0490: * @return The tree row index
0491: */
0492: public int getRowForLocation(SPoint point) {
0493: return Integer.parseInt(point.getCoordinates());
0494: }
0495:
0496: public void fireFinalEvents() {
0497: super .fireFinalEvents();
0498: fireDelayedExpansionEvents();
0499: getSelectionModel().fireDelayedFinalEvents();
0500: }
0501:
0502: /** @see LowLevelEventListener#isEpochCheckEnabled() */
0503: public boolean isEpochCheckEnabled() {
0504: return epochCheckEnabled;
0505: }
0506:
0507: /** @see LowLevelEventListener#isEpochCheckEnabled() */
0508: public void setEpochCheckEnabled(boolean epochCheckEnabled) {
0509: this .epochCheckEnabled = epochCheckEnabled;
0510: }
0511:
0512: public void setRootVisible(boolean rootVisible) {
0513: if (isRootVisible() != rootVisible) {
0514: treeState.setRootVisible(rootVisible);
0515: fireViewportChanged(false);
0516: reload();
0517: }
0518: }
0519:
0520: public boolean isRootVisible() {
0521: return treeState.isRootVisible();
0522: }
0523:
0524: public void setModel(TreeModel m) {
0525: if (model != null && treeModelListener != null)
0526: model.removeTreeModelListener(treeModelListener);
0527: model = m;
0528: treeState.setModel(m);
0529:
0530: if (model != null) {
0531: if (treeModelListener == null)
0532: treeModelListener = createTreeModelListener();
0533:
0534: if (treeModelListener != null)
0535: model.addTreeModelListener(treeModelListener);
0536:
0537: // Mark the root as expanded, if it isn't a leaf.
0538: if (!model.isLeaf(model.getRoot()))
0539: treeState.setExpandedState(
0540: new TreePath(model.getRoot()), true);
0541:
0542: fireViewportChanged(false);
0543: reload();
0544: }
0545: }
0546:
0547: public TreeModel getModel() {
0548: return model;
0549: }
0550:
0551: public int getRowCount() {
0552: return treeState.getRowCount();
0553: }
0554:
0555: public TreePath getPathForRow(int row) {
0556: return treeState.getPathForRow(row);
0557: }
0558:
0559: public int getRowForPath(TreePath path) {
0560: return treeState.getRowForPath(path);
0561: }
0562:
0563: protected int fillPathForAbsoluteRow(int row, Object node,
0564: ArrayList path) {
0565: // and check if it is the
0566: if (row == 0) {
0567: return 0;
0568: } // end of if ()
0569:
0570: for (int i = 0; i < model.getChildCount(node); i++) {
0571: path.add(model.getChild(node, i));
0572: row = fillPathForAbsoluteRow(row - 1, model.getChild(node,
0573: i), path);
0574: if (row == 0) {
0575: return 0;
0576: } // end of if ()
0577: path.remove(path.size() - 1);
0578: }
0579: return row;
0580: }
0581:
0582: public TreePath getPathForAbsoluteRow(int row) {
0583: // fill path in this array
0584: ArrayList path = new ArrayList(10);
0585:
0586: path.add(model.getRoot());
0587: fillPathForAbsoluteRow(row, model.getRoot(), path);
0588:
0589: return new TreePath(path.toArray());
0590: }
0591:
0592: /**
0593: * Sets the tree's selection model. When a null value is specified
0594: * an empty electionModel is used, which does not allow selections.
0595: *
0596: * @param selectionModel the TreeSelectionModel to use, or null to
0597: * disable selections
0598: * @see TreeSelectionModel
0599: */
0600: public void setSelectionModel(STreeSelectionModel selectionModel) {
0601: if (this .selectionModel != null)
0602: this .selectionModel
0603: .removeTreeSelectionListener(fwdSelectionEvents);
0604:
0605: if (selectionModel != null)
0606: selectionModel.addTreeSelectionListener(fwdSelectionEvents);
0607:
0608: if (selectionModel == null)
0609: this .selectionModel = SDefaultTreeSelectionModel.NO_SELECTION_MODEL;
0610: else
0611: this .selectionModel = selectionModel;
0612: }
0613:
0614: /**
0615: * Returns the model for selections. This should always return a
0616: * non-null value. If you don't want to allow anything to be selected
0617: * set the selection model to null, which forces an empty
0618: * selection model to be used.
0619: *
0620: * @see #setSelectionModel
0621: */
0622: public STreeSelectionModel getSelectionModel() {
0623: return selectionModel;
0624: }
0625:
0626: /**
0627: * Returns JTreePath instances representing the path between index0
0628: * and index1 (including index1).
0629: *
0630: * @param index0 an int specifying a display row, where 0 is the
0631: * first row in the display
0632: * @param index1 an int specifying a second display row
0633: * @return an array of TreePath objects, one for each node between
0634: * index0 and index1, inclusive
0635: */
0636: protected TreePath[] getPathBetweenRows(int index0, int index1) {
0637: int newMinIndex = Math.min(index0, index1);
0638: int newMaxIndex = Math.max(index0, index1);
0639:
0640: TreePath[] selection = new TreePath[newMaxIndex - newMinIndex
0641: + 1];
0642:
0643: for (int i = newMinIndex; i <= newMaxIndex; i++) {
0644: selection[i - newMinIndex] = getPathForRow(i);
0645: }
0646:
0647: return selection;
0648: }
0649:
0650: /**
0651: * Selects the node identified by the specified path. If any
0652: * component of the path is hidden (under a collapsed node), it is
0653: * exposed (made viewable).
0654: *
0655: * @param path the TreePath specifying the node to select
0656: */
0657: public void setSelectionPath(TreePath path) {
0658: getSelectionModel().setSelectionPath(path);
0659: }
0660:
0661: /**
0662: * Selects the nodes identified by the specified array of paths.
0663: * If any component in any of the paths is hidden (under a collapsed
0664: * node), it is exposed (made viewable).
0665: *
0666: * @param paths an array of TreePath objects that specifies the nodes
0667: * to select
0668: */
0669: public void setSelectionPaths(TreePath[] paths) {
0670: getSelectionModel().setSelectionPaths(paths);
0671: }
0672:
0673: /**
0674: * Selects the node at the specified row in the display.
0675: *
0676: * @param row the row to select, where 0 is the first row in
0677: * the display
0678: */
0679: public void setSelectionRow(int row) {
0680: int[] rows = { row };
0681: setSelectionRows(rows);
0682: }
0683:
0684: /**
0685: * Selects the nodes corresponding to each of the specified rows
0686: * in the display.
0687: *
0688: * @param rows an array of ints specifying the rows to select,
0689: * where 0 indicates the first row in the display
0690: */
0691: public void setSelectionRows(int[] rows) {
0692: if (rows == null)
0693: return;
0694:
0695: TreePath paths[] = new TreePath[rows.length];
0696: for (int i = 0; i < rows.length; i++) {
0697: paths[i] = getPathForRow(rows[i]);
0698: }
0699:
0700: setSelectionPaths(paths);
0701: }
0702:
0703: /**
0704: * Adds the node identified by the specified TreePath to the current
0705: * selection. If any component of the path isn't visible, it is
0706: * made visible.
0707: *
0708: * @param path the TreePath to add
0709: */
0710: public void addSelectionPath(TreePath path) {
0711: getSelectionModel().addSelectionPath(path);
0712: }
0713:
0714: /**
0715: * Adds each path in the array of paths to the current selection. If
0716: * any component of any of the paths isn't visible, it is
0717: * made visible.
0718: *
0719: * @param paths an array of TreePath objects that specifies the nodes
0720: * to add
0721: */
0722: public void addSelectionPaths(TreePath[] paths) {
0723: getSelectionModel().addSelectionPaths(paths);
0724: }
0725:
0726: /**
0727: * Adds the path at the specified row to the current selection.
0728: *
0729: * @param row an int specifying the row of the node to add,
0730: * where 0 is the first row in the display
0731: */
0732: public void addSelectionRow(int row) {
0733: int[] rows = { row };
0734: addSelectionRows(rows);
0735: }
0736:
0737: /**
0738: * Adds the paths at each of the specified rows to the current selection.
0739: *
0740: * @param rows an array of ints specifying the rows to add,
0741: * where 0 indicates the first row in the display
0742: */
0743: public void addSelectionRows(int[] rows) {
0744: if (rows != null) {
0745: int numRows = rows.length;
0746: TreePath[] paths = new TreePath[numRows];
0747:
0748: for (int counter = 0; counter < numRows; counter++)
0749: paths[counter] = getPathForRow(rows[counter]);
0750: addSelectionPaths(paths);
0751: }
0752: }
0753:
0754: /**
0755: * Returns the last path component in the first node of the current
0756: * selection.
0757: *
0758: * @return the last Object in the first selected node's TreePath,
0759: * or null if nothing is selected
0760: * @see TreePath#getLastPathComponent
0761: */
0762: public Object getLastSelectedPathComponent() {
0763: Object obj = null;
0764: TreePath selPath = getSelectionModel().getSelectionPath();
0765: if (selPath != null) {
0766: obj = selPath.getLastPathComponent();
0767: }
0768: return obj;
0769: }
0770:
0771: /**
0772: * Returns the path to the first selected node.
0773: *
0774: * @return the TreePath for the first selected node, or null if
0775: * nothing is currently selected
0776: */
0777: public TreePath getSelectionPath() {
0778: return getSelectionModel().getSelectionPath();
0779: }
0780:
0781: /**
0782: * Returns the paths of all selected values.
0783: *
0784: * @return an array of TreePath objects indicating the selected
0785: * nodes, or null if nothing is currently selected.
0786: */
0787: public TreePath[] getSelectionPaths() {
0788: return getSelectionModel().getSelectionPaths();
0789: }
0790:
0791: /**
0792: * Returns all of the currently selected rows.
0793: *
0794: * @return an array of ints that identifies all currently selected rows
0795: * where 0 is the first row in the display
0796: */
0797: public int[] getSelectionRows() {
0798: return getSelectionModel().getSelectionRows();
0799: }
0800:
0801: /**
0802: * Returns the number of nodes selected.
0803: *
0804: * @return the number of nodes selected
0805: */
0806: public int getSelectionCount() {
0807: return selectionModel.getSelectionCount();
0808: }
0809:
0810: /**
0811: * Gets the first selected row.
0812: *
0813: * @return an int designating the first selected row, where 0 is the
0814: * first row in the display
0815: */
0816: public int getMinSelectionRow() {
0817: return getSelectionModel().getMinSelectionRow();
0818: }
0819:
0820: /**
0821: * Gets the last selected row.
0822: *
0823: * @return an int designating the last selected row, where 0 is the
0824: * first row in the display
0825: */
0826: public int getMaxSelectionRow() {
0827: return getSelectionModel().getMaxSelectionRow();
0828: }
0829:
0830: /**
0831: * Returns the row index of the last node added to the selection.
0832: *
0833: * @return an int giving the row index of the last node added to the
0834: * selection, where 0 is the first row in the display
0835: */
0836: public int getLeadSelectionRow() {
0837: return getSelectionModel().getLeadSelectionRow();
0838: }
0839:
0840: /**
0841: * Returns the path of the last node added to the selection.
0842: *
0843: * @return the TreePath of the last node added to the selection.
0844: */
0845: public TreePath getLeadSelectionPath() {
0846: return getSelectionModel().getLeadSelectionPath();
0847: }
0848:
0849: /**
0850: * Returns true if the item identified by the path is currently selected.
0851: *
0852: * @param path a TreePath identifying a node
0853: * @return true if the node is selected
0854: */
0855: public boolean isPathSelected(TreePath path) {
0856: return getSelectionModel().isPathSelected(path);
0857: }
0858:
0859: /**
0860: * Returns true if the node identitifed by row is selected.
0861: *
0862: * @param row an int specifying a display row, where 0 is the first
0863: * row in the display
0864: * @return true if the node is selected
0865: */
0866: public boolean isRowSelected(int row) {
0867: return getSelectionModel().isRowSelected(row);
0868: }
0869:
0870: /**
0871: * Removes the nodes between index0 and index1, inclusive, from the
0872: * selection.
0873: *
0874: * @param index0 an int specifying a display row, where 0 is the
0875: * first row in the display
0876: * @param index1 an int specifying a second display row
0877: */
0878: public void removeSelectionInterval(int index0, int index1) {
0879: TreePath[] paths = getPathBetweenRows(index0, index1);
0880: this .getSelectionModel().removeSelectionPaths(paths);
0881: }
0882:
0883: /**
0884: * Removes the node identified by the specified path from the current
0885: * selection.
0886: *
0887: * @param path the TreePath identifying a node
0888: */
0889: public void removeSelectionPath(TreePath path) {
0890: getSelectionModel().removeSelectionPath(path);
0891: }
0892:
0893: /**
0894: * Removes the nodes identified by the specified paths from the
0895: * current selection.
0896: *
0897: * @param paths an array of TreePath objects that specifies the nodes
0898: * to remove
0899: */
0900: public void removeSelectionPaths(TreePath[] paths) {
0901: getSelectionModel().removeSelectionPaths(paths);
0902: }
0903:
0904: /**
0905: * Removes the path at the index <code>row</code> from the current
0906: * selection.
0907: *
0908: * @param row the row identifying the node to remove
0909: */
0910: public void removeSelectionRow(int row) {
0911: int[] rows = { row };
0912: removeSelectionRows(rows);
0913: }
0914:
0915: public void removeSelectionRows(int[] rows) {
0916: TreePath[] paths = new TreePath[rows.length];
0917: for (int i = 0; i < rows.length; i++)
0918: paths[i] = getPathForRow(rows[i]);
0919: removeSelectionPaths(paths);
0920: }
0921:
0922: public int getMaximumExpandedDepth() {
0923: int max = 0;
0924: for (int i = 0; i < getRowCount(); i++)
0925: max = Math.max(max, getPathForRow(i).getPathCount());
0926: return max;
0927: }
0928:
0929: /**
0930: * Expand this tree row.
0931: * If tree is inside a {@link SScrollPane} try to
0932: * adjust pane, so that as much as possible new
0933: * nodes are visible.
0934: * @param p the TreePath to expand
0935: * @deprecated This method is deprecated and should not be used because
0936: * expandPath(TreePath) is the proper method with the same functionality.
0937: */
0938: @Deprecated
0939: public void expandRow(TreePath p) {
0940: expandPath(p);
0941: }
0942:
0943: /**
0944: * Expand this tree row.
0945: * If tree is inside a {@link SScrollPane} try to
0946: * adjust pane, so that as much as possible new
0947: * nodes are visible.
0948: * @param p the TreePath to expand
0949: */
0950: public void expandPath(TreePath p) {
0951: treeState.setExpandedState(p, true);
0952:
0953: if (getViewportSize() != null) {
0954: Rectangle area = new Rectangle(getViewportSize());
0955: area.y = treeState.getRowForPath(p);
0956: area.height = model.getChildCount(p.getLastPathComponent()) + 1;
0957: scrollRectToVisible(area);
0958: }
0959:
0960: fireTreeExpanded(p);
0961: reload();
0962: }
0963:
0964: public void expandRow(int row) {
0965: expandPath(getPathForRow(row));
0966: }
0967:
0968: /**
0969: * Collapse this tree row.
0970: * If tree is inside a {@link SScrollPane} try to
0971: * adjust pane, so that as much as possible new
0972: * nodes are visible.
0973: * @param p the TreePath to expand
0974: * @deprecated This method is deprecated and should not be used because
0975: * collapsePath(TreePath) is the proper method with the same functionality.
0976: */
0977: @Deprecated
0978: public void collapseRow(TreePath p) {
0979: collapsePath(p);
0980: }
0981:
0982: public void collapsePath(TreePath p) {
0983: treeState.setExpandedState(p, false);
0984:
0985: fireTreeCollapsed(p);
0986: reload();
0987: }
0988:
0989: public void collapseRow(int row) {
0990: collapseRow(getPathForRow(row));
0991: }
0992:
0993: public boolean isVisible(TreePath path) {
0994: if (path != null) {
0995: TreePath parentPath = path.getParentPath();
0996:
0997: if (parentPath != null)
0998: return isExpanded(parentPath);
0999:
1000: // Root.
1001: return true;
1002: }
1003:
1004: return false;
1005: }
1006:
1007: public boolean isExpanded(TreePath path) {
1008: return treeState.isExpanded(path);
1009: }
1010:
1011: protected void togglePathSelection(TreePath path) {
1012: if (path != null) {
1013: if (isPathSelected(path)) {
1014: removeSelectionPath(path);
1015: } else {
1016: addSelectionPath(path);
1017: }
1018: }
1019: }
1020:
1021: protected void togglePathExpansion(TreePath path)
1022: throws ExpandVetoException {
1023: if (path != null) {
1024: if (treeState.isExpanded(path)) {
1025: fireTreeWillExpand(path, false);
1026: collapseRow(path);
1027: } else {
1028: fireTreeWillExpand(path, true);
1029: expandRow(path);
1030: }
1031: }
1032: }
1033:
1034: /**
1035: * This is for plafs only!
1036: * With this parameter the tree expands the given node
1037: */
1038: public String getExpansionParameter(int row, boolean absolute) {
1039: return (absolute ? "j" : "h") + row;
1040: }
1041:
1042: /**
1043: * This is for plafs only!
1044: * With this parameter the tree selects the given node
1045: */
1046: public String getSelectionParameter(int row, boolean absolute) {
1047: return (absolute ? "a" : "b") + row;
1048: }
1049:
1050: public void processLowLevelEvent(String action, String[] values) {
1051: processKeyEvents(values);
1052: if (action.endsWith("_keystroke"))
1053: return;
1054:
1055: this .lowLevelEvents = values;
1056: SForm.addArmedComponent(this );
1057: }
1058:
1059: /**
1060: * Set the indent depth in pixel between two nodes of a different level.
1061: * Note: only positive values apply, negative values are cut off at 0.
1062: * @param depth the depth to set
1063: */
1064: public void setNodeIndentDepth(int depth) {
1065: if (depth < 0) {
1066: depth = 0;
1067: }
1068: if (nodeIndentDepth != depth) {
1069: nodeIndentDepth = depth;
1070: reload();
1071: }
1072: }
1073:
1074: public int getNodeIndentDepth() {
1075: return nodeIndentDepth;
1076: }
1077:
1078: public void setCellRenderer(STreeCellRenderer x) {
1079: renderer = x;
1080: }
1081:
1082: public STreeCellRenderer getCellRenderer() {
1083: return renderer;
1084: }
1085:
1086: /**
1087: * Creates an instance of TreeModelHandler.
1088: */
1089: protected TreeModelListener createTreeModelListener() {
1090: return new TreeModelHandler();
1091: }
1092:
1093: /**
1094: * Listens to the model and updates the expandedState accordingly
1095: * when nodes are removed, or changed.
1096: */
1097: protected class TreeModelHandler implements TreeModelListener {
1098: public void treeNodesChanged(TreeModelEvent e) {
1099: if (e == null)
1100: return;
1101: treeState.treeNodesChanged(e);
1102: reload();
1103: }
1104:
1105: public void treeNodesInserted(TreeModelEvent e) {
1106: if (e == null)
1107: return;
1108: treeState.treeNodesInserted(e);
1109: fireViewportChanged(false);
1110: reload();
1111: }
1112:
1113: public void treeStructureChanged(TreeModelEvent e) {
1114: if (e == null)
1115: return;
1116: treeState.treeStructureChanged(e);
1117: fireViewportChanged(false);
1118: reload();
1119: }
1120:
1121: public void treeNodesRemoved(TreeModelEvent e) {
1122: if (e == null)
1123: return;
1124: treeState.treeNodesRemoved(e);
1125: fireViewportChanged(false);
1126: reload();
1127: }
1128: }
1129:
1130: public void setParent(SContainer p) {
1131: super .setParent(p);
1132: if (getCellRendererPane() != null)
1133: getCellRendererPane().setParent(p);
1134: }
1135:
1136: protected void setParentFrame(SFrame f) {
1137: super .setParentFrame(f);
1138: if (getCellRendererPane() != null)
1139: getCellRendererPane().setParentFrame(f);
1140: }
1141:
1142: // do not initalize with null!
1143: private SCellRendererPane cellRendererPane = new SCellRendererPane();
1144:
1145: public SCellRendererPane getCellRendererPane() {
1146: return cellRendererPane;
1147: }
1148:
1149: public void setCG(TreeCG cg) {
1150: super .setCG(cg);
1151: }
1152:
1153: /**
1154: * The size of the component in respect to scrollable units.
1155: */
1156: public Rectangle getScrollableViewportSize() {
1157: return new Rectangle(0, 0, 1, getRowCount());
1158: }
1159:
1160: /**
1161: * Returns the actual visible part of a scrollable.
1162: */
1163: public Rectangle getViewportSize() {
1164: return viewport;
1165: }
1166:
1167: /**
1168: * Sets the actual visible part of a scrollable.
1169: */
1170: public void setViewportSize(Rectangle newViewport) {
1171: Rectangle oldViewport = viewport;
1172: viewport = newViewport;
1173:
1174: if (isDifferent(oldViewport, newViewport)) {
1175: if (oldViewport == null || newViewport == null) {
1176: fireViewportChanged(true);
1177: fireViewportChanged(false);
1178: } else {
1179: if (newViewport.x != oldViewport.x
1180: || newViewport.width != oldViewport.width) {
1181: fireViewportChanged(true);
1182: }
1183: if (newViewport.y != oldViewport.y
1184: || newViewport.height != oldViewport.height) {
1185: fireViewportChanged(false);
1186: }
1187: }
1188: reload();
1189: }
1190: }
1191:
1192: /**
1193: * Adds the given <code>SViewportChangeListener</code> to the scrollable.
1194: *
1195: * @param l the listener to be added
1196: */
1197: public void addViewportChangeListener(SViewportChangeListener l) {
1198: addEventListener(SViewportChangeListener.class, l);
1199: }
1200:
1201: /**
1202: * Removes the given <code>SViewportChangeListener</code> from the scrollable.
1203: *
1204: * @param l the listener to be removed
1205: */
1206: public void removeViewportChangeListener(SViewportChangeListener l) {
1207: removeEventListener(SViewportChangeListener.class, l);
1208: }
1209:
1210: /**
1211: * Notifies all listeners that have registered interest for notification
1212: * on changes to this scrollable's viewport in the specified direction.
1213: *
1214: * @see EventListenerList
1215: */
1216: protected void fireViewportChanged(boolean horizontal) {
1217: Object[] listeners = getListenerList();
1218: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1219: if (listeners[i] == SViewportChangeListener.class) {
1220: SViewportChangeEvent event = new SViewportChangeEvent(
1221: this , horizontal);
1222: ((SViewportChangeListener) listeners[i + 1])
1223: .viewportChanged(event);
1224: }
1225: }
1226: }
1227: }
|