com.google.gwt.gen2.complexpanel.client.FastTree.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.gen2.complexpanel.client.FastTree.java

Source

/*
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.gen2.complexpanel.client;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasFocusHandlers;
import com.google.gwt.event.dom.client.HasKeyDownHandlers;
import com.google.gwt.event.dom.client.HasKeyPressHandlers;
import com.google.gwt.event.dom.client.HasKeyUpHandlers;
import com.google.gwt.event.dom.client.HasMouseDownHandlers;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.gen2.commonevent.shared.BeforeCloseEvent;
import com.google.gwt.gen2.commonevent.shared.BeforeCloseHandler;
import com.google.gwt.gen2.commonevent.shared.BeforeOpenEvent;
import com.google.gwt.gen2.commonevent.shared.BeforeOpenHandler;
import com.google.gwt.gen2.commonwidget.client.Decorator;
import com.google.gwt.gen2.commonwidget.client.impl.StandardCssImpl;
import com.google.gwt.gen2.widgetbase.client.Gen2CssInjector;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Accessibility;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.WidgetAdaptorImpl;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.google.gwt.widgetideas.client.event.KeyboardHandler;
import com.google.gwt.widgetideas.client.overrides.DOMHelper;
import com.google.gwt.widgetideas.client.overrides.WidgetIterators;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * Fast tree implementation.
 */
public class FastTree extends Panel
        implements HasClickHandlers, HasMouseDownHandlers, HasSelectionHandlers<FastTreeItem>, HasFocusHandlers,
        HasFastTreeItems, HasKeyDownHandlers, HasKeyPressHandlers, HasKeyUpHandlers, HasFastTreeItemHandlers {
    /**
     * Interface used to allow the widget access to css style names.
     * <p/>
     * The class names indicate the default gwt names for these styles.
     */
    static interface Css {

        String fastTree();

        String selectionBar();
    }

    static class StandardCss extends StandardCssImpl implements Css {

        static FastTree.Css DEFAULT_CSS;

        static Css ensureDefaultCss() {
            if (DEFAULT_CSS == null) {
                DEFAULT_CSS = createCss(STYLENAME_DEFAULT);
            }
            return DEFAULT_CSS;
        }

        public StandardCss(String styleName) {
            super(styleName, "gwt-FastTree");
        }

        public String fastTree() {
            return getWidgetStyleName();
        }

        public String selectionBar() {
            return " selection-bar";
        }
    }

    private static FocusImpl impl = FocusImpl.getFocusImplForPanel();

    static final String STYLENAME_DEFAULT = "gwt-FastTree";

    /**
     * Creates a {@link Css} instance with the given style name. Note, only the
     * primary style name changes.
     * 
     * @param styleName style name for the widget
     * @return the standard css
     */
    static Css createCss(final String styleName) {
        return new StandardCss(styleName);
    }

    /**
     * Injects the default styles as a css resource.
     */
    public static void injectDefaultCss() {
        Gen2CssInjector.addFastTreeDefault();
    }

    /**
     * Map of TreeItem.widget -> TreeItem.
     */
    private final Map<Widget, FastTreeItem> childWidgets = new HashMap<Widget, FastTreeItem>();
    private FastTreeItem curSelection;
    private final Element focusable;
    private final FastTreeItem root;
    private Event keyDown;
    private Event lastKeyDown;
    private boolean isKeyboardSupportEnabled = true;

    /**
     * Constructs a tree.
     */
    public FastTree() {
        this(StandardCss.ensureDefaultCss());
    }

    FastTree(final Css css) {
        setElement(DOM.createDiv());
        getElement().getStyle().setProperty("position", "relative");

        focusable = createFocusElement();
        setStyleName(focusable, css.selectionBar());

        sinkEvents(Event.ONMOUSEDOWN | Event.ONCLICK | Event.KEYEVENTS);

        // The 'root' item is invisible and serves only as a container
        // for all top-level items.
        root = buildRootItem();
        root.setTree(this);

        setStyleName(css.fastTree());
        moveFocusable(curSelection, null);

        // Add accessibility role to tree.
        Accessibility.setRole(getElement(), Accessibility.ROLE_TREE);
        Accessibility.setRole(focusable, Accessibility.ROLE_TREEITEM);
    }

    /**
     * Adds the widget as a root tree item.
     * 
     * @see com.google.gwt.user.client.ui.HasWidgets#add(com.google.gwt.user.client.ui.Widget)
     * @param widget widget to add.
     */
    @Override
    public void add(Widget widget) {
        addItem(widget);
    }

    public HandlerRegistration addBeforeCloseHandler(BeforeCloseHandler<FastTreeItem> handler) {
        return addHandler(handler, BeforeCloseEvent.getType());
    }

    public HandlerRegistration addBeforeOpenHandler(BeforeOpenHandler<FastTreeItem> handler) {
        return addHandler(handler, BeforeOpenEvent.getType());
    }

    public HandlerRegistration addBeforeSelectionHandler(BeforeSelectionHandler<FastTreeItem> handler) {
        return addHandler(handler, BeforeSelectionEvent.getType());
    }

    public HandlerRegistration addClickHandler(ClickHandler handler) {
        return addHandler(handler, ClickEvent.getType());
    }

    public HandlerRegistration addCloseHandler(CloseHandler<FastTreeItem> handler) {
        return addHandler(handler, CloseEvent.getType());
    }

    public HandlerRegistration addFocusHandler(FocusHandler handler) {
        return addHandler(handler, FocusEvent.getType());
    }

    public FastTreeItem addHtmlItem(String itemHtml) {
        FastTreeItem fastTreeItem = new FastTreeItem();
        fastTreeItem.setHTML(itemHtml);
        addItem(fastTreeItem);
        return fastTreeItem;
    }

    /**
     * Adds an item to the root level of this tree.
     * 
     * @param item the item to be added
     */
    public void addItem(FastTreeItem item) {
        getTreeRoot().addItem(item);
    }

    /**
     * Adds a simple tree item containing the specified html.
     * 
     * @param itemHtml the html of the item to be added
     * @return the item that was added
     * @deprecated use {@link #addHtmlItem(String)} instead
     */
    @Deprecated
    public FastTreeItem addItem(String itemHtml) {
        return addHtmlItem(itemHtml);
    }

    /**
     * Adds a new tree item containing the specified widget.
     * 
     * @param widget the widget to be added
     */
    public FastTreeItem addItem(Widget widget) {
        return getTreeRoot().addItem(widget);
    }

    public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
        return addHandler(handler, KeyDownEvent.getType());
    }

    public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
        return addHandler(handler, KeyPressEvent.getType());
    }

    public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) {
        return addHandler(handler, KeyUpEvent.getType());
    }

    public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
        return addDomHandler(handler, MouseDownEvent.getType());
    }

    public HandlerRegistration addOpenHandler(OpenHandler<FastTreeItem> handler) {
        return addHandler(handler, OpenEvent.getType());
    }

    public HandlerRegistration addSelectionHandler(SelectionHandler<FastTreeItem> handler) {
        return addHandler(handler, SelectionEvent.getType());
    }

    public FastTreeItem addTextItem(String itemText) {
        FastTreeItem fastTreeItem = new FastTreeItem();
        fastTreeItem.setText(itemText);
        addItem(fastTreeItem);
        return fastTreeItem;
    }

    /**
     * Clears all tree items from the current tree.
     */
    @Override
    public void clear() {
        int size = getTreeRoot().getChildCount();
        for (int i = size - 1; i >= 0; i--) {
            getTreeRoot().getChild(i).remove();
        }
    }

    /**
     * Ensures that the currently-selected item is visible, opening its parents
     * and scrolling the tree as necessary.
     */
    public void ensureSelectedItemVisible() {
        if (curSelection == null) {
            return;
        }

        FastTreeItem parent = curSelection.getParentItem();
        while (parent != null) {
            parent.setState(true);
            parent = parent.getParentItem();
        }
        moveFocus(curSelection, null);
    }

    public FastTreeItem getChild(int index) {
        return getTreeRoot().getChild(index);
    }

    public int getChildCount() {
        return getTreeRoot().getChildCount();
    }

    public int getChildIndex(FastTreeItem child) {
        return getTreeRoot().getChildIndex(child);
    }

    /**
     * Gets the top-level tree item at the specified index.
     * 
     * @param index the index to be retrieved
     * @return the item at that index
     */
    public FastTreeItem getItem(int index) {
        return getTreeRoot().getChild(index);
    }

    /**
     * Gets the number of items contained at the root of this tree.
     * 
     * @return this tree's item count
     */
    public int getItemCount() {
        return getTreeRoot().getChildCount();
    }

    /**
     * Gets the currently selected item.
     * 
     * @return the selected item
     */
    public FastTreeItem getSelectedItem() {
        return curSelection;
    }

    public int getTabIndex() {
        return impl.getTabIndex(focusable);
    }

    /**
     * The 'root' item is invisible and serves only as a container for all
     * top-level items.
     */
    public final FastTreeItem getTreeRoot() {
        return root;
    }

    /**
     * Check if keyboard support is enabled.
     * 
     * @return true if enabled, false if disabled
     */
    public boolean isKeyboardSupportEnabled() {
        return isKeyboardSupportEnabled;
    }

    public Iterator<Widget> iterator() {
        final Widget[] widgets = new Widget[childWidgets.size()];
        childWidgets.keySet().toArray(widgets);
        return WidgetIterators.createWidgetIterator(this, widgets);
    }

    @Override
    @SuppressWarnings("fallthrough")
    public void onBrowserEvent(Event e) {
        switch (e.getTypeInt()) {
        case Event.ONCLICK:
            Element element = DOM.eventGetTarget(e);
            if (shouldTreeDelegateFocusToElement(element)) {
                // The click event should have given focus to this element already.
                // Avoid moving focus back up to the tree (so that focusable widgets
                // attached to TreeItems can receive keyboard events).
            } else if (!hasModifiers(e)) {
                impl.focus(focusable);
            }
            return;
        case Event.ONMOUSEDOWN:
            boolean left = e.getButton() == Event.BUTTON_LEFT;

            if (left && !hasModifiers(e)) {
                elementClicked(getTreeRoot(), e);
            }
            return;

        case Event.ONKEYDOWN:
            keyDown = e;
            // Intentional fallthrough.
        case Event.ONKEYUP:
            if (e.getTypeInt() == Event.ONKEYUP) {
                // If we got here because of a key tab, then we need to make sure the
                // current tree item is selected.
                if (DOM.eventGetKeyCode(e) == KeyboardHandler.KEY_TAB) {
                    ArrayList<Element> chain = new ArrayList<Element>();
                    collectElementChain(chain, getElement(), DOM.eventGetTarget(e));
                    FastTreeItem item = findItemByChain(chain, 0, getTreeRoot());
                    if (item != getSelectedItem()) {
                        setSelectedItem(item, true);
                    }
                }
            }
            // Intentional fallthrough.
        case Event.ONKEYPRESS:
            if (!isKeyboardSupportEnabled || hasModifiers(e)) {
                break;
            }

            // Trying to avoid duplicate key downs and fire navigation despite
            // missing key downs.
            if (e.getTypeInt() != Event.ONKEYUP) {
                if (lastKeyDown == null || (!lastKeyDown.equals(keyDown))) {
                    keyboardNavigation(e);
                }
                if (e.getTypeInt() == Event.ONKEYPRESS) {
                    lastKeyDown = null;
                } else {
                    lastKeyDown = keyDown;
                }
            }
            if (DOMHelper.isArrowKey(DOM.eventGetKeyCode(e))) {
                DOM.eventCancelBubble(e, true);
                DOM.eventPreventDefault(e);
            }
            break;
        }
        super.onBrowserEvent(e);
    }

    @Override
    public boolean remove(Widget w) {
        // Validate.
        FastTreeItem item = childWidgets.get(w);
        if (item == null) {
            return false;
        }

        // Delegate to TreeItem.setWidget, which performs correct removal.
        item.setWidget(null);
        return true;
    }

    /**
     * Removes an item from the root level of this tree.
     * 
     * @param item the item to be removed
     */
    public void removeItem(FastTreeItem item) {
        getTreeRoot().removeItem(item);
    }

    /**
     * Removes all items from the root level of this tree.
     */
    public void removeItems() {
        while (getItemCount() > 0) {
            removeItem(getItem(0));
        }
    }

    /**
     * Enable or disable keyboard support. When disabled, the user will not be
     * able to navigate between tree items using the keyboard.
     * 
     * @param enabled true to enable, false to disable
     */
    public void setKeyboardSupportEnabled(boolean enabled) {
        this.isKeyboardSupportEnabled = enabled;
    }

    /**
     * Selects a specified item.
     * 
     * @param item the item to be selected, or <code>null</code> to deselect all
     *          items
     */
    public void setSelectedItem(FastTreeItem item) {
        setSelectedItem(item, true);
    }

    /**
     * Selects a specified item.
     * 
     * @param item the item to be selected, or <code>null</code> to deselect all
     *          items
     * @param fireEvents <code>true</code> to allow selection events to be fired
     */
    public void setSelectedItem(FastTreeItem item, boolean fireEvents) {
        if (item == null) {
            if (curSelection == null) {
                return;
            }
            curSelection.setSelection(false, fireEvents);
            curSelection = null;
            moveFocusable(null, null);
            return;
        }

        onSelection(item, fireEvents, true);
    }

    /**
     * Iterator of tree items.
     */
    public Iterator<FastTreeItem> treeItemIterator() {
        List<FastTreeItem> accum = new ArrayList<FastTreeItem>();
        getTreeRoot().dumpTreeItems(accum);
        return accum.iterator();
    }

    /**
     * @deprecated Use the public getTreeRoot() method instead.
     */
    @Deprecated
    protected FastTreeItem getRoot() {
        return root;
    }

    protected void keyboardNavigation(Event e) {
        // If nothing's selected, select the first item.
        if (curSelection == null) {
            if (getTreeRoot().getChildCount() > 0) {
                onSelection(getTreeRoot().getChild(0), true, true);
            }
            super.onBrowserEvent(e);
        } else {

            // Handle keyboard events if keyboard navigation is enabled

            switch (DOMHelper.standardizeKeycode(DOM.eventGetKeyCode(e))) {
            case KeyboardHandler.KEY_UP: {
                moveSelectionUp(curSelection);
                break;
            }
            case KeyboardHandler.KEY_DOWN: {
                moveSelectionDown(curSelection, true);
                break;
            }
            case KeyboardHandler.KEY_LEFT: {
                if (curSelection.isOpen()) {
                    curSelection.setState(false);
                } else {
                    FastTreeItem parent = curSelection.getParentItem();
                    if (parent != null) {
                        setSelectedItem(parent);
                    }
                }
                break;
            }
            case KeyboardHandler.KEY_RIGHT: {
                if (!curSelection.isOpen()) {
                    curSelection.setState(true);
                }
                // Do nothing if the element is already open.
                break;
            }
            }
        }
    }

    /**
     * Moves the selection bar around the given {@link FastTreeItem}.
     * 
     * @param item the item to move selection bar to
     * @deprecated as of April 16, 2009. use
     *             {@link #onSelection(FastTreeItem, boolean, boolean)} instead
     */
    @Deprecated
    protected void moveSelectionBar(FastTreeItem item) {
        moveFocusable(item, null);
    }

    /**
     * Moves to the next item, going into children as if dig is enabled.
     */
    protected void moveSelectionDown(FastTreeItem sel, boolean dig) {
        if (sel == getTreeRoot()) {
            return;
        }
        FastTreeItem parent = sel.getParentItem();
        if (parent == null) {
            parent = getTreeRoot();
        }
        int idx = parent.getChildIndex(sel);

        if (!dig || !sel.isOpen()) {
            if (idx < parent.getChildCount() - 1) {
                onSelection(parent.getChild(idx + 1), true, true);
            } else {
                moveSelectionDown(parent, false);
            }
        } else if (sel.getChildCount() > 0) {
            onSelection(sel.getChild(0), true, true);
        }
    }

    /**
     * Moves the selected item up one.
     */
    protected void moveSelectionUp(FastTreeItem sel) {
        FastTreeItem parent = sel.getParentItem();
        if (parent == null) {
            parent = getTreeRoot();
        }
        int idx = parent.getChildIndex(sel);

        if (idx > 0) {
            FastTreeItem sibling = parent.getChild(idx - 1);
            onSelection(findDeepestOpenChild(sibling), true, true);
        } else {
            onSelection(parent, true, true);
        }
    }

    @Override
    protected void onLoad() {
        if (getSelectedItem() != null) {
            moveFocusable(getSelectedItem(), null);
        }
    }

    /**
     * Called when a {@link FastTreeItem} is selected.
     * 
     * @param item the selected item
     * @param fireEvents true to fire events to handles
     * @param moveFocus true to move focus to the Tree
     */
    protected void onSelection(FastTreeItem item, boolean fireEvents, boolean moveFocus) {
        onSelection(item, fireEvents, moveFocus, null);
    }

    /**
     * Supply a decorator for the fast tree.
     * 
     * @return a decorator
     */
    protected Decorator supplyFastTreeDecorator() {
        return Decorator.DEFAULT;
    }

    /**
     * Supply a decorator for the tree items.
     * 
     * @return a decorator
     */
    protected Decorator supplyFastTreeItemDecorator() {
        return Decorator.DEFAULT;
    }

    void adopt(Widget widget, FastTreeItem treeItem) {
        assert (!childWidgets.containsKey(widget));
        childWidgets.put(widget, treeItem);
        WidgetAdaptorImpl.setParent(widget, this);
        //    super.adopt(widget);
    }

    /**
     * Called after the tree item is closed.
     */
    void afterClose(FastTreeItem fastTreeItem) {
        CloseEvent.fire(this, fastTreeItem);
    }

    /**
     * Called after the tree item is opened.
     */
    void afterOpen(FastTreeItem fastTreeItem) {
        OpenEvent.fire(this, fastTreeItem);
    }

    /**
     * Called before the tree item is closed.
     */

    void beforeClose(FastTreeItem fastTreeItem) {
        BeforeCloseEvent.fire(this, fastTreeItem);
    }

    /**
     * Called before the tree item is opened.
     */
    void beforeOpen(FastTreeItem fastTreeItem, boolean isFirstTime) {
        BeforeOpenEvent.fire(this, fastTreeItem, isFirstTime);
    }

    BeforeSelectionEvent<FastTreeItem> beforeSelected(FastTreeItem fastTreeItem) {
        return BeforeSelectionEvent.fire(this, fastTreeItem);
    }

    // @VisibleForTesting
    FastTreeItem findDeepestOpenChild(FastTreeItem item) {
        if (!item.isOpen() || item.getChildCount() == 0) {
            return item;
        }
        return findDeepestOpenChild(item.getChild(item.getChildCount() - 1));
    }

    /*
     * This method exists solely to support unit tests.
     */
    Map<Widget, FastTreeItem> getChildWidgets() {
        return childWidgets;
    }

    /**
     * Called when a tree item is selected.
     */
    void onSelected(FastTreeItem fastTreeItem) {
        SelectionEvent.fire(this, fastTreeItem);
    }

    void treeOrphan(Widget widget) {
        //    super.orphan(widget);
        WidgetAdaptorImpl.setParent(widget, null);

        // Logical detach.
        childWidgets.remove(widget);
    }

    /**
     * Helper to build the root item.
     */
    private FastTreeItem buildRootItem() {
        return new FastTreeItem() {
            @Override
            public void addItem(FastTreeItem item) {
                super.addItem(item);

                DOM.appendChild(FastTree.this.getElement(), item.getElement());

                // Explicitly set top-level items' parents to null.
                item.setParentItem(null);

                // Use no margin on top-most items.
                DOM.setIntStyleAttribute(item.getElement(), "margin", 0);
            }

            @Override
            public void removeItem(FastTreeItem item) {
                if (!getChildren().contains(item)) {
                    return;
                }

                // Update Item state.
                item.clearTree();
                item.setParentItem(null);
                getChildren().remove(item);

                DOM.removeChild(FastTree.this.getElement(), item.getElement());
            }
        };
    }

    /**
     * Collects parents going up the element tree, terminated at the tree root.
     */
    private void collectElementChain(ArrayList<Element> chain, Element hRoot, Element hElem) {
        if ((hElem == null) || hElem.equals(hRoot)) {
            return;
        }

        collectElementChain(chain, hRoot, DOM.getParent(hElem));
        chain.add(hElem);
    }

    private Element createFocusElement() {
        Element e = impl.createFocusable();
        e.getStyle().setProperty("position", "absolute");
        e.getStyle().setPropertyPx("width", 1);
        getElement().appendChild(e);
        DOM.sinkEvents(e, Event.FOCUSEVENTS | Event.ONMOUSEDOWN);
        // Needed for IE only
        e.setAttribute("focus", "false");
        return e;
    }

    /**
     * Disables the selection text on IE.
     */
    private native void disableSelection(Element element)
    /*-{
      element.onselectstart = function() {
        return false;
      };
    }-*/;

    private void elementClicked(FastTreeItem root, Event event) {
        Element target = DOM.eventGetTarget(event);
        ArrayList<Element> chain = new ArrayList<Element>();
        collectElementChain(chain, getElement(), target);
        FastTreeItem item = findItemByChain(chain, 0, root);
        if (item != null) {
            if (item.isInteriorNode() && item.getControlElement().equals(target)) {
                item.setState(!item.isOpen(), true);
                moveFocusable(curSelection, target);
                disableSelection(target);
                return;
            }
            onSelection(item, true, false, target);
        }
        return;
    }

    private FastTreeItem findItemByChain(ArrayList<Element> chain, int index, FastTreeItem root) {
        if (index == chain.size()) {
            return root;
        }

        Element hCurElem = chain.get(index);
        for (int i = 0, n = root.getChildCount(); i < n; ++i) {
            FastTreeItem child = root.getChild(i);
            if (child.getElement().equals(hCurElem)) {
                FastTreeItem retItem = findItemByChain(chain, index + 1, root.getChild(i));
                if (retItem == null) {
                    return child;
                }
                return retItem;
            }
        }

        return findItemByChain(chain, index + 1, root);
    }

    private boolean hasModifiers(Event event) {
        boolean alt = event.getAltKey();
        boolean ctrl = event.getCtrlKey();
        boolean meta = event.getMetaKey();
        boolean shift = event.getShiftKey();

        return alt || ctrl || meta || shift;
    }

    /**
     * Move the tree focus to the specified selected item.
     * 
     * @param selection the selected {@link FastTreeItem}
     * @param targetElem the element that was actually targeted
     */
    private void moveFocus(FastTreeItem selection, Element targetElem) {
        moveFocusable(selection, targetElem);
        DOM.scrollIntoView(focusable);
        Focusable focusableWidget = selection.getFocusable();
        if (focusableWidget != null) {
            focusableWidget.setFocus(true);
        } else {
            // Ensure Focus is set, as focus may have been previously delegated by
            // tree.
            impl.focus(focusable);
        }

        // Update ARIA attributes to reflect the information from the
        // newly-selected item.
        updateAriaAttributes(selection);
    }

    /**
     * Moves the selection bar around the given {@link FastTreeItem}.
     * 
     * @param item the item to move selection bar to
     * @param target the element to target
     */
    private void moveFocusable(FastTreeItem item, Element target) {
        if (item == null || item.isShowing() == false) {
            UIObject.setVisible(focusable, false);
            return;
        }

        // Get the element to focus around
        if (target == null) {
            target = item.getContentElement();
        }

        // Set the focusable's position and size to exactly underlap the target.
        int top = target.getAbsoluteTop() - getAbsoluteTop();
        int left = target.getAbsoluteLeft() - getAbsoluteLeft();
        focusable.getStyle().setPropertyPx("height", target.getOffsetHeight());
        focusable.getStyle().setPropertyPx("width", target.getOffsetWidth());
        focusable.getStyle().setPropertyPx("top", top);
        focusable.getStyle().setPropertyPx("left", left);
        UIObject.setVisible(focusable, true);
    }

    /**
     * Called when a {@link FastTreeItem} is selected.
     * 
     * @param item the selected item
     * @param fireEvents true to fire events to handles
     * @param moveFocus true to move focus to the Tree
     * @param targetElem the element that was actually targeted
     */
    private void onSelection(FastTreeItem item, boolean fireEvents, boolean moveFocus, Element targetElem) {
        // 'root' isn't a real item, so don't let it be selected
        // (some cases in the keyboard handler will try to do this)
        if (item == getTreeRoot()) {
            return;
        }

        // Don't fire events if the selection hasn't changed, but do move the
        // focusable element to the new target.
        if (curSelection == item) {
            moveFocusable(curSelection, targetElem);
            return;
        }

        if (fireEvents) {
            BeforeSelectionEvent<FastTreeItem> event = beforeSelected(item);
            if (event != null && event.isCanceled()) {
                return;
            }
        }

        if (curSelection != null) {
            curSelection.setSelection(false, fireEvents);
        }

        curSelection = item;

        if (curSelection != null) {
            if (moveFocus) {
                moveFocus(curSelection, targetElem);
            } else {
                // Move highlight even if we do no not need to move focus.
                moveFocusable(curSelection, targetElem);
            }

            // Select the item and fire the selection event.
            curSelection.setSelection(true, fireEvents);
        }
    }

    private native boolean shouldTreeDelegateFocusToElement(Element elem)
    /*-{
      var name = elem.nodeName;
      return ((name == "SELECT") ||
         (name == "INPUT")  ||
         (name == "TEXTAREA") ||
         (name == "OPTION") ||
         (name == "BUTTON") ||
         (name == "LABEL") 
      );
    }-*/;

    private void updateAriaAttributes(FastTreeItem selection) {

        // Set the 'aria-level' state. To do this, we need to compute the level of
        // the currently selected item.

        // We initialize itemLevel to -1 because the level value is zero-based.
        // Note that the root node is not a part of the TreeItem hierarchy, and we
        // do not consider the root node to have a designated level. The level of
        // the root's children is level 0, its children's children is level 1, etc.
        int curSelectionLevel = -1;
        FastTreeItem tmpItem = selection;

        while (tmpItem != null) {
            tmpItem = tmpItem.getParentItem();
            ++curSelectionLevel;
        }

        Element selectionContentElement = selection.getContentElement();
        Accessibility.setState(selectionContentElement, Accessibility.STATE_LEVEL,
                String.valueOf(curSelectionLevel + 1));

        // Set the 'aria-setsize' and 'aria-posinset' states. To do this, we need to
        // compute the the number of siblings that the currently selected item has,
        // and the item's position among its siblings.

        FastTreeItem curSelectionParent = selection.getParentItem();
        if (curSelectionParent == null) {
            curSelectionParent = getTreeRoot();
        }

        Accessibility.setState(selectionContentElement, Accessibility.STATE_SETSIZE,
                String.valueOf(curSelectionParent.getChildCount()));

        int selectionIndex = curSelectionParent.getChildIndex(selection);

        Accessibility.setState(selectionContentElement, Accessibility.STATE_POSINSET,
                String.valueOf(selectionIndex + 1));
    }
}