001: /*
002: * Created on 04.11.2004
003: */
004: package de.jwic.controls;
005:
006: import java.util.ArrayList;
007: import java.util.Enumeration;
008: import java.util.HashSet;
009: import java.util.Iterator;
010: import java.util.List;
011: import java.util.Set;
012: import java.util.Stack;
013: import java.util.StringTokenizer;
014:
015: import javax.swing.tree.TreeNode;
016:
017: import de.jwic.base.Control;
018: import de.jwic.base.IControlContainer;
019: import de.jwic.events.ElementSelectedEvent;
020: import de.jwic.events.ElementSelectedListener;
021:
022: /**
023: * A TreeControl is a tree that displays TreeNode objects that can be clicked.
024: *
025: * $Id: TreeControl.java,v 1.3 2007/04/26 16:14:25 lordsam Exp $
026: * @version $Revision: 1.3 $
027: * @author JBornemann
028: */
029: public class TreeControl extends Control {
030:
031: private static final long serialVersionUID = 1L;
032:
033: /** Click action for clicking on a node's title */
034: public final static String ACTION_CLICK = "click";
035: public final static String ACTION_SELECT = "select";
036: public final static String ACTION_DESELECT = "deselect";
037: public final static String ACTION_EXPAND = "expand";
038: public final static String ACTION_COLLAPSE = "collapse";
039:
040: /**
041: * Select the clicked node. If selected nodes exist before the sel is cleard.
042: * If the node has children it is expanded.
043: * ElemententSelectedEvent is dispatched.
044: * This is the default click mode.
045: */
046: public final static int CLICK_MODE_SELECT_EXPAND = 0;
047: /**
048: * Select or deselect the clicked node.
049: * ElemententSelectedEvent is dispatched.
050: * Allows multi select.
051: */
052: public final static int CLICK_MODE_MULTI_SELECT_DESELECT = 1;
053: /**
054: * Only ElemententSelectedEvent is dispatched.
055: */
056: public final static int CLICK_MODE_DISPATCH_ONLY = 2;
057: /**
058: * Select the clicked node. If selected nodes exist before the sel is cleard.
059: * ElemententSelectedEvent is dispatched.
060: */
061: public final static int CLICK_MODE_SELECT = 3;
062:
063: protected int clickMode = CLICK_MODE_SELECT_EXPAND;
064: /** Set of nodeID String of selected TreeNodes */
065: protected HashSet selected = new HashSet();
066: /** Set of nodeID String of expanded TreeNodes */
067: protected HashSet expanded = new HashSet();
068: /** Root TreeNode of this tree */
069: protected TreeNode rootNode = null;
070: /** List of listender to inform */
071: protected List selectionListeners = null;
072: /** Render root node */
073: protected boolean renderRootNode = true;
074:
075: /**
076: * @param container
077: */
078: public TreeControl(IControlContainer container) {
079: super (container, null);
080: }
081:
082: /**
083: * @param container
084: * @param name
085: */
086: public TreeControl(IControlContainer container, String name) {
087: super (container, name);
088: }
089:
090: /**
091: * Sets the root node.
092: * @param node
093: */
094: public void setRootNode(TreeNode node) {
095: rootNode = node;
096:
097: if (!renderRootNode && !rootNode.isLeaf()) {
098: // root node is not displayed, so expand root node
099: expand("0");
100: }
101: requireRedraw();
102: }
103:
104: /**
105: * Returns the root node.
106: * @return
107: */
108: public TreeNode getRootNode() {
109: return rootNode;
110: }
111:
112: /**
113: * Returns the TreeNode found by its nodeID.
114: * The root node's nodeID is "0".
115: * Root node's first child's nodeID is "0-0";
116: * @param nodeID
117: * @return
118: */
119: public TreeNode getNode(String nodeID) {
120:
121: if (nodeID.equals("0")) {
122: // return root
123: return rootNode;
124: } else {
125: // remove root String "0-"
126: nodeID = nodeID.substring(2);
127: }
128:
129: TreeNode node = rootNode;
130: for (Enumeration en = new StringTokenizer(nodeID, "-"); en
131: .hasMoreElements();) {
132: String token = (String) en.nextElement();
133: if (!token.equals("-")) {
134: int idx = Integer.parseInt(token);
135: node = node.getChildAt(idx);
136: }
137: }
138: return node;
139: }
140:
141: public static String getNodeID(TreeNode node) {
142: String key = null;
143: for (TreeNode parent = node.getParent(); parent != null; parent = node
144: .getParent()) {
145: int idx = parent.getIndex(node);
146: if (key == null) {
147: key = String.valueOf(idx);
148: } else {
149: key = String.valueOf(idx) + "-" + key;
150: }
151: node = parent;
152: }
153:
154: return key == null ? "0" : "0-" + key;
155: }
156:
157: /* (non-Javadoc)
158: * @see de.jwic.base.IControl#actionPerformed(java.lang.String, java.lang.String)
159: */
160: public void actionPerformed(String actionId, String parameter) {
161:
162: if (ACTION_CLICK.equals(actionId)) {
163: // node will be clicked
164: click(parameter);
165: } else if (ACTION_SELECT.equals(actionId)) {
166: // node will be selected
167: select(parameter);
168: } else if (ACTION_DESELECT.equals(actionId)) {
169: // node will be deselected
170: deselect(parameter);
171: } else if (ACTION_EXPAND.equals(actionId)) {
172: // node will be expanded
173: expand(parameter);
174: } else if (ACTION_COLLAPSE.equals(actionId)) {
175: // node will be expanded
176: collapse(parameter);
177: }
178: }
179:
180: /**
181: * Click the node.
182: * @param nodeID
183: */
184: public void click(String nodeID) {
185:
186: switch (clickMode) {
187:
188: case CLICK_MODE_SELECT_EXPAND: {
189: doSelectExpand(this , nodeID);
190: break;
191: }
192:
193: case CLICK_MODE_SELECT: {
194: doSelect(this , nodeID);
195: break;
196: }
197:
198: case CLICK_MODE_MULTI_SELECT_DESELECT: {
199: doMultiSelect(this , nodeID);
200: break;
201: }
202:
203: case CLICK_MODE_DISPATCH_ONLY: {
204: // The event is dispatched, without selecting the node.
205: // This is a somewhat strange behaivior, but it must remain for
206: // compatibility reasons.
207: sendElementSelectedEvent(nodeID);
208: break;
209: }
210: }
211:
212: }
213:
214: /**
215: * Do the multi select.
216: * @param tree
217: * @param nodeID
218: */
219: public static void doMultiSelect(TreeControl tree, String nodeID) {
220: if (!tree.selected.contains(nodeID)) {
221: // select the node if it is not selected
222: tree.select(nodeID);
223: } else {
224: // deselect selected node
225: tree.deselect(nodeID);
226: }
227: }
228:
229: /**
230: * Do the select and expand.
231: * @param tree
232: * @param nodeID
233: */
234: public static void doSelectExpand(TreeControl tree, String nodeID) {
235: doSelect(tree, nodeID);
236: if (!tree.getNode(nodeID).isLeaf()) {
237: // node isn't a leaf, so expand it
238: tree.expand(nodeID);
239: }
240: }
241:
242: /**
243: * Do the select and expand.
244: * @param tree
245: * @param nodeID
246: */
247: public static void doSelect(TreeControl tree, String nodeID) {
248: // select and expand the node
249: if (tree.selected.size() > 0) {
250: // remove proevious selection
251: tree.clearSelection();
252: }
253: tree.select(nodeID);
254: }
255:
256: /**
257: * Clear the selected elements.
258: */
259: public void clearSelection() {
260: selected.clear();
261: }
262:
263: /**
264: * Select the node.
265: * @param nodeID
266: */
267: public void select(String nodeID) {
268: selected.add(nodeID);
269: // dispatched ElementedSelectedEvent
270: sendElementSelectedEvent(nodeID);
271: setRequireRedraw(true);
272: }
273:
274: /**
275: * Deselect the node.
276: * @param nodeID
277: */
278: public void deselect(String nodeID) {
279: selected.remove(nodeID);
280: setRequireRedraw(true);
281: }
282:
283: /**
284: * Expand the node.
285: * @param nodeID
286: */
287: public void expand(String nodeID) {
288: expanded.add(nodeID);
289: setRequireRedraw(true);
290: }
291:
292: /**
293: * Expands the node and all its parents.
294: * @param nodeID
295: */
296: public void expandAll(String nodeID) {
297: // expand all its parents
298: String parentID = null;
299: for (Enumeration en = new StringTokenizer(nodeID, "-"); en
300: .hasMoreElements();) {
301: String token = (String) en.nextElement();
302: if (parentID != null) {
303: parentID += "-" + token;
304: } else {
305: parentID = token;
306: }
307: // expand parent
308: expand(parentID);
309: }
310: }
311:
312: /**
313: * Collapse the node.
314: * @param nodeID
315: */
316: public void collapse(String nodeID) {
317: expanded.remove(nodeID);
318: setRequireRedraw(true);
319: }
320:
321: /**
322: * Returns an Iterator of TreeControlNode objects that hold the TreeNode
323: * for rendering.
324: * @return
325: */
326: public Iterator getEntries() {
327: return new TreeEntryIterator(this , rootNode);
328: }
329:
330: /**
331: * Returns the Set of the selected nodeID Strings.
332: * @return
333: */
334: public Set getSelected() {
335: return selected;
336: }
337:
338: /**
339: * Returns the Set of the expanded nodeID Strings.
340: * @return
341: */
342: public Set getExpanded() {
343: return expanded;
344: }
345:
346: /**
347: * Sets the clicked mode.
348: * CLICK_MODE_SELECT_EXPAND and CLICK_MODE_MULTI_SELECT_DESELECT are supported.
349: * @return Returns the clickMode.
350: */
351: public int getClickMode() {
352: return clickMode;
353: }
354:
355: /**
356: * Return the clicked mode.
357: * CLICK_MODE_SELECT_EXPAND and CLICK_MODE_MULTI_SELECT_DESELECT are supported.
358: * @param clickMode
359: */
360: public void setClickMode(int clickMode) {
361: this .clickMode = clickMode;
362: }
363:
364: /**
365: * Register a listener that will be notified when node will be selected.
366: * @param listener
367: */
368: public void addElementSelectedListener(
369: ElementSelectedListener listener) {
370: if (selectionListeners == null) {
371: selectionListeners = new ArrayList();
372: }
373: selectionListeners.add(listener);
374: }
375:
376: /**
377: * Remove a listener.
378: * @param listener
379: */
380: public void removeElementSelectedListener(
381: ElementSelectedListener listener) {
382: if (selectionListeners != null) {
383: selectionListeners.remove(listener);
384: }
385: }
386:
387: /**
388: * Send the element selected event to the registerd listeners.
389: */
390: protected void sendElementSelectedEvent(String nodeID) {
391:
392: if (selectionListeners != null) {
393: ElementSelectedEvent e = new ElementSelectedEvent(this ,
394: nodeID);
395: for (Iterator it = selectionListeners.iterator(); it
396: .hasNext();) {
397: ElementSelectedListener osl = (ElementSelectedListener) it
398: .next();
399: osl.elementSelected(e);
400: }
401: }
402:
403: }
404:
405: /**
406: * If true (default) the root node is rendered.
407: * @return renderRootNode
408: */
409: public boolean isRenderRootNode() {
410: return renderRootNode;
411: }
412:
413: /**
414: * Returns if the root node is renderd.
415: * @param renderRootNode
416: */
417: public void setRenderRootNode(boolean renderRootNode) {
418: this .renderRootNode = renderRootNode;
419:
420: // set root node again to check that it is expanded
421: if (rootNode != null) {
422: setRootNode(rootNode);
423: }
424: }
425: }
426:
427: /**
428: * TreeEntryIterator provides an Iterator of TreeEntry objects.
429: */
430: class TreeEntryIterator implements Iterator {
431:
432: TreeControl treeControl = null;
433: /** Holds the path of TreeEntry */
434: Stack path = new Stack();
435: /** Next TreeNode that will be return */
436: TreeNode nextNode = null;
437:
438: /**
439: * NodeIterator constructor.
440: * @param rootNode
441: */
442: public TreeEntryIterator(TreeControl treeControl, TreeNode rootNode) {
443: this .treeControl = treeControl;
444: nextNode = rootNode;
445:
446: if (!treeControl.isRenderRootNode()) {
447: // hide root node, don't return it in method next()
448: next();
449: }
450: }
451:
452: /* (non-Javadoc)
453: * @see java.util.Iterator#remove()
454: */
455: public void remove() {
456: throw new UnsupportedOperationException();
457: }
458:
459: /* (non-Javadoc)
460: * @see java.util.Iterator#hasNext()
461: */
462: public boolean hasNext() {
463: return nextNode != null;
464: }
465:
466: /* (non-Javadoc)
467: * @see java.util.Iterator#next()
468: */
469: public Object next() {
470:
471: if (hasNext()) {
472:
473: String nodeID = "0";
474: int level = 0;
475: boolean isLast = true;
476: if (parent() != null) {
477: // use session
478: nodeID = parent().nodeID + "-" + (parent().curr - 1);
479: level = path.size();
480: isLast = parent().curr >= parent().node.getChildCount();
481: }
482:
483: // encapsulate the TreeNode
484: TreeEntry entry = new TreeEntry();
485: entry.setNode(nextNode);
486: entry.setParent(parent());
487: entry.setNodeID(nodeID);
488: entry.setSelected(treeControl.selected.contains(nodeID));
489: entry.setExpanded(treeControl.expanded.contains(nodeID));
490: entry.setLast(isLast);
491: entry.setLevel(level);
492:
493: // check if new parent needs to be created
494: if (parent() == null
495: || (level > 0 && entry.isExpanded() && nextNode
496: .getChildCount() > 0)) {
497: // create new parent
498: path.push(entry);
499: }
500:
501: // find next node
502: nextNode = null;
503: while (path.size() > 0 && nextNode == null) {
504: if (treeControl.expanded.contains(parent().nodeID)
505: && parent().curr < parent().node
506: .getChildCount()) {
507: // next child exists
508: nextNode = parent().node
509: .getChildAt(parent().curr++);
510: } else {
511: // end of session is reached, remove it from path
512: path.pop();
513: }
514: }
515:
516: // return current TreeControlNode
517: return entry;
518: }
519:
520: return null;
521: }
522:
523: /**
524: * Returns the current parent.
525: * Null is returned if no parent exists.
526: * @return
527: */
528: private TreeEntry parent() {
529: if (path.size() > 0) {
530: return (TreeEntry) path.lastElement();
531: }
532:
533: return null;
534: }
535:
536: }
|