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