1 /* 2 * Copyright (c) 2000, 2014, 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 public Rectangle getCharacterBounds(int i) { 940 try { 941 return editor.getUI().modelToView(editor, i); 942 } catch (BadLocationException e) { 943 return null; 944 } 945 } 946 947 /** 948 * Return the number of characters (valid indicies) 949 * 950 * @return the number of characters 951 */ 952 public int getCharCount() { 953 if (validateIfNecessary()) { 954 Element elem = elementInfo.getElement(); 955 return elem.getEndOffset() - elem.getStartOffset(); 956 } 957 return 0; 958 } 959 960 /** 961 * Return the zero-based offset of the caret. 962 * 963 * Note: That to the right of the caret will have the same index 964 * value as the offset (the caret is between two characters). 965 * @return the zero-based offset of the caret. 966 */ 967 public int getCaretPosition() { 968 View v = getView(); 969 if (v == null) { 970 return -1; 971 } 972 Container c = v.getContainer(); 973 if (c == null) { 974 return -1; 975 } 976 if (c instanceof JTextComponent) { 977 return ((JTextComponent)c).getCaretPosition(); 978 } else { 979 return -1; 980 } 981 } 982 983 /** 984 * IndexedSegment extends Segment adding the offset into the 985 * the model the <code>Segment</code> was asked for. 986 */ 987 private class IndexedSegment extends Segment { 988 /** 989 * Offset into the model that the position represents. 990 */ 991 public int modelOffset; 992 } 993 994 public String getAtIndex(int part, int index) { 995 return getAtIndex(part, index, 0); 996 } 997 998 999 public String getAfterIndex(int part, int index) { 1000 return getAtIndex(part, index, 1); 1001 } 1002 1003 public String getBeforeIndex(int part, int index) { 1004 return getAtIndex(part, index, -1); 1005 } 1006 1007 /** 1008 * Gets the word, sentence, or character at <code>index</code>. 1009 * If <code>direction</code> is non-null this will find the 1010 * next/previous word/sentence/character. 1011 */ 1012 private String getAtIndex(int part, int index, int direction) { 1013 if (model instanceof AbstractDocument) { 1014 ((AbstractDocument)model).readLock(); 1015 } 1016 try { 1017 if (index < 0 || index >= model.getLength()) { 1018 return null; 1019 } 1020 switch (part) { 1021 case AccessibleText.CHARACTER: 1022 if (index + direction < model.getLength() && 1023 index + direction >= 0) { 1024 return model.getText(index + direction, 1); 1025 } 1026 break; 1027 1028 1029 case AccessibleText.WORD: 1030 case AccessibleText.SENTENCE: 1031 IndexedSegment seg = getSegmentAt(part, index); 1032 if (seg != null) { 1033 if (direction != 0) { 1034 int next; 1035 1036 1037 if (direction < 0) { 1038 next = seg.modelOffset - 1; 1039 } 1040 else { 1041 next = seg.modelOffset + direction * seg.count; 1042 } 1043 if (next >= 0 && next <= model.getLength()) { 1044 seg = getSegmentAt(part, next); 1045 } 1046 else { 1047 seg = null; 1048 } 1049 } 1050 if (seg != null) { 1051 return new String(seg.array, seg.offset, 1052 seg.count); 1053 } 1054 } 1055 break; 1056 1057 default: 1058 break; 1059 } 1060 } catch (BadLocationException e) { 1061 } finally { 1062 if (model instanceof AbstractDocument) { 1063 ((AbstractDocument)model).readUnlock(); 1064 } 1065 } 1066 return null; 1067 } 1068 1069 /* 1070 * Returns the paragraph element for the specified index. 1071 */ 1072 private Element getParagraphElement(int index) { 1073 if (model instanceof PlainDocument ) { 1074 PlainDocument sdoc = (PlainDocument)model; 1075 return sdoc.getParagraphElement(index); 1076 } else if (model instanceof StyledDocument) { 1077 StyledDocument sdoc = (StyledDocument)model; 1078 return sdoc.getParagraphElement(index); 1079 } else { 1080 Element para; 1081 for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) { 1082 int pos = para.getElementIndex(index); 1083 para = para.getElement(pos); 1084 } 1085 if (para == null) { 1086 return null; 1087 } 1088 return para.getParentElement(); 1089 } 1090 } 1091 1092 /* 1093 * Returns a <code>Segment</code> containing the paragraph text 1094 * at <code>index</code>, or null if <code>index</code> isn't 1095 * valid. 1096 */ 1097 private IndexedSegment getParagraphElementText(int index) 1098 throws BadLocationException { 1099 Element para = getParagraphElement(index); 1100 1101 1102 if (para != null) { 1103 IndexedSegment segment = new IndexedSegment(); 1104 try { 1105 int length = para.getEndOffset() - para.getStartOffset(); 1106 model.getText(para.getStartOffset(), length, segment); 1107 } catch (BadLocationException e) { 1108 return null; 1109 } 1110 segment.modelOffset = para.getStartOffset(); 1111 return segment; 1112 } 1113 return null; 1114 } 1115 1116 1117 /** 1118 * Returns the Segment at <code>index</code> representing either 1119 * the paragraph or sentence as identified by <code>part</code>, or 1120 * null if a valid paragraph/sentence can't be found. The offset 1121 * will point to the start of the word/sentence in the array, and 1122 * the modelOffset will point to the location of the word/sentence 1123 * in the model. 1124 */ 1125 private IndexedSegment getSegmentAt(int part, int index) 1126 throws BadLocationException { 1127 1128 IndexedSegment seg = getParagraphElementText(index); 1129 if (seg == null) { 1130 return null; 1131 } 1132 BreakIterator iterator; 1133 switch (part) { 1134 case AccessibleText.WORD: 1135 iterator = BreakIterator.getWordInstance(getLocale()); 1136 break; 1137 case AccessibleText.SENTENCE: 1138 iterator = BreakIterator.getSentenceInstance(getLocale()); 1139 break; 1140 default: 1141 return null; 1142 } 1143 seg.first(); 1144 iterator.setText(seg); 1145 int end = iterator.following(index - seg.modelOffset + seg.offset); 1146 if (end == BreakIterator.DONE) { 1147 return null; 1148 } 1149 if (end > seg.offset + seg.count) { 1150 return null; 1151 } 1152 int begin = iterator.previous(); 1153 if (begin == BreakIterator.DONE || 1154 begin >= seg.offset + seg.count) { 1155 return null; 1156 } 1157 seg.modelOffset = seg.modelOffset + begin - seg.offset; 1158 seg.offset = begin; 1159 seg.count = end - begin; 1160 return seg; 1161 } 1162 1163 /** 1164 * Return the AttributeSet for a given character at a given index 1165 * 1166 * @param i the zero-based index into the text 1167 * @return the AttributeSet of the character 1168 */ 1169 public AttributeSet getCharacterAttribute(int i) { 1170 if (model instanceof StyledDocument) { 1171 StyledDocument doc = (StyledDocument)model; 1172 Element elem = doc.getCharacterElement(i); 1173 if (elem != null) { 1174 return elem.getAttributes(); 1175 } 1176 } 1177 return null; 1178 } 1179 1180 /** 1181 * Returns the start offset within the selected text. 1182 * If there is no selection, but there is 1183 * a caret, the start and end offsets will be the same. 1184 * 1185 * @return the index into the text of the start of the selection 1186 */ 1187 public int getSelectionStart() { 1188 return editor.getSelectionStart(); 1189 } 1190 1191 /** 1192 * Returns the end offset within the selected text. 1193 * If there is no selection, but there is 1194 * a caret, the start and end offsets will be the same. 1195 * 1196 * @return the index into the text of the end of the selection 1197 */ 1198 public int getSelectionEnd() { 1199 return editor.getSelectionEnd(); 1200 } 1201 1202 /** 1203 * Returns the portion of the text that is selected. 1204 * 1205 * @return the String portion of the text that is selected 1206 */ 1207 public String getSelectedText() { 1208 return editor.getSelectedText(); 1209 } 1210 1211 /* 1212 * Returns the text substring starting at the specified 1213 * offset with the specified length. 1214 */ 1215 private String getText(int offset, int length) 1216 throws BadLocationException { 1217 1218 if (model != null && model instanceof StyledDocument) { 1219 StyledDocument doc = (StyledDocument)model; 1220 return model.getText(offset, length); 1221 } else { 1222 return null; 1223 } 1224 } 1225 } 1226 } 1227 1228 /* 1229 * ElementInfo for images 1230 */ 1231 private class IconElementInfo extends ElementInfo implements Accessible { 1232 1233 private int width = -1; 1234 private int height = -1; 1235 1236 IconElementInfo(Element element, ElementInfo parent) { 1237 super(element, parent); 1238 } 1239 1240 protected void invalidate(boolean first) { 1241 super.invalidate(first); 1242 width = height = -1; 1243 } 1244 1245 private int getImageSize(Object key) { 1246 if (validateIfNecessary()) { 1247 int size = getIntAttr(getAttributes(), key, -1); 1248 1249 if (size == -1) { 1250 View v = getView(); 1251 1252 size = 0; 1253 if (v instanceof ImageView) { 1254 Image img = ((ImageView)v).getImage(); 1255 if (img != null) { 1256 if (key == HTML.Attribute.WIDTH) { 1257 size = img.getWidth(null); 1258 } 1259 else { 1260 size = img.getHeight(null); 1261 } 1262 } 1263 } 1264 } 1265 return size; 1266 } 1267 return 0; 1268 } 1269 1270 // begin AccessibleIcon implementation ... 1271 private AccessibleContext accessibleContext; 1272 1273 public AccessibleContext getAccessibleContext() { 1274 if (accessibleContext == null) { 1275 accessibleContext = new IconAccessibleContext(this); 1276 } 1277 return accessibleContext; 1278 } 1279 1280 /* 1281 * AccessibleContext for images 1282 */ 1283 protected class IconAccessibleContext extends HTMLAccessibleContext 1284 implements AccessibleIcon { 1285 1286 public IconAccessibleContext(ElementInfo elementInfo) { 1287 super(elementInfo); 1288 } 1289 1290 /** 1291 * Gets the accessibleName property of this object. The accessibleName 1292 * property of an object is a localized String that designates the purpose 1293 * of the object. For example, the accessibleName property of a label 1294 * or button might be the text of the label or button itself. In the 1295 * case of an object that doesn't display its name, the accessibleName 1296 * should still be set. For example, in the case of a text field used 1297 * to enter the name of a city, the accessibleName for the en_US locale 1298 * could be 'city.' 1299 * 1300 * @return the localized name of the object; null if this 1301 * object does not have a name 1302 * 1303 * @see #setAccessibleName 1304 */ 1305 public String getAccessibleName() { 1306 return getAccessibleIconDescription(); 1307 } 1308 1309 /** 1310 * Gets the accessibleDescription property of this object. If this 1311 * property isn't set, returns the content type of this 1312 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text"). 1313 * 1314 * @return the localized description of the object; <code>null</code> 1315 * if this object does not have a description 1316 * 1317 * @see #setAccessibleName 1318 */ 1319 public String getAccessibleDescription() { 1320 return editor.getContentType(); 1321 } 1322 1323 /** 1324 * Gets the role of this object. The role of the object is the generic 1325 * purpose or use of the class of this object. For example, the role 1326 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in 1327 * AccessibleRole are provided so component developers can pick from 1328 * a set of predefined roles. This enables assistive technologies to 1329 * provide a consistent interface to various tweaked subclasses of 1330 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components 1331 * that act like a push button) as well as distinguish between subclasses 1332 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes 1333 * and AccessibleRole.RADIO_BUTTON for radio buttons). 1334 * <p>Note that the AccessibleRole class is also extensible, so 1335 * custom component developers can define their own AccessibleRole's 1336 * if the set of predefined roles is inadequate. 1337 * 1338 * @return an instance of AccessibleRole describing the role of the object 1339 * @see AccessibleRole 1340 */ 1341 public AccessibleRole getAccessibleRole() { 1342 return AccessibleRole.ICON; 1343 } 1344 1345 public AccessibleIcon [] getAccessibleIcon() { 1346 AccessibleIcon [] icons = new AccessibleIcon[1]; 1347 icons[0] = this; 1348 return icons; 1349 } 1350 1351 /** 1352 * Gets the description of the icon. This is meant to be a brief 1353 * textual description of the object. For example, it might be 1354 * presented to a blind user to give an indication of the purpose 1355 * of the icon. 1356 * 1357 * @return the description of the icon 1358 */ 1359 public String getAccessibleIconDescription() { 1360 return ((ImageView)getView()).getAltText(); 1361 } 1362 1363 /** 1364 * Sets the description of the icon. This is meant to be a brief 1365 * textual description of the object. For example, it might be 1366 * presented to a blind user to give an indication of the purpose 1367 * of the icon. 1368 * 1369 * @param description the description of the icon 1370 */ 1371 public void setAccessibleIconDescription(String description) { 1372 } 1373 1374 /** 1375 * Gets the width of the icon 1376 * 1377 * @return the width of the icon. 1378 */ 1379 public int getAccessibleIconWidth() { 1380 if (width == -1) { 1381 width = getImageSize(HTML.Attribute.WIDTH); 1382 } 1383 return width; 1384 } 1385 1386 /** 1387 * Gets the height of the icon 1388 * 1389 * @return the height of the icon. 1390 */ 1391 public int getAccessibleIconHeight() { 1392 if (height == -1) { 1393 height = getImageSize(HTML.Attribute.HEIGHT); 1394 } 1395 return height; 1396 } 1397 } 1398 // ... end AccessibleIconImplementation 1399 } 1400 1401 1402 /** 1403 * TableElementInfo encapsulates information about a HTML.Tag.TABLE. 1404 * To make access fast it crates a grid containing the children to 1405 * allow for access by row, column. TableElementInfo will contain 1406 * TableRowElementInfos, which will contain TableCellElementInfos. 1407 * Any time one of the rows or columns becomes invalid the table is 1408 * invalidated. This is because any time one of the child attributes 1409 * changes the size of the grid may have changed. 1410 */ 1411 private class TableElementInfo extends ElementInfo 1412 implements Accessible { 1413 1414 protected ElementInfo caption; 1415 1416 /** 1417 * Allocation of the table by row x column. There may be holes (eg 1418 * nulls) depending upon the html, any cell that has a rowspan/colspan 1419 * > 1 will be contained multiple times in the grid. 1420 */ 1421 private TableCellElementInfo[][] grid; 1422 1423 1424 TableElementInfo(Element e, ElementInfo parent) { 1425 super(e, parent); 1426 } 1427 1428 public ElementInfo getCaptionInfo() { 1429 return caption; 1430 } 1431 1432 /** 1433 * Overriden to update the grid when validating. 1434 */ 1435 protected void validate() { 1436 super.validate(); 1437 updateGrid(); 1438 } 1439 1440 /** 1441 * Overriden to only alloc instances of TableRowElementInfos. 1442 */ 1443 protected void loadChildren(Element e) { 1444 1445 for (int counter = 0; counter < e.getElementCount(); counter++) { 1446 Element child = e.getElement(counter); 1447 AttributeSet attrs = child.getAttributes(); 1448 1449 if (attrs.getAttribute(StyleConstants.NameAttribute) == 1450 HTML.Tag.TR) { 1451 addChild(new TableRowElementInfo(child, this, counter)); 1452 1453 } else if (attrs.getAttribute(StyleConstants.NameAttribute) == 1454 HTML.Tag.CAPTION) { 1455 // Handle captions as a special case since all other 1456 // children are table rows. 1457 caption = createElementInfo(child, this); 1458 } 1459 } 1460 } 1461 1462 /** 1463 * Updates the grid. 1464 */ 1465 private void updateGrid() { 1466 // Determine the max row/col count. 1467 int delta = 0; 1468 int maxCols = 0; 1469 int rows; 1470 for (int counter = 0; counter < getChildCount(); counter++) { 1471 TableRowElementInfo row = getRow(counter); 1472 int prev = 0; 1473 for (int y = 0; y < delta; y++) { 1474 prev = Math.max(prev, getRow(counter - y - 1). 1475 getColumnCount(y + 2)); 1476 } 1477 delta = Math.max(row.getRowCount(), delta); 1478 delta--; 1479 maxCols = Math.max(maxCols, row.getColumnCount() + prev); 1480 } 1481 rows = getChildCount() + delta; 1482 1483 // Alloc 1484 grid = new TableCellElementInfo[rows][]; 1485 for (int counter = 0; counter < rows; counter++) { 1486 grid[counter] = new TableCellElementInfo[maxCols]; 1487 } 1488 // Update 1489 for (int counter = 0; counter < rows; counter++) { 1490 getRow(counter).updateGrid(counter); 1491 } 1492 } 1493 1494 /** 1495 * Returns the TableCellElementInfo at the specified index. 1496 */ 1497 public TableRowElementInfo getRow(int index) { 1498 return (TableRowElementInfo)getChild(index); 1499 } 1500 1501 /** 1502 * Returns the TableCellElementInfo by row and column. 1503 */ 1504 public TableCellElementInfo getCell(int r, int c) { 1505 if (validateIfNecessary() && r < grid.length && 1506 c < grid[0].length) { 1507 return grid[r][c]; 1508 } 1509 return null; 1510 } 1511 1512 /** 1513 * Returns the rowspan of the specified entry. 1514 */ 1515 public int getRowExtentAt(int r, int c) { 1516 TableCellElementInfo cell = getCell(r, c); 1517 1518 if (cell != null) { 1519 int rows = cell.getRowCount(); 1520 int delta = 1; 1521 1522 while ((r - delta) >= 0 && grid[r - delta][c] == cell) { 1523 delta++; 1524 } 1525 return rows - delta + 1; 1526 } 1527 return 0; 1528 } 1529 1530 /** 1531 * Returns the colspan of the specified entry. 1532 */ 1533 public int getColumnExtentAt(int r, int c) { 1534 TableCellElementInfo cell = getCell(r, c); 1535 1536 if (cell != null) { 1537 int cols = cell.getColumnCount(); 1538 int delta = 1; 1539 1540 while ((c - delta) >= 0 && grid[r][c - delta] == cell) { 1541 delta++; 1542 } 1543 return cols - delta + 1; 1544 } 1545 return 0; 1546 } 1547 1548 /** 1549 * Returns the number of rows in the table. 1550 */ 1551 public int getRowCount() { 1552 if (validateIfNecessary()) { 1553 return grid.length; 1554 } 1555 return 0; 1556 } 1557 1558 /** 1559 * Returns the number of columns in the table. 1560 */ 1561 public int getColumnCount() { 1562 if (validateIfNecessary() && grid.length > 0) { 1563 return grid[0].length; 1564 } 1565 return 0; 1566 } 1567 1568 // begin AccessibleTable implementation ... 1569 private AccessibleContext accessibleContext; 1570 1571 public AccessibleContext getAccessibleContext() { 1572 if (accessibleContext == null) { 1573 accessibleContext = new TableAccessibleContext(this); 1574 } 1575 return accessibleContext; 1576 } 1577 1578 /* 1579 * AccessibleContext for tables 1580 */ 1581 public class TableAccessibleContext extends HTMLAccessibleContext 1582 implements AccessibleTable { 1583 1584 private AccessibleHeadersTable rowHeadersTable; 1585 1586 public TableAccessibleContext(ElementInfo elementInfo) { 1587 super(elementInfo); 1588 } 1589 1590 /** 1591 * Gets the accessibleName property of this object. The accessibleName 1592 * property of an object is a localized String that designates the purpose 1593 * of the object. For example, the accessibleName property of a label 1594 * or button might be the text of the label or button itself. In the 1595 * case of an object that doesn't display its name, the accessibleName 1596 * should still be set. For example, in the case of a text field used 1597 * to enter the name of a city, the accessibleName for the en_US locale 1598 * could be 'city.' 1599 * 1600 * @return the localized name of the object; null if this 1601 * object does not have a name 1602 * 1603 * @see #setAccessibleName 1604 */ 1605 public String getAccessibleName() { 1606 // return the role of the object 1607 return getAccessibleRole().toString(); 1608 } 1609 1610 /** 1611 * Gets the accessibleDescription property of this object. If this 1612 * property isn't set, returns the content type of this 1613 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text"). 1614 * 1615 * @return the localized description of the object; <code>null</code> 1616 * if this object does not have a description 1617 * 1618 * @see #setAccessibleName 1619 */ 1620 public String getAccessibleDescription() { 1621 return editor.getContentType(); 1622 } 1623 1624 /** 1625 * Gets the role of this object. The role of the object is the generic 1626 * purpose or use of the class of this object. For example, the role 1627 * of a push button is AccessibleRole.PUSH_BUTTON. The roles in 1628 * AccessibleRole are provided so component developers can pick from 1629 * a set of predefined roles. This enables assistive technologies to 1630 * provide a consistent interface to various tweaked subclasses of 1631 * components (e.g., use AccessibleRole.PUSH_BUTTON for all components 1632 * that act like a push button) as well as distinguish between subclasses 1633 * that behave differently (e.g., AccessibleRole.CHECK_BOX for check boxes 1634 * and AccessibleRole.RADIO_BUTTON for radio buttons). 1635 * <p>Note that the AccessibleRole class is also extensible, so 1636 * custom component developers can define their own AccessibleRole's 1637 * if the set of predefined roles is inadequate. 1638 * 1639 * @return an instance of AccessibleRole describing the role of the object 1640 * @see AccessibleRole 1641 */ 1642 public AccessibleRole getAccessibleRole() { 1643 return AccessibleRole.TABLE; 1644 } 1645 1646 /** 1647 * Gets the 0-based index of this object in its accessible parent. 1648 * 1649 * @return the 0-based index of this object in its parent; -1 if this 1650 * object does not have an accessible parent. 1651 * 1652 * @see #getAccessibleParent 1653 * @see #getAccessibleChildrenCount 1654 * @gsee #getAccessibleChild 1655 */ 1656 public int getAccessibleIndexInParent() { 1657 return elementInfo.getIndexInParent(); 1658 } 1659 1660 /** 1661 * Returns the number of accessible children of the object. 1662 * 1663 * @return the number of accessible children of the object. 1664 */ 1665 public int getAccessibleChildrenCount() { 1666 return ((TableElementInfo)elementInfo).getRowCount() * 1667 ((TableElementInfo)elementInfo).getColumnCount(); 1668 } 1669 1670 /** 1671 * Returns the specified Accessible child of the object. The Accessible 1672 * children of an Accessible object are zero-based, so the first child 1673 * of an Accessible child is at index 0, the second child is at index 1, 1674 * and so on. 1675 * 1676 * @param i zero-based index of child 1677 * @return the Accessible child of the object 1678 * @see #getAccessibleChildrenCount 1679 */ 1680 public Accessible getAccessibleChild(int i) { 1681 int rowCount = ((TableElementInfo)elementInfo).getRowCount(); 1682 int columnCount = ((TableElementInfo)elementInfo).getColumnCount(); 1683 int r = i / rowCount; 1684 int c = i % columnCount; 1685 if (r < 0 || r >= rowCount || c < 0 || c >= columnCount) { 1686 return null; 1687 } else { 1688 return getAccessibleAt(r, c); 1689 } 1690 } 1691 1692 public AccessibleTable getAccessibleTable() { 1693 return this; 1694 } 1695 1696 /** 1697 * Returns the caption for the table. 1698 * 1699 * @return the caption for the table 1700 */ 1701 public Accessible getAccessibleCaption() { 1702 ElementInfo captionInfo = getCaptionInfo(); 1703 if (captionInfo instanceof Accessible) { 1704 return (Accessible)caption; 1705 } else { 1706 return null; 1707 } 1708 } 1709 1710 /** 1711 * Sets the caption for the table. 1712 * 1713 * @param a the caption for the table 1714 */ 1715 public void setAccessibleCaption(Accessible a) { 1716 } 1717 1718 /** 1719 * Returns the summary description of the table. 1720 * 1721 * @return the summary description of the table 1722 */ 1723 public Accessible getAccessibleSummary() { 1724 return null; 1725 } 1726 1727 /** 1728 * Sets the summary description of the table 1729 * 1730 * @param a the summary description of the table 1731 */ 1732 public void setAccessibleSummary(Accessible a) { 1733 } 1734 1735 /** 1736 * Returns the number of rows in the table. 1737 * 1738 * @return the number of rows in the table 1739 */ 1740 public int getAccessibleRowCount() { 1741 return ((TableElementInfo)elementInfo).getRowCount(); 1742 } 1743 1744 /** 1745 * Returns the number of columns in the table. 1746 * 1747 * @return the number of columns in the table 1748 */ 1749 public int getAccessibleColumnCount() { 1750 return ((TableElementInfo)elementInfo).getColumnCount(); 1751 } 1752 1753 /** 1754 * Returns the Accessible at a specified row and column 1755 * in the table. 1756 * 1757 * @param r zero-based row of the table 1758 * @param c zero-based column of the table 1759 * @return the Accessible at the specified row and column 1760 */ 1761 public Accessible getAccessibleAt(int r, int c) { 1762 TableCellElementInfo cellInfo = getCell(r, c); 1763 if (cellInfo != null) { 1764 return cellInfo.getAccessible(); 1765 } else { 1766 return null; 1767 } 1768 } 1769 1770 /** 1771 * Returns the number of rows occupied by the Accessible at 1772 * a specified row and column in the table. 1773 * 1774 * @return the number of rows occupied by the Accessible at a 1775 * given specified (row, column) 1776 */ 1777 public int getAccessibleRowExtentAt(int r, int c) { 1778 return ((TableElementInfo)elementInfo).getRowExtentAt(r, c); 1779 } 1780 1781 /** 1782 * Returns the number of columns occupied by the Accessible at 1783 * a specified row and column in the table. 1784 * 1785 * @return the number of columns occupied by the Accessible at a 1786 * given specified row and column 1787 */ 1788 public int getAccessibleColumnExtentAt(int r, int c) { 1789 return ((TableElementInfo)elementInfo).getColumnExtentAt(r, c); 1790 } 1791 1792 /** 1793 * Returns the row headers as an AccessibleTable. 1794 * 1795 * @return an AccessibleTable representing the row 1796 * headers 1797 */ 1798 public AccessibleTable getAccessibleRowHeader() { 1799 return rowHeadersTable; 1800 } 1801 1802 /** 1803 * Sets the row headers. 1804 * 1805 * @param table an AccessibleTable representing the 1806 * row headers 1807 */ 1808 public void setAccessibleRowHeader(AccessibleTable table) { 1809 } 1810 1811 /** 1812 * Returns the column headers as an AccessibleTable. 1813 * 1814 * @return an AccessibleTable representing the column 1815 * headers 1816 */ 1817 public AccessibleTable getAccessibleColumnHeader() { 1818 return null; 1819 } 1820 1821 /** 1822 * Sets the column headers. 1823 * 1824 * @param table an AccessibleTable representing the 1825 * column headers 1826 */ 1827 public void setAccessibleColumnHeader(AccessibleTable table) { 1828 } 1829 1830 /** 1831 * Returns the description of the specified row in the table. 1832 * 1833 * @param r zero-based row of the table 1834 * @return the description of the row 1835 */ 1836 public Accessible getAccessibleRowDescription(int r) { 1837 return null; 1838 } 1839 1840 /** 1841 * Sets the description text of the specified row of the table. 1842 * 1843 * @param r zero-based row of the table 1844 * @param a the description of the row 1845 */ 1846 public void setAccessibleRowDescription(int r, Accessible a) { 1847 } 1848 1849 /** 1850 * Returns the description text of the specified column in the table. 1851 * 1852 * @param c zero-based column of the table 1853 * @return the text description of the column 1854 */ 1855 public Accessible getAccessibleColumnDescription(int c) { 1856 return null; 1857 } 1858 1859 /** 1860 * Sets the description text of the specified column in the table. 1861 * 1862 * @param c zero-based column of the table 1863 * @param a the text description of the column 1864 */ 1865 public void setAccessibleColumnDescription(int c, Accessible a) { 1866 } 1867 1868 /** 1869 * Returns a boolean value indicating whether the accessible at 1870 * a specified row and column is selected. 1871 * 1872 * @param r zero-based row of the table 1873 * @param c zero-based column of the table 1874 * @return the boolean value true if the accessible at the 1875 * row and column is selected. Otherwise, the boolean value 1876 * false 1877 */ 1878 public boolean isAccessibleSelected(int r, int c) { 1879 if (validateIfNecessary()) { 1880 if (r < 0 || r >= getAccessibleRowCount() || 1881 c < 0 || c >= getAccessibleColumnCount()) { 1882 return false; 1883 } 1884 TableCellElementInfo cell = getCell(r, c); 1885 if (cell != null) { 1886 Element elem = cell.getElement(); 1887 int start = elem.getStartOffset(); 1888 int end = elem.getEndOffset(); 1889 return start >= editor.getSelectionStart() && 1890 end <= editor.getSelectionEnd(); 1891 } 1892 } 1893 return false; 1894 } 1895 1896 /** 1897 * Returns a boolean value indicating whether the specified row 1898 * is selected. 1899 * 1900 * @param r zero-based row of the table 1901 * @return the boolean value true if the specified row is selected. 1902 * Otherwise, false. 1903 */ 1904 public boolean isAccessibleRowSelected(int r) { 1905 if (validateIfNecessary()) { 1906 if (r < 0 || r >= getAccessibleRowCount()) { 1907 return false; 1908 } 1909 int nColumns = getAccessibleColumnCount(); 1910 1911 TableCellElementInfo startCell = getCell(r, 0); 1912 if (startCell == null) { 1913 return false; 1914 } 1915 int start = startCell.getElement().getStartOffset(); 1916 1917 TableCellElementInfo endCell = getCell(r, nColumns-1); 1918 if (endCell == null) { 1919 return false; 1920 } 1921 int end = endCell.getElement().getEndOffset(); 1922 1923 return start >= editor.getSelectionStart() && 1924 end <= editor.getSelectionEnd(); 1925 } 1926 return false; 1927 } 1928 1929 /** 1930 * Returns a boolean value indicating whether the specified column 1931 * is selected. 1932 * 1933 * @param c zero-based column of the table 1934 * @return the boolean value true if the specified column is selected. 1935 * Otherwise, false. 1936 */ 1937 public boolean isAccessibleColumnSelected(int c) { 1938 if (validateIfNecessary()) { 1939 if (c < 0 || c >= getAccessibleColumnCount()) { 1940 return false; 1941 } 1942 int nRows = getAccessibleRowCount(); 1943 1944 TableCellElementInfo startCell = getCell(0, c); 1945 if (startCell == null) { 1946 return false; 1947 } 1948 int start = startCell.getElement().getStartOffset(); 1949 1950 TableCellElementInfo endCell = getCell(nRows-1, c); 1951 if (endCell == null) { 1952 return false; 1953 } 1954 int end = endCell.getElement().getEndOffset(); 1955 return start >= editor.getSelectionStart() && 1956 end <= editor.getSelectionEnd(); 1957 } 1958 return false; 1959 } 1960 1961 /** 1962 * Returns the selected rows in a table. 1963 * 1964 * @return an array of selected rows where each element is a 1965 * zero-based row of the table 1966 */ 1967 public int [] getSelectedAccessibleRows() { 1968 if (validateIfNecessary()) { 1969 int nRows = getAccessibleRowCount(); 1970 Vector<Integer> vec = new Vector<Integer>(); 1971 1972 for (int i = 0; i < nRows; i++) { 1973 if (isAccessibleRowSelected(i)) { 1974 vec.addElement(Integer.valueOf(i)); 1975 } 1976 } 1977 int retval[] = new int[vec.size()]; 1978 for (int i = 0; i < retval.length; i++) { 1979 retval[i] = vec.elementAt(i).intValue(); 1980 } 1981 return retval; 1982 } 1983 return new int[0]; 1984 } 1985 1986 /** 1987 * Returns the selected columns in a table. 1988 * 1989 * @return an array of selected columns where each element is a 1990 * zero-based column of the table 1991 */ 1992 public int [] getSelectedAccessibleColumns() { 1993 if (validateIfNecessary()) { 1994 int nColumns = getAccessibleRowCount(); 1995 Vector<Integer> vec = new Vector<Integer>(); 1996 1997 for (int i = 0; i < nColumns; i++) { 1998 if (isAccessibleColumnSelected(i)) { 1999 vec.addElement(Integer.valueOf(i)); 2000 } 2001 } 2002 int retval[] = new int[vec.size()]; 2003 for (int i = 0; i < retval.length; i++) { 2004 retval[i] = vec.elementAt(i).intValue(); 2005 } 2006 return retval; 2007 } 2008 return new int[0]; 2009 } 2010 2011 // begin AccessibleExtendedTable implementation ------------- 2012 2013 /** 2014 * Returns the row number of an index in the table. 2015 * 2016 * @param index the zero-based index in the table 2017 * @return the zero-based row of the table if one exists; 2018 * otherwise -1. 2019 */ 2020 public int getAccessibleRow(int index) { 2021 if (validateIfNecessary()) { 2022 int numCells = getAccessibleColumnCount() * 2023 getAccessibleRowCount(); 2024 if (index >= numCells) { 2025 return -1; 2026 } else { 2027 return index / getAccessibleColumnCount(); 2028 } 2029 } 2030 return -1; 2031 } 2032 2033 /** 2034 * Returns the column number of an index in the table. 2035 * 2036 * @param index the zero-based index in the table 2037 * @return the zero-based column of the table if one exists; 2038 * otherwise -1. 2039 */ 2040 public int getAccessibleColumn(int index) { 2041 if (validateIfNecessary()) { 2042 int numCells = getAccessibleColumnCount() * 2043 getAccessibleRowCount(); 2044 if (index >= numCells) { 2045 return -1; 2046 } else { 2047 return index % getAccessibleColumnCount(); 2048 } 2049 } 2050 return -1; 2051 } 2052 2053 /** 2054 * Returns the index at a row and column in the table. 2055 * 2056 * @param r zero-based row of the table 2057 * @param c zero-based column of the table 2058 * @return the zero-based index in the table if one exists; 2059 * otherwise -1. 2060 */ 2061 public int getAccessibleIndex(int r, int c) { 2062 if (validateIfNecessary()) { 2063 if (r >= getAccessibleRowCount() || 2064 c >= getAccessibleColumnCount()) { 2065 return -1; 2066 } else { 2067 return r * getAccessibleColumnCount() + c; 2068 } 2069 } 2070 return -1; 2071 } 2072 2073 /** 2074 * Returns the row header at a row in a table. 2075 * @param r zero-based row of the table 2076 * 2077 * @return a String representing the row header 2078 * if one exists; otherwise null. 2079 */ 2080 public String getAccessibleRowHeader(int r) { 2081 if (validateIfNecessary()) { 2082 TableCellElementInfo cellInfo = getCell(r, 0); 2083 if (cellInfo.isHeaderCell()) { 2084 View v = cellInfo.getView(); 2085 if (v != null && model != null) { 2086 try { 2087 return model.getText(v.getStartOffset(), 2088 v.getEndOffset() - 2089 v.getStartOffset()); 2090 } catch (BadLocationException e) { 2091 return null; 2092 } 2093 } 2094 } 2095 } 2096 return null; 2097 } 2098 2099 /** 2100 * Returns the column header at a column in a table. 2101 * @param c zero-based column of the table 2102 * 2103 * @return a String representing the column header 2104 * if one exists; otherwise null. 2105 */ 2106 public String getAccessibleColumnHeader(int c) { 2107 if (validateIfNecessary()) { 2108 TableCellElementInfo cellInfo = getCell(0, c); 2109 if (cellInfo.isHeaderCell()) { 2110 View v = cellInfo.getView(); 2111 if (v != null && model != null) { 2112 try { 2113 return model.getText(v.getStartOffset(), 2114 v.getEndOffset() - 2115 v.getStartOffset()); 2116 } catch (BadLocationException e) { 2117 return null; 2118 } 2119 } 2120 } 2121 } 2122 return null; 2123 } 2124 2125 public void addRowHeader(TableCellElementInfo cellInfo, int rowNumber) { 2126 if (rowHeadersTable == null) { 2127 rowHeadersTable = new AccessibleHeadersTable(); 2128 } 2129 rowHeadersTable.addHeader(cellInfo, rowNumber); 2130 } 2131 // end of AccessibleExtendedTable implementation ------------ 2132 2133 protected class AccessibleHeadersTable implements AccessibleTable { 2134 2135 // Header information is modeled as a Hashtable of 2136 // ArrayLists where each Hashtable entry represents 2137 // a row containing one or more headers. 2138 private Hashtable<Integer, ArrayList<TableCellElementInfo>> headers = 2139 new Hashtable<Integer, ArrayList<TableCellElementInfo>>(); 2140 private int rowCount = 0; 2141 private int columnCount = 0; 2142 2143 public void addHeader(TableCellElementInfo cellInfo, int rowNumber) { 2144 Integer rowInteger = Integer.valueOf(rowNumber); 2145 ArrayList<TableCellElementInfo> list = headers.get(rowInteger); 2146 if (list == null) { 2147 list = new ArrayList<TableCellElementInfo>(); 2148 headers.put(rowInteger, list); 2149 } 2150 list.add(cellInfo); 2151 } 2152 2153 /** 2154 * Returns the caption for the table. 2155 * 2156 * @return the caption for the table 2157 */ 2158 public Accessible getAccessibleCaption() { 2159 return null; 2160 } 2161 2162 /** 2163 * Sets the caption for the table. 2164 * 2165 * @param a the caption for the table 2166 */ 2167 public void setAccessibleCaption(Accessible a) { 2168 } 2169 2170 /** 2171 * Returns the summary description of the table. 2172 * 2173 * @return the summary description of the table 2174 */ 2175 public Accessible getAccessibleSummary() { 2176 return null; 2177 } 2178 2179 /** 2180 * Sets the summary description of the table 2181 * 2182 * @param a the summary description of the table 2183 */ 2184 public void setAccessibleSummary(Accessible a) { 2185 } 2186 2187 /** 2188 * Returns the number of rows in the table. 2189 * 2190 * @return the number of rows in the table 2191 */ 2192 public int getAccessibleRowCount() { 2193 return rowCount; 2194 } 2195 2196 /** 2197 * Returns the number of columns in the table. 2198 * 2199 * @return the number of columns in the table 2200 */ 2201 public int getAccessibleColumnCount() { 2202 return columnCount; 2203 } 2204 2205 private TableCellElementInfo getElementInfoAt(int r, int c) { 2206 ArrayList<TableCellElementInfo> list = headers.get(Integer.valueOf(r)); 2207 if (list != null) { 2208 return list.get(c); 2209 } else { 2210 return null; 2211 } 2212 } 2213 2214 /** 2215 * Returns the Accessible at a specified row and column 2216 * in the table. 2217 * 2218 * @param r zero-based row of the table 2219 * @param c zero-based column of the table 2220 * @return the Accessible at the specified row and column 2221 */ 2222 public Accessible getAccessibleAt(int r, int c) { 2223 ElementInfo elementInfo = getElementInfoAt(r, c); 2224 if (elementInfo instanceof Accessible) { 2225 return (Accessible)elementInfo; 2226 } else { 2227 return null; 2228 } 2229 } 2230 2231 /** 2232 * Returns the number of rows occupied by the Accessible at 2233 * a specified row and column in the table. 2234 * 2235 * @return the number of rows occupied by the Accessible at a 2236 * given specified (row, column) 2237 */ 2238 public int getAccessibleRowExtentAt(int r, int c) { 2239 TableCellElementInfo elementInfo = getElementInfoAt(r, c); 2240 if (elementInfo != null) { 2241 return elementInfo.getRowCount(); 2242 } else { 2243 return 0; 2244 } 2245 } 2246 2247 /** 2248 * Returns the number of columns occupied by the Accessible at 2249 * a specified row and column in the table. 2250 * 2251 * @return the number of columns occupied by the Accessible at a 2252 * given specified row and column 2253 */ 2254 public int getAccessibleColumnExtentAt(int r, int c) { 2255 TableCellElementInfo elementInfo = getElementInfoAt(r, c); 2256 if (elementInfo != null) { 2257 return elementInfo.getRowCount(); 2258 } else { 2259 return 0; 2260 } 2261 } 2262 2263 /** 2264 * Returns the row headers as an AccessibleTable. 2265 * 2266 * @return an AccessibleTable representing the row 2267 * headers 2268 */ 2269 public AccessibleTable getAccessibleRowHeader() { 2270 return null; 2271 } 2272 2273 /** 2274 * Sets the row headers. 2275 * 2276 * @param table an AccessibleTable representing the 2277 * row headers 2278 */ 2279 public void setAccessibleRowHeader(AccessibleTable table) { 2280 } 2281 2282 /** 2283 * Returns the column headers as an AccessibleTable. 2284 * 2285 * @return an AccessibleTable representing the column 2286 * headers 2287 */ 2288 public AccessibleTable getAccessibleColumnHeader() { 2289 return null; 2290 } 2291 2292 /** 2293 * Sets the column headers. 2294 * 2295 * @param table an AccessibleTable representing the 2296 * column headers 2297 */ 2298 public void setAccessibleColumnHeader(AccessibleTable table) { 2299 } 2300 2301 /** 2302 * Returns the description of the specified row in the table. 2303 * 2304 * @param r zero-based row of the table 2305 * @return the description of the row 2306 */ 2307 public Accessible getAccessibleRowDescription(int r) { 2308 return null; 2309 } 2310 2311 /** 2312 * Sets the description text of the specified row of the table. 2313 * 2314 * @param r zero-based row of the table 2315 * @param a the description of the row 2316 */ 2317 public void setAccessibleRowDescription(int r, Accessible a) { 2318 } 2319 2320 /** 2321 * Returns the description text of the specified column in the table. 2322 * 2323 * @param c zero-based column of the table 2324 * @return the text description of the column 2325 */ 2326 public Accessible getAccessibleColumnDescription(int c) { 2327 return null; 2328 } 2329 2330 /** 2331 * Sets the description text of the specified column in the table. 2332 * 2333 * @param c zero-based column of the table 2334 * @param a the text description of the column 2335 */ 2336 public void setAccessibleColumnDescription(int c, Accessible a) { 2337 } 2338 2339 /** 2340 * Returns a boolean value indicating whether the accessible at 2341 * a specified row and column is selected. 2342 * 2343 * @param r zero-based row of the table 2344 * @param c zero-based column of the table 2345 * @return the boolean value true if the accessible at the 2346 * row and column is selected. Otherwise, the boolean value 2347 * false 2348 */ 2349 public boolean isAccessibleSelected(int r, int c) { 2350 return false; 2351 } 2352 2353 /** 2354 * Returns a boolean value indicating whether the specified row 2355 * is selected. 2356 * 2357 * @param r zero-based row of the table 2358 * @return the boolean value true if the specified row is selected. 2359 * Otherwise, false. 2360 */ 2361 public boolean isAccessibleRowSelected(int r) { 2362 return false; 2363 } 2364 2365 /** 2366 * Returns a boolean value indicating whether the specified column 2367 * is selected. 2368 * 2369 * @param c zero-based column of the table 2370 * @return the boolean value true if the specified column is selected. 2371 * Otherwise, false. 2372 */ 2373 public boolean isAccessibleColumnSelected(int c) { 2374 return false; 2375 } 2376 2377 /** 2378 * Returns the selected rows in a table. 2379 * 2380 * @return an array of selected rows where each element is a 2381 * zero-based row of the table 2382 */ 2383 public int [] getSelectedAccessibleRows() { 2384 return new int [0]; 2385 } 2386 2387 /** 2388 * Returns the selected columns in a table. 2389 * 2390 * @return an array of selected columns where each element is a 2391 * zero-based column of the table 2392 */ 2393 public int [] getSelectedAccessibleColumns() { 2394 return new int [0]; 2395 } 2396 } 2397 } // ... end AccessibleHeadersTable 2398 2399 /* 2400 * ElementInfo for table rows 2401 */ 2402 private class TableRowElementInfo extends ElementInfo { 2403 2404 private TableElementInfo parent; 2405 private int rowNumber; 2406 2407 TableRowElementInfo(Element e, TableElementInfo parent, int rowNumber) { 2408 super(e, parent); 2409 this.parent = parent; 2410 this.rowNumber = rowNumber; 2411 } 2412 2413 protected void loadChildren(Element e) { 2414 for (int x = 0; x < e.getElementCount(); x++) { 2415 AttributeSet attrs = e.getElement(x).getAttributes(); 2416 2417 if (attrs.getAttribute(StyleConstants.NameAttribute) == 2418 HTML.Tag.TH) { 2419 TableCellElementInfo headerElementInfo = 2420 new TableCellElementInfo(e.getElement(x), this, true); 2421 addChild(headerElementInfo); 2422 2423 AccessibleTable at = 2424 parent.getAccessibleContext().getAccessibleTable(); 2425 TableAccessibleContext tableElement = 2426 (TableAccessibleContext)at; 2427 tableElement.addRowHeader(headerElementInfo, rowNumber); 2428 2429 } else if (attrs.getAttribute(StyleConstants.NameAttribute) == 2430 HTML.Tag.TD) { 2431 addChild(new TableCellElementInfo(e.getElement(x), this, 2432 false)); 2433 } 2434 } 2435 } 2436 2437 /** 2438 * Returns the max of the rowspans of the cells in this row. 2439 */ 2440 public int getRowCount() { 2441 int rowCount = 1; 2442 if (validateIfNecessary()) { 2443 for (int counter = 0; counter < getChildCount(); 2444 counter++) { 2445 2446 TableCellElementInfo cell = (TableCellElementInfo) 2447 getChild(counter); 2448 2449 if (cell.validateIfNecessary()) { 2450 rowCount = Math.max(rowCount, cell.getRowCount()); 2451 } 2452 } 2453 } 2454 return rowCount; 2455 } 2456 2457 /** 2458 * Returns the sum of the column spans of the individual 2459 * cells in this row. 2460 */ 2461 public int getColumnCount() { 2462 int colCount = 0; 2463 if (validateIfNecessary()) { 2464 for (int counter = 0; counter < getChildCount(); 2465 counter++) { 2466 TableCellElementInfo cell = (TableCellElementInfo) 2467 getChild(counter); 2468 2469 if (cell.validateIfNecessary()) { 2470 colCount += cell.getColumnCount(); 2471 } 2472 } 2473 } 2474 return colCount; 2475 } 2476 2477 /** 2478 * Overriden to invalidate the table as well as 2479 * TableRowElementInfo. 2480 */ 2481 protected void invalidate(boolean first) { 2482 super.invalidate(first); 2483 getParent().invalidate(true); 2484 } 2485 2486 /** 2487 * Places the TableCellElementInfos for this element in 2488 * the grid. 2489 */ 2490 private void updateGrid(int row) { 2491 if (validateIfNecessary()) { 2492 boolean emptyRow = false; 2493 2494 while (!emptyRow) { 2495 for (int counter = 0; counter < grid[row].length; 2496 counter++) { 2497 if (grid[row][counter] == null) { 2498 emptyRow = true; 2499 break; 2500 } 2501 } 2502 if (!emptyRow) { 2503 row++; 2504 } 2505 } 2506 for (int col = 0, counter = 0; counter < getChildCount(); 2507 counter++) { 2508 TableCellElementInfo cell = (TableCellElementInfo) 2509 getChild(counter); 2510 2511 while (grid[row][col] != null) { 2512 col++; 2513 } 2514 for (int rowCount = cell.getRowCount() - 1; 2515 rowCount >= 0; rowCount--) { 2516 for (int colCount = cell.getColumnCount() - 1; 2517 colCount >= 0; colCount--) { 2518 grid[row + rowCount][col + colCount] = cell; 2519 } 2520 } 2521 col += cell.getColumnCount(); 2522 } 2523 } 2524 } 2525 2526 /** 2527 * Returns the column count of the number of columns that have 2528 * a rowcount >= rowspan. 2529 */ 2530 private int getColumnCount(int rowspan) { 2531 if (validateIfNecessary()) { 2532 int cols = 0; 2533 for (int counter = 0; counter < getChildCount(); 2534 counter++) { 2535 TableCellElementInfo cell = (TableCellElementInfo) 2536 getChild(counter); 2537 2538 if (cell.getRowCount() >= rowspan) { 2539 cols += cell.getColumnCount(); 2540 } 2541 } 2542 return cols; 2543 } 2544 return 0; 2545 } 2546 } 2547 2548 /** 2549 * TableCellElementInfo is used to represents the cells of 2550 * the table. 2551 */ 2552 private class TableCellElementInfo extends ElementInfo { 2553 2554 private Accessible accessible; 2555 private boolean isHeaderCell; 2556 2557 TableCellElementInfo(Element e, ElementInfo parent) { 2558 super(e, parent); 2559 this.isHeaderCell = false; 2560 } 2561 2562 TableCellElementInfo(Element e, ElementInfo parent, 2563 boolean isHeaderCell) { 2564 super(e, parent); 2565 this.isHeaderCell = isHeaderCell; 2566 } 2567 2568 /* 2569 * Returns whether this table cell is a header 2570 */ 2571 public boolean isHeaderCell() { 2572 return this.isHeaderCell; 2573 } 2574 2575 /* 2576 * Returns the Accessible representing this table cell 2577 */ 2578 public Accessible getAccessible() { 2579 accessible = null; 2580 getAccessible(this); 2581 return accessible; 2582 } 2583 2584 /* 2585 * Gets the outermost Accessible in the table cell 2586 */ 2587 private void getAccessible(ElementInfo elementInfo) { 2588 if (elementInfo instanceof Accessible) { 2589 accessible = (Accessible)elementInfo; 2590 } else { 2591 for (int i = 0; i < elementInfo.getChildCount(); i++) { 2592 getAccessible(elementInfo.getChild(i)); 2593 } 2594 } 2595 } 2596 2597 /** 2598 * Returns the rowspan attribute. 2599 */ 2600 public int getRowCount() { 2601 if (validateIfNecessary()) { 2602 return Math.max(1, getIntAttr(getAttributes(), 2603 HTML.Attribute.ROWSPAN, 1)); 2604 } 2605 return 0; 2606 } 2607 2608 /** 2609 * Returns the colspan attribute. 2610 */ 2611 public int getColumnCount() { 2612 if (validateIfNecessary()) { 2613 return Math.max(1, getIntAttr(getAttributes(), 2614 HTML.Attribute.COLSPAN, 1)); 2615 } 2616 return 0; 2617 } 2618 2619 /** 2620 * Overriden to invalidate the TableRowElementInfo as well as 2621 * the TableCellElementInfo. 2622 */ 2623 protected void invalidate(boolean first) { 2624 super.invalidate(first); 2625 getParent().invalidate(true); 2626 } 2627 } 2628 } 2629 2630 2631 /** 2632 * ElementInfo provides a slim down view of an Element. Each ElementInfo 2633 * can have any number of child ElementInfos that are not necessarily 2634 * direct children of the Element. As the Document changes various 2635 * ElementInfos become invalidated. Before accessing a particular portion 2636 * of an ElementInfo you should make sure it is valid by invoking 2637 * <code>validateIfNecessary</code>, this will return true if 2638 * successful, on the other hand a false return value indicates the 2639 * ElementInfo is not valid and can never become valid again (usually 2640 * the result of the Element the ElementInfo encapsulates being removed). 2641 */ 2642 private class ElementInfo { 2643 2644 /** 2645 * The children of this ElementInfo. 2646 */ 2647 private ArrayList<ElementInfo> children; 2648 /** 2649 * The Element this ElementInfo is providing information for. 2650 */ 2651 private Element element; 2652 /** 2653 * The parent ElementInfo, will be null for the root. 2654 */ 2655 private ElementInfo parent; 2656 /** 2657 * Indicates the validity of the ElementInfo. 2658 */ 2659 private boolean isValid; 2660 /** 2661 * Indicates if the ElementInfo can become valid. 2662 */ 2663 private boolean canBeValid; 2664 2665 2666 /** 2667 * Creates the root ElementInfo. 2668 */ 2669 ElementInfo(Element element) { 2670 this(element, null); 2671 } 2672 2673 /** 2674 * Creates an ElementInfo representing <code>element</code> with 2675 * the specified parent. 2676 */ 2677 ElementInfo(Element element, ElementInfo parent) { 2678 this.element = element; 2679 this.parent = parent; 2680 isValid = false; 2681 canBeValid = true; 2682 } 2683 2684 /** 2685 * Validates the receiver. This recreates the children as well. This 2686 * will be invoked within a <code>readLock</code>. If this is overriden 2687 * it MUST invoke supers implementation first! 2688 */ 2689 protected void validate() { 2690 isValid = true; 2691 loadChildren(getElement()); 2692 } 2693 2694 /** 2695 * Recreates the direct children of <code>info</code>. 2696 */ 2697 protected void loadChildren(Element parent) { 2698 if (!parent.isLeaf()) { 2699 for (int counter = 0, maxCounter = parent.getElementCount(); 2700 counter < maxCounter; counter++) { 2701 Element e = parent.getElement(counter); 2702 ElementInfo childInfo = createElementInfo(e, this); 2703 2704 if (childInfo != null) { 2705 addChild(childInfo); 2706 } 2707 else { 2708 loadChildren(e); 2709 } 2710 } 2711 } 2712 } 2713 2714 /** 2715 * Returns the index of the child in the parent, or -1 for the 2716 * root or if the parent isn't valid. 2717 */ 2718 public int getIndexInParent() { 2719 if (parent == null || !parent.isValid()) { 2720 return -1; 2721 } 2722 return parent.indexOf(this); 2723 } 2724 2725 /** 2726 * Returns the Element this <code>ElementInfo</code> represents. 2727 */ 2728 public Element getElement() { 2729 return element; 2730 } 2731 2732 /** 2733 * Returns the parent of this Element, or null for the root. 2734 */ 2735 public ElementInfo getParent() { 2736 return parent; 2737 } 2738 2739 /** 2740 * Returns the index of the specified child, or -1 if 2741 * <code>child</code> isn't a valid child. 2742 */ 2743 public int indexOf(ElementInfo child) { 2744 ArrayList<ElementInfo> children = this.children; 2745 2746 if (children != null) { 2747 return children.indexOf(child); 2748 } 2749 return -1; 2750 } 2751 2752 /** 2753 * Returns the child ElementInfo at <code>index</code>, or null 2754 * if <code>index</code> isn't a valid index. 2755 */ 2756 public ElementInfo getChild(int index) { 2757 if (validateIfNecessary()) { 2758 ArrayList<ElementInfo> children = this.children; 2759 2760 if (children != null && index >= 0 && 2761 index < children.size()) { 2762 return children.get(index); 2763 } 2764 } 2765 return null; 2766 } 2767 2768 /** 2769 * Returns the number of children the ElementInfo contains. 2770 */ 2771 public int getChildCount() { 2772 validateIfNecessary(); 2773 return (children == null) ? 0 : children.size(); 2774 } 2775 2776 /** 2777 * Adds a new child to this ElementInfo. 2778 */ 2779 protected void addChild(ElementInfo child) { 2780 if (children == null) { 2781 children = new ArrayList<ElementInfo>(); 2782 } 2783 children.add(child); 2784 } 2785 2786 /** 2787 * Returns the View corresponding to this ElementInfo, or null 2788 * if the ElementInfo can't be validated. 2789 */ 2790 protected View getView() { 2791 if (!validateIfNecessary()) { 2792 return null; 2793 } 2794 Object lock = lock(); 2795 try { 2796 View rootView = getRootView(); 2797 Element e = getElement(); 2798 int start = e.getStartOffset(); 2799 2800 if (rootView != null) { 2801 return getView(rootView, e, start); 2802 } 2803 return null; 2804 } finally { 2805 unlock(lock); 2806 } 2807 } 2808 2809 /** 2810 * Returns the Bounds for this ElementInfo, or null 2811 * if the ElementInfo can't be validated. 2812 */ 2813 public Rectangle getBounds() { 2814 if (!validateIfNecessary()) { 2815 return null; 2816 } 2817 Object lock = lock(); 2818 try { 2819 Rectangle bounds = getRootEditorRect(); 2820 View rootView = getRootView(); 2821 Element e = getElement(); 2822 2823 if (bounds != null && rootView != null) { 2824 try { 2825 return rootView.modelToView(e.getStartOffset(), 2826 Position.Bias.Forward, 2827 e.getEndOffset(), 2828 Position.Bias.Backward, 2829 bounds).getBounds(); 2830 } catch (BadLocationException ble) { } 2831 } 2832 } finally { 2833 unlock(lock); 2834 } 2835 return null; 2836 } 2837 2838 /** 2839 * Returns true if this ElementInfo is valid. 2840 */ 2841 protected boolean isValid() { 2842 return isValid; 2843 } 2844 2845 /** 2846 * Returns the AttributeSet associated with the Element, this will 2847 * return null if the ElementInfo can't be validated. 2848 */ 2849 protected AttributeSet getAttributes() { 2850 if (validateIfNecessary()) { 2851 return getElement().getAttributes(); 2852 } 2853 return null; 2854 } 2855 2856 /** 2857 * Returns the AttributeSet associated with the View that is 2858 * representing this Element, this will 2859 * return null if the ElementInfo can't be validated. 2860 */ 2861 protected AttributeSet getViewAttributes() { 2862 if (validateIfNecessary()) { 2863 View view = getView(); 2864 2865 if (view != null) { 2866 return view.getElement().getAttributes(); 2867 } 2868 return getElement().getAttributes(); 2869 } 2870 return null; 2871 } 2872 2873 /** 2874 * Convenience method for getting an integer attribute from the passed 2875 * in AttributeSet. 2876 */ 2877 protected int getIntAttr(AttributeSet attrs, Object key, int deflt) { 2878 if (attrs != null && attrs.isDefined(key)) { 2879 int i; 2880 String val = (String)attrs.getAttribute(key); 2881 if (val == null) { 2882 i = deflt; 2883 } 2884 else { 2885 try { 2886 i = Math.max(0, Integer.parseInt(val)); 2887 } catch (NumberFormatException x) { 2888 i = deflt; 2889 } 2890 } 2891 return i; 2892 } 2893 return deflt; 2894 } 2895 2896 /** 2897 * Validates the ElementInfo if necessary. Some ElementInfos may 2898 * never be valid again. You should check <code>isValid</code> before 2899 * using one. This will reload the children and invoke 2900 * <code>validate</code> if the ElementInfo is invalid and can become 2901 * valid again. This will return true if the receiver is valid. 2902 */ 2903 protected boolean validateIfNecessary() { 2904 if (!isValid() && canBeValid) { 2905 children = null; 2906 Object lock = lock(); 2907 2908 try { 2909 validate(); 2910 } finally { 2911 unlock(lock); 2912 } 2913 } 2914 return isValid(); 2915 } 2916 2917 /** 2918 * Invalidates the ElementInfo. Subclasses should override this 2919 * if they need to reset state once invalid. 2920 */ 2921 protected void invalidate(boolean first) { 2922 if (!isValid()) { 2923 if (canBeValid && !first) { 2924 canBeValid = false; 2925 } 2926 return; 2927 } 2928 isValid = false; 2929 canBeValid = first; 2930 if (children != null) { 2931 for (ElementInfo child : children) { 2932 child.invalidate(false); 2933 } 2934 children = null; 2935 } 2936 } 2937 2938 private View getView(View parent, Element e, int start) { 2939 if (parent.getElement() == e) { 2940 return parent; 2941 } 2942 int index = parent.getViewIndex(start, Position.Bias.Forward); 2943 2944 if (index != -1 && index < parent.getViewCount()) { 2945 return getView(parent.getView(index), e, start); 2946 } 2947 return null; 2948 } 2949 2950 private int getClosestInfoIndex(int index) { 2951 for (int counter = 0; counter < getChildCount(); counter++) { 2952 ElementInfo info = getChild(counter); 2953 2954 if (index < info.getElement().getEndOffset() || 2955 index == info.getElement().getStartOffset()) { 2956 return counter; 2957 } 2958 } 2959 return -1; 2960 } 2961 2962 private void update(DocumentEvent e) { 2963 if (!isValid()) { 2964 return; 2965 } 2966 ElementInfo parent = getParent(); 2967 Element element = getElement(); 2968 2969 do { 2970 DocumentEvent.ElementChange ec = e.getChange(element); 2971 if (ec != null) { 2972 if (element == getElement()) { 2973 // One of our children changed. 2974 invalidate(true); 2975 } 2976 else if (parent != null) { 2977 parent.invalidate(parent == getRootInfo()); 2978 } 2979 return; 2980 } 2981 element = element.getParentElement(); 2982 } while (parent != null && element != null && 2983 element != parent.getElement()); 2984 2985 if (getChildCount() > 0) { 2986 Element elem = getElement(); 2987 int pos = e.getOffset(); 2988 int index0 = getClosestInfoIndex(pos); 2989 if (index0 == -1 && 2990 e.getType() == DocumentEvent.EventType.REMOVE && 2991 pos >= elem.getEndOffset()) { 2992 // Event beyond our offsets. We may have represented this, 2993 // that is the remove may have removed one of our child 2994 // Elements that represented this, so, we should foward 2995 // to last element. 2996 index0 = getChildCount() - 1; 2997 } 2998 ElementInfo info = (index0 >= 0) ? getChild(index0) : null; 2999 if (info != null && 3000 (info.getElement().getStartOffset() == pos) && (pos > 0)) { 3001 // If at a boundary, forward the event to the previous 3002 // ElementInfo too. 3003 index0 = Math.max(index0 - 1, 0); 3004 } 3005 int index1; 3006 if (e.getType() != DocumentEvent.EventType.REMOVE) { 3007 index1 = getClosestInfoIndex(pos + e.getLength()); 3008 if (index1 < 0) { 3009 index1 = getChildCount() - 1; 3010 } 3011 } 3012 else { 3013 index1 = index0; 3014 // A remove may result in empty elements. 3015 while ((index1 + 1) < getChildCount() && 3016 getChild(index1 + 1).getElement().getEndOffset() == 3017 getChild(index1 + 1).getElement().getStartOffset()){ 3018 index1++; 3019 } 3020 } 3021 index0 = Math.max(index0, 0); 3022 // The check for isValid is here as in the process of 3023 // forwarding update our child may invalidate us. 3024 for (int i = index0; i <= index1 && isValid(); i++) { 3025 getChild(i).update(e); 3026 } 3027 } 3028 } 3029 } 3030 3031 /** 3032 * DocumentListener installed on the current Document. Will invoke 3033 * <code>update</code> on the <code>RootInfo</code> in response to 3034 * any event. 3035 */ 3036 private class DocumentHandler implements DocumentListener { 3037 public void insertUpdate(DocumentEvent e) { 3038 getRootInfo().update(e); 3039 } 3040 public void removeUpdate(DocumentEvent e) { 3041 getRootInfo().update(e); 3042 } 3043 public void changedUpdate(DocumentEvent e) { 3044 getRootInfo().update(e); 3045 } 3046 } 3047 3048 /* 3049 * PropertyChangeListener installed on the editor. 3050 */ 3051 private class PropertyChangeHandler implements PropertyChangeListener { 3052 public void propertyChange(PropertyChangeEvent evt) { 3053 if (evt.getPropertyName().equals("document")) { 3054 // handle the document change 3055 setDocument(editor.getDocument()); 3056 } 3057 } 3058 } 3059 }