001: /*
002: * Copyright Javelin Software, All rights reserved.
003: */
004:
005: package com.javelin.swinglets;
006:
007: import java.awt.*;
008: import java.awt.event.*;
009: import java.util.*;
010: import java.io.*;
011:
012: import javax.swing.tree.*;
013:
014: import com.javelin.swinglets.tree.*;
015: import com.javelin.swinglets.plaf.*;
016: import com.javelin.swinglets.event.*;
017:
018: /**
019: * STree defines a tree.
020: * <P>
021: * This fires an ActionEvent, when the ID is the row number.
022: *
023: * @author Robin Sharp
024: */
025:
026: public class STree extends SComponent {
027: /**
028: * Parameter that indicates the the NODE has been selected.
029: */
030: public final static String NODE = "_N";
031:
032: /**
033: * Action Event id that indicates a node has been expanded
034: */
035: public final static int NODE_EXPANDED = ActionEvent.ACTION_FIRST + 1;
036:
037: /**
038: * Action Event id that indicates a node has been collapsed
039: */
040: public final static int NODE_COLLAPSED = ActionEvent.ACTION_FIRST + 2;
041:
042: /**
043: * Parameter that indicates the the LABEL has been selected.
044: */
045: public final static String LABEL = "_L";
046:
047: /**
048: * Action Event id that indicates a label has been selected
049: */
050: public final static int LABEL_SELECTED = ActionEvent.ACTION_FIRST + 3;
051:
052: public final static String VIRTUAL_TREE = "V";
053:
054: /**
055: * Creates a STree with a swing table model
056: */
057: public STree(TreeModel model) {
058: this .model = model;
059:
060: expandPath(new TreePath(model.getRoot()));
061:
062: }
063:
064: /**
065: * Returns a STree with the specified TreeNode as its root, which
066: * displays the root node. By default, the tree defines a leaf node as any node
067: * without children.
068: */
069: public STree(TreeNode root) {
070: this (root, false);
071: }
072:
073: /**
074: * Returns a STree with the specified TreeNode as its root, which
075: * displays the root node and which decides whether a node is a
076: * leaf node in the specified manner.
077: */
078: public STree(TreeNode root, boolean asksAllowsChildren) {
079: this (new DefaultTreeModel(root, asksAllowsChildren));
080: }
081:
082: /**
083: * Returns a STree with the specified no tree model.
084: */
085: public STree() {
086: }
087:
088: /**
089: * Returns the name of the L&F class that renders this component.
090: */
091: public Class getUIClass() {
092: return STree.class;
093: }
094:
095: /**
096: * Set Tree cell renderer for the current look and feel.
097: */
098: public STree setTreeCellRenderer(STreeCellRenderer treeCellRenderer) {
099: treeCellRenderers.put(getLookAndFeel().getClass().getName(),
100: treeCellRenderer);
101:
102: return this ;
103: }
104:
105: /**
106: * Set Tree cell renderer for each look and feel.
107: */
108: public STree setTreeCellRenderer(String lookAndFeel,
109: STreeCellRenderer treeCellRenderer) {
110: treeCellRenderers.put(lookAndFeel, treeCellRenderer);
111:
112: return this ;
113: }
114:
115: /**
116: * Get Tree cell renderer, for the current look and feel.
117: */
118: public STreeCellRenderer getTreeCellRenderer() {
119: return getTreeCellRenderer(getLookAndFeel().getClass()
120: .getName());
121: }
122:
123: /**
124: * Get Tree cell renderer, or null.
125: * <P>
126: * If a renderer has not been installed for the look and feel, it is.
127: * picked up from the LookAndFeel as SLookAndFeel.TREE_DEFAULT_CELL_RENDERER.
128: */
129: public STreeCellRenderer getTreeCellRenderer(String lookAndFeel) {
130: STreeCellRenderer renderer = (STreeCellRenderer) treeCellRenderers
131: .get(lookAndFeel);
132:
133: if (renderer == null) {
134: Class clazz = getLookAndFeel().getUIDefaults()
135: .getTreeCellRenderer(
136: SLookAndFeel.TREE_DEFAULT_CELL_RENDERER);
137:
138: if (clazz != null) {
139: try {
140: renderer = (STreeCellRenderer) clazz.newInstance();
141: } catch (Exception e) {
142: //throw new IllegalArgumentException( "Cannot load renderer or class " + clazz.getName() );
143: }
144: }
145:
146: if (renderer != null) {
147: treeCellRenderers.put(lookAndFeel, renderer);
148: }
149: }
150:
151: return renderer;
152: }
153:
154: /**
155: * Set Model.
156: */
157: public STree setModel(TreeModel model) {
158: firePropertyChange("model", this .model, this .model = model);
159: return this ;
160: }
161:
162: /**
163: * Get Model.
164: */
165: public TreeModel getModel() {
166: return model;
167: }
168:
169: /**
170: * Get a sub component by name. This will descend the component
171: * hierarchy and return the SComponent, or null.
172: */
173: public SComponent getComponent(String name) {
174: if (getName().equals(name))
175: return this ;
176:
177: //TO DO
178:
179: return null;
180: }
181:
182: /**
183: * Set the root be visible.
184: */
185: public STree setRootVisible(boolean rootVisible) {
186: firePropertyChange("rootVisible", this .rootVisible,
187: this .rootVisible = rootVisible);
188: return this ;
189: }
190:
191: /**
192: * Is the root be visible.
193: */
194: public boolean isRootVisible() {
195: return rootVisible;
196: }
197:
198: /**
199: * Determines whether the node handles are to be displayed.
200: */
201: public STree setShowsRootHandles(boolean showsRootHandles) {
202: firePropertyChange("showsRootHandles", this .showsRootHandles,
203: this .showsRootHandles = showsRootHandles);
204: return this ;
205: }
206:
207: /**
208: * Returns true if handles for the root nodes are displayed.
209: */
210: public boolean getShowsRootHandles() {
211: return showsRootHandles;
212: }
213:
214: /**
215: * Set the background icon.
216: */
217: public STree setBackgroundIcon(SIcon backgroundIcon) {
218: firePropertyChange("backgroundIcon", this .backgroundIcon,
219: this .backgroundIcon = backgroundIcon);
220: return this ;
221: }
222:
223: /**
224: * Get Tree Background.
225: */
226: public SIcon getBackgroundIcon() {
227: return backgroundIcon;
228: }
229:
230: /**
231: * Return the number of nodes to be displayed.
232: */
233: public int getRowCount() {
234: return rowCount;
235: }
236:
237: /**
238: * Set the number of nodes to be displayed.
239: */
240: public STree setRowCount(int rowCount) {
241: firePropertyChange("rowCount", this .rowCount,
242: this .rowCount = rowCount);
243: return this ;
244: }
245:
246: /**
247: * Returns an Enumeration of the descendants of <code>path</code> that
248: * are currently expanded.
249: */
250: public Enumeration getExpandedDescendants(TreePath path) {
251: return getExpandedDescendants(new Vector(), path).elements();
252: }
253:
254: /**
255: * Recurse down the hierarchy, and add all expanded nodes any
256: * unexpanded children.
257: */
258: protected Vector getExpandedDescendants(Vector vector, TreePath path) {
259: //System.out.println( vector.size() + " " + path.getLastPathComponent() );
260: vector.addElement(path);
261:
262: //Now add all the children
263: TreeNode node = (TreeNode) path.getLastPathComponent();
264: for (int index = 0; index < node.getChildCount(); index++) {
265: TreePath childPath = new TreePath(
266: ((DefaultMutableTreeNode) node.getChildAt(index))
267: .getPath());
268:
269: if (isExpanded(childPath)) {
270: getExpandedDescendants(vector, childPath);
271: } else {
272: //System.out.println( vector.size() + " " + childPath.getLastPathComponent() );
273: vector.addElement(childPath);
274: }
275: }
276:
277: return vector;
278: }
279:
280: /**
281: * Returns true if the node identified by the path is currently expanded,
282: */
283: public boolean isExpanded(TreePath path) {
284: if (path == null)
285: return false;
286:
287: Object value = expandedState.get(path);
288: if (value == null || !((Boolean) value).booleanValue()) {
289: return false;
290: }
291:
292: TreePath parentPath = path.getParentPath();
293:
294: if (parentPath != null) {
295: return isExpanded(parentPath);
296: }
297:
298: return true;
299: }
300:
301: /**
302: * Returns true if the node at the specified display row is currently
303: * expanded.
304: */
305: public boolean isExpanded(int row) {
306: return isExpanded(getPathForRow(row));
307: }
308:
309: /**
310: * Returns true if the value identified by path is currently collapsed,
311: * this will return false if any of the values in path are currently
312: * not being displayed.
313: */
314: public boolean isCollapsed(TreePath path) {
315: return !isExpanded(path);
316: }
317:
318: /**
319: * Returns true if the node at the specified display row is collapsed.
320: */
321: public boolean isCollapsed(int row) {
322: return !isExpanded(row);
323: }
324:
325: /**
326: * Returns true if the value identified by path is currently viewable,
327: * which means it is either the root or all of its parents are exapnded,
328: * Otherwise, this method returns false.
329: */
330: public boolean isVisible(TreePath path) {
331: if (path != null) {
332: TreePath parentPath = path.getParentPath();
333:
334: if (parentPath != null) {
335: return isExpanded(parentPath);
336: }
337:
338: // Root.
339: return true;
340: }
341: return false;
342: }
343:
344: /**
345: * Ensures that the node identified by the specified path is expanded.
346: */
347: public void expandPath(TreePath path) {
348: TreeModel model = getModel();
349:
350: if (model == null)
351: return;
352:
353: if (path != null && model != null
354: && !model.isLeaf(path.getLastPathComponent())) {
355: setExpandedState(path, true);
356: }
357: }
358:
359: /**
360: * Ensures that the node in the specified row is expanded.
361: */
362: public void expandRow(int row) {
363: expandPath(getPathForRow(row));
364: }
365:
366: /**
367: * Ensures that the node identified by the specified path is collapsed.
368: */
369: public void collapsePath(TreePath path) {
370: setExpandedState(path, false);
371: }
372:
373: /**
374: * Ensures that the node in the specified row is collapsed.
375: */
376: public void collapseRow(int row) {
377: collapsePath(getPathForRow(row));
378: }
379:
380: /**
381: * Returns the path for the specified row, or null.
382: */
383: public TreePath getPathForRow(int row) {
384: int count = -1;
385:
386: TreePath path = null;
387: TreePath rootPath = new TreePath(getModel().getRoot());
388: for (Enumeration nodes = getExpandedDescendants(rootPath); nodes
389: .hasMoreElements();) {
390: path = (TreePath) nodes.nextElement();
391:
392: //System.out.println( "count=" + count + " " + path.getLastPathComponent() );
393:
394: count++;
395:
396: if (count >= row) {
397: return path;
398: }
399: }
400:
401: return null;
402: }
403:
404: /**
405: * Returns the row for the path, or -1.
406: */
407: public int getRowForPath(TreePath path) {
408: int count = -1;
409:
410: TreePath rootPath = new TreePath(getModel().getRoot());
411: for (Enumeration nodes = getExpandedDescendants(rootPath); nodes
412: .hasMoreElements();) {
413: rootPath = (TreePath) nodes.nextElement();
414:
415: //System.out.println( "count=" + count + " " + path.getLastPathComponent() );
416:
417: count++;
418:
419: if (rootPath.getLastPathComponent() == path
420: .getLastPathComponent()) {
421: return count;
422: }
423: }
424:
425: return -1;
426: }
427:
428: /**
429: * Is this path selected.
430: */
431: public boolean isPathSelected(TreePath path) {
432: if (selectedPath == null || path == null)
433: return false;
434:
435: return selectedPath.equals(path);
436: }
437:
438: /**
439: * Get the last Selected Path, or null.
440: */
441: public TreePath getSelectedPath() {
442: return selectedPath;
443: }
444:
445: /**
446: * Processes events occurring on this component.
447: */
448: protected void processEvent(AWTEvent event) {
449: if (event instanceof FormEvent) {
450: processFormEvent((FormEvent) event);
451: } else {
452: super .processEvent(event);
453: }
454: }
455:
456: /**
457: * Process the FormEvent to compute which node has been
458: * expanded or contracted.
459: */
460: protected void processFormEvent(FormEvent event) {
461: boolean virtualTree = event.getParameter(VIRTUAL_TREE) != null
462: && event.getParameter(VIRTUAL_TREE).equals("true");
463:
464: int row = 0;
465: boolean label = false;
466:
467: String key = null;
468:
469: //Get the name of the node that was pressed
470: for (Enumeration names = event.getParameterNames(); names
471: .hasMoreElements();) {
472: key = (String) names.nextElement();
473:
474: if (Character.isDigit(key.charAt(0))) {
475: try {
476: int index = key.indexOf('.');
477: if (index >= 0) {
478: row = Integer.parseInt(key.substring(0, index));
479: } else {
480: row = Integer.parseInt(key.substring(0));
481: }
482:
483: label = event.getParameter(key).equals(LABEL);
484: } catch (Exception e) { /*Fail Silently*/
485: }
486: break;
487: }
488: }
489:
490: if (key != null && Character.isDigit(key.charAt(0))) {
491: try {
492: int index = key.indexOf('.');
493: if (index >= 0) {
494: row = Integer.parseInt(key.substring(0, index));
495: } else {
496: row = Integer.parseInt(key.substring(0));
497: }
498: } catch (Exception e) { /*Fail Silently*/
499: }
500: }
501:
502: selectedPath = getPathForRow(row);
503:
504: //System.out.println( "SP=" + selectedPath.getLastPathComponent() );
505:
506: //If the parameter value if STree.LABEL don't expand
507: if (label) {
508: if (actionListener != null) {
509: processActionEvent(new TreeActionEvent(this ,
510: LABEL_SELECTED, selectedPath, row));
511: }
512:
513: //Fudge the tree to collapse
514: if (virtualTree) {
515: expandedState.clear();
516: expandedStack.removeAllElements();
517: }
518:
519: return;
520: } else if (STree.NODE.equals(event.getParameter(key))) {
521: if (isExpanded(selectedPath)) {
522: collapsePath(selectedPath);
523: if (actionListener != null) {
524: processActionEvent(new TreeActionEvent(this ,
525: NODE_COLLAPSED, selectedPath, row));
526: }
527:
528: if (virtualTree) {
529: if (selectedPath.getPathCount() > 1) {
530: selectedPath = selectedPath.getParentPath();
531: }
532: }
533: } else {
534: expandPath(selectedPath);
535: if (actionListener != null) {
536: processActionEvent(new TreeActionEvent(this ,
537: NODE_EXPANDED, selectedPath, row));
538: }
539: }
540:
541: }
542: }
543:
544: // PRIVATE ///////////////////////////////////////////////////////
545:
546: /**
547: * Sets the expanded state of the receiver. If <code>state</code> is
548: * true, all parents of <code>path</code> and path are marked as
549: * expanded. If <code>state</code> is false, all parents of
550: * <code>path</code> are marked EXPANDED, but <code>path</code> itself
551: * is marked collapsed.<p>
552: * This will fail if a TreeWillExpandListener vetos it.
553: */
554: protected void setExpandedState(TreePath path, boolean state) {
555: if (path == null)
556: return;
557:
558: // Make sure all parents of path are expanded.
559: Stack stack;
560: TreePath parentPath = path.getParentPath();
561:
562: if (expandedStack.size() == 0) {
563: stack = new Stack();
564: } else {
565: stack = (Stack) expandedStack.pop();
566: }
567:
568: try {
569: while (parentPath != null) {
570: if (isExpanded(parentPath)) {
571: parentPath = null;
572: } else {
573: stack.push(parentPath);
574: parentPath = parentPath.getParentPath();
575: }
576: }
577: for (int counter = stack.size() - 1; counter >= 0; counter--) {
578: parentPath = (TreePath) stack.pop();
579: expandedState.put(parentPath, Boolean.TRUE);
580: }
581: } finally {
582: if (expandedStack.size() < TEMP_STACK_SIZE) {
583: stack.removeAllElements();
584: expandedStack.push(stack);
585: }
586: }
587:
588: if (!state) {
589: // collapse last path.
590: Object cValue = expandedState.get(path);
591: if (cValue != null && ((Boolean) cValue).booleanValue()) {
592: expandedState.put(path, Boolean.FALSE);
593: }
594: } else {
595: // Expand last path.
596: Object cValue = expandedState.get(path);
597: if (cValue == null || !((Boolean) cValue).booleanValue()) {
598: expandedState.put(path, Boolean.TRUE);
599: }
600: }
601: }
602:
603: // PRIVATE /////////////////////////////////////////////////////////////////////
604:
605: protected Stack expandedStack = new Stack();
606: protected static int TEMP_STACK_SIZE = 11;
607:
608: protected Hashtable expandedState = new Hashtable();
609: protected SIcon backgroundIcon;
610:
611: protected boolean rootVisible = true;
612: protected boolean showsRootHandles = true;
613: protected int rowCount = 100;
614:
615: protected TreeModel model;
616: protected Hashtable treeCellRenderers = new Hashtable();
617:
618: protected TreePath selectedPath;
619:
620: }
|