1 /* 2 * Copyright (c) 2011, 2019, 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.Component; 29 import java.awt.Container; 30 import java.awt.Dimension; 31 import java.awt.KeyboardFocusManager; 32 import java.awt.Point; 33 import java.awt.Window; 34 import java.awt.event.KeyEvent; 35 import java.beans.PropertyChangeEvent; 36 import java.beans.PropertyChangeListener; 37 import java.lang.reflect.InvocationTargetException; 38 import java.util.ArrayList; 39 import java.util.HashSet; 40 import java.util.Set; 41 import java.util.concurrent.Callable; 42 43 import javax.accessibility.Accessible; 44 import javax.accessibility.AccessibleAction; 45 import javax.accessibility.AccessibleComponent; 46 import javax.accessibility.AccessibleContext; 47 import javax.accessibility.AccessibleRole; 48 import javax.accessibility.AccessibleSelection; 49 import javax.accessibility.AccessibleState; 50 import javax.accessibility.AccessibleStateSet; 51 import javax.accessibility.AccessibleTable; 52 import javax.accessibility.AccessibleText; 53 import javax.accessibility.AccessibleValue; 54 import javax.swing.Icon; 55 import javax.swing.JComponent; 56 import javax.swing.JEditorPane; 57 import javax.swing.JLabel; 58 import javax.swing.JMenuItem; 59 import javax.swing.JTextArea; 60 import javax.swing.KeyStroke; 61 62 import sun.awt.AWTAccessor; 63 import sun.lwawt.LWWindowPeer; 64 65 class CAccessibility implements PropertyChangeListener { 66 private static Set<String> ignoredRoles; 67 68 static { 69 // Need to load the native library for this code. java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { System.loadLibrary(R); return null; } })70 java.security.AccessController.doPrivileged( 71 new java.security.PrivilegedAction<Void>() { 72 public Void run() { 73 System.loadLibrary("awt"); 74 return null; 75 } 76 }); 77 } 78 79 static CAccessibility sAccessibility; getAccessibility(final String[] roles)80 static synchronized CAccessibility getAccessibility(final String[] roles) { 81 if (sAccessibility != null) return sAccessibility; 82 sAccessibility = new CAccessibility(); 83 84 if (roles != null) { 85 ignoredRoles = new HashSet<String>(roles.length); 86 for (final String role : roles) ignoredRoles.add(role); 87 } else { 88 ignoredRoles = new HashSet<String>(); 89 } 90 91 return sAccessibility; 92 } 93 CAccessibility()94 private CAccessibility() { 95 KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", this); 96 } 97 propertyChange(final PropertyChangeEvent evt)98 public void propertyChange(final PropertyChangeEvent evt) { 99 Object newValue = evt.getNewValue(); 100 if (newValue == null) return; 101 // Don't post focus on things that don't matter, i.e. alert, colorchooser, 102 // desktoppane, dialog, directorypane, filechooser, filler, fontchoose, 103 // frame, glasspane, layeredpane, optionpane, panel, rootpane, separator, 104 // tooltip, viewport, window. 105 // List taken from initializeRoles() in JavaComponentUtilities.m. 106 if (newValue instanceof Accessible) { 107 AccessibleContext nvAC = ((Accessible) newValue).getAccessibleContext(); 108 AccessibleRole nvRole = nvAC.getAccessibleRole(); 109 if (!ignoredRoles.contains(roleKey(nvRole))) { 110 focusChanged(); 111 } 112 } 113 } 114 focusChanged()115 private native void focusChanged(); 116 invokeAndWait(final Callable<T> callable, final Component c)117 static <T> T invokeAndWait(final Callable<T> callable, final Component c) { 118 try { 119 return LWCToolkit.invokeAndWait(callable, c); 120 } catch (final Exception e) { e.printStackTrace(); } 121 return null; 122 } 123 invokeAndWait(final Callable<T> callable, final Component c, final T defValue)124 static <T> T invokeAndWait(final Callable<T> callable, final Component c, final T defValue) { 125 T value = null; 126 try { 127 value = LWCToolkit.invokeAndWait(callable, c); 128 } catch (final Exception e) { e.printStackTrace(); } 129 130 return value != null ? value : defValue; 131 } 132 invokeLater(final Runnable runnable, final Component c)133 static void invokeLater(final Runnable runnable, final Component c) { 134 try { 135 LWCToolkit.invokeLater(runnable, c); 136 } catch (InvocationTargetException e) { e.printStackTrace(); } 137 } 138 getAccessibleActionDescription(final AccessibleAction aa, final int index, final Component c)139 public static String getAccessibleActionDescription(final AccessibleAction aa, final int index, final Component c) { 140 if (aa == null) return null; 141 142 return invokeAndWait(new Callable<String>() { 143 public String call() throws Exception { 144 return aa.getAccessibleActionDescription(index); 145 } 146 }, c); 147 } 148 149 public static void doAccessibleAction(final AccessibleAction aa, final int index, final Component c) { 150 // We make this an invokeLater because we don't need a reply. 151 if (aa == null) return; 152 153 invokeLater(new Runnable() { 154 public void run() { 155 aa.doAccessibleAction(index); 156 } 157 }, c); 158 } 159 160 public static Dimension getSize(final AccessibleComponent ac, final Component c) { 161 if (ac == null) return null; 162 163 return invokeAndWait(new Callable<Dimension>() { 164 public Dimension call() throws Exception { 165 return ac.getSize(); 166 } 167 }, c); 168 } 169 170 public static AccessibleSelection getAccessibleSelection(final AccessibleContext ac, final Component c) { 171 if (ac == null) return null; 172 173 return invokeAndWait(new Callable<AccessibleSelection>() { 174 public AccessibleSelection call() throws Exception { 175 return ac.getAccessibleSelection(); 176 } 177 }, c); 178 } 179 180 public static Accessible ax_getAccessibleSelection(final AccessibleContext ac, final int index, final Component c) { 181 if (ac == null) return null; 182 183 return invokeAndWait(new Callable<Accessible>() { 184 public Accessible call() throws Exception { 185 final AccessibleSelection as = ac.getAccessibleSelection(); 186 if (as == null) return null; 187 return as.getAccessibleSelection(index); 188 } 189 }, c); 190 } 191 192 // KCH - can we make this a postEvent? 193 public static void addAccessibleSelection(final AccessibleContext ac, final int index, final Component c) { 194 if (ac == null) return; 195 196 invokeLater(new Runnable() { 197 public void run() { 198 final AccessibleSelection as = ac.getAccessibleSelection(); 199 if (as == null) return; 200 as.addAccessibleSelection(index); 201 } 202 }, c); 203 } 204 205 public static AccessibleContext getAccessibleContext(final Accessible a, final Component c) { 206 if (a == null) return null; 207 208 return invokeAndWait(new Callable<AccessibleContext>() { 209 public AccessibleContext call() throws Exception { 210 return a.getAccessibleContext(); 211 } 212 }, c); 213 } 214 215 public static boolean isAccessibleChildSelected(final Accessible a, final int index, final Component c) { 216 if (a == null) return false; 217 218 return invokeAndWait(new Callable<Boolean>() { 219 public Boolean call() throws Exception { 220 final AccessibleContext ac = a.getAccessibleContext(); 221 if (ac == null) return Boolean.FALSE; 222 223 final AccessibleSelection as = ac.getAccessibleSelection(); 224 if (as == null) return Boolean.FALSE; 225 226 return as.isAccessibleChildSelected(index); 227 } 228 }, c, false); 229 } 230 231 public static AccessibleStateSet getAccessibleStateSet(final AccessibleContext ac, final Component c) { 232 if (ac == null) return null; 233 234 return invokeAndWait(new Callable<AccessibleStateSet>() { 235 public AccessibleStateSet call() throws Exception { 236 return ac.getAccessibleStateSet(); 237 } 238 }, c); 239 } 240 241 public static boolean contains(final AccessibleContext ac, final AccessibleState as, final Component c) { 242 if (ac == null || as == null) return false; 243 244 return invokeAndWait(new Callable<Boolean>() { 245 public Boolean call() throws Exception { 246 final AccessibleStateSet ass = ac.getAccessibleStateSet(); 247 if (ass == null) return null; 248 return ass.contains(as); 249 } 250 }, c, false); 251 } 252 253 static String getAccessibleRoleFor(final Accessible a) { 254 final AccessibleContext ac = a.getAccessibleContext(); 255 if (ac == null) return null; 256 257 final AccessibleRole role = ac.getAccessibleRole(); 258 return AWTAccessor.getAccessibleBundleAccessor().getKey(role); 259 } 260 261 public static String getAccessibleRole(final Accessible a, final Component c) { 262 if (a == null) return null; 263 264 return invokeAndWait(new Callable<String>() { 265 public String call() throws Exception { 266 final Accessible sa = CAccessible.getSwingAccessible(a); 267 final String role = getAccessibleRoleFor(a); 268 269 if (!"text".equals(role)) return role; 270 if (sa instanceof JTextArea || sa instanceof JEditorPane) { 271 return "textarea"; 272 } 273 return role; 274 } 275 }, c); 276 } 277 278 public static Point getLocationOnScreen(final AccessibleComponent ac, final Component c) { 279 if (ac == null) return null; 280 281 return invokeAndWait(new Callable<Point>() { 282 public Point call() throws Exception { 283 return ac.getLocationOnScreen(); 284 } 285 }, c); 286 } 287 288 public static int getCharCount(final AccessibleText at, final Component c) { 289 if (at == null) return 0; 290 291 return invokeAndWait(new Callable<Integer>() { 292 public Integer call() throws Exception { 293 return at.getCharCount(); 294 } 295 }, c, 0); 296 } 297 298 // Accessibility Threadsafety for JavaComponentAccessibility.m 299 public static Accessible getAccessibleParent(final Accessible a, final Component c) { 300 if (a == null) return null; 301 302 return invokeAndWait(new Callable<Accessible>() { 303 public Accessible call() throws Exception { 304 final AccessibleContext ac = a.getAccessibleContext(); 305 if (ac == null) return null; 306 return ac.getAccessibleParent(); 307 } 308 }, c); 309 } 310 311 public static int getAccessibleIndexInParent(final Accessible a, final Component c) { 312 if (a == null) return -1; 313 314 return invokeAndWait(new Callable<Integer>() { 315 public Integer call() throws Exception { 316 final AccessibleContext ac = a.getAccessibleContext(); 317 if (ac == null) return null; 318 return ac.getAccessibleIndexInParent(); 319 } 320 }, c, -1); 321 } 322 323 public static AccessibleComponent getAccessibleComponent(final Accessible a, final Component c) { 324 if (a == null) return null; 325 326 return invokeAndWait(new Callable<AccessibleComponent>() { 327 public AccessibleComponent call() throws Exception { 328 final AccessibleContext ac = a.getAccessibleContext(); 329 if (ac == null) return null; 330 return ac.getAccessibleComponent(); 331 } 332 }, c); 333 } 334 335 public static AccessibleValue getAccessibleValue(final Accessible a, final Component c) { 336 if (a == null) return null; 337 338 return invokeAndWait(new Callable<AccessibleValue>() { 339 public AccessibleValue call() throws Exception { 340 final AccessibleContext ac = a.getAccessibleContext(); 341 if (ac == null) return null; 342 343 AccessibleValue accessibleValue = ac.getAccessibleValue(); 344 return accessibleValue; 345 } 346 }, c); 347 } 348 349 public static String getAccessibleName(final Accessible a, final Component c) { 350 if (a == null) return null; 351 352 return invokeAndWait(new Callable<String>() { 353 public String call() throws Exception { 354 final AccessibleContext ac = a.getAccessibleContext(); 355 if (ac == null) return null; 356 357 final String accessibleName = ac.getAccessibleName(); 358 if (accessibleName == null) { 359 return ac.getAccessibleDescription(); 360 } 361 final String acceleratorText = getAcceleratorText(ac); 362 if (!acceleratorText.isEmpty()) { 363 return accessibleName +' '+ acceleratorText; 364 } 365 return accessibleName; 366 } 367 }, c); 368 } 369 370 public static AccessibleText getAccessibleText(final Accessible a, final Component c) { 371 if (a == null) return null; 372 373 return invokeAndWait(new Callable<AccessibleText>() { 374 public AccessibleText call() throws Exception { 375 final AccessibleContext ac = a.getAccessibleContext(); 376 if (ac == null) return null; 377 378 AccessibleText accessibleText = ac.getAccessibleText(); 379 return accessibleText; 380 } 381 }, c); 382 } 383 384 /* 385 * Returns the JMenuItem accelerator. Implementation of this method is based 386 * on AccessBridge.getAccelerator(AccessibleContext) to access the KeyStroke 387 * and on AquaMenuPainter.paintMenuItem() to convert it to string. 388 */ 389 @SuppressWarnings("deprecation") 390 private static String getAcceleratorText(AccessibleContext ac) { 391 String accText = ""; 392 Accessible parent = ac.getAccessibleParent(); 393 if (parent != null) { 394 // workaround for getAccessibleKeyBinding not returning the 395 // JMenuItem accelerator 396 int indexInParent = ac.getAccessibleIndexInParent(); 397 Accessible child = parent.getAccessibleContext() 398 .getAccessibleChild(indexInParent); 399 if (child instanceof JMenuItem) { 400 JMenuItem menuItem = (JMenuItem) child; 401 KeyStroke keyStroke = menuItem.getAccelerator(); 402 if (keyStroke != null) { 403 int modifiers = keyStroke.getModifiers(); 404 if (modifiers > 0) { 405 accText = KeyEvent.getKeyModifiersText(modifiers); 406 } 407 int keyCode = keyStroke.getKeyCode(); 408 if (keyCode != 0) { 409 accText += KeyEvent.getKeyText(keyCode); 410 } else { 411 accText += keyStroke.getKeyChar(); 412 } 413 } 414 } 415 } 416 return accText; 417 } 418 419 public static String getAccessibleDescription(final Accessible a, final Component c) { 420 if (a == null) return null; 421 422 return invokeAndWait(new Callable<String>() { 423 public String call() throws Exception { 424 final AccessibleContext ac = a.getAccessibleContext(); 425 if (ac == null) return null; 426 427 final String accessibleDescription = ac.getAccessibleDescription(); 428 if (accessibleDescription == null) { 429 if (c instanceof JComponent) { 430 String toolTipText = ((JComponent)c).getToolTipText(); 431 if (toolTipText != null) { 432 return toolTipText; 433 } 434 } 435 } 436 437 return accessibleDescription; 438 } 439 }, c); 440 } 441 442 public static boolean isFocusTraversable(final Accessible a, final Component c) { 443 if (a == null) return false; 444 445 return invokeAndWait(new Callable<Boolean>() { 446 public Boolean call() throws Exception { 447 final AccessibleContext ac = a.getAccessibleContext(); 448 if (ac == null) return null; 449 450 final AccessibleComponent aComp = ac.getAccessibleComponent(); 451 if (aComp == null) return null; 452 453 return aComp.isFocusTraversable(); 454 } 455 }, c, false); 456 } 457 458 public static Accessible accessibilityHitTest(final Container parent, final float hitPointX, final float hitPointY) { 459 return invokeAndWait(new Callable<Accessible>() { 460 public Accessible call() throws Exception { 461 final Point p = parent.getLocationOnScreen(); 462 463 // Make it into local coords 464 final Point localPoint = new Point((int)(hitPointX - p.getX()), (int)(hitPointY - p.getY())); 465 466 final Component component = parent.findComponentAt(localPoint); 467 if (component == null) return null; 468 469 final AccessibleContext axContext = component.getAccessibleContext(); 470 if (axContext == null) return null; 471 472 final AccessibleComponent axComponent = axContext.getAccessibleComponent(); 473 if (axComponent == null) return null; 474 475 final int numChildren = axContext.getAccessibleChildrenCount(); 476 if (numChildren > 0) { 477 // It has children, check to see which one is hit. 478 final Point p2 = axComponent.getLocationOnScreen(); 479 final Point localP2 = new Point((int)(hitPointX - p2.getX()), (int)(hitPointY - p2.getY())); 480 return CAccessible.getCAccessible(axComponent.getAccessibleAt(localP2)); 481 } 482 483 if (!(component instanceof Accessible)) return null; 484 return CAccessible.getCAccessible((Accessible)component); 485 } 486 }, parent); 487 } 488 489 public static AccessibleAction getAccessibleAction(final Accessible a, final Component c) { 490 if (a == null) return null; 491 492 return invokeAndWait(new Callable<AccessibleAction>() { 493 public AccessibleAction call() throws Exception { 494 final AccessibleContext ac = a.getAccessibleContext(); 495 if (ac == null) return null; 496 return ac.getAccessibleAction(); 497 } 498 }, c); 499 } 500 501 public static boolean isEnabled(final Accessible a, final Component c) { 502 if (a == null) return false; 503 504 return invokeAndWait(new Callable<Boolean>() { 505 public Boolean call() throws Exception { 506 final AccessibleContext ac = a.getAccessibleContext(); 507 if (ac == null) return null; 508 509 final AccessibleComponent aComp = ac.getAccessibleComponent(); 510 if (aComp == null) return null; 511 512 return aComp.isEnabled(); 513 } 514 }, c, false); 515 } 516 517 // KCH - can we make this a postEvent instead? 518 public static void requestFocus(final Accessible a, final Component c) { 519 if (a == null) return; 520 521 invokeLater(new Runnable() { 522 public void run() { 523 final AccessibleContext ac = a.getAccessibleContext(); 524 if (ac == null) return; 525 526 final AccessibleComponent aComp = ac.getAccessibleComponent(); 527 if (aComp == null) return; 528 529 aComp.requestFocus(); 530 } 531 }, c); 532 } 533 534 public static void requestSelection(final Accessible a, final Component c) { 535 if (a == null) return; 536 invokeLater(new Runnable() { 537 public void run() { 538 AccessibleContext ac = a.getAccessibleContext(); 539 if (ac == null) return; 540 int i = ac.getAccessibleIndexInParent(); 541 if (i == -1) return; 542 Accessible parent = ac.getAccessibleParent(); 543 AccessibleContext pac = parent.getAccessibleContext(); 544 if (pac == null) return; 545 AccessibleSelection as = pac.getAccessibleSelection(); 546 if (as == null) return; 547 as.addAccessibleSelection(i); 548 } 549 }, c); 550 } 551 552 public static Number getMaximumAccessibleValue(final Accessible a, final Component c) { 553 if (a == null) return null; 554 555 return invokeAndWait(new Callable<Number>() { 556 public Number call() throws Exception { 557 final AccessibleContext ac = a.getAccessibleContext(); 558 if (ac == null) return null; 559 560 final AccessibleValue av = ac.getAccessibleValue(); 561 if (av == null) return null; 562 563 return av.getMaximumAccessibleValue(); 564 } 565 }, c); 566 } 567 568 public static Number getMinimumAccessibleValue(final Accessible a, final Component c) { 569 if (a == null) return null; 570 571 return invokeAndWait(new Callable<Number>() { 572 public Number call() throws Exception { 573 final AccessibleContext ac = a.getAccessibleContext(); 574 if (ac == null) return null; 575 576 final AccessibleValue av = ac.getAccessibleValue(); 577 if (av == null) return null; 578 579 return av.getMinimumAccessibleValue(); 580 } 581 }, c); 582 } 583 584 public static String getAccessibleRoleDisplayString(final Accessible a, final Component c) { 585 if (a == null) return null; 586 587 return invokeAndWait(new Callable<String>() { 588 public String call() throws Exception { 589 final AccessibleContext ac = a.getAccessibleContext(); 590 if (ac == null) return null; 591 592 final AccessibleRole ar = ac.getAccessibleRole(); 593 if (ar == null) return null; 594 595 return ar.toDisplayString(); 596 } 597 }, c); 598 } 599 600 public static Number getCurrentAccessibleValue(final AccessibleValue av, final Component c) { 601 if (av == null) return null; 602 603 return invokeAndWait(new Callable<Number>() { 604 public Number call() throws Exception { 605 Number currentAccessibleValue = av.getCurrentAccessibleValue(); 606 return currentAccessibleValue; 607 } 608 }, c); 609 } 610 611 public static Accessible getFocusOwner(final Component c) { 612 return invokeAndWait(new Callable<Accessible>() { 613 public Accessible call() throws Exception { 614 Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 615 if (c == null || !(c instanceof Accessible)) return null; 616 return CAccessible.getCAccessible((Accessible)c); 617 } 618 }, c); 619 } 620 621 public static boolean[] getInitialAttributeStates(final Accessible a, final Component c) { 622 final boolean[] ret = new boolean[7]; 623 if (a == null) return ret; 624 625 return invokeAndWait(new Callable<boolean[]>() { 626 public boolean[] call() throws Exception { 627 final AccessibleContext aContext = a.getAccessibleContext(); 628 if (aContext == null) return ret; 629 630 final AccessibleComponent aComponent = aContext.getAccessibleComponent(); 631 ret[0] = (aComponent != null); 632 ret[1] = ((aComponent != null) && (aComponent.isFocusTraversable())); 633 ret[2] = (aContext.getAccessibleValue() != null); 634 ret[3] = (aContext.getAccessibleText() != null); 635 636 final AccessibleStateSet aStateSet = aContext.getAccessibleStateSet(); 637 ret[4] = (aStateSet.contains(AccessibleState.HORIZONTAL) || aStateSet.contains(AccessibleState.VERTICAL)); 638 ret[5] = (aContext.getAccessibleName() != null); 639 ret[6] = (aContext.getAccessibleChildrenCount() > 0); 640 return ret; 641 } 642 }, c); 643 } 644 645 // Duplicated from JavaComponentAccessibility 646 // Note that values >=0 are indexes into the child array 647 static final int JAVA_AX_ALL_CHILDREN = -1; 648 static final int JAVA_AX_SELECTED_CHILDREN = -2; 649 static final int JAVA_AX_VISIBLE_CHILDREN = -3; 650 651 // Each child takes up two entries in the array: one for itself and one for its role 652 public static Object[] getChildrenAndRoles(final Accessible a, final Component c, final int whichChildren, final boolean allowIgnored) { 653 if (a == null) return null; 654 return invokeAndWait(new Callable<Object[]>() { 655 public Object[] call() throws Exception { 656 ArrayList<Object> childrenAndRoles = new ArrayList<Object>(); 657 _addChildren(a, whichChildren, allowIgnored, childrenAndRoles); 658 659 /* In the case of fetching a selection, need to check to see if 660 * the active descendant is at the beginning of the list. If it 661 * is not it needs to be moved to the beginning of the list so 662 * VoiceOver will annouce it correctly. The list returned 663 * from Java is always in order from top to bottom, but when shift 664 * selecting downward (extending the list) or multi-selecting using 665 * the VO keys control+option+command+return the active descendant 666 * is not at the top of the list in the shift select down case and 667 * may not be in the multi select case. 668 */ 669 if (whichChildren == JAVA_AX_SELECTED_CHILDREN) { 670 if (!childrenAndRoles.isEmpty()) { 671 AccessibleContext activeDescendantAC = 672 CAccessible.getActiveDescendant(a); 673 if (activeDescendantAC != null) { 674 String activeDescendantName = 675 activeDescendantAC.getAccessibleName(); 676 AccessibleRole activeDescendantRole = 677 activeDescendantAC.getAccessibleRole(); 678 // Move active descendant to front of list. 679 // List contains pairs of each selected item's 680 // Accessible and AccessibleRole. 681 ArrayList<Object> newArray = new ArrayList<Object>(); 682 int count = childrenAndRoles.size(); 683 Accessible currentAccessible = null; 684 AccessibleContext currentAC = null; 685 String currentName = null; 686 AccessibleRole currentRole = null; 687 for (int i = 0; i < count; i+=2) { 688 // Is this the active descendant? 689 currentAccessible = (Accessible)childrenAndRoles.get(i); 690 currentAC = currentAccessible.getAccessibleContext(); 691 currentName = currentAC.getAccessibleName(); 692 currentRole = (AccessibleRole)childrenAndRoles.get(i+1); 693 if (currentName != null && currentName.equals(activeDescendantName) && 694 currentRole.equals(activeDescendantRole) ) { 695 newArray.add(0, currentAccessible); 696 newArray.add(1, currentRole); 697 } else { 698 newArray.add(currentAccessible); 699 newArray.add(currentRole); 700 } 701 } 702 childrenAndRoles = newArray; 703 } 704 } 705 } 706 707 if ((whichChildren < 0) || (whichChildren * 2 >= childrenAndRoles.size())) { 708 return childrenAndRoles.toArray(); 709 } 710 711 return new Object[] { childrenAndRoles.get(whichChildren * 2), childrenAndRoles.get((whichChildren * 2) + 1) }; 712 } 713 }, c); 714 } 715 716 private static final int JAVA_AX_ROWS = 1; 717 private static final int JAVA_AX_COLS = 2; 718 719 public static int getTableInfo(final Accessible a, final Component c, 720 final int info) { 721 if (a == null) return 0; 722 return invokeAndWait(() -> { 723 AccessibleContext ac = a.getAccessibleContext(); 724 AccessibleTable table = ac.getAccessibleTable(); 725 if (table != null) { 726 if (info == JAVA_AX_COLS) { 727 return table.getAccessibleColumnCount(); 728 } else if (info == JAVA_AX_ROWS) { 729 return table.getAccessibleRowCount(); 730 } 731 } 732 return 0; 733 }, c); 734 } 735 736 private static AccessibleRole getAccessibleRoleForLabel(JLabel l, AccessibleRole fallback) { 737 String text = l.getText(); 738 if (text != null && text.length() > 0) { 739 return fallback; 740 } 741 Icon icon = l.getIcon(); 742 if (icon != null) { 743 return AccessibleRole.ICON; 744 } 745 return fallback; 746 } 747 748 private static AccessibleRole getAccessibleRole(Accessible a) { 749 AccessibleContext ac = a.getAccessibleContext(); 750 AccessibleRole role = ac.getAccessibleRole(); 751 Object component = CAccessible.getSwingAccessible(a); 752 if (role == null) return null; 753 String roleString = role.toString(); 754 if ("label".equals(roleString) && component instanceof JLabel) { 755 return getAccessibleRoleForLabel((JLabel) component, role); 756 } 757 return role; 758 } 759 760 761 // Either gets the immediate children of a, or recursively gets all unignored children of a 762 private static void _addChildren(final Accessible a, final int whichChildren, final boolean allowIgnored, final ArrayList<Object> childrenAndRoles) { 763 if (a == null) return; 764 765 final AccessibleContext ac = a.getAccessibleContext(); 766 if (ac == null) return; 767 768 final int numChildren = ac.getAccessibleChildrenCount(); 769 770 // each child takes up two entries in the array: itself, and its role 771 // so the array holds alternating Accessible and AccessibleRole objects 772 for (int i = 0; i < numChildren; i++) { 773 final Accessible child = ac.getAccessibleChild(i); 774 if (child == null) continue; 775 776 final AccessibleContext context = child.getAccessibleContext(); 777 if (context == null) continue; 778 779 if (whichChildren == JAVA_AX_VISIBLE_CHILDREN) { 780 AccessibleComponent acomp = context.getAccessibleComponent(); 781 if (acomp == null || !acomp.isVisible()) { 782 continue; 783 } 784 } else if (whichChildren == JAVA_AX_SELECTED_CHILDREN) { 785 AccessibleSelection sel = ac.getAccessibleSelection(); 786 if (sel == null || !sel.isAccessibleChildSelected(i)) { 787 continue; 788 } 789 } 790 791 if (!allowIgnored) { 792 final AccessibleRole role = context.getAccessibleRole(); 793 if (role != null && ignoredRoles != null && ignoredRoles.contains(roleKey(role))) { 794 // Get the child's unignored children. 795 _addChildren(child, whichChildren, false, childrenAndRoles); 796 } else { 797 childrenAndRoles.add(child); 798 childrenAndRoles.add(getAccessibleRole(child)); 799 } 800 } else { 801 childrenAndRoles.add(child); 802 childrenAndRoles.add(getAccessibleRole(child)); 803 } 804 805 // If there is an index, and we are beyond it, time to finish up 806 if ((whichChildren >= 0) && (childrenAndRoles.size() / 2) >= (whichChildren + 1)) { 807 return; 808 } 809 } 810 } 811 812 private static native String roleKey(AccessibleRole aRole); 813 814 public static Object[] getChildren(final Accessible a, final Component c) { 815 if (a == null) return null; 816 return invokeAndWait(new Callable<Object[]>() { 817 public Object[] call() throws Exception { 818 final AccessibleContext ac = a.getAccessibleContext(); 819 if (ac == null) return null; 820 821 final int numChildren = ac.getAccessibleChildrenCount(); 822 final Object[] children = new Object[numChildren]; 823 for (int i = 0; i < numChildren; i++) { 824 children[i] = ac.getAccessibleChild(i); 825 } 826 return children; 827 } 828 }, c); 829 } 830 831 /** 832 * @return AWTView ptr, a peer of the CPlatformView associated with the toplevel container of the Accessible, if any 833 */ 834 private static long getAWTView(Accessible a) { 835 Accessible ax = CAccessible.getSwingAccessible(a); 836 if (!(ax instanceof Component)) return 0; 837 838 return invokeAndWait(new Callable<Long>() { 839 public Long call() throws Exception { 840 Component cont = (Component) ax; 841 while (cont != null && !(cont instanceof Window)) { 842 cont = cont.getParent(); 843 } 844 if (cont != null) { 845 LWWindowPeer peer = (LWWindowPeer) AWTAccessor.getComponentAccessor().getPeer(cont); 846 if (peer != null) { 847 return ((CPlatformWindow) peer.getPlatformWindow()).getContentView().getAWTView(); 848 } 849 } 850 return 0L; 851 } 852 }, (Component)ax); 853 } 854 } 855