changeset 6621:932776525610

RT-36391: Accessibility support for MenuBar Reviewed-by: fheidric
author Anthony Petrov <anthony.petrov@oracle.com>
date Fri, 04 Apr 2014 14:58:22 +0400
parents f8b1445d78b5
children 3ad008a21577
files modules/controls/src/main/java/com/sun/javafx/scene/control/skin/MenuBarSkin.java modules/controls/src/main/java/javafx/scene/control/MenuBar.java modules/graphics/src/main/java/com/sun/glass/ui/win/WinAccessible.java modules/graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java modules/graphics/src/main/java/javafx/scene/Scene.java modules/graphics/src/main/java/javafx/scene/accessibility/Attribute.java
diffstat 6 files changed, 118 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/MenuBarSkin.java	Fri Apr 04 13:53:55 2014 +1300
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/MenuBarSkin.java	Fri Apr 04 14:58:22 2014 +0400
@@ -42,6 +42,8 @@
 import javafx.geometry.NodeOrientation;
 import javafx.scene.Node;
 import javafx.scene.Scene;
+import javafx.scene.accessibility.Attribute;
+import javafx.scene.accessibility.Role;
 import javafx.scene.control.CustomMenuItem;
 import javafx.scene.control.Menu;
 import javafx.scene.control.MenuBar;
@@ -61,6 +63,7 @@
 import java.util.List;
 import java.util.WeakHashMap;
 import com.sun.javafx.menu.MenuBase;
+import com.sun.javafx.scene.SceneHelper;
 import com.sun.javafx.scene.control.GlobalMenuAdapter;
 import com.sun.javafx.scene.control.behavior.BehaviorBase;
 import com.sun.javafx.scene.traversal.Direction;
@@ -272,7 +275,7 @@
                 // RT-23147 when MenuBar's focusTraversable is true the first
                 // menu will visually indicate focus
                 unSelectMenus();
-                focusedMenuIndex = 0;
+                menuModeStart(0);
                 openMenuButton = ((MenuBarButton)container.getChildren().get(0));
                 openMenu = getSkinnable().getMenus().get(0);
                 openMenuButton.setHover();
@@ -390,7 +393,7 @@
 //                        container.getChildren().get(0).requestFocus();
                         if (focusedMenuIndex != 0) {
                             unSelectMenus();
-                            focusedMenuIndex = 0;
+                            menuModeStart(0);
                             openMenuButton = ((MenuBarButton)container.getChildren().get(0));
                             openMenu = getSkinnable().getMenus().get(0);
                             openMenuButton.setHover();
@@ -585,7 +588,7 @@
             menuButton.menuListener = (observable, oldValue, newValue) -> {
                 if (menu.isShowing()) {
                     menuButton.show();
-                    focusedMenuIndex = container.getChildren().indexOf(menuButton);
+                    menuModeStart(container.getChildren().indexOf(menuButton));
                 } else {
                     menuButton.hide();
                 }
@@ -623,7 +626,7 @@
                         openMenu.show();
                     }
                     // update FocusedIndex
-                    focusedMenuIndex = getMenuBarButtonIndex(menuButton);
+                    menuModeStart(getMenuBarButtonIndex(menuButton));
                 }
             });
             
@@ -753,7 +756,7 @@
             openMenuButton = (MenuBarButton)container.getChildren().get(focusedMenuIndex);
             openMenuButton.clearHover();
             openMenuButton = null;
-            focusedMenuIndex = -1;
+            menuModeEnd();
         }
     }
     
@@ -768,6 +771,23 @@
             openMenuButton.clearHover();
             openMenuButton = null;
         }
+        menuModeEnd();
+    }
+
+    private void menuModeStart(int newIndex) {
+        if (focusedMenuIndex == -1) {
+            SceneHelper.getSceneAccessor().setTransientFocusContainer(getSkinnable().getScene(), getSkinnable());
+        }
+        focusedMenuIndex = newIndex;
+    }
+
+    private void menuModeEnd() {
+        if (focusedMenuIndex != -1) {
+            SceneHelper.getSceneAccessor().setTransientFocusContainer(getSkinnable().getScene(), null);
+
+            /* Return the a11y focus to a control in the scene. */
+            getSkinnable().accSendNotification(Attribute.FOCUS_NODE);
+        }
         focusedMenuIndex = -1;
     }
     
@@ -844,7 +864,7 @@
             }
             index++;
         }
-        focusedMenuIndex = -1;
+        menuModeEnd();
     }
 
     private void clearMenuButtonHover() {
@@ -882,6 +902,18 @@
         
         private void setHover() {
             setHover(true);
+
+            /* Transfer the a11y focus to an item in the menu bar. */
+            menuBarSkin.getSkinnable().accSendNotification(Attribute.FOCUS_NODE);
+        }
+
+        @Override public Object accGetAttribute(Attribute attribute, Object... parameters) {
+            switch (attribute) {
+                case ROLE: return Role.MENU_ITEM;
+                case TITLE: return getText();
+                case FOCUS_ITEM: return MenuBarButton.this;
+                default: return super.accGetAttribute(attribute, parameters);
+            }
         }
     }
 
@@ -937,4 +969,20 @@
     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
         return getSkinnable().prefHeight(-1);
     }
+
+
+
+    /***************************************************************************
+     *                                                                         *
+     * Accessibility handling                                                  *
+     *                                                                         *
+     **************************************************************************/
+
+    /** @treatAsPrivate */
+    @Override public Object accGetAttribute(Attribute attribute, Object... parameters) {
+        switch (attribute) {
+            case FOCUS_NODE: return openMenuButton;
+            default: return super.accGetAttribute(attribute, parameters);
+        }
+    }
 }
--- a/modules/controls/src/main/java/javafx/scene/control/MenuBar.java	Fri Apr 04 13:53:55 2014 +1300
+++ b/modules/controls/src/main/java/javafx/scene/control/MenuBar.java	Fri Apr 04 14:58:22 2014 +0400
@@ -40,6 +40,9 @@
 import com.sun.javafx.scene.control.skin.MenuBarSkin;
 import javafx.css.Styleable;
 import javafx.css.StyleableProperty;
+import javafx.scene.accessibility.Action;
+import javafx.scene.accessibility.Attribute;
+import javafx.scene.accessibility.Role;
 
 /**
  * <p>
@@ -222,5 +225,22 @@
     protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
         return Boolean.FALSE;
     }
+
+
+
+    /***************************************************************************
+     *                                                                         *
+     * Accessibility handling                                                  *
+     *                                                                         *
+     **************************************************************************/
+
+    /** @treatAsPrivate */
+    @Override public Object accGetAttribute(Attribute attribute, Object... parameters) {
+        switch (attribute) {
+            case ROLE: return Role.MENU_BAR;
+            case FOCUS_NODE: // Skin
+            default: return super.accGetAttribute(attribute, parameters);
+        }
+    }
 }
 
--- a/modules/graphics/src/main/java/com/sun/glass/ui/win/WinAccessible.java	Fri Apr 04 13:53:55 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/win/WinAccessible.java	Fri Apr 04 14:58:22 2014 +0400
@@ -246,10 +246,26 @@
         switch (notification) {
             case FOCUS_NODE:
                 if (getView() != null) {
+                    // This is a Scene
                     long focus = GetFocus();
                     if (focus != 0) {
                         UiaRaiseAutomationEvent(focus, UIA_AutomationFocusChangedEventId);
                     }
+                } else {
+                    // This is a Scene.transientFocusContainer
+                    Node node = (Node)getAttribute(FOCUS_NODE);
+                    if (node != null) {
+                        UiaRaiseAutomationEvent(getAccessible(node), UIA_AutomationFocusChangedEventId);
+                    } else {
+                        // Delegate back to the Scene if the transient focus owner is null
+                        Scene scene = (Scene)getAttribute(SCENE);
+                        if (scene != null) {
+                            Accessible acc = scene.getAccessible();
+                            if (acc != null) {
+                                acc.sendNotification(FOCUS_NODE);
+                            }
+                        }
+                    }
                 }
                 break;
             case SELECTED_PAGE: {
@@ -449,6 +465,7 @@
             case SCROLL_PANE: return UIA_PaneControlTypeId;
             case SCROLL_BAR: return UIA_ScrollBarControlTypeId;
             case THUMB: return UIA_ThumbControlTypeId;
+            case MENU_BAR: return UIA_MenuBarControlTypeId;
             default: return 0;
         }
     }
@@ -672,7 +689,7 @@
                  * Windows won't work correctly unless the accessible returned in GetFocus() 
                  * answer TRUE in UIA_HasKeyboardFocusPropertyId.
                  * Note that UIA_HasKeyboardFocusPropertyId reports true for the main parent
-                 * of a 'focus item', but that doesn't seen to cause problems.
+                 * of a 'focus item', but that doesn't seem to cause problems.
                  */
                 if (Boolean.FALSE.equals(focus)) {
                     Scene scene = (Scene)getAttribute(SCENE);
--- a/modules/graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java	Fri Apr 04 13:53:55 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/javafx/scene/SceneHelper.java	Fri Apr 04 14:58:22 2014 +0400
@@ -26,6 +26,7 @@
 package com.sun.javafx.scene;
 
 import javafx.scene.Camera;
+import javafx.scene.Node;
 import javafx.scene.Parent;
 import javafx.scene.Scene;
 
@@ -67,6 +68,11 @@
         sceneAccessor = newAccessor;
     }
 
+    public static SceneAccessor getSceneAccessor() {
+        if (sceneAccessor == null) throw new IllegalStateException();
+        return sceneAccessor;
+    }
+
     public interface SceneAccessor {
         void setPaused(boolean paused);
 
@@ -75,6 +81,8 @@
         Camera getEffectiveCamera(Scene scene);
 
         Scene createPopupScene(Parent root);
+
+        void setTransientFocusContainer(Scene scene, Node node);
     }
 
     private static void forceInit(final Class<?> classToInit) {
--- a/modules/graphics/src/main/java/javafx/scene/Scene.java	Fri Apr 04 13:53:55 2014 +1300
+++ b/modules/graphics/src/main/java/javafx/scene/Scene.java	Fri Apr 04 14:58:22 2014 +0400
@@ -156,6 +156,12 @@
 
     private Camera defaultCamera;
 
+    /**
+     * A node that is temporarily responsible for the FOCUS_NODE
+     * accessibility attribute. E.g. a currently active MenuBar.
+     */
+    private Node transientFocusContainer;
+
     //Neither width nor height are initialized and will be calculated according to content when this Scene
     //is shown for the first time.
 //    public Scene() {
@@ -392,6 +398,11 @@
                                        }
                                    };
                         }
+
+                        @Override
+                        public void setTransientFocusContainer(Scene scene, Node node) {
+                            scene.transientFocusContainer = node;
+                        }
                     });
         }
 
@@ -6196,7 +6207,12 @@
                         }
                         case ROLE: return Role.PARENT;
                         case SCENE: return Scene.this;
-                        case FOCUS_NODE: return getFocusOwner();
+                        case FOCUS_NODE: {
+                            if (transientFocusContainer != null) {
+                                return transientFocusContainer.accGetAttribute(Attribute.FOCUS_NODE);
+                            }
+                            return getFocusOwner();
+                        }
                         default:
                     }
                     return super.getAttribute(attribute, parameters);
--- a/modules/graphics/src/main/java/javafx/scene/accessibility/Attribute.java	Fri Apr 04 13:53:55 2014 +1300
+++ b/modules/graphics/src/main/java/javafx/scene/accessibility/Attribute.java	Fri Apr 04 14:58:22 2014 +0400
@@ -128,6 +128,7 @@
      * Type: Node
      *
      * This attribute is requested to the Scene, where it maps to {@link Scene#focusOwnerProperty()}
+     * The Scene can delegate the request to its current transient focus container.
      */
     FOCUS_NODE("FocusNode", Node.class),