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.core.client.GWT;
019: import com.google.gwt.user.client.DOM;
020: import com.google.gwt.user.client.Element;
021: import com.google.gwt.user.client.Event;
022:
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028:
029: /**
030: * A standard hierarchical tree widget. The tree contains a hierarchy of
031: * {@link com.google.gwt.user.client.ui.TreeItem TreeItems} that the user can
032: * open, close, and select.
033: * <p>
034: * <img class='gallery' src='Tree.png'/>
035: * </p>
036: * <h3>CSS Style Rules</h3>
037: * <ul class='css'>
038: * <li>.gwt-Tree { the tree itself }</li>
039: * <li>.gwt-Tree .gwt-TreeItem { a tree item }</li>
040: * <li>.gwt-Tree .gwt-TreeItem-selected { a selected tree item }</li>
041: * </ul>
042: * <p>
043: * <h3>Example</h3>
044: * {@example com.google.gwt.examples.TreeExample}
045: * </p>
046: */
047: public class Tree extends Widget implements HasWidgets,
048: SourcesTreeEvents, HasFocus {
049:
050: /**
051: * Provides images to support the the deprecated case where a url prefix is
052: * passed in through {@link Tree#setImageBase(String)}. This class is used in
053: * such a way that it will be completely removed by the compiler if the
054: * deprecated methods, {@link Tree#setImageBase(String)} and
055: * {@link Tree#getImageBase()}, are not called.
056: */
057: private static class ImagesFromImageBase implements TreeImages {
058:
059: /**
060: * A convience image prototype that implements
061: * {@link AbstractImagePrototype#applyTo(Image)} for a specified image name.
062: */
063: private class Prototype extends AbstractImagePrototype {
064: private final String imageUrl;
065:
066: Prototype(String url) {
067: imageUrl = url;
068: }
069:
070: @Override
071: public void applyTo(Image image) {
072: image.setUrl(baseUrl + imageUrl);
073: }
074:
075: @Override
076: public Image createImage() {
077: // NOTE: This class is only used internally and, therefore only needs
078: // to support applyTo(Image).
079: throw new UnsupportedOperationException(
080: "createImage is unsupported.");
081: }
082:
083: @Override
084: public String getHTML() {
085: // NOTE: This class is only used internally and, therefore only needs
086: // to support applyTo(Image).
087: throw new UnsupportedOperationException(
088: "getHTML is unsupported.");
089: }
090: }
091:
092: private final String baseUrl;
093:
094: ImagesFromImageBase(String baseUrl) {
095: this .baseUrl = baseUrl;
096: }
097:
098: public AbstractImagePrototype treeClosed() {
099: return new Prototype("tree_closed.gif");
100: }
101:
102: public AbstractImagePrototype treeLeaf() {
103: return new Prototype("tree_white.gif");
104: }
105:
106: public AbstractImagePrototype treeOpen() {
107: return new Prototype("tree_open.gif");
108: }
109:
110: String getBaseUrl() {
111: return baseUrl;
112: }
113: }
114:
115: /**
116: * Map of TreeItem.widget -> TreeItem.
117: */
118: private final Map<Widget, TreeItem> childWidgets = new HashMap<Widget, TreeItem>();
119: private TreeItem curSelection;
120: private final Element focusable;
121: private FocusListenerCollection focusListeners;
122: private TreeImages images;
123: private KeyboardListenerCollection keyboardListeners;
124: private TreeListenerCollection listeners;
125: private MouseListenerCollection mouseListeners = null;
126: private final TreeItem root;
127:
128: /**
129: * Keeps track of the last event type seen. We do this to determine if we have
130: * a duplicate key down.
131: */
132: private int lastEventType;
133:
134: /**
135: * Constructs an empty tree.
136: */
137: public Tree() {
138: this (GWT.<TreeImages> create(TreeImages.class));
139: }
140:
141: /**
142: * Constructs a tree that uses the specified image bundle for images.
143: *
144: * @param images a bundle that provides tree specific images
145: */
146: public Tree(TreeImages images) {
147: this .images = images;
148: setElement(DOM.createDiv());
149: DOM.setStyleAttribute(getElement(), "position", "relative");
150: focusable = FocusPanel.impl.createFocusable();
151: DOM.setStyleAttribute(focusable, "fontSize", "0");
152: DOM.setStyleAttribute(focusable, "position", "absolute");
153: DOM.setIntStyleAttribute(focusable, "zIndex", -1);
154: DOM.appendChild(getElement(), focusable);
155:
156: sinkEvents(Event.MOUSEEVENTS | Event.ONCLICK | Event.KEYEVENTS);
157: DOM.sinkEvents(focusable, Event.FOCUSEVENTS);
158:
159: // The 'root' item is invisible and serves only as a container
160: // for all top-level items.
161: root = new TreeItem() {
162: @Override
163: public void addItem(TreeItem item) {
164: // If this element already belongs to a tree or tree item, remove it.
165: if ((item.getParentItem() != null)
166: || (item.getTree() != null)) {
167: item.remove();
168: }
169: DOM.appendChild(Tree.this .getElement(), item
170: .getElement());
171:
172: item.setTree(this .getTree());
173:
174: // Explicitly set top-level items' parents to null.
175: item.setParentItem(null);
176: getChildren().add(item);
177:
178: // Use no margin on top-most items.
179: DOM.setIntStyleAttribute(item.getElement(),
180: "marginLeft", 0);
181: }
182:
183: @Override
184: public void removeItem(TreeItem item) {
185: if (!getChildren().contains(item)) {
186: return;
187: }
188:
189: // Update Item state.
190: item.setTree(null);
191: item.setParentItem(null);
192: getChildren().remove(item);
193:
194: DOM.removeChild(Tree.this .getElement(), item
195: .getElement());
196: }
197: };
198: root.setTree(this );
199: setStyleName("gwt-Tree");
200: }
201:
202: /**
203: * Adds the widget as a root tree item.
204: *
205: * @see com.google.gwt.user.client.ui.HasWidgets#add(com.google.gwt.user.client.ui.Widget)
206: * @param widget widget to add.
207: */
208: public void add(Widget widget) {
209: addItem(widget);
210: }
211:
212: public void addFocusListener(FocusListener listener) {
213: if (focusListeners == null) {
214: focusListeners = new FocusListenerCollection();
215: }
216: focusListeners.add(listener);
217: }
218:
219: /**
220: * Adds a simple tree item containing the specified text.
221: *
222: * @param itemText the text of the item to be added
223: * @return the item that was added
224: */
225: public TreeItem addItem(String itemText) {
226: TreeItem ret = new TreeItem(itemText);
227: addItem(ret);
228:
229: return ret;
230: }
231:
232: /**
233: * Adds an item to the root level of this tree.
234: *
235: * @param item the item to be added
236: */
237: public void addItem(TreeItem item) {
238: root.addItem(item);
239: }
240:
241: /**
242: * Adds a new tree item containing the specified widget.
243: *
244: * @param widget the widget to be added
245: */
246: public TreeItem addItem(Widget widget) {
247: return root.addItem(widget);
248: }
249:
250: public void addKeyboardListener(KeyboardListener listener) {
251: if (keyboardListeners == null) {
252: keyboardListeners = new KeyboardListenerCollection();
253: }
254: keyboardListeners.add(listener);
255: }
256:
257: public void addMouseListener(MouseListener listener) {
258: if (mouseListeners == null) {
259: mouseListeners = new MouseListenerCollection();
260: }
261: mouseListeners.add(listener);
262: }
263:
264: public void addTreeListener(TreeListener listener) {
265: if (listeners == null) {
266: listeners = new TreeListenerCollection();
267: }
268: listeners.add(listener);
269: }
270:
271: /**
272: * Clears all tree items from the current tree.
273: */
274: public void clear() {
275: int size = root.getChildCount();
276: for (int i = size - 1; i >= 0; i--) {
277: root.getChild(i).remove();
278: }
279: }
280:
281: /**
282: * Ensures that the currently-selected item is visible, opening its parents
283: * and scrolling the tree as necessary.
284: */
285: public void ensureSelectedItemVisible() {
286: if (curSelection == null) {
287: return;
288: }
289:
290: TreeItem parent = curSelection.getParentItem();
291: while (parent != null) {
292: parent.setState(true);
293: parent = parent.getParentItem();
294: }
295: }
296:
297: /**
298: * Gets this tree's default image package.
299: *
300: * @return the tree's image package
301: * @see #setImageBase
302: * @deprecated Use {@link #Tree(TreeImages)} as it provides a more efficent
303: * and manageable way to supply a set of images to be used within
304: * a tree.
305: */
306: @Deprecated
307: public String getImageBase() {
308: return (images instanceof ImagesFromImageBase) ? ((ImagesFromImageBase) images)
309: .getBaseUrl()
310: : GWT.getModuleBaseURL();
311: }
312:
313: /**
314: * Gets the top-level tree item at the specified index.
315: *
316: * @param index the index to be retrieved
317: * @return the item at that index
318: */
319: public TreeItem getItem(int index) {
320: return root.getChild(index);
321: }
322:
323: /**
324: * Gets the number of items contained at the root of this tree.
325: *
326: * @return this tree's item count
327: */
328: public int getItemCount() {
329: return root.getChildCount();
330: }
331:
332: /**
333: * Gets the currently selected item.
334: *
335: * @return the selected item
336: */
337: public TreeItem getSelectedItem() {
338: return curSelection;
339: }
340:
341: public int getTabIndex() {
342: return FocusPanel.impl.getTabIndex(focusable);
343: }
344:
345: public Iterator<Widget> iterator() {
346: final Widget[] widgets = new Widget[childWidgets.size()];
347: childWidgets.keySet().toArray(widgets);
348: return WidgetIterators.createWidgetIterator(this , widgets);
349: }
350:
351: @Override
352: public void onBrowserEvent(Event event) {
353: int eventType = DOM.eventGetType(event);
354: switch (eventType) {
355: case Event.ONCLICK: {
356: Element e = DOM.eventGetTarget(event);
357: if (shouldTreeDelegateFocusToElement(e)) {
358: // The click event should have given focus to this element already.
359: // Avoid moving focus back up to the tree (so that focusable widgets
360: // attached to TreeItems can receive keyboard events).
361: } else {
362: setFocus(true);
363: }
364: break;
365: }
366: case Event.ONMOUSEDOWN: {
367: if (mouseListeners != null) {
368: mouseListeners.fireMouseEvent(this , event);
369: }
370:
371: // Currently, the way we're using image bundles causes extraneous events
372: // to be sunk on individual items' open/close images. This leads to an
373: // extra event reaching the Tree, which we will ignore here.
374: if (DOM.eventGetCurrentTarget(event).equals(getElement())) {
375: elementClicked(root, DOM.eventGetTarget(event));
376: }
377: break;
378: }
379:
380: case Event.ONMOUSEUP: {
381: if (mouseListeners != null) {
382: mouseListeners.fireMouseEvent(this , event);
383: }
384: break;
385: }
386:
387: case Event.ONMOUSEMOVE: {
388: if (mouseListeners != null) {
389: mouseListeners.fireMouseEvent(this , event);
390: }
391: break;
392: }
393:
394: case Event.ONMOUSEOVER: {
395: if (mouseListeners != null) {
396: mouseListeners.fireMouseEvent(this , event);
397: }
398: break;
399: }
400:
401: case Event.ONMOUSEOUT: {
402: if (mouseListeners != null) {
403: mouseListeners.fireMouseEvent(this , event);
404: }
405: break;
406: }
407:
408: case Event.ONFOCUS:
409: // If we already have focus, ignore the focus event.
410: if (focusListeners != null) {
411: focusListeners.fireFocusEvent(this , event);
412: }
413: break;
414:
415: case Event.ONBLUR: {
416: if (focusListeners != null) {
417: focusListeners.fireFocusEvent(this , event);
418: }
419:
420: break;
421: }
422:
423: case Event.ONKEYDOWN:
424: // If nothing's selected, select the first item.
425: if (curSelection == null) {
426: if (root.getChildCount() > 0) {
427: onSelection(root.getChild(0), true, true);
428: }
429: super .onBrowserEvent(event);
430: return;
431: }
432:
433: if (lastEventType == Event.ONKEYDOWN) {
434: // Two key downs in a row signal a duplicate event. Multiple key
435: // downs can be triggered in the current configuration independent
436: // of the browser.
437: return;
438: }
439:
440: // Handle keyboard events if keyboard navigation is enabled
441: if (isKeyboardNavigationEnabled(curSelection)) {
442: switch (DOM.eventGetKeyCode(event)) {
443: case KeyboardListener.KEY_UP: {
444: moveSelectionUp(curSelection);
445: DOM.eventPreventDefault(event);
446: break;
447: }
448: case KeyboardListener.KEY_DOWN: {
449: moveSelectionDown(curSelection, true);
450: DOM.eventPreventDefault(event);
451: break;
452: }
453: case KeyboardListener.KEY_LEFT: {
454: if (curSelection.getState()) {
455: curSelection.setState(false);
456: } else {
457: TreeItem parent = curSelection.getParentItem();
458: if (parent != null) {
459: setSelectedItem(parent);
460: }
461: }
462: DOM.eventPreventDefault(event);
463: break;
464: }
465: case KeyboardListener.KEY_RIGHT: {
466: if (!curSelection.getState()) {
467: curSelection.setState(true);
468: } else if (curSelection.getChildCount() > 0) {
469: setSelectedItem(curSelection.getChild(0));
470: }
471: DOM.eventPreventDefault(event);
472: break;
473: }
474: }
475: }
476:
477: // Intentional fallthrough.
478: case Event.ONKEYUP:
479: if (eventType == Event.ONKEYUP) {
480: // If we got here because of a key tab, then we need to make sure the
481: // current tree item is selected.
482: if (DOM.eventGetKeyCode(event) == KeyboardListener.KEY_TAB) {
483: ArrayList<Element> chain = new ArrayList<Element>();
484: collectElementChain(chain, getElement(), DOM
485: .eventGetTarget(event));
486: TreeItem item = findItemByChain(chain, 0, root);
487: if (item != getSelectedItem()) {
488: setSelectedItem(item, true);
489: }
490: }
491: }
492:
493: // Intentional fallthrough.
494: case Event.ONKEYPRESS: {
495: if (keyboardListeners != null) {
496: keyboardListeners.fireKeyboardEvent(this , event);
497: }
498: break;
499: }
500: }
501:
502: // We must call SynthesizedWidget's implementation for all other events.
503: super .onBrowserEvent(event);
504: lastEventType = eventType;
505: }
506:
507: public boolean remove(Widget w) {
508: // Validate.
509: TreeItem item = childWidgets.get(w);
510: if (item == null) {
511: return false;
512: }
513:
514: // Delegate to TreeItem.setWidget, which performs correct removal.
515: item.setWidget(null);
516: return true;
517: }
518:
519: public void removeFocusListener(FocusListener listener) {
520: if (focusListeners != null) {
521: focusListeners.remove(listener);
522: }
523: }
524:
525: /**
526: * Removes an item from the root level of this tree.
527: *
528: * @param item the item to be removed
529: */
530: public void removeItem(TreeItem item) {
531: root.removeItem(item);
532: }
533:
534: /**
535: * Removes all items from the root level of this tree.
536: */
537: public void removeItems() {
538: while (getItemCount() > 0) {
539: removeItem(getItem(0));
540: }
541: }
542:
543: public void removeKeyboardListener(KeyboardListener listener) {
544: if (keyboardListeners != null) {
545: keyboardListeners.remove(listener);
546: }
547: }
548:
549: public void removeTreeListener(TreeListener listener) {
550: if (listeners != null) {
551: listeners.remove(listener);
552: }
553: }
554:
555: public void setAccessKey(char key) {
556: FocusPanel.impl.setAccessKey(focusable, key);
557: }
558:
559: public void setFocus(boolean focus) {
560: if (focus) {
561: FocusPanel.impl.focus(focusable);
562: } else {
563: FocusPanel.impl.blur(focusable);
564: }
565: }
566:
567: /**
568: * Sets the base URL under which this tree will find its default images. These
569: * images must be named "tree_white.gif", "tree_open.gif", and
570: * "tree_closed.gif".
571: *
572: * @param baseUrl
573: * @deprecated Use {@link #Tree(TreeImages)} as it provides a more efficent
574: * and manageable way to supply a set of images to be used within
575: * a tree.
576: */
577: @Deprecated
578: public void setImageBase(String baseUrl) {
579: images = new ImagesFromImageBase(baseUrl);
580: root.updateStateRecursive();
581: }
582:
583: /**
584: * Selects a specified item.
585: *
586: * @param item the item to be selected, or <code>null</code> to deselect all
587: * items
588: */
589: public void setSelectedItem(TreeItem item) {
590: setSelectedItem(item, true);
591: }
592:
593: /**
594: * Selects a specified item.
595: *
596: * @param item the item to be selected, or <code>null</code> to deselect all
597: * items
598: * @param fireEvents <code>true</code> to allow selection events to be fired
599: */
600: public void setSelectedItem(TreeItem item, boolean fireEvents) {
601: if (item == null) {
602: if (curSelection == null) {
603: return;
604: }
605: curSelection.setSelected(false);
606: curSelection = null;
607: return;
608: }
609:
610: onSelection(item, fireEvents, true);
611: }
612:
613: public void setTabIndex(int index) {
614: FocusPanel.impl.setTabIndex(focusable, index);
615: }
616:
617: /**
618: * Iterator of tree items.
619: */
620: public Iterator<TreeItem> treeItemIterator() {
621: List<TreeItem> accum = new ArrayList<TreeItem>();
622: root.addTreeItems(accum);
623: return accum.iterator();
624: }
625:
626: @Override
627: protected void doAttachChildren() {
628: // Ensure that all child widgets are attached.
629: for (Iterator<Widget> it = iterator(); it.hasNext();) {
630: Widget child = it.next();
631: child.onAttach();
632: }
633: DOM.setEventListener(focusable, this );
634: }
635:
636: @Override
637: protected void doDetachChildren() {
638: // Ensure that all child widgets are detached.
639: for (Iterator<Widget> it = iterator(); it.hasNext();) {
640: Widget child = it.next();
641: child.onDetach();
642: }
643: DOM.setEventListener(focusable, null);
644: }
645:
646: /**
647: * Indicates if keyboard navigation is enabled for the Tree and for a given
648: * TreeItem. Subclasses of Tree can override this function to selectively
649: * enable or disable keyboard navigation.
650: *
651: * @param currentItem the currently selected TreeItem
652: * @return <code>true</code> if the Tree will response to arrow keys by
653: * changing the currently selected item
654: */
655: protected boolean isKeyboardNavigationEnabled(TreeItem currentItem) {
656: return true;
657: }
658:
659: @Override
660: protected void onLoad() {
661: root.updateStateRecursive();
662: }
663:
664: void adopt(Widget widget, TreeItem treeItem) {
665: assert (!childWidgets.containsKey(widget));
666: childWidgets.put(widget, treeItem);
667: widget.setParent(this );
668: }
669:
670: void fireStateChanged(TreeItem item) {
671: if (listeners != null) {
672: listeners.fireItemStateChanged(item);
673: }
674: }
675:
676: /*
677: * This method exists solely to support unit tests.
678: */
679: Map<Widget, TreeItem> getChildWidgets() {
680: return childWidgets;
681: }
682:
683: TreeImages getImages() {
684: return images;
685: }
686:
687: void orphan(Widget widget) {
688: // Validation should already be done.
689: assert (widget.getParent() == this );
690:
691: // Orphan.
692: widget.setParent(null);
693:
694: // Logical detach.
695: childWidgets.remove(widget);
696: }
697:
698: /**
699: * Collects parents going up the element tree, terminated at the tree root.
700: */
701: private void collectElementChain(ArrayList<Element> chain,
702: Element hRoot, Element hElem) {
703: if ((hElem == null) || DOM.compare(hElem, hRoot)) {
704: return;
705: }
706:
707: collectElementChain(chain, hRoot, DOM.getParent(hElem));
708: chain.add(hElem);
709: }
710:
711: private boolean elementClicked(TreeItem root, Element hElem) {
712: ArrayList<Element> chain = new ArrayList<Element>();
713: collectElementChain(chain, getElement(), hElem);
714:
715: TreeItem item = findItemByChain(chain, 0, root);
716: if (item != null) {
717: if (DOM.isOrHasChild(item.getImageElement(), hElem)) {
718: item.setState(!item.getState(), true);
719: return true;
720: } else if (DOM.isOrHasChild(item.getElement(), hElem)) {
721: onSelection(item, true,
722: !shouldTreeDelegateFocusToElement(hElem));
723: return true;
724: }
725: }
726:
727: return false;
728: }
729:
730: private TreeItem findDeepestOpenChild(TreeItem item) {
731: if (!item.getState()) {
732: return item;
733: }
734: return findDeepestOpenChild(item
735: .getChild(item.getChildCount() - 1));
736: }
737:
738: private TreeItem findItemByChain(ArrayList<Element> chain, int idx,
739: TreeItem root) {
740: if (idx == chain.size()) {
741: return root;
742: }
743:
744: Element hCurElem = chain.get(idx);
745: for (int i = 0, n = root.getChildCount(); i < n; ++i) {
746: TreeItem child = root.getChild(i);
747: if (DOM.compare(child.getElement(), hCurElem)) {
748: TreeItem retItem = findItemByChain(chain, idx + 1, root
749: .getChild(i));
750: if (retItem == null) {
751: return child;
752: }
753: return retItem;
754: }
755: }
756:
757: return findItemByChain(chain, idx + 1, root);
758: }
759:
760: /**
761: * Move the tree focus to the specified selected item.
762: *
763: * @param selection
764: */
765: private void moveFocus(TreeItem selection) {
766: HasFocus focusableWidget = selection.getFocusableWidget();
767: if (focusableWidget != null) {
768: focusableWidget.setFocus(true);
769: DOM.scrollIntoView(((Widget) focusableWidget).getElement());
770: } else {
771: // Get the location and size of the given item's content element relative
772: // to the tree.
773: Element selectedElem = selection.getContentElem();
774: int containerLeft = getAbsoluteLeft();
775: int containerTop = getAbsoluteTop();
776:
777: int left = DOM.getAbsoluteLeft(selectedElem)
778: - containerLeft;
779: int top = DOM.getAbsoluteTop(selectedElem) - containerTop;
780: int width = DOM.getElementPropertyInt(selectedElem,
781: "offsetWidth");
782: int height = DOM.getElementPropertyInt(selectedElem,
783: "offsetHeight");
784:
785: // Set the focusable element's position and size to exactly underlap the
786: // item's content element.
787: DOM.setIntStyleAttribute(focusable, "left", left);
788: DOM.setIntStyleAttribute(focusable, "top", top);
789: DOM.setIntStyleAttribute(focusable, "width", width);
790: DOM.setIntStyleAttribute(focusable, "height", height);
791:
792: // Scroll it into view.
793: DOM.scrollIntoView(focusable);
794:
795: // Ensure Focus is set, as focus may have been previously delegated by
796: // tree.
797: FocusPanel.impl.focus(focusable);
798: }
799: }
800:
801: /**
802: * Moves to the next item, going into children as if dig is enabled.
803: */
804: private void moveSelectionDown(TreeItem sel, boolean dig) {
805: if (sel == root) {
806: return;
807: }
808:
809: TreeItem parent = sel.getParentItem();
810: if (parent == null) {
811: parent = root;
812: }
813: int idx = parent.getChildIndex(sel);
814:
815: if (!dig || !sel.getState()) {
816: if (idx < parent.getChildCount() - 1) {
817: onSelection(parent.getChild(idx + 1), true, true);
818: } else {
819: moveSelectionDown(parent, false);
820: }
821: } else if (sel.getChildCount() > 0) {
822: onSelection(sel.getChild(0), true, true);
823: }
824: }
825:
826: /**
827: * Moves the selected item up one.
828: */
829: private void moveSelectionUp(TreeItem sel) {
830: TreeItem parent = sel.getParentItem();
831: if (parent == null) {
832: parent = root;
833: }
834: int idx = parent.getChildIndex(sel);
835:
836: if (idx > 0) {
837: TreeItem sibling = parent.getChild(idx - 1);
838: onSelection(findDeepestOpenChild(sibling), true, true);
839: } else {
840: onSelection(parent, true, true);
841: }
842: }
843:
844: private void onSelection(TreeItem item, boolean fireEvents,
845: boolean moveFocus) {
846: // 'root' isn't a real item, so don't let it be selected
847: // (some cases in the keyboard handler will try to do this)
848: if (item == root) {
849: return;
850: }
851:
852: if (curSelection != null) {
853: curSelection.setSelected(false);
854: }
855:
856: curSelection = item;
857:
858: if (moveFocus && curSelection != null) {
859: moveFocus(curSelection);
860:
861: // Select the item and fire the selection event.
862: curSelection.setSelected(true);
863: if (fireEvents && (listeners != null)) {
864: listeners.fireItemSelected(curSelection);
865: }
866: }
867: }
868:
869: private native boolean shouldTreeDelegateFocusToElement(Element elem) /*-{
870: var name = elem.nodeName;
871: return ((name == "SELECT") ||
872: (name == "INPUT") ||
873: (name == "TEXTAREA") ||
874: (name == "OPTION") ||
875: (name == "BUTTON") ||
876: (name == "LABEL"));
877: }-*/;
878: }
|