/*
* 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 org.gwt.mosaic.ui.client.tree;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.gwt.mosaic.override.client.DOMHelper;
import org.gwt.mosaic.override.client.WidgetIterators;
import org.gwt.mosaic.ui.client.Decorator;
import org.gwt.mosaic.ui.client.event.BeforeCloseEvent;
import org.gwt.mosaic.ui.client.event.BeforeCloseHandler;
import org.gwt.mosaic.ui.client.event.BeforeOpenEvent;
import org.gwt.mosaic.ui.client.event.BeforeOpenHandler;
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.KeyCodes;
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.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.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;
/**
* Fast tree implementation.
*/
public class FastTree extends Panel implements HasClickHandlers,
HasMouseDownHandlers, HasSelectionHandlers<FastTreeItem>, HasFocusHandlers,
HasFastTreeItems, HasKeyDownHandlers, HasKeyPressHandlers,
HasKeyUpHandlers, HasFastTreeItemHandlers {
private static FocusImpl impl = FocusImpl.getFocusImplForPanel();
static final String STYLENAME_DEFAULT = "gwt-FastTree";
/**
* 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() {
setElement(DOM.createDiv());
getElement().getStyle().setProperty("position", "relative");
focusable = createFocusElement();
setStyleName(focusable, "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(STYLENAME_DEFAULT);
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());
}
/**
* 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 text.
*
* @param itemText the text of the item to be added
* @return the item that was added
*/
public FastTreeItem addItem(String itemText) {
FastTreeItem fastTreeItem = new FastTreeItem(itemText);
addItem(fastTreeItem);
return fastTreeItem;
}
/**
* 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());
}
/**
* 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) == KeyCodes.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;
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 KeyCodes.KEY_UP: {
moveSelectionUp(curSelection);
break;
}
case KeyCodes.KEY_DOWN: {
moveSelectionDown(curSelection, true);
break;
}
case KeyCodes.KEY_LEFT: {
if (curSelection.isOpen()) {
curSelection.setState(false);
} else {
FastTreeItem parent = curSelection.getParentItem();
if (parent != null) {
setSelectedItem(parent);
}
}
break;
}
case KeyCodes.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));
}
}