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.List;
012:
013: import net.mygwt.ui.client.Events;
014: import net.mygwt.ui.client.Style;
015: import net.mygwt.ui.client.event.BaseEvent;
016: import net.mygwt.ui.client.widget.Component;
017:
018: import com.google.gwt.user.client.Element;
019:
020: /**
021: * A item in a <code>Tree</code>. All events are bubbled to the item's parent
022: * tree.
023: *
024: * <dl>
025: * <dt><b>Events:</b></dt>
026: *
027: * <dd><b>BeforeAdd</b> : (widget, item, index)<br>
028: * <div>Fires before a item is added or inserted. Listeners can set the
029: * <code>doit</code> field to <code>false</code> to cancel the action.</div>
030: * <ul>
031: * <li>widget : this</li>
032: * <li>item : the item being added</li>
033: * <li>index : the index at which the item will be added</li>
034: * </ul>
035: * </dd>
036: *
037: * <dd><b>BeforeRemove</b> : (widget, item)<br>
038: * <div>Fires before a item is removed. Listeners can set the <code>doit</code>
039: * field to <code>false</code> to cancel the action.</div>
040: * <ul>
041: * <li>widget : this</li>
042: * <li>item : the item being removed</li>
043: * </ul>
044: * </dd>
045: *
046: * <dd><b>BeforeExpand</b> : (widget)<br>
047: * <div>Fires before a item is expanded. Listeners can set the <code>doit</code>
048: * field to <code>false</code> to cancel the expand.</div>
049: * <ul>
050: * <li>widget : this</li>
051: * </ul>
052: * </dd>
053: *
054: * <dd><b>BeforeCollapse</b> : (widget)<br>
055: * <div>Fires before a item is collapsed. Listeners can set the
056: * <code>doit</code> field to <code>false</code> to cancel the collapse.</div>
057: * <ul>
058: * <li>widget : this</li>
059: * </ul>
060: * </dd>
061: *
062: * <dd><b>Add</b> : (widget, item, index)<br>
063: * <div>Fires after a item has been added or inserted.</div>
064: * <ul>
065: * <li>widget : this</li>
066: * <li>item : the item that was added</li>
067: * <li>index : the index at which the item will be added</li>
068: * </ul>
069: * </dd>
070: *
071: * <dd><b>Remove</b> : (widget, item)<br>
072: * <div>Fires after a item has been removed.</div>
073: * <ul>
074: * <li>widget : this</li>
075: * <li>item : the item being removed</li>
076: * </ul>
077: * </dd>
078: *
079: * <dd><b>Expand</b> : (widget)<br>
080: * <div>Fires after a item has been expanded.</div>
081: * <ul>
082: * <li>widget : this</li>
083: * </ul>
084: * </dd>
085: *
086: * <dd><b>Collapse</b> : (widget)<br>
087: * <div>Fires ater a item is collapsed.</div>
088: * <ul>
089: * <li>widget : this</li>
090: * </ul>
091: * </dd>
092: *
093: * <dd><b>CheckChange</b> : (widget)<br>
094: * <div>Fires after a check state change.</div>
095: * <ul>
096: * <li>widget : this</li>
097: * </ul>
098: * </dd>
099: *
100: * <dt><b>CSS:</b></dt>
101: * <dd>.my-tree-item (the item itself)</dd>
102: * <dd>.my-tree-item-text span (the tree item text)</dd>
103: * </dl>
104: */
105: public class TreeItem extends Component {
106:
107: protected Tree tree;
108:
109: protected boolean root, expanded, checked;
110: protected TreeItemUI ui;
111: protected boolean childrenRendered;
112:
113: TreeItem parentItem;
114: boolean leaf = true;
115: String text, iconStyle;
116: private List children;
117: private String textStyle;
118:
119: /**
120: * Creates a new tree item.
121: */
122: public TreeItem() {
123: children = new ArrayList();
124: }
125:
126: /**
127: * Creates a new tree item.
128: *
129: * @param text the item's text
130: */
131: public TreeItem(String text) {
132: this ();
133: setText(text);
134: }
135:
136: /**
137: * Adds a child item.
138: *
139: * @param item the item to be added
140: */
141: public void add(TreeItem item) {
142: insert(item, getItemCount());
143: }
144:
145: public boolean fireEvent(int type, BaseEvent be) {
146: boolean result = super .fireEvent(type, be);
147: if (tree != null && result) {
148: return tree.fireEvent(type, be);
149: }
150: return result;
151: }
152:
153: /**
154: * Returns the item's first child.
155: *
156: * @return the first child or <code>null</code>
157: */
158: public TreeItem firstChild() {
159: return getItem(0);
160: }
161:
162: public Element getContainer() {
163: return ui.containerEl;
164: }
165:
166: /**
167: * Returns the item's node depth.
168: *
169: * @return the depth
170: */
171: public int getDepth() {
172: int depth = 0;
173: TreeItem p = getParentItem();
174: while (p != null) {
175: depth++;
176: p = p.getParentItem();
177: }
178: return depth;
179: }
180:
181: /**
182: * Returns the item's icon style.
183: *
184: * @return the icon style
185: */
186: public String getIconStyle() {
187: return iconStyle;
188: }
189:
190: /**
191: * Returns the item at the specified index.
192: *
193: * @param index the index
194: * @return the item at the index
195: */
196: public TreeItem getItem(int index) {
197: if ((index < 0) || (index >= getItemCount()))
198: return null;
199: return (TreeItem) children.get(index);
200: }
201:
202: /**
203: * Returns the number of child items.
204: *
205: * @return the child count
206: */
207: public int getItemCount() {
208: return children.size();
209: }
210:
211: /**
212: * Returns the item's children.
213: *
214: * @return the children items
215: */
216: public TreeItem[] getItems() {
217: return (TreeItem[]) children.toArray(new TreeItem[0]);
218: }
219:
220: /**
221: * Returns the item's parent.
222: *
223: * @return the parent item
224: */
225: public TreeItem getParentItem() {
226: return parentItem;
227: }
228:
229: /**
230: * Returns the path for this node. The path can be used to expand or select
231: * this node programmatically.
232: *
233: * @return a comma seperated list of tree item id's
234: */
235: public String getPath() {
236: StringBuffer sb = new StringBuffer();
237: TreeItem p = this ;
238: while (p != null) {
239: String id = p.getId();
240: sb.insert(0, "," + id);
241: p = p.getParentItem();
242: }
243: return sb.toString().substring(1);
244: }
245:
246: /**
247: * Returns the item's text.
248: *
249: * @return the text
250: */
251: public String getText() {
252: return text;
253: }
254:
255: public String getTextStyle() {
256: return textStyle;
257: }
258:
259: /**
260: * Returns the item's ui instance.
261: *
262: * @return the ui instance
263: */
264: public TreeItemUI getUI() {
265: return ui;
266: }
267:
268: /**
269: * Returns <code>true</code> if the item's has children.
270: *
271: * @return the children state
272: */
273: public boolean hasChildren() {
274: return getItemCount() > 0;
275: }
276:
277: /**
278: * Returns the index of the item or -1 if not found.
279: *
280: * @param item the child item
281: * @return the item's index
282: */
283: public int indexOf(TreeItem item) {
284: return children.indexOf(item);
285: }
286:
287: /**
288: * Inserts a child item at the specified position.
289: *
290: * @param item the item to be added
291: * @param index index at which the specified element is to be inserted
292: */
293: public void insert(TreeItem item, int index) {
294: if (fireEvent(Events.BeforeAdd, this , item, index)) {
295: item.parentItem = this ;
296: item.setTree(tree);
297:
298: tree.registerItem(item);
299: children.add(index, item);
300: leaf = false;
301:
302: if (childrenRendered) {
303: item.render();
304: }
305:
306: if (rendered && !root) {
307: ui.updateJoint();
308: ui.onIconStyleChange(getIconStyle());
309: }
310: fireEvent(Events.Add, this , item, index);
311: }
312:
313: }
314:
315: /**
316: * Returns <code>true</code> if the item is checked.
317: *
318: * @return the checked state
319: */
320: public boolean isChecked() {
321: return checked;
322: }
323:
324: /**
325: * Returns <code>true</code> if the item is expanded, and <code>false</code>
326: * otherwise.
327: *
328: * @return the expanded state
329: */
330: public boolean isExpanded() {
331: return expanded;
332: }
333:
334: /**
335: * Returns <code>true</code> if the item is a leaf, and <code>false</code>
336: * otherwise. The leaf state allows a tree item to specify if it has children
337: * before the children have been realized.
338: *
339: * @return the leaf state
340: */
341: public boolean isLeaf() {
342: return leaf;
343: }
344:
345: /**
346: * Returns <code>true</code> if the item is a root item.
347: *
348: * @return the rooot state
349: */
350: public boolean isRoot() {
351: return root;
352: }
353:
354: public void onBaseEvent(BaseEvent be) {
355: if (ui != null) {
356: ui.getListener().handleEvent(be);
357: }
358: }
359:
360: /**
361: * Removes a child from the item.
362: *
363: * @param item the item to be removed
364: */
365: public void remove(TreeItem item) {
366: if (!children.contains(item)) {
367: return;
368: }
369: if (fireEvent(Events.BeforeRemove, this , item)) {
370: if (tree.getSelectionModel() != null) {
371: tree.getSelectionModel().deselect(item);
372: }
373: children.remove(item);
374: tree.unregisterItem(item);
375: item.tree = null;
376: item.parentItem = null;
377: if (rendered && item.rendered) {
378: ui.removeItem(item);
379: }
380: fireEvent(Events.Remove, this , item);
381: }
382:
383: }
384:
385: /**
386: * Removes all child items.
387: */
388: public void removeAll() {
389: int count = getItemCount();
390: for (int i = 0; i < count; i++) {
391: remove(getItem(0));
392: }
393: leaf = true;
394: }
395:
396: /**
397: * Sets the item's checked value.
398: *
399: * @param checked <code>true</code> to check
400: */
401: public void setChecked(boolean checked) {
402: this .checked = checked;
403: if (rendered) {
404: ui.onCheckChange(checked);
405: if (checked) {
406: switch (tree.checkStyle) {
407: case Style.PARENT:
408: TreeItem p = getParentItem();
409: while (p != null && !p.root) {
410: p.setChecked(true);
411: p = p.getParentItem();
412: }
413: break;
414: case Style.CHILDREN:
415: for (int i = 0; i < getItemCount(); i++) {
416: getItem(i).setChecked(true);
417: }
418: break;
419: }
420:
421: } else {
422: switch (tree.checkStyle) {
423: case Style.PARENT:
424: clearCheckChildren(this );
425: break;
426: case Style.CHILDREN:
427: for (int i = 0; i < getItemCount(); i++) {
428: getItem(i).setChecked(false);
429: }
430: break;
431: }
432: }
433: }
434: }
435:
436: /**
437: * Sets the item's expanded state.
438: *
439: * @param expanded <code>true</code> to expand
440: */
441: public void setExpanded(boolean expanded) {
442: setExpanded(expanded, false);
443: }
444:
445: /**
446: * Sets the item's expand state.
447: *
448: * @param expanded <code>true</code> to expand
449: * @param deep <code>true</code> to expand all children
450: */
451: public void setExpanded(boolean expanded, boolean deep) {
452: if (expanded && root) {
453: this .expanded = false;
454: } else if (!expanded && root) {
455: this .expanded = true;
456: }
457:
458: if (!isRendered()) {
459: return;
460: }
461:
462: if (expanded) {
463: if (!this .expanded && !isLeaf()) {
464: if (fireEvent(Events.BeforeExpand)) {
465: this .expanded = true;
466: if (!childrenRendered) {
467: renderChildren();
468: }
469: ui.expand();
470: TreeItem p = parentItem;
471: while (p != null && !p.root) {
472: if (p.expanded == false) {
473: p.setExpanded(true);
474: }
475: p = p.parentItem;
476: }
477: }
478: if (deep) {
479: expandChildren(deep);
480: }
481: } else {
482: if (deep) {
483: expandChildren(deep);
484: }
485: }
486: }
487:
488: else if (this .expanded && !expanded) {
489: if (fireEvent(Events.BeforeCollapse)) {
490: this .expanded = false;
491: ui.collapse();
492: }
493: if (deep) {
494: for (int i = 0; i < getItemCount(); i++) {
495: TreeItem item = getItem(i);
496: item.setExpanded(false, true);
497: }
498: }
499: }
500: }
501:
502: /**
503: * Sets the item's icon style.
504: *
505: * @param style the icon style
506: */
507: public void setIconStyle(String style) {
508: this .iconStyle = style;
509: if (rendered) {
510: ui.onIconStyleChange(style);
511: }
512: }
513:
514: /**
515: * Sets the item's leaf state. The leaf state allows a tree item to specify if
516: * it has children before the children have been realized.
517: *
518: * @param leaf the state
519: */
520: public void setLeaf(boolean leaf) {
521: this .leaf = leaf;
522: }
523:
524: /**
525: * Sets the item's text.
526: *
527: * @param text the new text
528: */
529: public void setText(String text) {
530: this .text = text;
531: if (rendered) {
532: ui.onTextChange(text);
533: }
534: }
535:
536: public void setTextStyle(String style) {
537: this .textStyle = style;
538: if (isRendered()) {
539: getUI().onTextStyleChange(textStyle);
540: }
541: }
542:
543: /**
544: * Toggles the item's expand state.
545: */
546: public void toggle() {
547: setExpanded(!isExpanded());
548: }
549:
550: /**
551: * Subclasses may override.
552: *
553: * @return the ui
554: */
555: protected TreeItemUI getTreeItemUI() {
556: return new TreeItemUI(this );
557: }
558:
559: protected void onRender() {
560: ui = getTreeItemUI();
561: }
562:
563: protected void renderChildren() {
564: int count = getItemCount();
565: for (int i = 0; i < count; i++) {
566: getItem(i).render();
567: }
568: childrenRendered = true;
569: }
570:
571: protected void setChildrenRendered(boolean rendered) {
572: childrenRendered = rendered;
573: }
574:
575: protected void setRoot(boolean isRoot) {
576: root = isRoot;
577: }
578:
579: protected void setTree(Tree tree) {
580: this .tree = tree;
581: }
582:
583: boolean isFirst() {
584: if (isRoot())
585: return true;
586: return this == parentItem.getItem(0);
587: }
588:
589: boolean isLast() {
590: if (isRoot())
591: return true;
592: return this == parentItem
593: .getItem(parentItem.getItemCount() - 1);
594: }
595:
596: TreeItem lastChild() {
597: return getItem(getItemCount() - 1);
598: }
599:
600: TreeItem nextSibling() {
601: if (parentItem == null)
602: return null;
603: int index = parentItem.indexOf(this );
604: return parentItem.getItem(index + 1);
605: }
606:
607: TreeItem previousSibling() {
608: if (parentItem == null)
609: return null;
610: int index = parentItem.indexOf(this );
611: return parentItem.getItem(index - 1);
612: }
613:
614: private void clearCheckChildren(TreeItem parent) {
615: for (int i = 0; i < parent.getItemCount(); i++) {
616: TreeItem sub = parent.getItem(i);
617: sub.setChecked(false);
618: clearCheckChildren(sub);
619: }
620: }
621:
622: private void expandChildren(boolean deep) {
623: for (int i = 0; i < getItemCount(); i++) {
624: TreeItem item = getItem(i);
625: item.setExpanded(true, deep);
626: }
627: }
628:
629: }
|