8160893: [macosx] JMenuItems in JPopupMenu are not accessible

Post events for MenuOpened/Closed/ItemSelected

Reviewed-by: ant, alexsch
This commit is contained in:
Pete Brunet 2016-10-06 20:31:59 -05:00
parent 6e147fc49f
commit 27ca3765bb
3 changed files with 194 additions and 15 deletions

View File

@ -29,11 +29,9 @@ import sun.lwawt.LWWindowPeer;
import java.awt.*;
import java.beans.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.concurrent.Callable;
import sun.awt.AWTAccessor;
import javax.accessibility.*;
import javax.swing.*;
@ -73,8 +71,20 @@ class CAccessibility implements PropertyChangeListener {
}
public void propertyChange(final PropertyChangeEvent evt) {
if (evt.getNewValue() == null) return;
focusChanged();
Object newValue = evt.getNewValue();
if (newValue == null) return;
// Don't post focus on things that don't matter, i.e. alert, colorchooser,
// desktoppane, dialog, directorypane, filechooser, filler, fontchoose,
// frame, glasspane, layeredpane, optionpane, panel, rootpane, separator,
// tooltip, viewport, window.
// List taken from initializeRoles() in JavaComponentUtilities.m.
if (newValue instanceof Accessible) {
AccessibleContext nvAC = ((Accessible) newValue).getAccessibleContext();
AccessibleRole nvRole = nvAC.getAccessibleRole();
if (!ignoredRoles.contains(roleKey(nvRole))) {
focusChanged();
}
}
}
private native void focusChanged();
@ -683,9 +693,15 @@ class CAccessibility implements PropertyChangeListener {
if (context == null) continue;
if (whichChildren == JAVA_AX_VISIBLE_CHILDREN) {
if (!context.getAccessibleComponent().isVisible()) continue;
AccessibleComponent acomp = context.getAccessibleComponent();
if (acomp == null || !acomp.isVisible()) {
continue;
}
} else if (whichChildren == JAVA_AX_SELECTED_CHILDREN) {
if (!ac.getAccessibleSelection().isAccessibleChildSelected(i)) continue;
AccessibleSelection sel = ac.getAccessibleSelection();
if (sel == null || !sel.isAccessibleChildSelected(i)) {
continue;
}
}
if (!allowIgnored) {

View File

@ -39,7 +39,10 @@ import javax.swing.event.ChangeListener;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_CARET_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_STATE_PROPERTY;
import static javax.accessibility.AccessibleContext.ACCESSIBLE_TEXT_PROPERTY;
import javax.accessibility.AccessibleRole;
import javax.accessibility.AccessibleState;
import sun.awt.AWTAccessor;
@ -63,6 +66,9 @@ class CAccessible extends CFRetainedResource implements Accessible {
private static native void valueChanged(long ptr);
private static native void selectedTextChanged(long ptr);
private static native void selectionChanged(long ptr);
private static native void menuOpened(long ptr);
private static native void menuClosed(long ptr);
private static native void menuItemSelected(long ptr);
private Accessible accessible;
@ -111,16 +117,45 @@ class CAccessible extends CFRetainedResource implements Accessible {
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if ( ptr != 0 ) {
Object newValue = e.getNewValue();
Object oldValue = e.getOldValue();
if (name.compareTo(ACCESSIBLE_CARET_PROPERTY) == 0) {
selectedTextChanged(ptr);
} else if (name.compareTo(ACCESSIBLE_TEXT_PROPERTY) == 0 ) {
valueChanged(ptr);
} else if (name.compareTo(ACCESSIBLE_SELECTION_PROPERTY) == 0 ) {
selectionChanged(ptr);
} else if (name.compareTo(ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY) == 0 ) {
Object nv = e.getNewValue();
if (nv instanceof AccessibleContext) {
activeDescendant = (AccessibleContext)nv;
} else if (name.compareTo(ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY) == 0 ) {
if (newValue instanceof AccessibleContext) {
activeDescendant = (AccessibleContext)newValue;
}
} else if (name.compareTo(ACCESSIBLE_STATE_PROPERTY) == 0) {
AccessibleContext thisAC = accessible.getAccessibleContext();
AccessibleRole thisRole = thisAC.getAccessibleRole();
Accessible parentAccessible = thisAC.getAccessibleParent();
AccessibleRole parentRole = null;
if (parentAccessible != null) {
parentRole = parentAccessible.getAccessibleContext().getAccessibleRole();
}
// At least for now don't handle combo box menu state changes.
// This may change when later fixing issues which currently
// exist for combo boxes, but for now the following is only
// for JPopupMenus, not for combobox menus.
if (parentRole != AccessibleRole.COMBO_BOX) {
if (thisRole == AccessibleRole.POPUP_MENU) {
if ( newValue != null &&
((AccessibleState)newValue) == AccessibleState.VISIBLE ) {
menuOpened(ptr);
} else if ( oldValue != null &&
((AccessibleState)oldValue) == AccessibleState.VISIBLE ) {
menuClosed(ptr);
}
} else if (thisRole == AccessibleRole.MENU_ITEM) {
if ( newValue != null &&
((AccessibleState)newValue) == AccessibleState.FOCUSED ) {
menuItemSelected(ptr);
}
}
}
}
}

View File

@ -66,7 +66,6 @@ static JNF_CLASS_CACHE(sjc_CAccessible, "sun/lwawt/macosx/CAccessible");
static JNF_MEMBER_CACHE(jf_ptr, sjc_CAccessible, "ptr", "J");
static JNF_STATIC_MEMBER_CACHE(sjm_getCAccessible, sjc_CAccessible, "getCAccessible", "(Ljavax/accessibility/Accessible;)Lsun/lwawt/macosx/CAccessible;");
static jobject sAccessibilityClass = NULL;
// sAttributeNamesForRoleCache holds the names of the attributes to which each java
@ -213,6 +212,24 @@ static NSObject *sAttributeNamesLOCK = nil;
NSAccessibilityPostNotification(self, NSAccessibilitySelectedChildrenChangedNotification);
}
- (void)postMenuOpened
{
AWT_ASSERT_APPKIT_THREAD;
NSAccessibilityPostNotification(self, (NSString *)kAXMenuOpenedNotification);
}
- (void)postMenuClosed
{
AWT_ASSERT_APPKIT_THREAD;
NSAccessibilityPostNotification(self, (NSString *)kAXMenuClosedNotification);
}
- (void)postMenuItemSelected
{
AWT_ASSERT_APPKIT_THREAD;
NSAccessibilityPostNotification(self, (NSString *)kAXMenuItemSelectedNotification);
}
- (BOOL)isEqual:(id)anObject
{
if (![anObject isKindOfClass:[self class]]) return NO;
@ -278,8 +295,7 @@ static NSObject *sAttributeNamesLOCK = nil;
+ (jobject) getCAccessible:(jobject)jaccessible withEnv:(JNIEnv *)env {
if (JNFIsInstanceOf(env, jaccessible, &sjc_CAccessible)) {
return jaccessible;
}
else if (JNFIsInstanceOf(env, jaccessible, &sjc_Accessible)) {
} else if (JNFIsInstanceOf(env, jaccessible, &sjc_Accessible)) {
return JNFCallStaticObjectMethod(env, sjm_getCAccessible, jaccessible);
}
return NULL;
@ -368,6 +384,14 @@ static NSObject *sAttributeNamesLOCK = nil;
// must init freshly -alloc'd object
[newChild initWithParent:parent withEnv:env withAccessible:jCAX withIndex:index withView:view withJavaRole:javaRole]; // must init new instance
// If creating a JPopupMenu (not a combobox popup list) need to fire menuOpened.
// This is the only way to know if the menu is opening; visible state change
// can't be caught because the listeners are not set up in time.
if ( [javaRole isEqualToString:@"popupmenu"] &&
![[parent javaRole] isEqualToString:@"combobox"] ) {
[newChild postMenuOpened];
}
// must hard retain pointer poked into Java object
[newChild retain];
JNFSetLongField(env, jCAX, jf_ptr, ptr_to_jlong(newChild));
@ -634,6 +658,15 @@ static NSObject *sAttributeNamesLOCK = nil;
return moreNames;
}
}
// popupmenu's return values not selected children
if ( [javaRole isEqualToString:@"popupmenu"] &&
![[[self parent] javaRole] isEqualToString:@"combobox"] ) {
NSMutableArray *moreNames =
[[NSMutableArray alloc] initWithCapacity: [names count] + 1];
[moreNames addObjectsFromArray: names];
[moreNames addObject:NSAccessibilityValueAttribute];
return moreNames;
}
return names;
} // end @synchronized
@ -707,6 +740,7 @@ static NSObject *sAttributeNamesLOCK = nil;
return value;
}
- (BOOL)accessibilityIsChildrenAttributeSettable
{
return NO;
@ -939,6 +973,13 @@ static NSObject *sAttributeNamesLOCK = nil;
if (fNSRole == nil) {
NSString *javaRole = [self javaRole];
fNSRole = [sRoles objectForKey:javaRole];
// The sRoles NSMutableDictionary maps popupmenu to Mac's popup button.
// JComboBox behavior currently relies on this. However this is not the
// proper mapping for a JPopupMenu so fix that.
if ( [javaRole isEqualToString:@"popupmenu"] &&
![[[self parent] javaRole] isEqualToString:@"combobox"] ) {
fNSRole = NSAccessibilityMenuRole;
}
if (fNSRole == nil) {
// this component has assigned itself a custom AccessibleRole not in the sRoles array
fNSRole = javaRole;
@ -947,6 +988,7 @@ static NSObject *sAttributeNamesLOCK = nil;
}
return fNSRole;
}
- (BOOL)accessibilityIsRoleAttributeSettable
{
return NO;
@ -1046,8 +1088,7 @@ static NSObject *sAttributeNamesLOCK = nil;
- (NSString *)accessibilitySubroleAttribute
{
NSString *value = nil;
if ([[self javaRole] isEqualToString:@"passwordtext"])
{
if ([[self javaRole] isEqualToString:@"passwordtext"]) {
value = NSAccessibilitySecureTextFieldSubrole;
}
/*
@ -1123,6 +1164,45 @@ static NSObject *sAttributeNamesLOCK = nil;
JNIEnv* env = [ThreadUtilities getJNIEnv];
// Need to handle popupmenus differently.
//
// At least for now don't handle combo box menus.
// This may change when later fixing issues which currently
// exist for combo boxes, but for now the following is only
// for JPopupMenus, not for combobox menus.
id parent = [self parent];
if ( [[self javaRole] isEqualToString:@"popupmenu"] &&
![[parent javaRole] isEqualToString:@"combobox"] ) {
NSArray *children =
[JavaComponentAccessibility childrenOfParent:self
withEnv:env
withChildrenCode:JAVA_AX_ALL_CHILDREN
allowIgnored:YES];
if ([children count] > 0) {
// handle case of AXMenuItem
// need to ask menu what is selected
NSArray *selectedChildrenOfMenu =
[self accessibilitySelectedChildrenAttribute];
JavaComponentAccessibility *selectedMenuItem =
[selectedChildrenOfMenu objectAtIndex:0];
if (selectedMenuItem != nil) {
jobject itemValue =
JNFCallStaticObjectMethod( env,
sjm_getAccessibleName,
selectedMenuItem->fAccessible,
selectedMenuItem->fComponent ); // AWT_THREADING Safe (AWTRunLoop)
if (itemValue == NULL) {
return nil;
}
NSString* itemString = JNFJavaToNSString(env, itemValue);
(*env)->DeleteLocalRef(env, itemValue);
return itemString;
} else {
return nil;
}
}
}
// ask Java for the component's accessibleValue. In java, the "accessibleValue" just means a numerical value
// a text value is taken care of in JavaTextAccessibility
@ -1343,6 +1423,54 @@ JNF_COCOA_ENTER(env);
JNF_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CAccessible
* Method: menuOpened
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_menuOpened
(JNIEnv *env, jclass jklass, jlong element)
{
JNF_COCOA_ENTER(env);
[ThreadUtilities performOnMainThread:@selector(postMenuOpened)
on:(JavaComponentAccessibility *)jlong_to_ptr(element)
withObject:nil
waitUntilDone:NO];
JNF_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CAccessible
* Method: menuClosed
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_menuClosed
(JNIEnv *env, jclass jklass, jlong element)
{
JNF_COCOA_ENTER(env);
[ThreadUtilities performOnMainThread:@selector(postMenuClosed)
on:(JavaComponentAccessibility *)jlong_to_ptr(element)
withObject:nil
waitUntilDone:NO];
JNF_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CAccessible
* Method: menuItemSelected
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_menuItemSelected
(JNIEnv *env, jclass jklass, jlong element)
{
JNF_COCOA_ENTER(env);
[ThreadUtilities performOnMainThread:@selector(postMenuItemSelected)
on:(JavaComponentAccessibility *)jlong_to_ptr(element)
withObject:nil
waitUntilDone:NO];
JNF_COCOA_EXIT(env);
}
/*
* Class: sun_lwawt_macosx_CAccessible
* Method: unregisterFromCocoaAXSystem