001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.client.ui;
017:
018: import com.google.gwt.user.client.DOM;
019: import com.google.gwt.user.client.Element;
020:
021: import java.util.ArrayList;
022: import java.util.List;
023:
024: /**
025: * An item that can be contained within a
026: * {@link com.google.gwt.user.client.ui.Tree}.
027: * <p>
028: * <h3>Example</h3>
029: * {@example com.google.gwt.examples.TreeExample}
030: * </p>
031: */
032: public class TreeItem extends UIObject implements HasHTML {
033:
034: private ArrayList<TreeItem> children = new ArrayList<TreeItem>();
035: private Element itemTable, contentElem, childSpanElem;
036: private final Image statusImage = new Image();
037: private boolean open;
038: private TreeItem parent;
039: private boolean selected;
040: private Object userObject;
041: private Tree tree;
042: private Widget widget;
043:
044: /**
045: * Creates an empty tree item.
046: */
047: public TreeItem() {
048: setElement(DOM.createDiv());
049: itemTable = DOM.createTable();
050: contentElem = DOM.createSpan();
051: childSpanElem = DOM.createSpan();
052:
053: // Uses the following Element hierarchy:
054: // <div (handle)>
055: // <table (itemElem)>
056: // <tr>
057: // <td><img (imgElem)/></td>
058: // <td><span (contents)/></td>
059: // </tr>
060: // </table>
061: // <span (childSpanElem)> children </span>
062: // </div>
063:
064: Element tbody = DOM.createTBody(), tr = DOM.createTR();
065: Element tdImg = DOM.createTD(), tdContent = DOM.createTD();
066: DOM.appendChild(itemTable, tbody);
067: DOM.appendChild(tbody, tr);
068: DOM.appendChild(tr, tdImg);
069: DOM.appendChild(tr, tdContent);
070: DOM.setStyleAttribute(tdImg, "verticalAlign", "middle");
071: DOM.setStyleAttribute(tdContent, "verticalAlign", "middle");
072:
073: DOM.appendChild(getElement(), itemTable);
074: DOM.appendChild(getElement(), childSpanElem);
075: DOM.appendChild(tdImg, statusImage.getElement());
076: DOM.appendChild(tdContent, contentElem);
077:
078: DOM.setStyleAttribute(contentElem, "display", "inline");
079: DOM.setStyleAttribute(getElement(), "whiteSpace", "nowrap");
080: DOM.setStyleAttribute(childSpanElem, "whiteSpace", "nowrap");
081: setStyleName(contentElem, "gwt-TreeItem", true);
082: }
083:
084: /**
085: * Constructs a tree item with the given HTML.
086: *
087: * @param html the item's HTML
088: */
089: public TreeItem(String html) {
090: this ();
091: setHTML(html);
092: }
093:
094: /**
095: * Constructs a tree item with the given <code>Widget</code>.
096: *
097: * @param widget the item's widget
098: */
099: public TreeItem(Widget widget) {
100: this ();
101: setWidget(widget);
102: }
103:
104: /**
105: * Adds a child tree item containing the specified text.
106: *
107: * @param itemText the text to be added
108: * @return the item that was added
109: */
110: public TreeItem addItem(String itemText) {
111: TreeItem ret = new TreeItem(itemText);
112: addItem(ret);
113: return ret;
114: }
115:
116: /**
117: * Adds another item as a child to this one.
118: *
119: * @param item the item to be added
120: */
121:
122: public void addItem(TreeItem item) {
123: // Detach item from existing parent.
124: if ((item.getParentItem() != null) || (item.getTree() != null)) {
125: item.remove();
126: }
127:
128: // Logical attach.
129: item.setParentItem(this );
130: children.add(item);
131:
132: // Physical attach.
133: DOM.setStyleAttribute(item.getElement(), "marginLeft", "16px");
134: DOM.appendChild(childSpanElem, item.getElement());
135:
136: // Adopt.
137: item.setTree(tree);
138:
139: if (children.size() == 1) {
140: updateState();
141: }
142: }
143:
144: /**
145: * Adds a child tree item containing the specified widget.
146: *
147: * @param widget the widget to be added
148: * @return the item that was added
149: */
150: public TreeItem addItem(Widget widget) {
151: TreeItem ret = new TreeItem(widget);
152: addItem(ret);
153: return ret;
154: }
155:
156: /**
157: * Gets the child at the specified index.
158: *
159: * @param index the index to be retrieved
160: * @return the item at that index
161: */
162:
163: public TreeItem getChild(int index) {
164: if ((index < 0) || (index >= children.size())) {
165: return null;
166: }
167:
168: return children.get(index);
169: }
170:
171: /**
172: * Gets the number of children contained in this item.
173: *
174: * @return this item's child count.
175: */
176:
177: public int getChildCount() {
178: return children.size();
179: }
180:
181: /**
182: * Gets the index of the specified child item.
183: *
184: * @param child the child item to be found
185: * @return the child's index, or <code>-1</code> if none is found
186: */
187:
188: public int getChildIndex(TreeItem child) {
189: return children.indexOf(child);
190: }
191:
192: public String getHTML() {
193: return DOM.getInnerHTML(contentElem);
194: }
195:
196: /**
197: * Gets this item's parent.
198: *
199: * @return the parent item
200: */
201: public TreeItem getParentItem() {
202: return parent;
203: }
204:
205: /**
206: * Gets whether this item's children are displayed.
207: *
208: * @return <code>true</code> if the item is open
209: */
210: public boolean getState() {
211: return open;
212: }
213:
214: public String getText() {
215: return DOM.getInnerText(contentElem);
216: }
217:
218: /**
219: * Gets the tree that contains this item.
220: *
221: * @return the containing tree
222: */
223: public final Tree getTree() {
224: return tree;
225: }
226:
227: /**
228: * Gets the user-defined object associated with this item.
229: *
230: * @return the item's user-defined object
231: */
232: public Object getUserObject() {
233: return userObject;
234: }
235:
236: /**
237: * Gets the <code>Widget</code> associated with this tree item.
238: *
239: * @return the widget
240: */
241: public Widget getWidget() {
242: return widget;
243: }
244:
245: /**
246: * Determines whether this item is currently selected.
247: *
248: * @return <code>true</code> if it is selected
249: */
250: public boolean isSelected() {
251: return selected;
252: }
253:
254: /**
255: * Removes this item from its tree.
256: */
257: public void remove() {
258: if (parent != null) {
259: // If this item has a parent, remove self from it.
260: parent.removeItem(this );
261: } else if (tree != null) {
262: // If the item has no parent, but is in the Tree, it must be a top-level
263: // element.
264: tree.removeItem(this );
265: }
266: }
267:
268: /**
269: * Removes one of this item's children.
270: *
271: * @param item the item to be removed
272: */
273:
274: public void removeItem(TreeItem item) {
275: // Validate.
276: if (!children.contains(item)) {
277: return;
278: }
279:
280: // Orphan.
281: item.setTree(null);
282:
283: // Physical detach.
284: DOM.removeChild(childSpanElem, item.getElement());
285:
286: // Logical detach.
287: item.setParentItem(null);
288: children.remove(item);
289:
290: if (children.size() == 0) {
291: updateState();
292: }
293: }
294:
295: /**
296: * Removes all of this item's children.
297: */
298: public void removeItems() {
299: while (getChildCount() > 0) {
300: removeItem(getChild(0));
301: }
302: }
303:
304: public void setHTML(String html) {
305: setWidget(null);
306: DOM.setInnerHTML(contentElem, html);
307: }
308:
309: /**
310: * Selects or deselects this item.
311: *
312: * @param selected <code>true</code> to select the item, <code>false</code>
313: * to deselect it
314: */
315: public void setSelected(boolean selected) {
316: if (this .selected == selected) {
317: return;
318: }
319: this .selected = selected;
320: setStyleName(contentElem, "gwt-TreeItem-selected", selected);
321: }
322:
323: /**
324: * Sets whether this item's children are displayed.
325: *
326: * @param open whether the item is open
327: */
328: public void setState(boolean open) {
329: setState(open, true);
330: }
331:
332: /**
333: * Sets whether this item's children are displayed.
334: *
335: * @param open whether the item is open
336: * @param fireEvents <code>true</code> to allow open/close events to be
337: * fired
338: */
339: public void setState(boolean open, boolean fireEvents) {
340: if (open && children.size() == 0) {
341: return;
342: }
343:
344: this .open = open;
345: updateState();
346:
347: if (fireEvents && tree != null) {
348: tree.fireStateChanged(this );
349: }
350: }
351:
352: public void setText(String text) {
353: setWidget(null);
354: DOM.setInnerText(contentElem, text);
355: }
356:
357: /**
358: * Sets the user-defined object associated with this item.
359: *
360: * @param userObj the item's user-defined object
361: */
362: public void setUserObject(Object userObj) {
363: userObject = userObj;
364: }
365:
366: /**
367: * Sets the current widget. Any existing child widget will be removed.
368: *
369: * @param newWidget Widget to set
370: */
371: public void setWidget(Widget newWidget) {
372: // Detach new child from old parent.
373: if (newWidget != null) {
374: newWidget.removeFromParent();
375: }
376:
377: // Detach old child from tree.
378: if (widget != null && tree != null) {
379: tree.orphan(widget);
380: }
381:
382: // Physical detach old from self.
383: // Clear out any existing content before adding a widget.
384: DOM.setInnerHTML(contentElem, "");
385:
386: // Logical detach old/attach new.
387: widget = newWidget;
388:
389: if (newWidget != null) {
390: // Physical attach new.
391: DOM.appendChild(contentElem, newWidget.getElement());
392:
393: // Attach child to tree.
394: if (tree != null) {
395: tree.adopt(widget, this );
396: }
397: }
398: }
399:
400: /**
401: * Returns the widget, if any, that should be focused on if this TreeItem is
402: * selected.
403: *
404: * @return widget to be focused.
405: */
406: protected HasFocus getFocusableWidget() {
407: Widget w = getWidget();
408: if (w instanceof HasFocus) {
409: return (HasFocus) w;
410: } else {
411: return null;
412: }
413: }
414:
415: void addTreeItems(List<TreeItem> accum) {
416: for (int i = 0; i < children.size(); i++) {
417: TreeItem item = children.get(i);
418: accum.add(item);
419: item.addTreeItems(accum);
420: }
421: }
422:
423: ArrayList<TreeItem> getChildren() {
424: return children;
425: }
426:
427: Element getContentElem() {
428: return contentElem;
429: }
430:
431: int getContentHeight() {
432: return DOM.getElementPropertyInt(itemTable, "offsetHeight");
433: }
434:
435: Element getImageElement() {
436: return statusImage.getElement();
437: }
438:
439: void setParentItem(TreeItem parent) {
440: this .parent = parent;
441: }
442:
443: void setTree(Tree newTree) {
444: // Early out.
445: if (tree == newTree) {
446: return;
447: }
448:
449: // Remove this item from existing tree.
450: if (tree != null) {
451: if (tree.getSelectedItem() == this ) {
452: tree.setSelectedItem(null);
453: }
454:
455: if (widget != null) {
456: tree.orphan(widget);
457: }
458: }
459:
460: tree = newTree;
461: for (int i = 0, n = children.size(); i < n; ++i) {
462: children.get(i).setTree(newTree);
463: }
464: updateState();
465:
466: if (newTree != null) {
467: if (widget != null) {
468: // Add my widget to the new tree.
469: newTree.adopt(widget, this );
470: }
471: }
472: }
473:
474: void updateState() {
475: // If the tree hasn't been set, there is no visual state to update.
476: if (tree == null) {
477: return;
478: }
479:
480: TreeImages images = tree.getImages();
481:
482: if (children.size() == 0) {
483: UIObject.setVisible(childSpanElem, false);
484: images.treeLeaf().applyTo(statusImage);
485: return;
486: }
487:
488: // We must use 'display' rather than 'visibility' here,
489: // or the children will always take up space.
490: if (open) {
491: UIObject.setVisible(childSpanElem, true);
492: images.treeOpen().applyTo(statusImage);
493: } else {
494: UIObject.setVisible(childSpanElem, false);
495: images.treeClosed().applyTo(statusImage);
496: }
497: }
498:
499: void updateStateRecursive() {
500: updateState();
501: for (int i = 0, n = children.size(); i < n; ++i) {
502: children.get(i).updateStateRecursive();
503: }
504: }
505: }
|