Fix for issue #1566. Patch by: ajr. Desk review by jgw. git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2465 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java index e7ddd75..7b8ca3a 100644 --- a/user/src/com/google/gwt/user/client/ui/Tree.java +++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -15,18 +15,18 @@ */ package com.google.gwt.user.client.ui; -import com.google.gwt.core.client.GWT; -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.i18n.client.LocaleInfo; - import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import com.google.gwt.core.client.GWT; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; + /** * A standard hierarchical tree widget. The tree contains a hierarchy of * {@link com.google.gwt.user.client.ui.TreeItem TreeItems} that the user can @@ -110,6 +110,11 @@ return baseUrl; } } + + private static final int OTHER_KEY_UP = 63232; + private static final int OTHER_KEY_DOWN = 63233; + private static final int OTHER_KEY_LEFT = 63234; + private static final int OTHER_KEY_RIGHT = 63235; static native boolean shouldTreeDelegateFocusToElement(Element elem) /*-{ var name = elem.nodeName; @@ -120,6 +125,51 @@ (name == "BUTTON") || (name == "LABEL")); }-*/; + + private static boolean isArrowKey(int code) { + switch (code) { + case OTHER_KEY_DOWN: + case OTHER_KEY_RIGHT: + case OTHER_KEY_UP: + case OTHER_KEY_LEFT: + case KeyboardListener.KEY_DOWN: + case KeyboardListener.KEY_RIGHT: + case KeyboardListener.KEY_UP: + case KeyboardListener.KEY_LEFT: + return true; + default: + return false; + } + } + + /** + * Normalized key codes. Also switches KEY_RIGHT and KEY_LEFT in RTL + * languages. + */ + private static int standardizeKeycode(int code) { + switch (code) { + case OTHER_KEY_DOWN: + code = KeyboardListener.KEY_DOWN; + break; + case OTHER_KEY_RIGHT: + code = KeyboardListener.KEY_RIGHT; + break; + case OTHER_KEY_UP: + code = KeyboardListener.KEY_UP; + break; + case OTHER_KEY_LEFT: + code = KeyboardListener.KEY_LEFT; + break; + } + if (LocaleInfo.getCurrentLocale().isRTL()) { + if (code == KeyboardListener.KEY_RIGHT) { + code = KeyboardListener.KEY_LEFT; + } else if (code == KeyboardListener.KEY_LEFT) { + code = KeyboardListener.KEY_RIGHT; + } + } + return code; + } /** * Map of TreeItem.widget -> TreeItem. @@ -134,16 +184,11 @@ private TreeListenerCollection listeners; private MouseListenerCollection mouseListeners = null; private TreeItem root; + private boolean lastWasKeyDown; - /** - * Keeps track of the last event type seen. We do this to determine if we have - * a duplicate key down. - */ - private int lastEventType; private Image leafImage; private Image openImage; private Image closedImage; - private String indentValue; /** @@ -334,8 +379,33 @@ } @Override + @SuppressWarnings("fallthrough") public void onBrowserEvent(Event event) { int eventType = DOM.eventGetType(event); + + switch (eventType) { + case Event.ONKEYDOWN: { + // If nothing's selected, select the first item. + if (curSelection == null) { + if (root.getChildCount() > 0) { + onSelection(root.getChild(0), true, true); + } + super.onBrowserEvent(event); + return; + } + } + + // Intentional fallthrough. + case Event.ONKEYPRESS: + case Event.ONKEYUP: + // Issue 1890: Do not block history navigation via alt+left/right + if (DOM.eventGetAltKey(event) || DOM.eventGetMetaKey(event)) { + super.onBrowserEvent(event); + return; + } + break; + } + switch (eventType) { case Event.ONCLICK: { Element e = DOM.eventGetTarget(event); @@ -404,97 +474,55 @@ break; } - + + case Event.ONKEYDOWN: { + keyboardNavigation(event); + lastWasKeyDown = true; + break; + } + + case Event.ONKEYPRESS: { + if (!lastWasKeyDown) { + keyboardNavigation(event); + } + lastWasKeyDown = false; + break; + } + + case Event.ONKEYUP: { + if (DOM.eventGetKeyCode(event) == KeyboardListener.KEY_TAB) { + ArrayList<Element> chain = new ArrayList<Element>(); + collectElementChain(chain, getElement(), DOM.eventGetTarget(event)); + TreeItem item = findItemByChain(chain, 0, root); + if (item != getSelectedItem()) { + setSelectedItem(item, true); + } + } + + lastWasKeyDown = false; + break; + } + } + + switch (eventType) { case Event.ONKEYDOWN: - // Issue 1890: Do not block history navigation via alt+left/right - if (DOM.eventGetAltKey(event) || DOM.eventGetMetaKey(event)) { - super.onBrowserEvent(event); - return; - } - - // If nothing's selected, select the first item. - if (curSelection == null) { - if (root.getChildCount() > 0) { - onSelection(root.getChild(0), true, true); - } - super.onBrowserEvent(event); - return; - } - - if (lastEventType == Event.ONKEYDOWN) { - // Two key downs in a row signal a duplicate event. Multiple key - // downs can be triggered in the current configuration independent - // of the browser. - return; - } - - // Handle keyboard events if keyboard navigation is enabled - if (isKeyboardNavigationEnabled(curSelection)) { - switch (DOM.eventGetKeyCode(event)) { - case KeyboardListener.KEY_UP: { - moveSelectionUp(curSelection); - DOM.eventPreventDefault(event); - break; - } - case KeyboardListener.KEY_DOWN: { - moveSelectionDown(curSelection, true); - DOM.eventPreventDefault(event); - break; - } - case KeyboardListener.KEY_LEFT: { - - if (LocaleInfo.getCurrentLocale().isRTL()) { - maybeExpandTreeItem(); - } else { - maybeCollapseTreeItem(); - } - - DOM.eventPreventDefault(event); - break; - } - case KeyboardListener.KEY_RIGHT: { - - if (LocaleInfo.getCurrentLocale().isRTL()) { - maybeCollapseTreeItem(); - } else { - maybeExpandTreeItem(); - } - - DOM.eventPreventDefault(event); - break; - } - } - } - - // Intentional fallthrough. case Event.ONKEYUP: - if (eventType == 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(event) == KeyboardListener.KEY_TAB) { - ArrayList<Element> chain = new ArrayList<Element>(); - collectElementChain(chain, getElement(), DOM.eventGetTarget(event)); - TreeItem item = findItemByChain(chain, 0, root); - if (item != getSelectedItem()) { - setSelectedItem(item, true); - } - } - } - - // Intentional fallthrough. case Event.ONKEYPRESS: { if (keyboardListeners != null) { keyboardListeners.fireKeyboardEvent(this, event); } + if (isArrowKey(DOM.eventGetKeyCode(event))) { + DOM.eventCancelBubble(event, true); + DOM.eventPreventDefault(event); + } break; } } // We must call SynthesizedWidget's implementation for all other events. super.onBrowserEvent(event); - lastEventType = eventType; } - + public boolean remove(Widget w) { // Validate. TreeItem item = childWidgets.get(w); @@ -911,6 +939,35 @@ Accessibility.setRole(focusable, Accessibility.ROLE_TREEITEM); } + private void keyboardNavigation(Event event) { + // Handle keyboard events if keyboard navigation is enabled + if (isKeyboardNavigationEnabled(curSelection)) { + int code = DOM.eventGetKeyCode(event); + + switch (standardizeKeycode(code)) { + case KeyboardListener.KEY_UP: { + moveSelectionUp(curSelection); + break; + } + case KeyboardListener.KEY_DOWN: { + moveSelectionDown(curSelection, true); + break; + } + case KeyboardListener.KEY_LEFT: { + maybeCollapseTreeItem(); + break; + } + case KeyboardListener.KEY_RIGHT: { + maybeExpandTreeItem(); + break; + } + default: { + return; + } + } + } + } + private void maybeCollapseTreeItem() { TreeItem topClosedParent = getTopClosedParent(curSelection); @@ -1100,8 +1157,7 @@ Element curSelectionContentElem = curSelection.getContentElem(); // Set the 'aria-level' state. To do this, we need to compute the level of - // the - // currently selected item. + // 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 hierachy, and we @@ -1121,8 +1177,7 @@ // 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. + // and the item's position among its siblings. TreeItem curSelectionParent = curSelection.getParentItem(); if (curSelectionParent == null) { @@ -1161,10 +1216,10 @@ Accessibility.STATE_SELECTED, "true"); // Update the 'aria-activedescendant' state for the focusable element to - // match the id - // of the currently selected item + // match the id of the currently selected item Accessibility.setState(focusable, Accessibility.STATE_ACTIVEDESCENDANT, DOM.getElementAttribute(curSelectionContentElem, "id")); } -} \ No newline at end of file +} + \ No newline at end of file