001: /*
002: * MyGWT Widget Library
003: * Copyright(c) 2007, MyGWT.
004: * licensing@mygwt.net
005: *
006: * http://mygwt.net/license
007: */
008: package net.mygwt.ui.client.widget.tree;
009:
010: import java.util.ArrayList;
011: import java.util.HashMap;
012: import java.util.Iterator;
013: import java.util.List;
014: import java.util.Map;
015:
016: import net.mygwt.ui.client.Events;
017: import net.mygwt.ui.client.MyDOM;
018: import net.mygwt.ui.client.MyGWT;
019: import net.mygwt.ui.client.Style;
020: import net.mygwt.ui.client.event.BaseEvent;
021: import net.mygwt.ui.client.widget.Component;
022: import net.mygwt.ui.client.widget.menu.Menu;
023:
024: import com.google.gwt.user.client.DOM;
025: import com.google.gwt.user.client.Element;
026: import com.google.gwt.user.client.Event;
027:
028: /**
029: * A standard hierarchical tree widget. The tree contains a hierarchy of
030: * <code>TreeItems</code> that the user can open, close, and select.
031: *
032: * <dl>
033: * <dt><b>Styles:</b></dt>
034: * <dd>SINGLE, MULTI, CHECK</dd>
035: *
036: * <dt><b>Events:</b></dt>
037: *
038: * <dd><b>BeforeAdd</b> : (widget, item, index)<br>
039: * <div>Fires before a item is added or inserted. Listeners can set the
040: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
041: * <ul>
042: * <li>widget : the parent item</li>
043: * <li>item : the item being added</li>
044: * <li>index : the index at which the item will be added</li>
045: * </ul>
046: * </dd>
047: *
048: * <dd><b>BeforeRemove</b> : (widget, item)<br>
049: * <div>Fires before a item is removed. Listeners can set the <code>doit</code>
050: * field to <code>false</code> to cancel the action.</div>
051: * <ul>
052: * <li>widget : the parent item</li>
053: * <li>item : the item being removed</li>
054: * </ul>
055: * </dd>
056: *
057: * <dd><b>BeforeExpand</b> : (widget, item)<br>
058: * <div>Fires before a item is expanded. Listeners can set the <code>doit</code>
059: * field to <code>false</code> to cancel the expand.</div>
060: * <ul>
061: * <li>widget : the parent item</li>
062: * <li>item : the item being expanded</li>
063: * </ul>
064: * </dd>
065: *
066: * <dd><b>BeforeCollapse</b> : (widget, item)<br>
067: * <div>Fires before a item is collapsed. Listeners can set the
068: * <code>doit</code> field to <code>false</code> to cancel the collapse.</div>
069: * <ul>
070: * <li>widget : the parent item</li>
071: * <li>item : the item being expanded</li>
072: * </ul>
073: * </dd>
074: *
075: * <dd><b>Add</b> : (widget, item, index)<br>
076: * <div>Fires after a item has been added or inserted.</div>
077: * <ul>
078: * <li>widget : the parent item</li>
079: * <li>item : the item that was added</li>
080: * <li>index : the index at which the item will be added</li>
081: * </ul>
082: * </dd>
083: *
084: * <dd><b>Remove</b> : (widget, item)<br>
085: * <div>Fires after a item has been removed.</div>
086: * <ul>
087: * <li>widget : the parent item</li>
088: * <li>item : the item being removed</li>
089: * </ul>
090: * </dd>
091: *
092: * <dd><b>Expand</b> : (widget, item)<br>
093: * <div>Fires after a item has been expanded.</div>
094: * <ul>
095: * <li>widget : the parent item</li>
096: * <li>item : the item being expanded</li>
097: * </ul>
098: * </dd>
099: *
100: * <dd><b>Collapse</b> : (widget, item)<br>
101: * <div>Fires ater a item is collapsed.</div>
102: * <ul>
103: * <li>widget : the parent item</li>
104: * <li>item : the item being collapsed</li>
105: * </ul>
106: * </dd>
107: *
108: * <dd><b>CheckChange</b> : (widget)<br>
109: * <div>Fires after a check state change.</div>
110: * <ul>
111: * <li>widget : the check item</li>
112: * </ul>
113: * </dd>
114: * </dd>
115: *
116: * <dd><b>ContextMenu</b> : (widget)<br>
117: * <div>Fires before the tree's context menu is shown.</div>
118: * <ul>
119: * <li>widget : this</li>
120: * </ul>
121: * </dd>
122: *
123: * <dt><b>CSS:</b></dt>
124: * <dd>.my-tree (the tree itself)</dd>
125: * <dd>.my-tree-item-text span (the tree item text)</dd>
126: * </dl>
127: */
128: public class Tree extends Component {
129:
130: protected TreeItem root;
131:
132: int checkStyle = Style.PARENT;
133: int checkNodes = Style.DEFAULT;
134: private String nodeImageStyle = "tree-folder";
135: private String openNodeImageStyle = "tree-folder-open";
136: private String itemImageStyle;
137: private boolean animate = true;
138: private int indentWidth = 18;
139: protected TreeSelectionModel sm;
140: private Map nodeHash;
141: private int selectionMode = Style.SINGLE;
142:
143: /**
144: * Creates a new single select tree.
145: */
146: public Tree() {
147: this (Style.NONE);
148: }
149:
150: /**
151: * Creates a new tree.
152: *
153: * @param style the tree style
154: */
155: public Tree(int style) {
156: super (style | Style.FOCUSABLE);
157:
158: if ((style & Style.MULTI) != 0) {
159: selectionMode = Style.MULTI;
160: }
161:
162: createRootItem();
163: root.root = true;
164:
165: nodeHash = new HashMap();
166: }
167:
168: /**
169: * Collapses all item's.
170: */
171: public void collapseAll() {
172: root.setExpanded(false, true);
173: }
174:
175: /**
176: * Deselects a item.
177: *
178: * @param item the item to be deselected
179: */
180: public void deselect(TreeItem item) {
181: sm.deselect(item);
182: }
183:
184: /**
185: * Deselects all selections.
186: */
187: public void deselectAll() {
188: sm.deselectAll();
189: }
190:
191: /**
192: * Expands all item's.
193: */
194: public void expandAll() {
195: root.setExpanded(true, true);
196: }
197:
198: /**
199: * Expands a specified path. A path can be retrieved from a tree item with
200: * {@link TreeItem#getPath()}.
201: *
202: * @param path the path to expand
203: * @return <code>true</code> if all paths expanded
204: */
205: public boolean expandPath(String path) {
206: String[] ids = path.split(",");
207: if (ids.length == 0)
208: return false;
209: if (ids[0].equals(root.getId())) {
210: root.setExpanded(true);
211:
212: TreeItem current = root;
213: for (int i = 1; i < ids.length; i++) {
214: String id = ids[i];
215: boolean match = false;
216: for (int j = 0; j < current.getItemCount(); j++) {
217: TreeItem child = current.getItem(j);
218: if (!match && child.getId().equals(id)) {
219: child.setExpanded(true);
220: current = child;
221: match = true;
222: break;
223: }
224: }
225: if (!match) {
226: return false;
227: }
228: }
229:
230: }
231: return true;
232: }
233:
234: /**
235: * Returns the tree whose element or child elements match the passed target.
236: *
237: * @param element the target element
238: * @return the matching tree item or <code>null</code> if no match
239: */
240: public TreeItem findItem(Element element) {
241: Element elem = MyDOM.findParent("my-treeitem", element, 15);
242: if (elem != null) {
243: String id = DOM.getElementProperty(elem, "id");
244: if (id != null && !id.equals("")) {
245: TreeItem item = getItemById(id);
246: return item;
247: }
248: }
249: return null;
250: }
251:
252: /**
253: * Returns all tree item's contained by the tree.
254: *
255: * @return all tree item's
256: */
257: public TreeItem[] getAllItems() {
258: List temp = new ArrayList();
259: temp.add(root);
260: Iterator it = nodeHash.values().iterator();
261: while (it.hasNext()) {
262: temp.add(it.next());
263: }
264: return (TreeItem[]) temp.toArray(new TreeItem[temp.size()]);
265: }
266:
267: /**
268: * Returns <code>true</code> if animation is enabled.
269: *
270: * @return the animate state
271: */
272: public boolean getAnimate() {
273: return animate;
274: }
275:
276: /**
277: * Returns a list of id's for all checked items.
278: *
279: * @return the list of checked id's
280: */
281: public TreeItem[] getChecked() {
282: List list = new ArrayList();
283: Iterator it = nodeHash.values().iterator();
284: while (it.hasNext()) {
285: TreeItem item = (TreeItem) it.next();
286: if (item.isChecked()) {
287: list.add(item);
288: }
289: }
290: return (TreeItem[]) list.toArray(new TreeItem[0]);
291: }
292:
293: public Menu getContextMenu() {
294: // made public
295: return super .getContextMenu();
296: }
297:
298: /**
299: * Returns the indent width.
300: *
301: * @return the indent width
302: */
303: public int getIndentWidth() {
304: return indentWidth;
305: }
306:
307: /**
308: * Returns the item by id.
309: *
310: * @param id the id of the element to return
311: * @return the item
312: */
313: public TreeItem getItemById(String id) {
314: return (TreeItem) nodeHash.get(id);
315: }
316:
317: /**
318: * Returns the item image style.
319: *
320: * @return the item image style
321: */
322: public String getItemImageStyle() {
323: return itemImageStyle;
324: }
325:
326: /**
327: * Returns the node image style.
328: *
329: * @return the node image style
330: */
331: public String getNodeImageStyle() {
332: return nodeImageStyle;
333: }
334:
335: /**
336: * Returns the open node image style.
337: *
338: * @return the open node image style
339: */
340: public String getOpenNodeImageStyle() {
341: return openNodeImageStyle;
342: }
343:
344: /**
345: * Returns the tree's root item.
346: *
347: * @return the root item
348: */
349: public TreeItem getRootItem() {
350: return root;
351: }
352:
353: /**
354: * Returns the selected item.
355: *
356: * @return the selected item or <code>null</code> if no selection
357: */
358: public TreeItem getSelectedItem() {
359: return sm.getSelected();
360: }
361:
362: /**
363: * Returns an array of selected items.
364: *
365: * @return the selected items
366: */
367: public TreeItem[] getSelection() {
368: if (sm == null) {
369: return new TreeItem[0];
370: }
371: if (sm instanceof MultiSelectionModel) {
372: return sm.getSelection();
373: } else {
374: TreeItem item = getSelectedItem();
375: if (item != null) {
376: return new TreeItem[] { item };
377: }
378: return new TreeItem[0];
379: }
380: }
381:
382: /**
383: * Returns the selection mode.
384: *
385: * @return the selection mode
386: */
387: public int getSelectionMode() {
388: return selectionMode;
389: }
390:
391: /**
392: * Returns the tree's selection model.
393: *
394: * @return the selection model
395: */
396: public TreeSelectionModel getSelectionModel() {
397: return sm;
398: }
399:
400: public void onBaseEvent(BaseEvent be) {
401:
402: }
403:
404: public void onBrowserEvent(Event event) {
405: int type = DOM.eventGetType(event);
406:
407: // hack to receive keyboard events in safari
408: if (MyGWT.isSafari && type == Events.KeyDown) {
409: if (sm.getSelected() != null) {
410: BaseEvent be = new BaseEvent();
411: be.event = event;
412: sm.onKeyDown(be);
413: return;
414: }
415: }
416: if (type == Events.MouseUp) {
417: if (DOM.eventGetButton(event) == Event.BUTTON_RIGHT
418: || (MyGWT.isMac && DOM.eventGetCtrlKey(event))) {
419: super .onBrowserEvent(event);
420: return;
421: }
422: }
423:
424: if (isEnabled()) {
425: TreeItem item = findItem(DOM.eventGetTarget(event));
426: if (item != null) {
427: item.onBrowserEvent(event);
428: }
429: }
430: }
431:
432: /**
433: * Specifies if expand / collapse should be animated. Default value is
434: * <code>true</code>.
435: *
436: * @param animate <code>true</code> to enable animations
437: */
438: public void setAnimate(boolean animate) {
439: this .animate = animate;
440: }
441:
442: /**
443: * Sets what nodes should have a check box. Default value is DEFAULT.
444: *
445: * @see Style#DEFAULT - both nodes and leafs
446: * @see Style#PARENT - only nodes with children
447: * @see Style#LEAF - only leafs
448: *
449: * @param value the value
450: */
451: public void setCheckNodes(int value) {
452: this .checkNodes = value;
453: }
454:
455: /**
456: * Sets the tree check style which determines how checking cascades. Default
457: * value is CHILD.
458: *
459: * @param style the check style SINGLE, PARENT, CHILDREN.
460: */
461: public void setCheckStyle(int style) {
462: this .checkStyle = style;
463: }
464:
465: public void setContextMenu(Menu menu) {
466: super .setContextMenu(menu);
467: }
468:
469: /**
470: * Sets the number of pixels child items are indented. Default value is 18.
471: *
472: * @param indentWidth the indent width
473: */
474: public void setIndentWidth(int indentWidth) {
475: this .indentWidth = indentWidth;
476: }
477:
478: /**
479: * Sets the item image style. If specified all items without a image style
480: * will use this style. Default value is <code>null</code>.
481: *
482: * @param itemImageStyle the item image style
483: */
484: public void setItemImageStyle(String itemImageStyle) {
485: this .itemImageStyle = itemImageStyle;
486: }
487:
488: /**
489: * Sets the global icon style for tree items with children. Default value is
490: * 'tree-folder'.
491: *
492: * @param nodeImageStyle the node image style
493: */
494: public void setNodeImageStyle(String nodeImageStyle) {
495: this .nodeImageStyle = nodeImageStyle;
496: }
497:
498: /**
499: * Sets the global icon style for open tree items. Default value is
500: * 'tree-folder-open'.
501: *
502: * @param openNodeImageStyle the open node image style
503: */
504: public void setOpenNodeImageStyle(String openNodeImageStyle) {
505: this .openNodeImageStyle = openNodeImageStyle;
506: }
507:
508: /**
509: * Sets the selection to the tree items. The current selection is cleared
510: * before the new items are selected. If the tree is single-select then all
511: * items are ignored.
512: *
513: *
514: * @param selected the items to select
515: */
516: public void setSelection(List selected) {
517: if (sm instanceof MultiSelectionModel) {
518: ((MultiSelectionModel) sm).selectItems(selected);
519: }
520: }
521:
522: /**
523: * Sets the selection to the item. The current selection is cleared before the
524: * new items are selected.
525: *
526: * @param item the item to select
527: */
528: public void setSelection(TreeItem item) {
529: sm.select(item);
530: }
531:
532: /**
533: * Sets the selection mode for the list. Calling after the tree has been
534: * rendered will have no effect.
535: *
536: * @param selectionMode the selection mode
537: */
538: public void setSelectionMode(int selectionMode) {
539: if (!isRendered()) {
540: this .selectionMode = selectionMode;
541: }
542: }
543:
544: protected void createRootItem() {
545: root = new RootTreeItem(this );
546: root.tree = this ;
547: }
548:
549: protected void onRender() {
550: setElement(DOM.createDiv());
551: setStyleName("my-tree");
552:
553: if (selectionMode == Style.MULTI) {
554: sm = new MultiSelectionModel();
555: } else {
556: sm = new TreeSelectionModel();
557: }
558:
559: this .sm.init(this );
560:
561: DOM.appendChild(getElement(), root.getElement());
562:
563: if (!root.childrenRendered) {
564: root.renderChildren();
565: }
566:
567: disableTextSelection(true);
568: sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.KEYEVENTS
569: | Event.MOUSEEVENTS);
570: }
571:
572: protected void onRightClick(BaseEvent be) {
573: TreeItem item = findItem(be.getTarget());
574: if (selectionMode == Style.SINGLE) {
575: if (item != null) {
576: setSelection(item);
577: }
578: } else {
579: if (item != null && !sm.isSelected(item)) {
580: setSelection(item);
581: }
582: }
583: super .onRightClick(be);
584: }
585:
586: void registerItem(TreeItem item) {
587: nodeHash.put(item.getId(), item);
588: }
589:
590: void unregisterItem(TreeItem item) {
591: int count = item.getItemCount();
592: for (int i = 0; i < count; i++) {
593: unregisterItem(item.getItem(i));
594: }
595: nodeHash.remove(item.getId());
596: }
597: }
|