1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.lwawt.macosx; 27 28 import java.awt.*; 29 import java.beans.*; 30 import java.lang.reflect.Field; 31 import java.lang.reflect.InvocationTargetException; 32 import java.util.*; 33 import java.util.concurrent.Callable; 34 35 import javax.accessibility.*; 36 import javax.swing.*; 37 38 class CAccessibility implements PropertyChangeListener { 39 private static Set<String> ignoredRoles; 40 41 static { 42 // Need to load the native library for this code. 43 java.security.AccessController.doPrivileged( 44 new java.security.PrivilegedAction<Void>() { 45 public Void run() { 46 System.loadLibrary("awt"); 47 return null; 48 } 49 }); 50 } 51 52 static CAccessibility sAccessibility; 53 static synchronized CAccessibility getAccessibility(final String[] roles) { 54 if (sAccessibility != null) return sAccessibility; 55 sAccessibility = new CAccessibility(); 56 57 if (roles != null) { 58 ignoredRoles = new HashSet<String>(roles.length); 59 for (final String role : roles) ignoredRoles.add(role); 60 } else { 61 ignoredRoles = new HashSet<String>(); 62 } 63 64 return sAccessibility; 65 } 66 67 private CAccessibility() { 68 KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", this); 69 } 70 71 public void propertyChange(final PropertyChangeEvent evt) { 72 if (evt.getNewValue() == null) return; 73 focusChanged(); 74 } 75 76 private native void focusChanged(); 77 78 static <T> T invokeAndWait(final Callable<T> callable, final Component c) { 79 try { 80 return LWCToolkit.invokeAndWait(callable, c); 81 } catch (final Exception e) { e.printStackTrace(); } 82 return null; 83 } 84 85 static void invokeLater(final Runnable runnable, final Component c) { 86 try { 87 LWCToolkit.invokeLater(runnable, c); 88 } catch (InvocationTargetException e) { e.printStackTrace(); } 89 } 90 91 public static String getAccessibleActionDescription(final AccessibleAction aa, final int index, final Component c) { 92 if (aa == null) return null; 93 94 return invokeAndWait(new Callable<String>() { 95 public String call() throws Exception { 96 return aa.getAccessibleActionDescription(index); 97 } 98 }, c); 99 } 100 101 public static void doAccessibleAction(final AccessibleAction aa, final int index, final Component c) { 102 // We make this an invokeLater because we don't need a reply. 103 if (aa == null) return; 104 105 invokeLater(new Runnable() { 106 public void run() { 107 aa.doAccessibleAction(index); 108 } 109 }, c); 110 } 111 112 public static Dimension getSize(final AccessibleComponent ac, final Component c) { 113 if (ac == null) return null; 114 115 return invokeAndWait(new Callable<Dimension>() { 116 public Dimension call() throws Exception { 117 return ac.getSize(); 118 } 119 }, c); 120 } 121 122 public static AccessibleSelection getAccessibleSelection(final AccessibleContext ac, final Component c) { 123 if (ac == null) return null; 124 125 return invokeAndWait(new Callable<AccessibleSelection>() { 126 public AccessibleSelection call() throws Exception { 127 return ac.getAccessibleSelection(); 128 } 129 }, c); 130 } 131 132 public static Accessible ax_getAccessibleSelection(final AccessibleContext ac, final int index, final Component c) { 133 if (ac == null) return null; 134 135 return invokeAndWait(new Callable<Accessible>() { 136 public Accessible call() throws Exception { 137 final AccessibleSelection as = ac.getAccessibleSelection(); 138 if (as == null) return null; 139 return as.getAccessibleSelection(index); 140 } 141 }, c); 142 } 143 144 // KCH - can we make this a postEvent? 145 public static void addAccessibleSelection(final AccessibleContext ac, final int index, final Component c) { 146 if (ac == null) return; 147 148 invokeLater(new Runnable() { 149 public void run() { 150 final AccessibleSelection as = ac.getAccessibleSelection(); 151 if (as == null) return; 152 as.addAccessibleSelection(index); 153 } 154 }, c); 155 } 156 157 public static AccessibleContext getAccessibleContext(final Accessible a, final Component c) { 158 if (a == null) return null; 159 160 return invokeAndWait(new Callable<AccessibleContext>() { 161 public AccessibleContext call() throws Exception { 162 return a.getAccessibleContext(); 163 } 164 }, c); 165 } 166 167 public static boolean isAccessibleChildSelected(final Accessible a, final int index, final Component c) { 168 if (a == null) return false; 169 170 return invokeAndWait(new Callable<Boolean>() { 171 public Boolean call() throws Exception { 172 final AccessibleContext ac = a.getAccessibleContext(); 173 if (ac == null) return Boolean.FALSE; 174 175 final AccessibleSelection as = ac.getAccessibleSelection(); 176 if (as == null) return Boolean.FALSE; 177 178 return new Boolean(as.isAccessibleChildSelected(index)); 179 } 180 }, c); 181 } 182 183 public static AccessibleStateSet getAccessibleStateSet(final AccessibleContext ac, final Component c) { 184 if (ac == null) return null; 185 186 return invokeAndWait(new Callable<AccessibleStateSet>() { 187 public AccessibleStateSet call() throws Exception { 188 return ac.getAccessibleStateSet(); 189 } 190 }, c); 191 } 192 193 public static boolean contains(final AccessibleContext ac, final AccessibleState as, final Component c) { 194 if (ac == null || as == null) return false; 195 196 return invokeAndWait(new Callable<Boolean>() { 197 public Boolean call() throws Exception { 198 final AccessibleStateSet ass = ac.getAccessibleStateSet(); 199 if (ass == null) return null; 200 return ass.contains(as); 201 } 202 }, c); 203 } 204 205 static Field getAccessibleBundleKeyFieldWithReflection() { 206 try { 207 final Field fieldKey = AccessibleBundle.class.getDeclaredField("key"); 208 fieldKey.setAccessible(true); 209 return fieldKey; 210 } catch (final SecurityException e) { 211 e.printStackTrace(); 212 } catch (final NoSuchFieldException e) { 213 e.printStackTrace(); 214 } 215 return null; 216 } 217 private static final Field FIELD_KEY = getAccessibleBundleKeyFieldWithReflection(); 218 219 static String getAccessibleRoleFor(final Accessible a) { 220 final AccessibleContext ac = a.getAccessibleContext(); 221 if (ac == null) return null; 222 223 final AccessibleRole role = ac.getAccessibleRole(); 224 try { 225 return (String)FIELD_KEY.get(role); 226 } catch (final IllegalArgumentException e) { 227 e.printStackTrace(); 228 } catch (final IllegalAccessException e) { 229 e.printStackTrace(); 230 } 231 return null; 232 } 233 234 public static String getAccessibleRole(final Accessible a, final Component c) { 235 if (a == null) return null; 236 237 return invokeAndWait(new Callable<String>() { 238 public String call() throws Exception { 239 final Accessible sa = CAccessible.getSwingAccessible(a); 240 final String role = getAccessibleRoleFor(a); 241 242 if (!"text".equals(role)) return role; 243 if (sa instanceof JTextArea || sa instanceof JEditorPane) { 244 return "textarea"; 245 } 246 return role; 247 } 248 }, c); 249 } 250 251 public static Point getLocationOnScreen(final AccessibleComponent ac, final Component c) { 252 if (ac == null) return null; 253 254 return invokeAndWait(new Callable<Point>() { 255 public Point call() throws Exception { 256 return ac.getLocationOnScreen(); 257 } 258 }, c); 259 } 260 261 public static int getCharCount(final AccessibleText at, final Component c) { 262 if (at == null) return 0; 263 264 return invokeAndWait(new Callable<Integer>() { 265 public Integer call() throws Exception { 266 return at.getCharCount(); 267 } 268 }, c); 269 } 270 271 // Accessibility Threadsafety for JavaComponentAccessibility.m 272 public static Accessible getAccessibleParent(final Accessible a, final Component c) { 273 if (a == null) return null; 274 275 return invokeAndWait(new Callable<Accessible>() { 276 public Accessible call() throws Exception { 277 final AccessibleContext ac = a.getAccessibleContext(); 278 if (ac == null) return null; 279 return ac.getAccessibleParent(); 280 } 281 }, c); 282 } 283 284 public static int getAccessibleIndexInParent(final Accessible a, final Component c) { 285 if (a == null) return 0; 286 287 return invokeAndWait(new Callable<Integer>() { 288 public Integer call() throws Exception { 289 final AccessibleContext ac = a.getAccessibleContext(); 290 if (ac == null) return null; 291 return ac.getAccessibleIndexInParent(); 292 } 293 }, c); 294 } 295 296 public static AccessibleComponent getAccessibleComponent(final Accessible a, final Component c) { 297 if (a == null) return null; 298 299 return invokeAndWait(new Callable<AccessibleComponent>() { 300 public AccessibleComponent call() throws Exception { 301 final AccessibleContext ac = a.getAccessibleContext(); 302 if (ac == null) return null; 303 return ac.getAccessibleComponent(); 304 } 305 }, c); 306 } 307 308 public static AccessibleValue getAccessibleValue(final Accessible a, final Component c) { 309 if (a == null) return null; 310 311 return invokeAndWait(new Callable<AccessibleValue>() { 312 public AccessibleValue call() throws Exception { 313 final AccessibleContext ac = a.getAccessibleContext(); 314 if (ac == null) return null; 315 316 AccessibleValue accessibleValue = ac.getAccessibleValue(); 317 return accessibleValue; 318 } 319 }, c); 320 } 321 322 public static String getAccessibleName(final Accessible a, final Component c) { 323 if (a == null) return null; 324 325 return invokeAndWait(new Callable<String>() { 326 public String call() throws Exception { 327 final AccessibleContext ac = a.getAccessibleContext(); 328 if (ac == null) return null; 329 330 final String accessibleName = ac.getAccessibleName(); 331 if (accessibleName == null) { 332 return ac.getAccessibleDescription(); 333 } 334 return accessibleName; 335 } 336 }, c); 337 } 338 339 public static AccessibleText getAccessibleText(final Accessible a, final Component c) { 340 if (a == null) return null; 341 342 return invokeAndWait(new Callable<AccessibleText>() { 343 public AccessibleText call() throws Exception { 344 final AccessibleContext ac = a.getAccessibleContext(); 345 if (ac == null) return null; 346 347 AccessibleText accessibleText = ac.getAccessibleText(); 348 return accessibleText; 349 } 350 }, c); 351 } 352 353 public static String getAccessibleDescription(final Accessible a, final Component c) { 354 if (a == null) return null; 355 356 return invokeAndWait(new Callable<String>() { 357 public String call() throws Exception { 358 final AccessibleContext ac = a.getAccessibleContext(); 359 if (ac == null) return null; 360 361 final String accessibleDescription = ac.getAccessibleDescription(); 362 if (accessibleDescription == null) { 363 if (c instanceof JComponent) { 364 String toolTipText = ((JComponent)c).getToolTipText(); 365 if (toolTipText != null) { 366 return toolTipText; 367 } 368 } 369 } 370 371 return accessibleDescription; 372 } 373 }, c); 374 } 375 376 public static boolean isFocusTraversable(final Accessible a, final Component c) { 377 if (a == null) return false; 378 379 return invokeAndWait(new Callable<Boolean>() { 380 public Boolean call() throws Exception { 381 final AccessibleContext ac = a.getAccessibleContext(); 382 if (ac == null) return null; 383 384 final AccessibleComponent aComp = ac.getAccessibleComponent(); 385 if (aComp == null) return null; 386 387 return aComp.isFocusTraversable(); 388 } 389 }, c); 390 } 391 392 public static Accessible accessibilityHitTest(final Container parent, final float hitPointX, final float hitPointY) { 393 return invokeAndWait(new Callable<Accessible>() { 394 public Accessible call() throws Exception { 395 final Point p = parent.getLocationOnScreen(); 396 397 // Make it into local coords 398 final Point localPoint = new Point((int)(hitPointX - p.getX()), (int)(hitPointY - p.getY())); 399 400 final Component component = parent.findComponentAt(localPoint); 401 if (component == null) return null; 402 403 final AccessibleContext axContext = component.getAccessibleContext(); 404 if (axContext == null) return null; 405 406 final AccessibleComponent axComponent = axContext.getAccessibleComponent(); 407 if (axComponent == null) return null; 408 409 final int numChildren = axContext.getAccessibleChildrenCount(); 410 if (numChildren > 0) { 411 // It has children, check to see which one is hit. 412 final Point p2 = axComponent.getLocationOnScreen(); 413 final Point localP2 = new Point((int)(hitPointX - p2.getX()), (int)(hitPointY - p2.getY())); 414 return CAccessible.getCAccessible(axComponent.getAccessibleAt(localP2)); 415 } 416 417 if (!(component instanceof Accessible)) return null; 418 return CAccessible.getCAccessible((Accessible)component); 419 } 420 }, parent); 421 } 422 423 public static AccessibleAction getAccessibleAction(final Accessible a, final Component c) { 424 return invokeAndWait(new Callable<AccessibleAction>() { 425 public AccessibleAction call() throws Exception { 426 final AccessibleContext ac = a.getAccessibleContext(); 427 if (ac == null) return null; 428 return ac.getAccessibleAction(); 429 } 430 }, c); 431 } 432 433 public static boolean isEnabled(final Accessible a, final Component c) { 434 if (a == null) return false; 435 436 return invokeAndWait(new Callable<Boolean>() { 437 public Boolean call() throws Exception { 438 final AccessibleContext ac = a.getAccessibleContext(); 439 if (ac == null) return null; 440 441 final AccessibleComponent aComp = ac.getAccessibleComponent(); 442 if (aComp == null) return null; 443 444 return aComp.isEnabled(); 445 } 446 }, c); 447 } 448 449 // KCH - can we make this a postEvent instead? 450 public static void requestFocus(final Accessible a, final Component c) { 451 if (a == null) return; 452 453 invokeLater(new Runnable() { 454 public void run() { 455 final AccessibleContext ac = a.getAccessibleContext(); 456 if (ac == null) return; 457 458 final AccessibleComponent aComp = ac.getAccessibleComponent(); 459 if (aComp == null) return; 460 461 aComp.requestFocus(); 462 } 463 }, c); 464 } 465 466 public static Number getMaximumAccessibleValue(final Accessible a, final Component c) { 467 if (a == null) return null; 468 469 return invokeAndWait(new Callable<Number>() { 470 public Number call() throws Exception { 471 final AccessibleContext ac = a.getAccessibleContext(); 472 if (ac == null) return null; 473 474 final AccessibleValue av = ac.getAccessibleValue(); 475 if (av == null) return null; 476 477 return av.getMaximumAccessibleValue(); 478 } 479 }, c); 480 } 481 482 public static Number getMinimumAccessibleValue(final Accessible a, final Component c) { 483 if (a == null) return null; 484 485 return invokeAndWait(new Callable<Number>() { 486 public Number call() throws Exception { 487 final AccessibleContext ac = a.getAccessibleContext(); 488 if (ac == null) return null; 489 490 final AccessibleValue av = ac.getAccessibleValue(); 491 if (av == null) return null; 492 493 return av.getMinimumAccessibleValue(); 494 } 495 }, c); 496 } 497 498 public static String getAccessibleRoleDisplayString(final Accessible a, final Component c) { 499 if (a == null) return null; 500 501 return invokeAndWait(new Callable<String>() { 502 public String call() throws Exception { 503 final AccessibleContext ac = a.getAccessibleContext(); 504 if (ac == null) return null; 505 506 final AccessibleRole ar = ac.getAccessibleRole(); 507 if (ar == null) return null; 508 509 return ar.toDisplayString(); 510 } 511 }, c); 512 } 513 514 public static Number getCurrentAccessibleValue(final AccessibleValue av, final Component c) { 515 if (av == null) return null; 516 517 return invokeAndWait(new Callable<Number>() { 518 public Number call() throws Exception { 519 Number currentAccessibleValue = av.getCurrentAccessibleValue(); 520 return currentAccessibleValue; 521 } 522 }, c); 523 } 524 525 public static Accessible getFocusOwner(final Component c) { 526 return invokeAndWait(new Callable<Accessible>() { 527 public Accessible call() throws Exception { 528 Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 529 if (c == null || !(c instanceof Accessible)) return null; 530 return CAccessible.getCAccessible((Accessible)c); 531 } 532 }, c); 533 } 534 535 public static boolean[] getInitialAttributeStates(final Accessible a, final Component c) { 536 final boolean[] ret = new boolean[7]; 537 if (a == null) return ret; 538 539 return invokeAndWait(new Callable<boolean[]>() { 540 public boolean[] call() throws Exception { 541 final AccessibleContext aContext = a.getAccessibleContext(); 542 if (aContext == null) return ret; 543 544 final AccessibleComponent aComponent = aContext.getAccessibleComponent(); 545 ret[0] = (aComponent != null); 546 ret[1] = ((aComponent != null) && (aComponent.isFocusTraversable())); 547 ret[2] = (aContext.getAccessibleValue() != null); 548 ret[3] = (aContext.getAccessibleText() != null); 549 550 final AccessibleStateSet aStateSet = aContext.getAccessibleStateSet(); 551 ret[4] = (aStateSet.contains(AccessibleState.HORIZONTAL) || aStateSet.contains(AccessibleState.VERTICAL)); 552 ret[5] = (aContext.getAccessibleName() != null); 553 ret[6] = (aContext.getAccessibleChildrenCount() > 0); 554 return ret; 555 } 556 }, c); 557 } 558 559 // Duplicated from JavaComponentAccessibility 560 // Note that values >=0 are indexes into the child array 561 final static int JAVA_AX_ALL_CHILDREN = -1; 562 final static int JAVA_AX_SELECTED_CHILDREN = -2; 563 final static int JAVA_AX_VISIBLE_CHILDREN = -3; 564 565 // Each child takes up two entries in the array: one for itself and one for its role 566 public static Object[] getChildrenAndRoles(final Accessible a, final Component c, final int whichChildren, final boolean allowIgnored) { 567 if (a == null) return null; 568 return invokeAndWait(new Callable<Object[]>() { 569 public Object[] call() throws Exception { 570 final ArrayList<Object> childrenAndRoles = new ArrayList<Object>(); 571 _addChildren(a, whichChildren, allowIgnored, childrenAndRoles); 572 573 if ((whichChildren < 0) || (whichChildren * 2 >= childrenAndRoles.size())) { 574 return childrenAndRoles.toArray(); 575 } 576 577 return new Object[] { childrenAndRoles.get(whichChildren * 2), childrenAndRoles.get((whichChildren * 2) + 1) }; 578 } 579 }, c); 580 } 581 582 private static AccessibleRole getAccessibleRoleForLabel(JLabel l, AccessibleRole fallback) { 583 String text = l.getText(); 584 if (text != null && text.length() > 0) { 585 return fallback; 586 } 587 Icon icon = l.getIcon(); 588 if (icon != null) { 589 return AccessibleRole.ICON; 590 } 591 return fallback; 592 } 593 594 private static AccessibleRole getAccessibleRole(Accessible a) { 595 AccessibleContext ac = a.getAccessibleContext(); 596 AccessibleRole role = ac.getAccessibleRole(); 597 Object component = CAccessible.getSwingAccessible(a); 598 if (role == null) return null; 599 String roleString = role.toString(); 600 if ("label".equals(roleString) && component instanceof JLabel) { 601 return getAccessibleRoleForLabel((JLabel) component, role); 602 } 603 return role; 604 } 605 606 607 // Either gets the immediate children of a, or recursively gets all unignored children of a 608 private static void _addChildren(final Accessible a, final int whichChildren, final boolean allowIgnored, final ArrayList<Object> childrenAndRoles) { 609 if (a == null) return; 610 611 final AccessibleContext ac = a.getAccessibleContext(); 612 if (ac == null) return; 613 614 final int numChildren = ac.getAccessibleChildrenCount(); 615 616 // each child takes up two entries in the array: itself, and its role 617 // so the array holds alternating Accessible and AccessibleRole objects 618 for (int i = 0; i < numChildren; i++) { 619 final Accessible child = ac.getAccessibleChild(i); 620 if (child == null) continue; 621 622 final AccessibleContext context = child.getAccessibleContext(); 623 if (context == null) continue; 624 625 if (whichChildren == JAVA_AX_VISIBLE_CHILDREN) { 626 if (!context.getAccessibleComponent().isVisible()) continue; 627 } else if (whichChildren == JAVA_AX_SELECTED_CHILDREN) { 628 if (!ac.getAccessibleSelection().isAccessibleChildSelected(i)) continue; 629 } 630 631 if (!allowIgnored) { 632 final AccessibleRole role = context.getAccessibleRole(); 633 if (role != null && ignoredRoles != null && ignoredRoles.contains(roleKey(role))) { 634 // Get the child's unignored children. 635 _addChildren(child, whichChildren, false, childrenAndRoles); 636 } else { 637 childrenAndRoles.add(child); 638 childrenAndRoles.add(getAccessibleRole(child)); 639 } 640 } else { 641 childrenAndRoles.add(child); 642 childrenAndRoles.add(getAccessibleRole(child)); 643 } 644 645 // If there is an index, and we are beyond it, time to finish up 646 if ((whichChildren >= 0) && (childrenAndRoles.size() / 2) >= (whichChildren + 1)) { 647 return; 648 } 649 } 650 } 651 652 private static native String roleKey(AccessibleRole aRole); 653 654 public static Object[] getChildren(final Accessible a, final Component c) { 655 if (a == null) return null; 656 return invokeAndWait(new Callable<Object[]>() { 657 public Object[] call() throws Exception { 658 final AccessibleContext ac = a.getAccessibleContext(); 659 if (ac == null) return null; 660 661 final int numChildren = ac.getAccessibleChildrenCount(); 662 final Object[] children = new Object[numChildren]; 663 for (int i = 0; i < numChildren; i++) { 664 children[i] = ac.getAccessibleChild(i); 665 } 666 return children; 667 } 668 }, c); 669 } 670 }