diff --git a/apps/toys/Hello/src/main/java/hello/HelloPopupMenu.java b/apps/toys/Hello/src/main/java/hello/HelloPopupMenu.java --- a/apps/toys/Hello/src/main/java/hello/HelloPopupMenu.java +++ b/apps/toys/Hello/src/main/java/hello/HelloPopupMenu.java @@ -25,11 +25,14 @@ package hello; +import com.sun.webkit.ContextMenuItem; + import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ContextMenu; +import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.paint.Color; import javafx.stage.Stage; @@ -64,10 +67,27 @@ MenuItem item3 = new MenuItem("Templates"); item3.setOnAction(e -> System.out.println("Templates")); + + MenuItem item4 = new MenuItem("About"); + item1.setOnAction(e -> System.out.println("About")); - popupMenu.getItems().add(item1); - popupMenu.getItems().add(item2); - popupMenu.getItems().add(item3); + MenuItem item5 = new MenuItem("Preferences"); + item2.setOnAction(e -> System.out.println("Preferences")); + + MenuItem item6 = new MenuItem("Templates"); + item3.setOnAction(e -> System.out.println("Templates")); + + Menu menu = new Menu(); + menu.getItems().add(item1); + menu.getItems().add(item2); + menu.getItems().add(item3); + menu.setText("Another Menu"); + + + popupMenu.getItems().add(item4); + popupMenu.getItems().add(item5); + popupMenu.getItems().add(item6); + popupMenu.getItems().add(menu); final Button button = new Button("Click me"); button.setContextMenu(popupMenu); @@ -77,6 +97,11 @@ root.getChildren().add(button); return scene; } + + class TestMenuItem extends MenuItem { + + + } /** * @param args the command line arguments diff --git a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuContent.java b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuContent.java --- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuContent.java +++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuContent.java @@ -53,6 +53,8 @@ import javafx.geometry.*; import javafx.scene.Node; import javafx.scene.Parent; +import javafx.scene.accessibility.Attribute; +import javafx.scene.accessibility.Role; import javafx.scene.control.*; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; @@ -1429,7 +1431,23 @@ return null; } - + + @Override + public Object accGetAttribute(Attribute attribute, Object... parameters) { + switch(attribute) { + case TITLE: return item.getText(); + case ROLE: return Role.MENUITEM; + } + return super.accGetAttribute(attribute, parameters); + } + } + + @Override + public Object accGetAttribute(Attribute attribute, Object... parameters) { + switch(attribute) { + case ROLE: return Role.CONTEXTMENU; + } + return super.accGetAttribute(attribute, parameters); } diff --git a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuSkin.java b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuSkin.java --- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuSkin.java +++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ContextMenuSkin.java @@ -30,6 +30,7 @@ import javafx.event.Event; import javafx.event.EventHandler; import javafx.scene.Node; +import javafx.scene.accessibility.Attribute; import javafx.scene.control.ContextMenu; import javafx.scene.control.Menu; import javafx.scene.control.Skin; @@ -125,4 +126,22 @@ root.styleProperty().unbind(); if (tlFocus != null) tlFocus.dispose(); } + + /** + * Accessibility hook to allow controls to use the skin to redefine attributes. + */ + public Object accGetAttribute(Attribute attribute, Object... parameters) { + + /* + * Because the skin is does not inherit from SkinBase, we have make sure we got to the right Class + * */ + if (BehaviorSkinBase.IS_TOUCH_SUPPORTED && + popupMenu.getStyleClass().contains("text-input-context-menu")) { + EmbeddedTextContextMenuContent menuContent = (EmbeddedTextContextMenuContent)root; + return menuContent.accGetAttribute(attribute, parameters); + } else { + ContextMenuContent menuContent = (ContextMenuContent)root; + return menuContent.accGetAttribute(attribute, parameters); + } + } } diff --git a/modules/controls/src/main/java/javafx/scene/control/ChoiceBox.java b/modules/controls/src/main/java/javafx/scene/control/ChoiceBox.java --- a/modules/controls/src/main/java/javafx/scene/control/ChoiceBox.java +++ b/modules/controls/src/main/java/javafx/scene/control/ChoiceBox.java @@ -36,10 +36,14 @@ import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.event.ActionEvent; +import javafx.scene.accessibility.Action; +import javafx.scene.accessibility.Attribute; +import javafx.scene.accessibility.Role; import javafx.util.StringConverter; +import javafx.css.PseudoClass; -import javafx.css.PseudoClass; import com.sun.javafx.scene.control.skin.ChoiceBoxSkin; + import javafx.beans.DefaultProperty; /** @@ -164,6 +168,7 @@ private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper() { @Override protected void invalidated() { pseudoClassStateChanged(SHOWING_PSEUDOCLASS_STATE, get()); + accSendNotification(Attribute.EXPANDED); } @Override @@ -282,6 +287,7 @@ if (sm != null) { sm.select(super.getValue()); } + accSendNotification(Attribute.TITLE); } }; public final void setValue(T value) { valueProperty().set(value); } @@ -413,4 +419,35 @@ } } } + + /** @treatAsPrivate */ + @Override + public Object accGetAttribute(Attribute attribute, Object... parameters) { + switch(attribute) { + case ROLE: return Role.CHOICEBOX; + case TITLE: + //let the skin first. + Object title = super.accGetAttribute(attribute, parameters); + if (title != null) return title; + StringConverter converter = getConverter(); + if (converter == null) { + return getValue() != null ? getValue().toString() : ""; + } + return converter.toString(getValue()); + case EXPANDED: + System.err.println("_____-------EXPANDING"); + return isShowing(); + default: return super.accGetAttribute(attribute, parameters); + } + } + + @Override + public void accExecuteAction(Action action, Object... parameters) { + switch (action) { + case COLLAPSE: hide(); break; + case EXPAND: show(); break; + default: super.accExecuteAction(action); break; + } + } + } diff --git a/modules/controls/src/main/java/javafx/scene/control/ContextMenu.java b/modules/controls/src/main/java/javafx/scene/control/ContextMenu.java --- a/modules/controls/src/main/java/javafx/scene/control/ContextMenu.java +++ b/modules/controls/src/main/java/javafx/scene/control/ContextMenu.java @@ -38,10 +38,12 @@ import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Scene; +import javafx.scene.accessibility.Attribute; import javafx.stage.Window; import com.sun.javafx.Utils; import com.sun.javafx.collections.TrackableObservableList; +import com.sun.javafx.scene.control.skin.ContextMenuContent; import com.sun.javafx.scene.control.skin.ContextMenuSkin; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -273,8 +275,23 @@ doShow(anchor, screenX, screenY); } + boolean isRootMenu; + Node owner; //HACK: save for reference when hiding; + private void doShow(Node anchor, double screenX, double screenY) { Event.fireEvent(this, new Event(Menu.ON_SHOWING)); + //HACK - not sure if this is best time/place for these calls. + if (anchor instanceof ContextMenuContent.MenuItemContainer) { + //sub menus + System.err.println("MenuOpen"); + anchor.accSendNotification(Attribute.MENUOPEN); + } else { + //Since ContextMenus are recursive, the root menu calls start + System.err.println("MenuStart"); + anchor.accSendNotification(Attribute.MENUSTART); + isRootMenu = true; + } + owner = anchor; if(isImpl_showRelativeToWindow()) { final Scene scene = (anchor == null) ? null : anchor.getScene(); final Window win = (scene == null) ? null : scene.getWindow(); @@ -297,6 +314,13 @@ Event.fireEvent(this, new Event(Menu.ON_HIDING)); super.hide(); Event.fireEvent(this, new Event(Menu.ON_HIDDEN)); + if (isRootMenu) { + System.err.println("MenuEnd"); + owner.accSendNotification(Attribute.MENUEND); + } else { + System.err.println("MenuClose"); + owner.accSendNotification(Attribute.MENUCLOSE); + } } /** {@inheritDoc} */ diff --git a/modules/graphics/src/main/java/com/sun/glass/ui/PlatformAccessible.java b/modules/graphics/src/main/java/com/sun/glass/ui/PlatformAccessible.java --- a/modules/graphics/src/main/java/com/sun/glass/ui/PlatformAccessible.java +++ b/modules/graphics/src/main/java/com/sun/glass/ui/PlatformAccessible.java @@ -27,6 +27,7 @@ import static javafx.scene.accessibility.Attribute.PARENT; import static javafx.scene.accessibility.Attribute.ROLE; +import javafx.scene.Scene; import javafx.scene.Node; import javafx.scene.accessibility.Accessible; import javafx.scene.accessibility.Action; @@ -51,12 +52,38 @@ } public void dispose() { - if (accessible != null) accessible = null; - view = null; + accessible = null; +// view = null; KEEP IT } protected boolean isDisposed() { - return accessible == null; + if (accessible == null) return true; + if (view != null) { + //in the tear down process the view is closed first and the accessible (to it) sometime later. + //it is possible the accessible is called back during this window. + //musta void work as it leads to crash + if (accessible != null && view.getNativeView() == 0) { + new Exception("+++++++called in the mean time " + this + " scene " + getAttribute(Attribute.SCENE)).printStackTrace(System.out); + } + if (view.isClosed()) return true; + } else { +// //the window is gettign disposed +// Scene scene = (Scene)getAttribute(Attribute.SCENE); +// if (scene == null) return true; +// Accessible acc = (Accessible)scene.getAccessible(); +// if (acc == null) return true; +// PlatformAccessible pacc = acc.impl_getDelegate(); +// if (pacc.isDisposed()) return true; + Scene scene = (Scene)getAttribute(Attribute.SCENE); + if (scene == null) return true; + Accessible acc = (Accessible)scene.getAccessible(); + if (acc == null) return true; + if (acc != null) { +// PlatformAccessible pacc = acc.impl_getDelegate(); +// if (pacc.isDisposed()) return true; + } + } + return false; } public String toString() { diff --git a/modules/graphics/src/main/java/com/sun/glass/ui/View.java b/modules/graphics/src/main/java/com/sun/glass/ui/View.java --- a/modules/graphics/src/main/java/com/sun/glass/ui/View.java +++ b/modules/graphics/src/main/java/com/sun/glass/ui/View.java @@ -517,6 +517,11 @@ if (host != null) { host.setView(null); // will call this.setWindow(null) } +// if (viewAcc != null) { +// viewAcc.dispose(); +// viewAcc = null; +// } + System.out.println("VIEW IS DISPOSED"); this.isValid = false; _close(this.ptr); this.ptr = 0; @@ -1095,13 +1100,14 @@ * On Windows it happens on WM_GETOBJECT. * On Mac it happens on NSView#accessibilityAttributeNames. */ - long getAccessible() { + Accessible viewAcc;//keep track to diposed in close() + public long getAccessible() { Application.checkEventThread(); checkNotClosed(); if (accessible) { - Accessible acc = eventHandler.getSceneAccessible(); - if (acc != null) { - PlatformAccessible pAcc = acc.impl_getDelegate(); + viewAcc = eventHandler.getSceneAccessible(); + if (viewAcc != null) { + PlatformAccessible pAcc = viewAcc.impl_getDelegate(); pAcc.setView(this); return pAcc.getNativeAccessible(); } diff --git a/modules/graphics/src/main/java/com/sun/glass/ui/win/WinAccessible.java b/modules/graphics/src/main/java/com/sun/glass/ui/win/WinAccessible.java --- a/modules/graphics/src/main/java/com/sun/glass/ui/win/WinAccessible.java +++ b/modules/graphics/src/main/java/com/sun/glass/ui/win/WinAccessible.java @@ -123,6 +123,9 @@ private static final int UIA_HyperlinkControlTypeId = 50005; private static final int UIA_ImageControlTypeId = 50006; private static final int UIA_ListControlTypeId = 50008; + private static final int UIA_MenuControlTypeId = 50009; + private static final int UIA_MenuBarControlTypeId = 50010; + private static final int UIA_MenuItemControlTypeId = 50011; private static final int UIA_ProgressBarControlTypeId = 50012; private static final int UIA_RadioButtonControlTypeId = 50013; private static final int UIA_ScrollBarControlTypeId = 50014; @@ -154,8 +157,12 @@ private static final int RowOrColumnMajor_Indeterminate = 2; /* Event ID constants */ + private static final int UIA_MenuOpenedEventId = 20003; private static final int UIA_AutomationPropertyChangedEventId= 20004; private static final int UIA_AutomationFocusChangedEventId = 20005; + private static final int UIA_MenuClosedEventId = 20007; + private static final int UIA_MenuModeEndEventId = 20019; + private static final int UIA_MenuModeStartEventId = 20018; private static final int UIA_SelectionItem_ElementRemovedFromSelectionEventId = 20011; private static final int UIA_SelectionItem_ElementSelectedEventId = 20012; @@ -233,7 +240,41 @@ @Override public void sendNotification(Attribute notification) { + if (isDisposed()) return; + switch (notification) { + case MENUSTART: + case MENUEND: + case MENUOPEN: + case MENUCLOSE: + return; +// case MENUSTART: { +// System.err.println("++Event : " + notification + " from: " + this); +// // UiaRaiseAutomationEvent(peer, UIA_MenuModeStartEventId); +// Scene scene = (Scene)getAttribute(SCENE); +// if (scene != null) { +// long ptr = ((WinAccessible)scene.getAccessible().impl_getDelegate()).getNativeAccessible(); +// UiaRaiseAutomationEvent(ptr, UIA_MenuModeStartEventId); +// } +// break; +// } +// case MENUOPEN: +// System.err.println("++Event : " + notification + " from: " + this); +// UiaRaiseAutomationEvent(peer, UIA_MenuOpenedEventId); +// break; +// case MENUCLOSE: +// System.err.println("++Event : " + notification + " from: " + this); +// UiaRaiseAutomationEvent(peer, UIA_MenuClosedEventId); +// break; +// case MENUEND: +// System.err.println("++Event : " + notification + " from: " + this); +//// UiaRaiseAutomationEvent(peer, UIA_MenuModeEndEventId); +// Scene scene = (Scene)getAttribute(SCENE); +// if (scene != null) { +// long ptr = ((WinAccessible)scene.getAccessible().impl_getDelegate()).getNativeAccessible(); +// UiaRaiseAutomationEvent(ptr, UIA_MenuModeEndEventId); +// } +// break; case FOCUS_NODE: if (getView() != null) { long focus = GetFocus(); @@ -411,6 +452,8 @@ Role role = (Role)getAttribute(ROLE); if (role == null) return UIA_GroupControlTypeId; switch (role) { + case CONTEXTMENU: return UIA_MenuControlTypeId; + case MENUITEM: return UIA_MenuItemControlTypeId; case BUTTON: case TOGGLE_BUTTON: case INCREMENT_BUTTON: @@ -434,6 +477,7 @@ case IMAGE: return UIA_ImageControlTypeId; case RADIO_BUTTON: return UIA_RadioButtonControlTypeId; case CHECKBOX: return UIA_CheckBoxControlTypeId; + case CHOICEBOX: case COMBOBOX: return UIA_ComboBoxControlTypeId; case HYPERLINK: return UIA_HyperlinkControlTypeId; case TREE_VIEW: return UIA_TreeControlTypeId; @@ -458,6 +502,7 @@ Role role = (Role)getAttribute(ROLE); boolean impl = false; switch (role) { + case MENUITEM: case HYPERLINK: case BUTTON: case INCREMENT_BUTTON: @@ -544,6 +589,10 @@ case TOGGLE_BUTTON: impl = patternId == UIA_TogglePatternId; break; + case CHOICEBOX: + impl = patternId == UIA_ValuePatternId || + patternId == UIA_ExpandCollapsePatternId; + break; case TITLED_PANE: case TOOLBAR: impl = patternId == UIA_ExpandCollapsePatternId; @@ -765,8 +814,25 @@ switch (direction) { case NavigateDirection_Parent: { /* Return null for the top level node */ - if (getView() != null) return 0L; - + View view = getView() ; + if (view != null) { +// ///but this window can be owned!!! +// Window window = view.getWindow(); +// if (window == null) { +// System.out.println("View Without Window WTF"); +// return 0L; +// } +// Window parentWindow = window.getOwner(); +// if (parentWindow != null) { +// View parentView = parentWindow.getView(); +// System.out.println("asking for the parent of the popup "+ parentView + " " + this); +// if (parentView != null) { +// return parentView.getAccessible(); +// } +// +// } + return 0l; + } if (treeCell) { node = (Node)getAttribute(TREE_ITEM_PARENT); if (node == null) { @@ -782,8 +848,11 @@ if (node == null) { /* scene root node case */ Scene scene = (Scene)getAttribute(SCENE); + if (!scene.getWindow().isShowing()) { + System.out.println("---------------------------------------- SCENE NO VISIBLE, CAN IT BE DISPOSED"); + } if (scene != null) { - Accessible acc = scene.getAccessible(); + Accessible acc = scene.getAccessible();//IF THE ENTIRE TREE IS GETTING DISPOSED, THIS CAN CAUSE NEW OBJECT TO GET CREATED WinAccessible winAcc = (WinAccessible)acc.impl_getDelegate(); return winAcc.getNativeAccessible(); } @@ -830,6 +899,10 @@ } } } + } else { + //LOOK FOR WINDOWS + System.out.println("********************************************"); + } break; } @@ -851,11 +924,15 @@ ObservableList children = (ObservableList)getAttribute(CHILDREN); int size = children != null ? children.size() : 0; if (size > 0) { + //SHOULD CHILDREN WINDOW BE FIRST OR LAST (FOR THE VIEW!= NULL CASE) if (direction == NavigateDirection_FirstChild) { node = children.get(0); } else { node = children.get(size - 1); } + } else { + //no root node but possibly sub window2s ? + } } break; @@ -879,9 +956,11 @@ long GetFocus() { Node node = (Node)getAttribute(FOCUS_NODE); + System.err.println(this + " - Node: " + node); if (node == null) return 0L; Node item = (Node)node.getAccessible().getAttribute(FOCUS_ITEM); if (item != null) return getAccessible(item); + System.err.println(" - Item: " + item); return getAccessible(node); } diff --git a/modules/graphics/src/main/java/javafx/scene/Scene.java b/modules/graphics/src/main/java/javafx/scene/Scene.java --- a/modules/graphics/src/main/java/javafx/scene/Scene.java +++ b/modules/graphics/src/main/java/javafx/scene/Scene.java @@ -710,7 +710,9 @@ tk.removeSceneTkPulseListener(scenePulseListener); impl_peer.dispose(); impl_peer = null; + //let this be disposed by glass along with another native resources if (accessible != null) { + System.out.println("SCENE ACCESSIBLE DISPOSED"); accessible.dispose(); accessible = null; } @@ -6152,6 +6154,7 @@ * @treatAsPrivate */ public Accessible getAccessible() { + if (impl_peer == null) return null; if (accessible == null) { accessible = new Accessible() { @Override public Object getAttribute(Attribute attribute, diff --git a/modules/graphics/src/main/java/javafx/scene/accessibility/Accessible.java b/modules/graphics/src/main/java/javafx/scene/accessibility/Accessible.java --- a/modules/graphics/src/main/java/javafx/scene/accessibility/Accessible.java +++ b/modules/graphics/src/main/java/javafx/scene/accessibility/Accessible.java @@ -88,6 +88,8 @@ * @param notification the attribute which value has changed */ public void sendNotification(Attribute notification) { - delegate.sendNotification(notification); + if (delegate != null) { + delegate.sendNotification(notification); + } } } diff --git a/modules/graphics/src/main/java/javafx/scene/accessibility/Attribute.java b/modules/graphics/src/main/java/javafx/scene/accessibility/Attribute.java --- a/modules/graphics/src/main/java/javafx/scene/accessibility/Attribute.java +++ b/modules/graphics/src/main/java/javafx/scene/accessibility/Attribute.java @@ -363,6 +363,10 @@ * Type: Boolean */ VISITED("Visited", Boolean.class), + MENUOPEN("OpenMenu", Node.class), + MENUCLOSE("CloseMenu", Node.class), + MENUSTART("StartMenu", Node.class), + MENUEND("EndMenu", Node.class), ; private String name; diff --git a/modules/graphics/src/main/java/javafx/scene/accessibility/Role.java b/modules/graphics/src/main/java/javafx/scene/accessibility/Role.java --- a/modules/graphics/src/main/java/javafx/scene/accessibility/Role.java +++ b/modules/graphics/src/main/java/javafx/scene/accessibility/Role.java @@ -31,6 +31,8 @@ * @treatAsPrivate */ public enum Role { + CONTEXTMENU, + MENUITEM, /** * Accordion @@ -51,6 +53,13 @@ * Actions: FIRE */ CHECKBOX, + + /** + * ChoiceBox. + * Attributes: Title, TOGGLE_STATE + * Actions: Fire + */ + CHOICEBOX, /** * ComboBox.