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