1 /* 2 * Copyright (c) 2000, 2018, 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 javax.swing.text.html; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.beans.*; 31 import java.util.*; 32 import javax.swing.*; 33 import javax.swing.event.*; 34 import javax.swing.text.*; 35 import javax.accessibility.*; 36 import java.text.BreakIterator; 37 38 /* 39 * The AccessibleHTML class provide information about the contents 40 * of a HTML document to assistive technologies. 41 * 42 * @author Lynn Monsanto 43 */ 44 class AccessibleHTML implements Accessible { 45 46 /** 47 * The editor. 48 */ 49 private JEditorPane editor; 50 /** 51 * Current model. 52 */ 53 private Document model; 54 /** 55 * DocumentListener installed on the current model. 56 */ 57 private DocumentListener docListener; 58 /** 59 * PropertyChangeListener installed on the editor 60 */ 61 private PropertyChangeListener propChangeListener; 62 /** 63 * The root ElementInfo for the document 64 */ 65 private ElementInfo rootElementInfo; 66 /* 67 * The root accessible context for the document 68 */ 69 private RootHTMLAccessibleContext rootHTMLAccessibleContext; 70 71 public AccessibleHTML(JEditorPane pane) { 72 editor = pane; 73 propChangeListener = new PropertyChangeHandler(); 74 setDocument(editor.getDocument()); 75 76 docListener = new DocumentHandler(); 77 } 78 79 /** 80 * Sets the document. 81 */ 82 private void setDocument(Document document) { 83 if (model != null) { 84 model.removeDocumentListener(docListener); 85 } 86 if (editor != null) { 87 editor.removePropertyChangeListener(propChangeListener); 88 } 89 this.model = document; 90 if (model != null) { 91 if (rootElementInfo != null) { 92 rootElementInfo.invalidate(false); 93 } 94 buildInfo(); 95 model.addDocumentListener(docListener); 96 } 97 else { 98 rootElementInfo = null; 99 } 100 if (editor != null) { 101 editor.addPropertyChangeListener(propChangeListener); 102 } 103 } 104 105 /** 106 * Returns the Document currently presenting information for. 107 */ 108 private Document getDocument() { 109 return model; 110 } 111 112 /** 113 * Returns the JEditorPane providing information for. 114 */ 115 private JEditorPane getTextComponent() { 116 return editor; 117 } 118 119 /** 120 * Returns the ElementInfo representing the root Element. 121 */ 122 private ElementInfo getRootInfo() { 123 return rootElementInfo; 124 } 125 126 /** 127 * Returns the root <code>View</code> associated with the current text 128 * component. 129 */ 130 private View getRootView() { 131 return getTextComponent().getUI().getRootView(getTextComponent()); 132 } 133 134 /** 135 * Returns the bounds the root View will be rendered in. 136 */ 137 private Rectangle getRootEditorRect() { 138 Rectangle alloc = getTextComponent().getBounds(); 139 if ((alloc.width > 0) && (alloc.height > 0)) { 140 alloc.x = alloc.y = 0; 141 Insets insets = editor.getInsets(); 142 alloc.x += insets.left; 143 alloc.y += insets.top; 144 alloc.width -= insets.left + insets.right; 145 alloc.height -= insets.top + insets.bottom; 146 return alloc; 147 } 148 return null; 149 } 150 151 /** 152 * If possible acquires a lock on the Document. If a lock has been 153 * obtained a key will be retured that should be passed to 154 * <code>unlock</code>. 155 */ 156 private Object lock() { 157 Document document = getDocument(); 158 159 if (document instanceof AbstractDocument) { 160 ((AbstractDocument)document).readLock(); 161 return document; 162 } 163 return null; 164 } 165 166 /** 167 * Releases a lock previously obtained via <code>lock</code>. 168 */ 169 private void unlock(Object key) { 170 if (key != null) { 171 ((AbstractDocument)key).readUnlock(); 172 } 173 } 174 175 /** 176 * Rebuilds the information from the current info. 177 */ 178 private void buildInfo() { 179 Object lock = lock(); 180 181 try { 182 Document doc = getDocument(); 183 Element root = doc.getDefaultRootElement(); 184 185 rootElementInfo = new ElementInfo(root); 186 rootElementInfo.validate(); 187 } finally { 188 unlock(lock); 189 } 190 } 191 192 /* 193 * Create an ElementInfo subclass based on the passed in Element. 194 */ 195 ElementInfo createElementInfo(Element e, ElementInfo parent) { 196 AttributeSet attrs = e.getAttributes(); 197 198 if (attrs != null) { 199 Object name = attrs.getAttribute(StyleConstants.NameAttribute); 200 201 if (name == HTML.Tag.IMG) { 202 return new IconElementInfo(e, parent); 203 } 204 else if (name == HTML.Tag.CONTENT || name == HTML.Tag.CAPTION) { 205 return new TextElementInfo(e, parent); 206 } 207 else if (name == HTML.Tag.TABLE) { 208 return new TableElementInfo(e, parent); 209 } 210 } 211 return null; 212 } 213 214 /** 215 * Returns the root AccessibleContext for the document 216 */ 217 public AccessibleContext getAccessibleContext() { 218 if (rootHTMLAccessibleContext == null) { 219 rootHTMLAccessibleContext = 220 new RootHTMLAccessibleContext(rootElementInfo); 221 } 222 return rootHTMLAccessibleContext; 223 } 224 225 /* 226 * The roow AccessibleContext for the document 227 */ 228 private class RootHTMLAccessibleContext extends HTMLAccessibleContext { 229 230 public RootHTMLAccessibleContext(ElementInfo elementInfo) { 231 super(elementInfo); 232 } 233 234 /** 235 * Gets the accessibleName property of this object. The accessibleName 236 * property of an object is a localized String that designates the purpose 237 * of the object. For example, the accessibleName property of a label 238 * or button might be the text of the label or button itself. In the 239 * case of an object that doesn't display its name, the accessibleName 240 * should still be set. For example, in the case of a text field used 241 * to enter the name of a city, the accessibleName for the en_US locale 242 * could be 'city.' 243 * 244 * @return the localized name of the object; null if this 245 * object does not have a name 246 * 247 * @see #setAccessibleName 248 */ 249 public String getAccessibleName() { 250 if (model != null) { 251 return (String)model.getProperty(Document.TitleProperty); 252 } else { 253 return null; 254 } 255 } 256 257 /** 258 * Gets the accessibleDescription property of this object. If this 259 * property isn't set, returns the content type of this 260 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text"). 261 * 262 * @return the localized description of the object; <code>null</code> 263 * if this object does not have a description 264 * 265 * @see #setAccessibleName 266 */ 267 public String getAccessibleDescription() { 268 return editor.getContentType(); 269 } 270 271 /** 272 * Gets the role of this object. The role of the object is the generic 273 * purpose or use of the class of this object. For example, the role 274 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in 275 * AccessibleRole are provided so component developers can pick from 276 * a set of predefined roles. This enables assistive technologies to 277 * provide a consistent interface to various tweaked subclasses of 278 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components 279 * that act like a push button) as well as distinguish between subclasses 280 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes 281 * and AccessibleRole.RADIO_BUTTON for radio buttons). 282 * <p>Note that the AccessibleRole class is also extensible, so 283 * custom component developers can define their own AccessibleRole's 284 * if the set of predefined roles is inadequate. 285 * 286 * @return an instance of AccessibleRole describing the role of the object 287 * @see AccessibleRole 288 */ 289 public AccessibleRole getAccessibleRole() { 290 return AccessibleRole.TEXT; 291 } 292 } 293 294 /* 295 * Base AccessibleContext class for HTML elements 296 */ 297 protected abstract class HTMLAccessibleContext extends AccessibleContext 298 implements Accessible, AccessibleComponent { 299 300 protected ElementInfo elementInfo; 301 302 public HTMLAccessibleContext(ElementInfo elementInfo) { 303 this.elementInfo = elementInfo; 304 } 305 306 // begin AccessibleContext implementation ... 307 public AccessibleContext getAccessibleContext() { 308 return this; 309 } 310 311 /** 312 * Gets the state set of this object. 313 * 314 * @return an instance of AccessibleStateSet describing the states 315 * of the object 316 * @see AccessibleStateSet 317 */ 318 public AccessibleStateSet getAccessibleStateSet() { 319 AccessibleStateSet states = new AccessibleStateSet(); 320 Component comp = getTextComponent(); 321 322 if (comp.isEnabled()) { 323 states.add(AccessibleState.ENABLED); 324 } 325 if (comp instanceof JTextComponent && 326 ((JTextComponent)comp).isEditable()) { 327 328 states.add(AccessibleState.EDITABLE); 329 states.add(AccessibleState.FOCUSABLE); 330 } 331 if (comp.isVisible()) { 332 states.add(AccessibleState.VISIBLE); 333 } 334 if (comp.isShowing()) { 335 states.add(AccessibleState.SHOWING); 336 } 337 return states; 338 } 339 340 /** 341 * Gets the 0-based index of this object in its accessible parent. 342 * 343 * @return the 0-based index of this object in its parent; -1 if this 344 * object does not have an accessible parent. 345 * 346 * @see #getAccessibleParent 347 * @see #getAccessibleChildrenCount 348 * @see #getAccessibleChild 349 */ 350 public int getAccessibleIndexInParent() { 351 return elementInfo.getIndexInParent(); 352 } 353 354 /** 355 * Returns the number of accessible children of the object. 356 * 357 * @return the number of accessible children of the object. 358 */ 359 public int getAccessibleChildrenCount() { 360 return elementInfo.getChildCount(); 361 } 362 363 /** 364 * Returns the specified Accessible child of the object. The Accessible 365 * children of an Accessible object are zero-based, so the first child 366 * of an Accessible child is at index 0, the second child is at index 1, 367 * and so on. 368 * 369 * @param i zero-based index of child 370 * @return the Accessible child of the object 371 * @see #getAccessibleChildrenCount 372 */ 373 public Accessible getAccessibleChild(int i) { 374 ElementInfo childInfo = elementInfo.getChild(i); 375 if (childInfo != null && childInfo instanceof Accessible) { 376 return (Accessible)childInfo; 377 } else { 378 return null; 379 } 380 } 381 382 /** 383 * Gets the locale of the component. If the component does not have a 384 * locale, then the locale of its parent is returned. 385 * 386 * @return this component's locale. If this component does not have 387 * a locale, the locale of its parent is returned. 388 * 389 * @exception IllegalComponentStateException 390 * If the Component does not have its own locale and has not yet been 391 * added to a containment hierarchy such that the locale can be 392 * determined from the containing parent. 393 */ 394 public Locale getLocale() throws IllegalComponentStateException { 395 return editor.getLocale(); 396 } 397 // ... end AccessibleContext implementation 398 399 // begin AccessibleComponent implementation ... 400 public AccessibleComponent getAccessibleComponent() { 401 return this; 402 } 403 404 /** 405 * Gets the background color of this object. 406 * 407 * @return the background color, if supported, of the object; 408 * otherwise, null 409 * @see #setBackground 410 */ 411 public Color getBackground() { 412 return getTextComponent().getBackground(); 413 } 414 415 /** 416 * Sets the background color of this object. 417 * 418 * @param c the new Color for the background 419 * @see #setBackground 420 */ 421 public void setBackground(Color c) { 422 getTextComponent().setBackground(c); 423 } 424 425 /** 426 * Gets the foreground color of this object. 427 * 428 * @return the foreground color, if supported, of the object; 429 * otherwise, null 430 * @see #setForeground 431 */ 432 public Color getForeground() { 433 return getTextComponent().getForeground(); 434 } 435 436 /** 437 * Sets the foreground color of this object. 438 * 439 * @param c the new Color for the foreground 440 * @see #getForeground 441 */ 442 public void setForeground(Color c) { 443 getTextComponent().setForeground(c); 444 } 445 446 /** 447 * Gets the Cursor of this object. 448 * 449 * @return the Cursor, if supported, of the object; otherwise, null 450 * @see #setCursor 451 */ 452 public Cursor getCursor() { 453 return getTextComponent().getCursor(); 454 } 455 456 /** 457 * Sets the Cursor of this object. 458 * 459 * @param cursor the new Cursor for the object 460 * @see #getCursor 461 */ 462 public void setCursor(Cursor cursor) { 463 getTextComponent().setCursor(cursor); 464 } 465 466 /** 467 * Gets the Font of this object. 468 * 469 * @return the Font,if supported, for the object; otherwise, null 470 * @see #setFont 471 */ 472 public Font getFont() { 473 return getTextComponent().getFont(); 474 } 475 476 /** 477 * Sets the Font of this object. 478 * 479 * @param f the new Font for the object 480 * @see #getFont 481 */ 482 public void setFont(Font f) { 483 getTextComponent().setFont(f); 484 } 485 486 /** 487 * Gets the FontMetrics of this object. 488 * 489 * @param f the Font 490 * @return the FontMetrics, if supported, the object; otherwise, null 491 * @see #getFont 492 */ 493 public FontMetrics getFontMetrics(Font f) { 494 return getTextComponent().getFontMetrics(f); 495 } 496 497 /** 498 * Determines if the object is enabled. Objects that are enabled 499 * will also have the AccessibleState.ENABLED state set in their 500 * AccessibleStateSets. 501 * 502 * @return true if object is enabled; otherwise, false 503 * @see #setEnabled 504 * @see AccessibleContext#getAccessibleStateSet 505 * @see AccessibleState#ENABLED 506 * @see AccessibleStateSet 507 */ 508 public boolean isEnabled() { 509 return getTextComponent().isEnabled(); 510 } 511 512 /** 513 * Sets the enabled state of the object. 514 * 515 * @param b if true, enables this object; otherwise, disables it 516 * @see #isEnabled 517 */ 518 public void setEnabled(boolean b) { 519 getTextComponent().setEnabled(b); 520 } 521 522 /** 523 * Determines if the object is visible. Note: this means that the 524 * object intends to be visible; however, it may not be 525 * showing on the screen because one of the objects that this object 526 * is contained by is currently not visible. To determine if an object 527 * is showing on the screen, use isShowing(). 528 * <p>Objects that are visible will also have the 529 * AccessibleState.VISIBLE state set in their AccessibleStateSets. 530 * 531 * @return true if object is visible; otherwise, false 532 * @see #setVisible 533 * @see AccessibleContext#getAccessibleStateSet 534 * @see AccessibleState#VISIBLE 535 * @see AccessibleStateSet 536 */ 537 public boolean isVisible() { 538 return getTextComponent().isVisible(); 539 } 540 541 /** 542 * Sets the visible state of the object. 543 * 544 * @param b if true, shows this object; otherwise, hides it 545 * @see #isVisible 546 */ 547 public void setVisible(boolean b) { 548 getTextComponent().setVisible(b); 549 } 550 551 /** 552 * Determines if the object is showing. This is determined by checking 553 * the visibility of the object and its ancestors. 554 * Note: this 555 * will return true even if the object is obscured by another (for 556 * example, it is underneath a menu that was pulled down). 557 * 558 * @return true if object is showing; otherwise, false 559 */ 560 public boolean isShowing() { 561 return getTextComponent().isShowing(); 562 } 563 564 /** 565 * Checks whether the specified point is within this object's bounds, 566 * where the point's x and y coordinates are defined to be relative 567 * to the coordinate system of the object. 568 * 569 * @param p the Point relative to the coordinate system of the object 570 * @return true if object contains Point; otherwise false 571 * @see #getBounds 572 */ 573 public boolean contains(Point p) { 574 Rectangle r = getBounds(); 575 if (r != null) { 576 return r.contains(p.x, p.y); 577 } else { 578 return false; 579 } 580 } 581 582 /** 583 * Returns the location of the object on the screen. 584 * 585 * @return the location of the object on screen; null if this object 586 * is not on the screen 587 * @see #getBounds 588 * @see #getLocation 589 */ 590 public Point getLocationOnScreen() { 591 Point editorLocation = getTextComponent().getLocationOnScreen(); 592 Rectangle r = getBounds(); 593 if (r != null) { 594 return new Point(editorLocation.x + r.x, 595 editorLocation.y + r.y); 596 } else { 597 return null; 598 } 599 } 600 601 /** 602 * Gets the location of the object relative to the parent in the form 603 * of a point specifying the object's top-left corner in the screen's 604 * coordinate space. 605 * 606 * @return An instance of Point representing the top-left corner of the 607 * object's bounds in the coordinate space of the screen; null if 608 * this object or its parent are not on the screen 609 * @see #getBounds 610 * @see #getLocationOnScreen 611 */ 612 public Point getLocation() { 613 Rectangle r = getBounds(); 614 if (r != null) { 615 return new Point(r.x, r.y); 616 } else { 617 return null; 618 } 619 } 620 621 /** 622 * Sets the location of the object relative to the parent. 623 * @param p the new position for the top-left corner 624 * @see #getLocation 625 */ 626 public void setLocation(Point p) { 627 } 628 629 /** 630 * Gets the bounds of this object in the form of a Rectangle object. 631 * The bounds specify this object's width, height, and location 632 * relative to its parent. 633 * 634 * @return A rectangle indicating this component's bounds; null if 635 * this object is not on the screen. 636 * @see #contains 637 */ 638 public Rectangle getBounds() { 639 return elementInfo.getBounds(); 640 } 641 642 /** 643 * Sets the bounds of this object in the form of a Rectangle object. 644 * The bounds specify this object's width, height, and location 645 * relative to its parent. 646 * 647 * @param r rectangle indicating this component's bounds 648 * @see #getBounds 649 */ 650 public void setBounds(Rectangle r) { 651 } 652 653 /** 654 * Returns the size of this object in the form of a Dimension object. 655 * The height field of the Dimension object contains this object's 656 * height, and the width field of the Dimension object contains this 657 * object's width. 658 * 659 * @return A Dimension object that indicates the size of this component; 660 * null if this object is not on the screen 661 * @see #setSize 662 */ 663 public Dimension getSize() { 664 Rectangle r = getBounds(); 665 if (r != null) { 666 return new Dimension(r.width, r.height); 667 } else { 668 return null; 669 } 670 } 671 672 /** 673 * Resizes this object so that it has width and height. 674 * 675 * @param d The dimension specifying the new size of the object. 676 * @see #getSize 677 */ 678 public void setSize(Dimension d) { 679 Component comp = getTextComponent(); 680 comp.setSize(d); 681 } 682 683 /** 684 * Returns the Accessible child, if one exists, contained at the local 685 * coordinate Point. 686 * 687 * @param p The point relative to the coordinate system of this object. 688 * @return the Accessible, if it exists, at the specified location; 689 * otherwise null 690 */ 691 public Accessible getAccessibleAt(Point p) { 692 ElementInfo innerMostElement = getElementInfoAt(rootElementInfo, p); 693 if (innerMostElement instanceof Accessible) { 694 return (Accessible)innerMostElement; 695 } else { 696 return null; 697 } 698 } 699 700 private ElementInfo getElementInfoAt(ElementInfo elementInfo, Point p) { 701 if (elementInfo.getBounds() == null) { 702 return null; 703 } 704 if (elementInfo.getChildCount() == 0 && 705 elementInfo.getBounds().contains(p)) { 706 return elementInfo; 707 708 } else { 709 if (elementInfo instanceof TableElementInfo) { 710 // Handle table caption as a special case since it's the 711 // only table child that is not a table row. 712 ElementInfo captionInfo = 713 ((TableElementInfo)elementInfo).getCaptionInfo(); 714 if (captionInfo != null) { 715 Rectangle bounds = captionInfo.getBounds(); 716 if (bounds != null && bounds.contains(p)) { 717 return captionInfo; 718 } 719 } 720 } 721 for (int i = 0; i < elementInfo.getChildCount(); i++) 722 { 723 ElementInfo childInfo = elementInfo.getChild(i); 724 ElementInfo retValue = getElementInfoAt(childInfo, p); 725 if (retValue != null) { 726 return retValue; 727 } 728 } 729 } 730 return null; 731 } 732 733 /** 734 * Returns whether this object can accept focus or not. Objects that 735 * can accept focus will also have the AccessibleState.FOCUSABLE state 736 * set in their AccessibleStateSets. 737 * 738 * @return true if object can accept focus; otherwise false 739 * @see AccessibleContext#getAccessibleStateSet 740 * @see AccessibleState#FOCUSABLE 741 * @see AccessibleState#FOCUSED 742 * @see AccessibleStateSet 743 */ 744 public boolean isFocusTraversable() { 745 Component comp = getTextComponent(); 746 if (comp instanceof JTextComponent) { 747 if (((JTextComponent)comp).isEditable()) { 748 return true; 749 } 750 } 751 return false; 752 } 753 754 /** 755 * Requests focus for this object. If this object cannot accept focus, 756 * nothing will happen. Otherwise, the object will attempt to take 757 * focus. 758 * @see #isFocusTraversable 759 */ 760 public void requestFocus() { 761 // TIGER - 4856191 762 if (! isFocusTraversable()) { 763 return; 764 } 765 766 Component comp = getTextComponent(); 767 if (comp instanceof JTextComponent) { 768 769 comp.requestFocusInWindow(); 770 771 try { 772 if (elementInfo.validateIfNecessary()) { 773 // set the caret position to the start of this component 774 Element elem = elementInfo.getElement(); 775 ((JTextComponent)comp).setCaretPosition(elem.getStartOffset()); 776 777 // fire a AccessibleState.FOCUSED property change event 778 AccessibleContext ac = editor.getAccessibleContext(); 779 PropertyChangeEvent pce = new PropertyChangeEvent(this, 780 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 781 null, AccessibleState.FOCUSED); 782 ac.firePropertyChange( 783 AccessibleContext.ACCESSIBLE_STATE_PROPERTY, 784 null, pce); 785 } 786 } catch (IllegalArgumentException e) { 787 // don't fire property change event 788 } 789 } 790 } 791 792 /** 793 * Adds the specified focus listener to receive focus events from this 794 * component. 795 * 796 * @param l the focus listener 797 * @see #removeFocusListener 798 */ 799 public void addFocusListener(FocusListener l) { 800 getTextComponent().addFocusListener(l); 801 } 802 803 /** 804 * Removes the specified focus listener so it no longer receives focus 805 * events from this component. 806 * 807 * @param l the focus listener 808 * @see #addFocusListener 809 */ 810 public void removeFocusListener(FocusListener l) { 811 getTextComponent().removeFocusListener(l); 812 } 813 // ... end AccessibleComponent implementation 814 } // ... end HTMLAccessibleContext 815 816 817 818 /* 819 * ElementInfo for text 820 */ 821 class TextElementInfo extends ElementInfo implements Accessible { 822 823 TextElementInfo(Element element, ElementInfo parent) { 824 super(element, parent); 825 } 826 827 // begin AccessibleText implementation ... 828 private AccessibleContext accessibleContext; 829 830 public AccessibleContext getAccessibleContext() { 831 if (accessibleContext == null) { 832 accessibleContext = new TextAccessibleContext(this); 833 } 834 return accessibleContext; 835 } 836 837 /* 838 * AccessibleContext for text elements 839 */ 840 public class TextAccessibleContext extends HTMLAccessibleContext 841 implements AccessibleText { 842 843 public TextAccessibleContext(ElementInfo elementInfo) { 844 super(elementInfo); 845 } 846 847 public AccessibleText getAccessibleText() { 848 return this; 849 } 850 851 /** 852 * Gets the accessibleName property of this object. The accessibleName 853 * property of an object is a localized String that designates the purpose 854 * of the object. For example, the accessibleName property of a label 855 * or button might be the text of the label or button itself. In the 856 * case of an object that doesn't display its name, the accessibleName 857 * should still be set. For example, in the case of a text field used 858 * to enter the name of a city, the accessibleName for the en_US locale 859 * could be 'city.' 860 * 861 * @return the localized name of the object; null if this 862 * object does not have a name 863 * 864 * @see #setAccessibleName 865 */ 866 public String getAccessibleName() { 867 if (model != null) { 868 return (String)model.getProperty(Document.TitleProperty); 869 } else { 870 return null; 871 } 872 } 873 874 /** 875 * Gets the accessibleDescription property of this object. If this 876 * property isn't set, returns the content type of this 877 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text"). 878 * 879 * @return the localized description of the object; <code>null</code> 880 * if this object does not have a description 881 * 882 * @see #setAccessibleName 883 */ 884 public String getAccessibleDescription() { 885 return editor.getContentType(); 886 } 887 888 /** 889 * Gets the role of this object. The role of the object is the generic 890 * purpose or use of the class of this object. For example, the role 891 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in 892 * AccessibleRole are provided so component developers can pick from 893 * a set of predefined roles. This enables assistive technologies to 894 * provide a consistent interface to various tweaked subclasses of 895 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components 896 * that act like a push button) as well as distinguish between subclasses 897 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes 898 * and AccessibleRole.RADIO_BUTTON for radio buttons). 899 * <p>Note that the AccessibleRole class is also extensible, so 900 * custom component developers can define their own AccessibleRole's 901 * if the set of predefined roles is inadequate. 902 * 903 * @return an instance of AccessibleRole describing the role of the object 904 * @see AccessibleRole 905 */ 906 public AccessibleRole getAccessibleRole() { 907 return AccessibleRole.TEXT; 908 } 909 910 /** 911 * Given a point in local coordinates, return the zero-based index 912 * of the character under that Point. If the point is invalid, 913 * this method returns -1. 914 * 915 * @param p the Point in local coordinates 916 * @return the zero-based index of the character under Point p; if 917 * Point is invalid returns -1. 918 */ 919 @SuppressWarnings("deprecation") 920 public int getIndexAtPoint(Point p) { 921 View v = getView(); 922 if (v != null) { 923 return v.viewToModel(p.x, p.y, getBounds()); 924 } else { 925 return -1; 926 } 927 } 928 929 /** 930 * Determine the bounding box of the character at the given 931 * index into the string. The bounds are returned in local 932 * coordinates. If the index is invalid an empty rectangle is 933 * returned. 934 * 935 * @param i the index into the String 936 * @return the screen coordinates of the character's the bounding box, 937 * if index is invalid returns an empty rectangle. 938 */ 939 @SuppressWarnings("deprecation") 940 public Rectangle getCharacterBounds(int i) { 941 try { 942 return editor.getUI().modelToView(editor, i); 943 } catch (BadLocationException e) { 944 return null; 945 } 946 } 947 948 /** 949 * Return the number of characters (valid indicies) 950 * 951 * @return the number of characters 952 */ 953 public int getCharCount() { 954 if (validateIfNecessary()) { 955 Element elem = elementInfo.getElement(); 956 return elem.getEndOffset() - elem.getStartOffset(); 957 } 958 return 0; 959 } 960 961 /** 962 * Return the zero-based offset of the caret. 963 * 964 * Note: That to the right of the caret will have the same index 965 * value as the offset (the caret is between two characters). 966 * @return the zero-based offset of the caret. 967 */ 968 public int getCaretPosition() { 969 View v = getView(); 970 if (v == null) { 971 return -1; 972 } 973 Container c = v.getContainer(); 974 if (c == null) { 975 return -1; 976 } 977 if (c instanceof JTextComponent) { 978 return ((JTextComponent)c).getCaretPosition(); 979 } else { 980 return -1; 981 } 982 } 983 984 /** 985 * IndexedSegment extends Segment adding the offset into the 986 * the model the <code>Segment</code> was asked for. 987 */ 988 private class IndexedSegment extends Segment { 989 /** 990 * Offset into the model that the position represents. 991 */ 992 public int modelOffset; 993 } 994 995 public String getAtIndex(int part, int index) { 996 return getAtIndex(part, index, 0); 997 } 998 999 1000 public String getAfterIndex(int part, int index) { 1001 return getAtIndex(part, index, 1); 1002 } 1003 1004 public String getBeforeIndex(int part, int index) { 1005 return getAtIndex(part, index, -1); 1006 } 1007 1008 /** 1009 * Gets the word, sentence, or character at <code>index</code>. 1010 * If <code>direction</code> is non-null this will find the 1011 * next/previous word/sentence/character. 1012 */ 1013 private String getAtIndex(int part, int index, int direction) { 1014 if (model instanceof AbstractDocument) { 1015 ((AbstractDocument)model).readLock(); 1016 } 1017 try { 1018 if (index < 0 || index >= model.getLength()) { 1019 return null; 1020 } 1021 switch (part) { 1022 case AccessibleText.CHARACTER: 1023 if (index + direction < model.getLength() && 1024 index + direction >= 0) { 1025 return model.getText(index + direction, 1); 1026 } 1027 break; 1028 1029 1030 case AccessibleText.WORD: 1031 case AccessibleText.SENTENCE: 1032 IndexedSegment seg = getSegmentAt(part, index); 1033 if (seg != null) { 1034 if (direction != 0) { 1035 int next; 1036 1037 1038 if (direction < 0) { 1039 next = seg.modelOffset - 1; 1040 } 1041 else { 1042 next = seg.modelOffset + direction * seg.count; 1043 } 1044 if (next >= 0 && next <= model.getLength()) { 1045 seg = getSegmentAt(part, next); 1046 } 1047 else { 1048 seg = null; 1049 } 1050 } 1051 if (seg != null) { 1052 return new String(seg.array, seg.offset, 1053 seg.count); 1054 } 1055 } 1056 break; 1057 1058 default: 1059 break; 1060 } 1061 } catch (BadLocationException e) { 1062 } finally { 1063 if (model instanceof AbstractDocument) { 1064 ((AbstractDocument)model).readUnlock(); 1065 } 1066 } 1067 return null; 1068 } 1069 1070 /* 1071 * Returns the paragraph element for the specified index. 1072 */ 1073 private Element getParagraphElement(int index) { 1074 if (model instanceof PlainDocument ) { 1075 PlainDocument sdoc = (PlainDocument)model; 1076 return sdoc.getParagraphElement(index); 1077 } else if (model instanceof StyledDocument) { 1078 StyledDocument sdoc = (StyledDocument)model; 1079 return sdoc.getParagraphElement(index); 1080 } else { 1081 Element para; 1082 for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) { 1083 int pos = para.getElementIndex(index); 1084 para = para.getElement(pos); 1085 } 1086 if (para == null) { 1087 return null; 1088 } 1089 return para.getParentElement(); 1090 } 1091 } 1092 1093 /* 1094 * Returns a <code>Segment</code> containing the paragraph text 1095 * at <code>index</code>, or null if <code>index</code> isn't 1096 * valid. 1097 */ 1098 private IndexedSegment getParagraphElementText(int index) 1099 throws BadLocationException { 1100 Element para = getParagraphElement(index); 1101 1102 1103 if (para != null) { 1104 IndexedSegment segment = new IndexedSegment(); 1105 try { 1106 int length = para.getEndOffset() - para.getStartOffset(); 1107 model.getText(para.getStartOffset(), length, segment); 1108 } catch (BadLocationException e) { 1109 return null; 1110 } 1111 segment.modelOffset = para.getStartOffset(); 1112 return segment; 1113 } 1114 return null; 1115 } 1116 1117 1118 /** 1119 * Returns the Segment at <code>index</code> representing either 1120 * the paragraph or sentence as identified by <code>part</code>, or 1121 * null if a valid paragraph/sentence can't be found. The offset 1122 * will point to the start of the word/sentence in the array, and 1123 * the modelOffset will point to the location of the word/sentence 1124 * in the model. 1125 */ 1126 private IndexedSegment getSegmentAt(int part, int index) 1127 throws BadLocationException { 1128 1129 IndexedSegment seg = getParagraphElementText(index); 1130 if (seg == null) { 1131 return null; 1132 } 1133 BreakIterator iterator; 1134 switch (part) { 1135 case AccessibleText.WORD: 1136 iterator = BreakIterator.getWordInstance(getLocale()); 1137 break; 1138 case AccessibleText.SENTENCE: 1139 iterator = BreakIterator.getSentenceInstance(getLocale()); 1140 break; 1141 default: 1142 return null; 1143 } 1144 seg.first(); 1145 iterator.setText(seg); 1146 int end = iterator.following(index - seg.modelOffset + seg.offset); 1147 if (end == BreakIterator.DONE) { 1148 return null; 1149 } 1150 if (end > seg.offset + seg.count) { 1151 return null; 1152 } 1153 int begin = iterator.previous(); 1154 if (begin == BreakIterator.DONE || 1155 begin >= seg.offset + seg.count) { 1156 return null; 1157 } 1158 seg.modelOffset = seg.modelOffset + begin - seg.offset; 1159 seg.offset = begin; 1160 seg.count = end - begin; 1161 return seg; 1162 } 1163 1164 /** 1165 * Return the AttributeSet for a given character at a given index 1166 * 1167 * @param i the zero-based index into the text 1168 * @return the AttributeSet of the character 1169 */ 1170 public AttributeSet getCharacterAttribute(int i) { 1171 if (model instanceof StyledDocument) { 1172 StyledDocument doc = (StyledDocument)model; 1173 Element elem = doc.getCharacterElement(i); 1174 if (elem != null) { 1175 return elem.getAttributes(); 1176 } 1177 } 1178 return null; 1179 } 1180 1181 /** 1182 * Returns the start offset within the selected text. 1183 * If there is no selection, but there is 1184 * a caret, the start and end offsets will be the same. 1185 * 1186 * @return the index into the text of the start of the selection 1187 */ 1188 public int getSelectionStart() { 1189 return editor.getSelectionStart(); 1190 } 1191 1192 /** 1193 * Returns the end offset within the selected text. 1194 * If there is no selection, but there is 1195 * a caret, the start and end offsets will be the same. 1196 * 1197 * @return the index into the text of the end of the selection 1198 */ 1199 public int getSelectionEnd() { 1200 return editor.getSelectionEnd(); 1201 } 1202 1203 /** 1204 * Returns the portion of the text that is selected. 1205 * 1206 * @return the String portion of the text that is selected 1207 */ 1208 public String getSelectedText() { 1209 return editor.getSelectedText(); 1210 } 1211 1212 /* 1213 * Returns the text substring starting at the specified 1214 * offset with the specified length. 1215 */ 1216 private String getText(int offset, int length) 1217 throws BadLocationException { 1218 1219 if (model != null && model instanceof StyledDocument) { 1220 StyledDocument doc = (StyledDocument)model; 1221 return model.getText(offset, length); 1222 } else { 1223 return null; 1224 } 1225 } 1226 } 1227 } 1228 1229 /* 1230 * ElementInfo for images 1231 */ 1232 private class IconElementInfo extends ElementInfo implements Accessible { 1233 1234 private int width = -1; 1235 private int height = -1; 1236 1237 IconElementInfo(Element element, ElementInfo parent) { 1238 super(element, parent); 1239 } 1240 1241 protected void invalidate(boolean first) { 1242 super.invalidate(first); 1243 width = height = -1; 1244 } 1245 1246 private int getImageSize(Object key) { 1247 if (validateIfNecessary()) { 1248 int size = getIntAttr(getAttributes(), key, -1); 1249 1250 if (size == -1) { 1251 View v = getView(); 1252 1253 size = 0; 1254 if (v instanceof ImageView) { 1255 Image img = ((ImageView)v).getImage(); 1256 if (img != null) { 1257 if (key == HTML.Attribute.WIDTH) { 1258 size = img.getWidth(null); 1259 } 1260 else { 1261 size = img.getHeight(null); 1262 } 1263 } 1264 } 1265 } 1266 return size; 1267 } 1268 return 0; 1269 } 1270 1271 // begin AccessibleIcon implementation ... 1272 private AccessibleContext accessibleContext; 1273 1274 public AccessibleContext getAccessibleContext() { 1275 if (accessibleContext == null) { 1276 accessibleContext = new IconAccessibleContext(this); 1277 } 1278 return accessibleContext; 1279 } 1280 1281 /* 1282 * AccessibleContext for images 1283 */ 1284 protected class IconAccessibleContext extends HTMLAccessibleContext 1285 implements AccessibleIcon { 1286 1287 public IconAccessibleContext(ElementInfo elementInfo) { 1288 super(elementInfo); 1289 } 1290 1291 /** 1292 * Gets the accessibleName property of this object. The accessibleName 1293 * property of an object is a localized String that designates the purpose 1294 * of the object. For example, the accessibleName property of a label 1295 * or button might be the text of the label or button itself. In the 1296 * case of an object that doesn't display its name, the accessibleName 1297 * should still be set. For example, in the case of a text field used 1298 * to enter the name of a city, the accessibleName for the en_US locale 1299 * could be 'city.' 1300 * 1301 * @return the localized name of the object; null if this 1302 * object does not have a name 1303 * 1304 * @see #setAccessibleName 1305 */ 1306 public String getAccessibleName() { 1307 return getAccessibleIconDescription(); 1308 } 1309 1310 /** 1311 * Gets the accessibleDescription property of this object. If this 1312 * property isn't set, returns the content type of this 1313 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text"). 1314 * 1315 * @return the localized description of the object; <code>null</code> 1316 * if this object does not have a description 1317 * 1318 * @see #setAccessibleName 1319 */ 1320 public String getAccessibleDescription() { 1321 return editor.getContentType(); 1322 } 1323 1324 /** 1325 * Gets the role of this object. The role of the object is the generic 1326 * purpose or use of the class of this object. For example, the role 1327 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in 1328 * AccessibleRole are provided so component developers can pick from 1329 * a set of predefined roles. This enables assistive technologies to 1330 * provide a consistent interface to various tweaked subclasses of 1331 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components 1332 * that act like a push button) as well as distinguish between subclasses 1333 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes 1334 * and AccessibleRole.RADIO_BUTTON for radio buttons). 1335 * <p>Note that the AccessibleRole class is also extensible, so 1336 * custom component developers can define their own AccessibleRole's 1337 * if the set of predefined roles is inadequate. 1338 * 1339 * @return an instance of AccessibleRole describing the role of the object 1340 * @see AccessibleRole 1341 */ 1342 public AccessibleRole getAccessibleRole() { 1343 return AccessibleRole.ICON; 1344 } 1345 1346 public AccessibleIcon [] getAccessibleIcon() { 1347 AccessibleIcon [] icons = new AccessibleIcon[1]; 1348 icons[0] = this; 1349 return icons; 1350 } 1351 1352 /** 1353 * Gets the description of the icon. This is meant to be a brief 1354 * textual description of the object. For example, it might be 1355 * presented to a blind user to give an indication of the purpose 1356 * of the icon. 1357 * 1358 * @return the description of the icon 1359 */ 1360 public String getAccessibleIconDescription() { 1361 return ((ImageView)getView()).getAltText(); 1362 } 1363 1364 /** 1365 * Sets the description of the icon. This is meant to be a brief 1366 * textual description of the object. For example, it might be 1367 * presented to a blind user to give an indication of the purpose 1368 * of the icon. 1369 * 1370 * @param description the description of the icon 1371 */ 1372 public void setAccessibleIconDescription(String description) { 1373 } 1374 1375 /** 1376 * Gets the width of the icon 1377 * 1378 * @return the width of the icon. 1379 */ 1380 public int getAccessibleIconWidth() { 1381 if (width == -1) { 1382 width = getImageSize(HTML.Attribute.WIDTH); 1383 } 1384 return width; 1385 } 1386 1387 /** 1388 * Gets the height of the icon 1389 * 1390 * @return the height of the icon. 1391 */ 1392 public int getAccessibleIconHeight() { 1393 if (height == -1) { 1394 height = getImageSize(HTML.Attribute.HEIGHT); 1395 } 1396 return height; 1397 } 1398 } 1399 // ... end AccessibleIconImplementation 1400 } 1401 1402 1403 /** 1404 * TableElementInfo encapsulates information about a HTML.Tag.TABLE. 1405 * To make access fast it crates a grid containing the children to 1406 * allow for access by row, column. TableElementInfo will contain 1407 * TableRowElementInfos, which will contain TableCellElementInfos. 1408 * Any time one of the rows or columns becomes invalid the table is 1409 * invalidated. This is because any time one of the child attributes 1410 * changes the size of the grid may have changed. 1411 */ 1412 private class TableElementInfo extends ElementInfo 1413 implements Accessible { 1414 1415 protected ElementInfo caption; 1416 1417 /** 1418 * Allocation of the table by row x column. There may be holes (eg 1419 * nulls) depending upon the html, any cell that has a rowspan/colspan 1420 * > 1 will be contained multiple times in the grid. 1421 */ 1422 private TableCellElementInfo[][] grid; 1423 1424 1425 TableElementInfo(Element e, ElementInfo parent) { 1426 super(e, parent); 1427 } 1428 1429 public ElementInfo getCaptionInfo() { 1430 return caption; 1431 } 1432 1433 /** 1434 * Overriden to update the grid when validating. 1435 */ 1436 protected void validate() { 1437 super.validate(); 1438 updateGrid(); 1439 } 1440 1441 /** 1442 * Overriden to only alloc instances of TableRowElementInfos. 1443 */ 1444 protected void loadChildren(Element e) { 1445 1446 for (int counter = 0; counter < e.getElementCount(); counter++) { 1447 Element child = e.getElement(counter); 1448 AttributeSet attrs = child.getAttributes(); 1449 1450 if (attrs.getAttribute(StyleConstants.NameAttribute) == 1451 HTML.Tag.TR) { 1452 addChild(new TableRowElementInfo(child, this, counter)); 1453 1454 } else if (attrs.getAttribute(StyleConstants.NameAttribute) == 1455 HTML.Tag.CAPTION) { 1456 // Handle captions as a special case since all other 1457 // children are table rows. 1458 caption = createElementInfo(child, this); 1459 } 1460 } 1461 } 1462 1463 /** 1464 * Updates the grid. 1465 */ 1466 private void updateGrid() { 1467 // Determine the max row/col count. 1468 int delta = 0; 1469 int maxCols = 0; 1470 int rows; 1471 for (int counter = 0; counter < getChildCount(); counter++) { 1472 TableRowElementInfo row = getRow(counter); 1473 int prev = 0; 1474 for (int y = 0; y < delta; y++) { 1475 prev = Math.max(prev, getRow(counter - y - 1). 1476 getColumnCount(y + 2)); 1477 } 1478 delta = Math.max(row.getRowCount(), delta); 1479 delta--; 1480 maxCols = Math.max(maxCols, row.getColumnCount() + prev); 1481 } 1482 rows = getChildCount() + delta; 1483 1484 // Alloc 1485 grid = new TableCellElementInfo[rows][]; 1486 for (int counter = 0; counter < rows; counter++) { 1487 grid[counter] = new TableCellElementInfo[maxCols]; 1488 } 1489 // Update 1490 for (int counter = 0; counter < rows; counter++) { 1491 getRow(counter).updateGrid(counter); 1492 } 1493 } 1494 1495 /** 1496 * Returns the TableCellElementInfo at the specified index. 1497 */ 1498 public TableRowElementInfo getRow(int index) { 1499 return (TableRowElementInfo)getChild(index); 1500 } 1501 1502 /** 1503 * Returns the TableCellElementInfo by row and column. 1504 */ 1505 public TableCellElementInfo getCell(int r, int c) { 1506 if (validateIfNecessary() && r < grid.length && 1507 c < grid[0].length) { 1508 return grid[r][c]; 1509 } 1510 return null; 1511 } 1512 1513 /** 1514 * Returns the rowspan of the specified entry. 1515 */ 1516 public int getRowExtentAt(int r, int c) { 1517 TableCellElementInfo cell = getCell(r, c); 1518 1519 if (cell != null) { 1520 int rows = cell.getRowCount(); 1521 int delta = 1; 1522 1523 while ((r - delta) >= 0 && grid[r - delta][c] == cell) { 1524 delta++; 1525 } 1526 return rows - delta + 1; 1527 } 1528 return 0; 1529 } 1530 1531 /** 1532 * Returns the colspan of the specified entry. 1533 */ 1534 public int getColumnExtentAt(int r, int c) { 1535 TableCellElementInfo cell = getCell(r, c); 1536 1537 if (cell != null) { 1538 int cols = cell.getColumnCount(); 1539 int delta = 1; 1540 1541 while ((c - delta) >= 0 && grid[r][c - delta] == cell) { 1542 delta++; 1543 } 1544 return cols - delta + 1; 1545 } 1546 return 0; 1547 } 1548 1549 /** 1550 * Returns the number of rows in the table. 1551 */ 1552 public int getRowCount() { 1553 if (validateIfNecessary()) { 1554 return grid.length; 1555 } 1556 return 0; 1557 } 1558 1559 /** 1560 * Returns the number of columns in the table. 1561 */ 1562 public int getColumnCount() { 1563 if (validateIfNecessary() && grid.length > 0) { 1564 return grid[0].length; 1565 } 1566 return 0; 1567 } 1568 1569 // begin AccessibleTable implementation ... 1570 private AccessibleContext accessibleContext; 1571 1572 public AccessibleContext getAccessibleContext() { 1573 if (accessibleContext == null) { 1574 accessibleContext = new TableAccessibleContext(this); 1575 } 1576 return accessibleContext; 1577 } 1578 1579 /* 1580 * AccessibleContext for tables 1581 */ 1582 public class TableAccessibleContext extends HTMLAccessibleContext 1583 implements AccessibleTable { 1584 1585 private AccessibleHeadersTable rowHeadersTable; 1586 1587 public TableAccessibleContext(ElementInfo elementInfo) { 1588 super(elementInfo); 1589 } 1590 1591 /** 1592 * Gets the accessibleName property of this object. The accessibleName 1593 * property of an object is a localized String that designates the purpose 1594 * of the object. For example, the accessibleName property of a label 1595 * or button might be the text of the label or button itself. In the 1596 * case of an object that doesn't display its name, the accessibleName 1597 * should still be set. For example, in the case of a text field used 1598 * to enter the name of a city, the accessibleName for the en_US locale 1599 * could be 'city.' 1600 * 1601 * @return the localized name of the object; null if this 1602 * object does not have a name 1603 * 1604 * @see #setAccessibleName 1605 */ 1606 public String getAccessibleName() { 1607 // return the role of the object 1608 return getAccessibleRole().toString(); 1609 } 1610 1611 /** 1612 * Gets the accessibleDescription property of this object. If this 1613 * property isn't set, returns the content type of this 1614 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text"). 1615 * 1616 * @return the localized description of the object; <code>null</code> 1617 * if this object does not have a description 1618 * 1619 * @see #setAccessibleName 1620 */ 1621 public String getAccessibleDescription() { 1622 return editor.getContentType(); 1623 } 1624 1625 /** 1626 * Gets the role of this object. The role of the object is the generic 1627 * purpose or use of the class of this object. For example, the role 1628 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in 1629 * AccessibleRole are provided so component developers can pick from 1630 * a set of predefined roles. This enables assistive technologies to 1631 * provide a consistent interface to various tweaked subclasses of 1632 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components 1633 * that act like a push button) as well as distinguish between subclasses 1634 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes 1635 * and AccessibleRole.RADIO_BUTTON for radio buttons). 1636 * <p>Note that the AccessibleRole class is also extensible, so 1637 * custom component developers can define their own AccessibleRole's 1638 * if the set of predefined roles is inadequate. 1639 * 1640 * @return an instance of AccessibleRole describing the role of the object 1641 * @see AccessibleRole 1642 */ 1643 public AccessibleRole getAccessibleRole() { 1644 return AccessibleRole.TABLE; 1645 } 1646 1647 /** 1648 * Gets the 0-based index of this object in its accessible parent. 1649 * 1650 * @return the 0-based index of this object in its parent; -1 if this 1651 * object does not have an accessible parent. 1652 * 1653 * @see #getAccessibleParent 1654 * @see #getAccessibleChildrenCount 1655 * @gsee #getAccessibleChild 1656 */ 1657 public int getAccessibleIndexInParent() { 1658 return elementInfo.getIndexInParent(); 1659 } 1660 1661 /** 1662 * Returns the number of accessible children of the object. 1663 * 1664 * @return the number of accessible children of the object. 1665 */ 1666 public int getAccessibleChildrenCount() { 1667 return ((TableElementInfo)elementInfo).getRowCount() * 1668 ((TableElementInfo)elementInfo).getColumnCount(); 1669 } 1670 1671 /** 1672 * Returns the specified Accessible child of the object. The Accessible 1673 * children of an Accessible object are zero-based, so the first child 1674 * of an Accessible child is at index 0, the second child is at index 1, 1675 * and so on. 1676 * 1677 * @param i zero-based index of child 1678 * @return the Accessible child of the object 1679 * @see #getAccessibleChildrenCount 1680 */ 1681 public Accessible getAccessibleChild(int i) { 1682 int rowCount = ((TableElementInfo)elementInfo).getRowCount(); 1683 int columnCount = ((TableElementInfo)elementInfo).getColumnCount(); 1684 int r = i / rowCount; 1685 int c = i % columnCount; 1686 if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) { 1687 return null; 1688 } else { 1689 return getAccessibleAt(r, c); 1690 } 1691 } 1692 1693 public AccessibleTable getAccessibleTable() { 1694 return this; 1695 } 1696 1697 /** 1698 * Returns the caption for the table. 1699 * 1700 * @return the caption for the table 1701 */ 1702 public Accessible getAccessibleCaption() { 1703 ElementInfo captionInfo = getCaptionInfo(); 1704 if (captionInfo instanceof Accessible) { 1705 return (Accessible)caption; 1706 } else { 1707 return null; 1708 } 1709 } 1710 1711 /** 1712 * Sets the caption for the table. 1713 * 1714 * @param a the caption for the table 1715 */ 1716 public void setAccessibleCaption(Accessible a) { 1717 } 1718 1719 /** 1720 * Returns the summary description of the table. 1721 * 1722 * @return the summary description of the table 1723 */ 1724 public Accessible getAccessibleSummary() { 1725 return null; 1726 } 1727 1728 /** 1729 * Sets the summary description of the table 1730 * 1731 * @param a the summary description of the table 1732 */ 1733 public void setAccessibleSummary(Accessible a) { 1734 } 1735 1736 /** 1737 * Returns the number of rows in the table. 1738 * 1739 * @return the number of rows in the table 1740 */ 1741 public int getAccessibleRowCount() { 1742 return ((TableElementInfo)elementInfo).getRowCount(); 1743 } 1744 1745 /** 1746 * Returns the number of columns in the table. 1747 * 1748 * @return the number of columns in the table 1749 */ 1750 public int getAccessibleColumnCount() { 1751 return ((TableElementInfo)elementInfo).getColumnCount(); 1752 } 1753 1754 /** 1755 * Returns the Accessible at a specified row and column 1756 * in the table. 1757 * 1758 * @param r zero-based row of the table 1759 * @param c zero-based column of the table 1760 * @return the Accessible at the specified row and column 1761 */ 1762 public Accessible getAccessibleAt(int r, int c) { 1763 TableCellElementInfo cellInfo = getCell(r, c); 1764 if (cellInfo != null) { 1765 return cellInfo.getAccessible(); 1766 } else { 1767 return null; 1768 } 1769 } 1770 1771 /** 1772 * Returns the number of rows occupied by the Accessible at 1773 * a specified row and column in the table. 1774 * 1775 * @return the number of rows occupied by the Accessible at a 1776 * given specified (row, column) 1777 */ 1778 public int getAccessibleRowExtentAt(int r, int c) { 1779 return ((TableElementInfo)elementInfo).getRowExtentAt(r, c); 1780 } 1781 1782 /** 1783 * Returns the number of columns occupied by the Accessible at 1784 * a specified row and column in the table. 1785 * 1786 * @return the number of columns occupied by the Accessible at a 1787 * given specified row and column 1788 */ 1789 public int getAccessibleColumnExtentAt(int r, int c) { 1790 return ((TableElementInfo)elementInfo).getColumnExtentAt(r, c); 1791 } 1792 1793 /** 1794 * Returns the row headers as an AccessibleTable. 1795 * 1796 * @return an AccessibleTable representing the row 1797 * headers 1798 */ 1799 public AccessibleTable getAccessibleRowHeader() { 1800 return rowHeadersTable; 1801 } 1802 1803 /** 1804 * Sets the row headers. 1805 * 1806 * @param table an AccessibleTable representing the 1807 * row headers 1808 */ 1809 public void setAccessibleRowHeader(AccessibleTable table) { 1810 } 1811 1812 /** 1813 * Returns the column headers as an AccessibleTable. 1814 * 1815 * @return an AccessibleTable representing the column 1816 * headers 1817 */ 1818 public AccessibleTable getAccessibleColumnHeader() { 1819 return null; 1820 } 1821 1822 /** 1823 * Sets the column headers. 1824 * 1825 * @param table an AccessibleTable representing the 1826 * column headers 1827 */ 1828 public void setAccessibleColumnHeader(AccessibleTable table) { 1829 } 1830 1831 /** 1832 * Returns the description of the specified row in the table. 1833 * 1834 * @param r zero-based row of the table 1835 * @return the description of the row 1836 */ 1837 public Accessible getAccessibleRowDescription(int r) { 1838 return null; 1839 } 1840 1841 /** 1842 * Sets the description text of the specified row of the table. 1843 * 1844 * @param r zero-based row of the table 1845 * @param a the description of the row 1846 */ 1847 public void setAccessibleRowDescription(int r, Accessible a) { 1848 } 1849 1850 /** 1851 * Returns the description text of the specified column in the table. 1852 * 1853 * @param c zero-based column of the table 1854 * @return the text description of the column 1855 */ 1856 public Accessible getAccessibleColumnDescription(int c) { 1857 return null; 1858 } 1859 1860 /** 1861 * Sets the description text of the specified column in the table. 1862 * 1863 * @param c zero-based column of the table 1864 * @param a the text description of the column 1865 */ 1866 public void setAccessibleColumnDescription(int c, Accessible a) { 1867 } 1868 1869 /** 1870 * Returns a boolean value indicating whether the accessible at 1871 * a specified row and column is selected. 1872 * 1873 * @param r zero-based row of the table 1874 * @param c zero-based column of the table 1875 * @return the boolean value true if the accessible at the 1876 * row and column is selected. Otherwise, the boolean value 1877 * false 1878 */ 1879 public boolean isAccessibleSelected(int r, int c) { 1880 if (validateIfNecessary()) { 1881 if (r < 0 || r >= getAccessibleRowCount() || 1882 c < 0 || c >= getAccessibleColumnCount()) { 1883 return false; 1884 } 1885 TableCellElementInfo cell = getCell(r, c); 1886 if (cell != null) { 1887 Element elem = cell.getElement(); 1888 int start = elem.getStartOffset(); 1889 int end = elem.getEndOffset(); 1890 return start >= editor.getSelectionStart() && 1891 end <= editor.getSelectionEnd(); 1892 } 1893 } 1894 return false; 1895 } 1896 1897 /** 1898 * Returns a boolean value indicating whether the specified row 1899 * is selected. 1900 * 1901 * @param r zero-based row of the table 1902 * @return the boolean value true if the specified row is selected. 1903 * Otherwise, false. 1904 */ 1905 public boolean isAccessibleRowSelected(int r) { 1906 if (validateIfNecessary()) { 1907 if (r < 0 || r >= getAccessibleRowCount()) { 1908 return false; 1909 } 1910 int nColumns = getAccessibleColumnCount(); 1911 1912 TableCellElementInfo startCell = getCell(r, 0); 1913 if (startCell == null) { 1914 return false; 1915 } 1916 int start = startCell.getElement().getStartOffset(); 1917 1918 TableCellElementInfo endCell = getCell(r, nColumns-1); 1919 if (endCell == null) { 1920 return false; 1921 } 1922 int end = endCell.getElement().getEndOffset(); 1923 1924 return start >= editor.getSelectionStart() && 1925 end <= editor.getSelectionEnd(); 1926 } 1927 return false; 1928 } 1929 1930 /** 1931 * Returns a boolean value indicating whether the specified column 1932 * is selected. 1933 * 1934 * @param c zero-based column of the table 1935 * @return the boolean value true if the specified column is selected. 1936 * Otherwise, false. 1937 */ 1938 public boolean isAccessibleColumnSelected(int c) { 1939 if (validateIfNecessary()) { 1940 if (c < 0 || c >= getAccessibleColumnCount()) { 1941 return false; 1942 } 1943 int nRows = getAccessibleRowCount(); 1944 1945 TableCellElementInfo startCell = getCell(0, c); 1946 if (startCell == null) { 1947 return false; 1948 } 1949 int start = startCell.getElement().getStartOffset(); 1950 1951 TableCellElementInfo endCell = getCell(nRows-1, c); 1952 if (endCell == null) { 1953 return false; 1954 } 1955 int end = endCell.getElement().getEndOffset(); 1956 return start >= editor.getSelectionStart() && 1957 end <= editor.getSelectionEnd(); 1958 } 1959 return false; 1960 } 1961 1962 /** 1963 * Returns the selected rows in a table. 1964 * 1965 * @return an array of selected rows where each element is a 1966 * zero-based row of the table 1967 */ 1968 public int [] getSelectedAccessibleRows() { 1969 if (validateIfNecessary()) { 1970 int nRows = getAccessibleRowCount(); 1971 Vector<Integer> vec = new Vector<Integer>(); 1972 1973 for (int i = 0; i < nRows; i++) { 1974 if (isAccessibleRowSelected(i)) { 1975 vec.addElement(Integer.valueOf(i)); 1976 } 1977 } 1978 int[] retval = new int[vec.size()]; 1979 for (int i = 0; i < retval.length; i++) { 1980 retval[i] = vec.elementAt(i).intValue(); 1981 } 1982 return retval; 1983 } 1984 return new int[0]; 1985 } 1986 1987 /** 1988 * Returns the selected columns in a table. 1989 * 1990 * @return an array of selected columns where each element is a 1991 * zero-based column of the table 1992 */ 1993 public int [] getSelectedAccessibleColumns() { 1994 if (validateIfNecessary()) { 1995 int nColumns = getAccessibleRowCount(); 1996 Vector<Integer> vec = new Vector<Integer>(); 1997 1998 for (int i = 0; i < nColumns; i++) { 1999 if (isAccessibleColumnSelected(i)) { 2000 vec.addElement(Integer.valueOf(i)); 2001 } 2002 } 2003 int[] retval = new int[vec.size()]; 2004 for (int i = 0; i < retval.length; i++) { 2005 retval[i] = vec.elementAt(i).intValue(); 2006 } 2007 return retval; 2008 } 2009 return new int[0]; 2010 } 2011 2012 // begin AccessibleExtendedTable implementation ------------- 2013 2014 /** 2015 * Returns the row number of an index in the table. 2016 * 2017 * @param index the zero-based index in the table 2018 * @return the zero-based row of the table if one exists; 2019 * otherwise -1. 2020 */ 2021 public int getAccessibleRow(int index) { 2022 if (validateIfNecessary()) { 2023 int numCells = getAccessibleColumnCount() * 2024 getAccessibleRowCount(); 2025 if (index >= numCells) { 2026 return -1; 2027 } else { 2028 return index / getAccessibleColumnCount(); 2029 } 2030 } 2031 return -1; 2032 } 2033 2034 /** 2035 * Returns the column number of an index in the table. 2036 * 2037 * @param index the zero-based index in the table 2038 * @return the zero-based column of the table if one exists; 2039 * otherwise -1. 2040 */ 2041 public int getAccessibleColumn(int index) { 2042 if (validateIfNecessary()) { 2043 int numCells = getAccessibleColumnCount() * 2044 getAccessibleRowCount(); 2045 if (index >= numCells) { 2046 return -1; 2047 } else { 2048 return index % getAccessibleColumnCount(); 2049 } 2050 } 2051 return -1; 2052 } 2053 2054 /** 2055 * Returns the index at a row and column in the table. 2056 * 2057 * @param r zero-based row of the table 2058 * @param c zero-based column of the table 2059 * @return the zero-based index in the table if one exists; 2060 * otherwise -1. 2061 */ 2062 public int getAccessibleIndex(int r, int c) { 2063 if (validateIfNecessary()) { 2064 if (r >= getAccessibleRowCount() || 2065 c >= getAccessibleColumnCount()) { 2066 return -1; 2067 } else { 2068 return r * getAccessibleColumnCount() + c; 2069 } 2070 } 2071 return -1; 2072 } 2073 2074 /** 2075 * Returns the row header at a row in a table. 2076 * @param r zero-based row of the table 2077 * 2078 * @return a String representing the row header 2079 * if one exists; otherwise null. 2080 */ 2081 public String getAccessibleRowHeader(int r) { 2082 if (validateIfNecessary()) { 2083 TableCellElementInfo cellInfo = getCell(r, 0); 2084 if (cellInfo.isHeaderCell()) { 2085 View v = cellInfo.getView(); 2086 if (v != null && model != null) { 2087 try { 2088 return model.getText(v.getStartOffset(), 2089 v.getEndOffset() - 2090 v.getStartOffset()); 2091 } catch (BadLocationException e) { 2092 return null; 2093 } 2094 } 2095 } 2096 } 2097 return null; 2098 } 2099 2100 /** 2101 * Returns the column header at a column in a table. 2102 * @param c zero-based column of the table 2103 * 2104 * @return a String representing the column header 2105 * if one exists; otherwise null. 2106 */ 2107 public String getAccessibleColumnHeader(int c) { 2108 if (validateIfNecessary()) { 2109 TableCellElementInfo cellInfo = getCell(0, c); 2110 if (cellInfo.isHeaderCell()) { 2111 View v = cellInfo.getView(); 2112 if (v != null && model != null) { 2113 try { 2114 return model.getText(v.getStartOffset(), 2115 v.getEndOffset() - 2116 v.getStartOffset()); 2117 } catch (BadLocationException e) { 2118 return null; 2119 } 2120 } 2121 } 2122 } 2123 return null; 2124 } 2125 2126 public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) { 2127 if (rowHeadersTable == null) { 2128 rowHeadersTable = new AccessibleHeadersTable(); 2129 } 2130 rowHeadersTable.addHeader(cellInfo, rowNumber); 2131 } 2132 // end of AccessibleExtendedTable implementation ------------ 2133 2134 protected class AccessibleHeadersTable implements AccessibleTable { 2135 2136 // Header information is modeled as a Hashtable of 2137 // ArrayLists where each Hashtable entry represents 2138 // a row containing one or more headers. 2139 private Hashtable<Integer, ArrayList<TableCellElementInfo>> headers = 2140 new Hashtable<Integer, ArrayList<TableCellElementInfo>>(); 2141 private int rowCount = 0; 2142 private int columnCount = 0; 2143 2144 public void addHeader(TableCellElementInfo cellInfo, int rowNumber) { 2145 Integer rowInteger = Integer.valueOf(rowNumber); 2146 ArrayList<TableCellElementInfo> list = headers.get(rowInteger); 2147 if (list == null) { 2148 list = new ArrayList<TableCellElementInfo>(); 2149 headers.put(rowInteger, list); 2150 } 2151 list.add(cellInfo); 2152 } 2153 2154 /** 2155 * Returns the caption for the table. 2156 * 2157 * @return the caption for the table 2158 */ 2159 public Accessible getAccessibleCaption() { 2160 return null; 2161 } 2162 2163 /** 2164 * Sets the caption for the table. 2165 * 2166 * @param a the caption for the table 2167 */ 2168 public void setAccessibleCaption(Accessible a) { 2169 } 2170 2171 /** 2172 * Returns the summary description of the table. 2173 * 2174 * @return the summary description of the table 2175 */ 2176 public Accessible getAccessibleSummary() { 2177 return null; 2178 } 2179 2180 /** 2181 * Sets the summary description of the table 2182 * 2183 * @param a the summary description of the table 2184 */ 2185 public void setAccessibleSummary(Accessible a) { 2186 } 2187 2188 /** 2189 * Returns the number of rows in the table. 2190 * 2191 * @return the number of rows in the table 2192 */ 2193 public int getAccessibleRowCount() { 2194 return rowCount; 2195 } 2196 2197 /** 2198 * Returns the number of columns in the table. 2199 * 2200 * @return the number of columns in the table 2201 */ 2202 public int getAccessibleColumnCount() { 2203 return columnCount; 2204 } 2205 2206 private TableCellElementInfo getElementInfoAt(int r, int c) { 2207 ArrayList<TableCellElementInfo> list = headers.get(Integer.valueOf(r)); 2208 if (list != null) { 2209 return list.get(c); 2210 } else { 2211 return null; 2212 } 2213 } 2214 2215 /** 2216 * Returns the Accessible at a specified row and column 2217 * in the table. 2218 * 2219 * @param r zero-based row of the table 2220 * @param c zero-based column of the table 2221 * @return the Accessible at the specified row and column 2222 */ 2223 public Accessible getAccessibleAt(int r, int c) { 2224 ElementInfo elementInfo = getElementInfoAt(r, c); 2225 if (elementInfo instanceof Accessible) { 2226 return (Accessible)elementInfo; 2227 } else { 2228 return null; 2229 } 2230 } 2231 2232 /** 2233 * Returns the number of rows occupied by the Accessible at 2234 * a specified row and column in the table. 2235 * 2236 * @return the number of rows occupied by the Accessible at a 2237 * given specified (row, column) 2238 */ 2239 public int getAccessibleRowExtentAt(int r, int c) { 2240 TableCellElementInfo elementInfo = getElementInfoAt(r, c); 2241 if (elementInfo != null) { 2242 return elementInfo.getRowCount(); 2243 } else { 2244 return 0; 2245 } 2246 } 2247 2248 /** 2249 * Returns the number of columns occupied by the Accessible at 2250 * a specified row and column in the table. 2251 * 2252 * @return the number of columns occupied by the Accessible at a 2253 * given specified row and column 2254 */ 2255 public int getAccessibleColumnExtentAt(int r, int c) { 2256 TableCellElementInfo elementInfo = getElementInfoAt(r, c); 2257 if (elementInfo != null) { 2258 return elementInfo.getRowCount(); 2259 } else { 2260 return 0; 2261 } 2262 } 2263 2264 /** 2265 * Returns the row headers as an AccessibleTable. 2266 * 2267 * @return an AccessibleTable representing the row 2268 * headers 2269 */ 2270 public AccessibleTable getAccessibleRowHeader() { 2271 return null; 2272 } 2273 2274 /** 2275 * Sets the row headers. 2276 * 2277 * @param table an AccessibleTable representing the 2278 * row headers 2279 */ 2280 public void setAccessibleRowHeader(AccessibleTable table) { 2281 } 2282 2283 /** 2284 * Returns the column headers as an AccessibleTable. 2285 * 2286 * @return an AccessibleTable representing the column 2287 * headers 2288 */ 2289 public AccessibleTable getAccessibleColumnHeader() { 2290 return null; 2291 } 2292 2293 /** 2294 * Sets the column headers. 2295 * 2296 * @param table an AccessibleTable representing the 2297 * column headers 2298 */ 2299 public void setAccessibleColumnHeader(AccessibleTable table) { 2300 } 2301 2302 /** 2303 * Returns the description of the specified row in the table. 2304 * 2305 * @param r zero-based row of the table 2306 * @return the description of the row 2307 */ 2308 public Accessible getAccessibleRowDescription(int r) { 2309 return null; 2310 } 2311 2312 /** 2313 * Sets the description text of the specified row of the table. 2314 * 2315 * @param r zero-based row of the table 2316 * @param a the description of the row 2317 */ 2318 public void setAccessibleRowDescription(int r, Accessible a) { 2319 } 2320 2321 /** 2322 * Returns the description text of the specified column in the table. 2323 * 2324 * @param c zero-based column of the table 2325 * @return the text description of the column 2326 */ 2327 public Accessible getAccessibleColumnDescription(int c) { 2328 return null; 2329 } 2330 2331 /** 2332 * Sets the description text of the specified column in the table. 2333 * 2334 * @param c zero-based column of the table 2335 * @param a the text description of the column 2336 */ 2337 public void setAccessibleColumnDescription(int c, Accessible a) { 2338 } 2339 2340 /** 2341 * Returns a boolean value indicating whether the accessible at 2342 * a specified row and column is selected. 2343 * 2344 * @param r zero-based row of the table 2345 * @param c zero-based column of the table 2346 * @return the boolean value true if the accessible at the 2347 * row and column is selected. Otherwise, the boolean value 2348 * false 2349 */ 2350 public boolean isAccessibleSelected(int r, int c) { 2351 return false; 2352 } 2353 2354 /** 2355 * Returns a boolean value indicating whether the specified row 2356 * is selected. 2357 * 2358 * @param r zero-based row of the table 2359 * @return the boolean value true if the specified row is selected. 2360 * Otherwise, false. 2361 */ 2362 public boolean isAccessibleRowSelected(int r) { 2363 return false; 2364 } 2365 2366 /** 2367 * Returns a boolean value indicating whether the specified column 2368 * is selected. 2369 * 2370 * @param c zero-based column of the table 2371 * @return the boolean value true if the specified column is selected. 2372 * Otherwise, false. 2373 */ 2374 public boolean isAccessibleColumnSelected(int c) { 2375 return false; 2376 } 2377 2378 /** 2379 * Returns the selected rows in a table. 2380 * 2381 * @return an array of selected rows where each element is a 2382 * zero-based row of the table 2383 */ 2384 public int [] getSelectedAccessibleRows() { 2385 return new int [0]; 2386 } 2387 2388 /** 2389 * Returns the selected columns in a table. 2390 * 2391 * @return an array of selected columns where each element is a 2392 * zero-based column of the table 2393 */ 2394 public int [] getSelectedAccessibleColumns() { 2395 return new int [0]; 2396 } 2397 } 2398 } // ... end AccessibleHeadersTable 2399 2400 /* 2401 * ElementInfo for table rows 2402 */ 2403 private class TableRowElementInfo extends ElementInfo { 2404 2405 private TableElementInfo parent; 2406 private int rowNumber; 2407 2408 TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) { 2409 super(e, parent); 2410 this.parent = parent; 2411 this.rowNumber = rowNumber; 2412 } 2413 2414 protected void loadChildren(Element e) { 2415 for (int x = 0; x < e.getElementCount(); x++) { 2416 AttributeSet attrs = e.getElement(x).getAttributes(); 2417 2418 if (attrs.getAttribute(StyleConstants.NameAttribute) == 2419 HTML.Tag.TH) { 2420 TableCellElementInfo headerElementInfo = 2421 new TableCellElementInfo(e.getElement(x), this, true); 2422 addChild(headerElementInfo); 2423 2424 AccessibleTable at = 2425 parent.getAccessibleContext().getAccessibleTable(); 2426 TableAccessibleContext tableElement = 2427 (TableAccessibleContext)at; 2428 tableElement.addRowHeader(headerElementInfo, rowNumber); 2429 2430 } else if (attrs.getAttribute(StyleConstants.NameAttribute) == 2431 HTML.Tag.TD) { 2432 addChild(new TableCellElementInfo(e.getElement(x), this, 2433 false)); 2434 } 2435 } 2436 } 2437 2438 /** 2439 * Returns the max of the rowspans of the cells in this row. 2440 */ 2441 public int getRowCount() { 2442 int rowCount = 1; 2443 if (validateIfNecessary()) { 2444 for (int counter = 0; counter < getChildCount(); 2445 counter++) { 2446 2447 TableCellElementInfo cell = (TableCellElementInfo) 2448 getChild(counter); 2449 2450 if (cell.validateIfNecessary()) { 2451 rowCount = Math.max(rowCount, cell.getRowCount()); 2452 } 2453 } 2454 } 2455 return rowCount; 2456 } 2457 2458 /** 2459 * Returns the sum of the column spans of the individual 2460 * cells in this row. 2461 */ 2462 public int getColumnCount() { 2463 int colCount = 0; 2464 if (validateIfNecessary()) { 2465 for (int counter = 0; counter < getChildCount(); 2466 counter++) { 2467 TableCellElementInfo cell = (TableCellElementInfo) 2468 getChild(counter); 2469 2470 if (cell.validateIfNecessary()) { 2471 colCount += cell.getColumnCount(); 2472 } 2473 } 2474 } 2475 return colCount; 2476 } 2477 2478 /** 2479 * Overriden to invalidate the table as well as 2480 * TableRowElementInfo. 2481 */ 2482 protected void invalidate(boolean first) { 2483 super.invalidate(first); 2484 getParent().invalidate(true); 2485 } 2486 2487 /** 2488 * Places the TableCellElementInfos for this element in 2489 * the grid. 2490 */ 2491 private void updateGrid(int row) { 2492 if (validateIfNecessary()) { 2493 boolean emptyRow = false; 2494 2495 while (!emptyRow) { 2496 for (int counter = 0; counter < grid[row].length; 2497 counter++) { 2498 if (grid[row][counter] == null) { 2499 emptyRow = true; 2500 break; 2501 } 2502 } 2503 if (!emptyRow) { 2504 row++; 2505 } 2506 } 2507 for (int col = 0, counter = 0; counter < getChildCount(); 2508 counter++) { 2509 TableCellElementInfo cell = (TableCellElementInfo) 2510 getChild(counter); 2511 2512 while (grid[row][col] != null) { 2513 col++; 2514 } 2515 for (int rowCount = cell.getRowCount() - 1; 2516 rowCount >= 0; rowCount--) { 2517 for (int colCount = cell.getColumnCount() - 1; 2518 colCount >= 0; colCount--) { 2519 grid[row + rowCount][col + colCount] = cell; 2520 } 2521 } 2522 col += cell.getColumnCount(); 2523 } 2524 } 2525 } 2526 2527 /** 2528 * Returns the column count of the number of columns that have 2529 * a rowcount >= rowspan. 2530 */ 2531 private int getColumnCount(int rowspan) { 2532 if (validateIfNecessary()) { 2533 int cols = 0; 2534 for (int counter = 0; counter < getChildCount(); 2535 counter++) { 2536 TableCellElementInfo cell = (TableCellElementInfo) 2537 getChild(counter); 2538 2539 if (cell.getRowCount() >= rowspan) { 2540 cols += cell.getColumnCount(); 2541 } 2542 } 2543 return cols; 2544 } 2545 return 0; 2546 } 2547 } 2548 2549 /** 2550 * TableCellElementInfo is used to represents the cells of 2551 * the table. 2552 */ 2553 private class TableCellElementInfo extends ElementInfo { 2554 2555 private Accessible accessible; 2556 private boolean isHeaderCell; 2557 2558 TableCellElementInfo(Element e, ElementInfo parent) { 2559 super(e, parent); 2560 this.isHeaderCell = false; 2561 } 2562 2563 TableCellElementInfo(Element e, ElementInfo parent, 2564 boolean isHeaderCell) { 2565 super(e, parent); 2566 this.isHeaderCell = isHeaderCell; 2567 } 2568 2569 /* 2570 * Returns whether this table cell is a header 2571 */ 2572 public boolean isHeaderCell() { 2573 return this.isHeaderCell; 2574 } 2575 2576 /* 2577 * Returns the Accessible representing this table cell 2578 */ 2579 public Accessible getAccessible() { 2580 accessible = null; 2581 getAccessible(this); 2582 return accessible; 2583 } 2584 2585 /* 2586 * Gets the outermost Accessible in the table cell 2587 */ 2588 private void getAccessible(ElementInfo elementInfo) { 2589 if (elementInfo instanceof Accessible) { 2590 accessible = (Accessible)elementInfo; 2591 } else { 2592 for (int i = 0; i < elementInfo.getChildCount(); i++) { 2593 getAccessible(elementInfo.getChild(i)); 2594 } 2595 } 2596 } 2597 2598 /** 2599 * Returns the rowspan attribute. 2600 */ 2601 public int getRowCount() { 2602 if (validateIfNecessary()) { 2603 return Math.max(1, getIntAttr(getAttributes(), 2604 HTML.Attribute.ROWSPAN, 1)); 2605 } 2606 return 0; 2607 } 2608 2609 /** 2610 * Returns the colspan attribute. 2611 */ 2612 public int getColumnCount() { 2613 if (validateIfNecessary()) { 2614 return Math.max(1, getIntAttr(getAttributes(), 2615 HTML.Attribute.COLSPAN, 1)); 2616 } 2617 return 0; 2618 } 2619 2620 /** 2621 * Overriden to invalidate the TableRowElementInfo as well as 2622 * the TableCellElementInfo. 2623 */ 2624 protected void invalidate(boolean first) { 2625 super.invalidate(first); 2626 getParent().invalidate(true); 2627 } 2628 } 2629 } 2630 2631 2632 /** 2633 * ElementInfo provides a slim down view of an Element. Each ElementInfo 2634 * can have any number of child ElementInfos that are not necessarily 2635 * direct children of the Element. As the Document changes various 2636 * ElementInfos become invalidated. Before accessing a particular portion 2637 * of an ElementInfo you should make sure it is valid by invoking 2638 * <code>validateIfNecessary</code>, this will return true if 2639 * successful, on the other hand a false return value indicates the 2640 * ElementInfo is not valid and can never become valid again (usually 2641 * the result of the Element the ElementInfo encapsulates being removed). 2642 */ 2643 private class ElementInfo { 2644 2645 /** 2646 * The children of this ElementInfo. 2647 */ 2648 private ArrayList<ElementInfo> children; 2649 /** 2650 * The Element this ElementInfo is providing information for. 2651 */ 2652 private Element element; 2653 /** 2654 * The parent ElementInfo, will be null for the root. 2655 */ 2656 private ElementInfo parent; 2657 /** 2658 * Indicates the validity of the ElementInfo. 2659 */ 2660 private boolean isValid; 2661 /** 2662 * Indicates if the ElementInfo can become valid. 2663 */ 2664 private boolean canBeValid; 2665 2666 2667 /** 2668 * Creates the root ElementInfo. 2669 */ 2670 ElementInfo(Element element) { 2671 this(element, null); 2672 } 2673 2674 /** 2675 * Creates an ElementInfo representing <code>element</code> with 2676 * the specified parent. 2677 */ 2678 ElementInfo(Element element, ElementInfo parent) { 2679 this.element = element; 2680 this.parent = parent; 2681 isValid = false; 2682 canBeValid = true; 2683 } 2684 2685 /** 2686 * Validates the receiver. This recreates the children as well. This 2687 * will be invoked within a <code>readLock</code>. If this is overriden 2688 * it MUST invoke supers implementation first! 2689 */ 2690 protected void validate() { 2691 isValid = true; 2692 loadChildren(getElement()); 2693 } 2694 2695 /** 2696 * Recreates the direct children of <code>info</code>. 2697 */ 2698 protected void loadChildren(Element parent) { 2699 if (!parent.isLeaf()) { 2700 for (int counter = 0, maxCounter = parent.getElementCount(); 2701 counter < maxCounter; counter++) { 2702 Element e = parent.getElement(counter); 2703 ElementInfo childInfo = createElementInfo(e, this); 2704 2705 if (childInfo != null) { 2706 addChild(childInfo); 2707 } 2708 else { 2709 loadChildren(e); 2710 } 2711 } 2712 } 2713 } 2714 2715 /** 2716 * Returns the index of the child in the parent, or -1 for the 2717 * root or if the parent isn't valid. 2718 */ 2719 public int getIndexInParent() { 2720 if (parent == null || !parent.isValid()) { 2721 return -1; 2722 } 2723 return parent.indexOf(this); 2724 } 2725 2726 /** 2727 * Returns the Element this <code>ElementInfo</code> represents. 2728 */ 2729 public Element getElement() { 2730 return element; 2731 } 2732 2733 /** 2734 * Returns the parent of this Element, or null for the root. 2735 */ 2736 public ElementInfo getParent() { 2737 return parent; 2738 } 2739 2740 /** 2741 * Returns the index of the specified child, or -1 if 2742 * <code>child</code> isn't a valid child. 2743 */ 2744 public int indexOf(ElementInfo child) { 2745 ArrayList<ElementInfo> children = this.children; 2746 2747 if (children != null) { 2748 return children.indexOf(child); 2749 } 2750 return -1; 2751 } 2752 2753 /** 2754 * Returns the child ElementInfo at <code>index</code>, or null 2755 * if <code>index</code> isn't a valid index. 2756 */ 2757 public ElementInfo getChild(int index) { 2758 if (validateIfNecessary()) { 2759 ArrayList<ElementInfo> children = this.children; 2760 2761 if (children != null && index >= 0 && 2762 index < children.size()) { 2763 return children.get(index); 2764 } 2765 } 2766 return null; 2767 } 2768 2769 /** 2770 * Returns the number of children the ElementInfo contains. 2771 */ 2772 public int getChildCount() { 2773 validateIfNecessary(); 2774 return (children == null) ? 0 : children.size(); 2775 } 2776 2777 /** 2778 * Adds a new child to this ElementInfo. 2779 */ 2780 protected void addChild(ElementInfo child) { 2781 if (children == null) { 2782 children = new ArrayList<ElementInfo>(); 2783 } 2784 children.add(child); 2785 } 2786 2787 /** 2788 * Returns the View corresponding to this ElementInfo, or null 2789 * if the ElementInfo can't be validated. 2790 */ 2791 protected View getView() { 2792 if (!validateIfNecessary()) { 2793 return null; 2794 } 2795 Object lock = lock(); 2796 try { 2797 View rootView = getRootView(); 2798 Element e = getElement(); 2799 int start = e.getStartOffset(); 2800 2801 if (rootView != null) { 2802 return getView(rootView, e, start); 2803 } 2804 return null; 2805 } finally { 2806 unlock(lock); 2807 } 2808 } 2809 2810 /** 2811 * Returns the Bounds for this ElementInfo, or null 2812 * if the ElementInfo can't be validated. 2813 */ 2814 public Rectangle getBounds() { 2815 if (!validateIfNecessary()) { 2816 return null; 2817 } 2818 Object lock = lock(); 2819 try { 2820 Rectangle bounds = getRootEditorRect(); 2821 View rootView = getRootView(); 2822 Element e = getElement(); 2823 2824 if (bounds != null && rootView != null) { 2825 try { 2826 return rootView.modelToView(e.getStartOffset(), 2827 Position.Bias.Forward, 2828 e.getEndOffset(), 2829 Position.Bias.Backward, 2830 bounds).getBounds(); 2831 } catch (BadLocationException ble) { } 2832 } 2833 } finally { 2834 unlock(lock); 2835 } 2836 return null; 2837 } 2838 2839 /** 2840 * Returns true if this ElementInfo is valid. 2841 */ 2842 protected boolean isValid() { 2843 return isValid; 2844 } 2845 2846 /** 2847 * Returns the AttributeSet associated with the Element, this will 2848 * return null if the ElementInfo can't be validated. 2849 */ 2850 protected AttributeSet getAttributes() { 2851 if (validateIfNecessary()) { 2852 return getElement().getAttributes(); 2853 } 2854 return null; 2855 } 2856 2857 /** 2858 * Returns the AttributeSet associated with the View that is 2859 * representing this Element, this will 2860 * return null if the ElementInfo can't be validated. 2861 */ 2862 protected AttributeSet getViewAttributes() { 2863 if (validateIfNecessary()) { 2864 View view = getView(); 2865 2866 if (view != null) { 2867 return view.getElement().getAttributes(); 2868 } 2869 return getElement().getAttributes(); 2870 } 2871 return null; 2872 } 2873 2874 /** 2875 * Convenience method for getting an integer attribute from the passed 2876 * in AttributeSet. 2877 */ 2878 protected int getIntAttr(AttributeSet attrs, Object key, int deflt) { 2879 if (attrs != null && attrs.isDefined(key)) { 2880 int i; 2881 String val = (String)attrs.getAttribute(key); 2882 if (val == null) { 2883 i = deflt; 2884 } 2885 else { 2886 try { 2887 i = Math.max(0, Integer.parseInt(val)); 2888 } catch (NumberFormatException x) { 2889 i = deflt; 2890 } 2891 } 2892 return i; 2893 } 2894 return deflt; 2895 } 2896 2897 /** 2898 * Validates the ElementInfo if necessary. Some ElementInfos may 2899 * never be valid again. You should check <code>isValid</code> before 2900 * using one. This will reload the children and invoke 2901 * <code>validate</code> if the ElementInfo is invalid and can become 2902 * valid again. This will return true if the receiver is valid. 2903 */ 2904 protected boolean validateIfNecessary() { 2905 if (!isValid() && canBeValid) { 2906 children = null; 2907 Object lock = lock(); 2908 2909 try { 2910 validate(); 2911 } finally { 2912 unlock(lock); 2913 } 2914 } 2915 return isValid(); 2916 } 2917 2918 /** 2919 * Invalidates the ElementInfo. Subclasses should override this 2920 * if they need to reset state once invalid. 2921 */ 2922 protected void invalidate(boolean first) { 2923 if (!isValid()) { 2924 if (canBeValid && !first) { 2925 canBeValid = false; 2926 } 2927 return; 2928 } 2929 isValid = false; 2930 canBeValid = first; 2931 if (children != null) { 2932 for (ElementInfo child : children) { 2933 child.invalidate(false); 2934 } 2935 children = null; 2936 } 2937 } 2938 2939 private View getView(View parent, Element e, int start) { 2940 if (parent.getElement() == e) { 2941 return parent; 2942 } 2943 int index = parent.getViewIndex(start, Position.Bias.Forward); 2944 2945 if (index != -1 && index < parent.getViewCount()) { 2946 return getView(parent.getView(index), e, start); 2947 } 2948 return null; 2949 } 2950 2951 private int getClosestInfoIndex(int index) { 2952 for (int counter = 0; counter < getChildCount(); counter++) { 2953 ElementInfo info = getChild(counter); 2954 2955 if (index < info.getElement().getEndOffset() || 2956 index == info.getElement().getStartOffset()) { 2957 return counter; 2958 } 2959 } 2960 return -1; 2961 } 2962 2963 private void update(DocumentEvent e) { 2964 if (!isValid()) { 2965 return; 2966 } 2967 ElementInfo parent = getParent(); 2968 Element element = getElement(); 2969 2970 do { 2971 DocumentEvent.ElementChange ec = e.getChange(element); 2972 if (ec != null) { 2973 if (element == getElement()) { 2974 // One of our children changed. 2975 invalidate(true); 2976 } 2977 else if (parent != null) { 2978 parent.invalidate(parent == getRootInfo()); 2979 } 2980 return; 2981 } 2982 element = element.getParentElement(); 2983 } while (parent != null && element != null && 2984 element != parent.getElement()); 2985 2986 if (getChildCount() > 0) { 2987 Element elem = getElement(); 2988 int pos = e.getOffset(); 2989 int index0 = getClosestInfoIndex(pos); 2990 if (index0 == -1 && 2991 e.getType() == DocumentEvent.EventType.REMOVE && 2992 pos >= elem.getEndOffset()) { 2993 // Event beyond our offsets. We may have represented this, 2994 // that is the remove may have removed one of our child 2995 // Elements that represented this, so, we should foward 2996 // to last element. 2997 index0 = getChildCount() - 1; 2998 } 2999 ElementInfo info = (index0 >= 0) ? getChild(index0) : null; 3000 if (info != null && 3001 (info.getElement().getStartOffset() == pos) && (pos > 0)) { 3002 // If at a boundary, forward the event to the previous 3003 // ElementInfo too. 3004 index0 = Math.max(index0 - 1, 0); 3005 } 3006 int index1; 3007 if (e.getType() != DocumentEvent.EventType.REMOVE) { 3008 index1 = getClosestInfoIndex(pos + e.getLength()); 3009 if (index1 < 0) { 3010 index1 = getChildCount() - 1; 3011 } 3012 } 3013 else { 3014 index1 = index0; 3015 // A remove may result in empty elements. 3016 while ((index1 + 1) < getChildCount() && 3017 getChild(index1 + 1).getElement().getEndOffset() == 3018 getChild(index1 + 1).getElement().getStartOffset()){ 3019 index1++; 3020 } 3021 } 3022 index0 = Math.max(index0, 0); 3023 // The check for isValid is here as in the process of 3024 // forwarding update our child may invalidate us. 3025 for (int i = index0; i <= index1 && isValid(); i++) { 3026 getChild(i).update(e); 3027 } 3028 } 3029 } 3030 } 3031 3032 /** 3033 * DocumentListener installed on the current Document. Will invoke 3034 * <code>update</code> on the <code>RootInfo</code> in response to 3035 * any event. 3036 */ 3037 private class DocumentHandler implements DocumentListener { 3038 public void insertUpdate(DocumentEvent e) { 3039 getRootInfo().update(e); 3040 } 3041 public void removeUpdate(DocumentEvent e) { 3042 getRootInfo().update(e); 3043 } 3044 public void changedUpdate(DocumentEvent e) { 3045 getRootInfo().update(e); 3046 } 3047 } 3048 3049 /* 3050 * PropertyChangeListener installed on the editor. 3051 */ 3052 private class PropertyChangeHandler implements PropertyChangeListener { 3053 public void propertyChange(PropertyChangeEvent evt) { 3054 if (evt.getPropertyName().equals("document")) { 3055 // handle the document change 3056 setDocument(editor.getDocument()); 3057 } 3058 } 3059 } 3060 }