View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, 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  package javax.swing.text.html;
26  
27  import sun.awt.AppContext;
28  
29  import java.lang.reflect.Method;
30  import java.awt.*;
31  import java.awt.event.*;
32  import java.io.*;
33  import java.net.MalformedURLException;
34  import java.net.URL;
35  import javax.swing.text.*;
36  import javax.swing.*;
37  import javax.swing.border.*;
38  import javax.swing.event.*;
39  import javax.swing.plaf.TextUI;
40  import java.util.*;
41  import javax.accessibility.*;
42  import java.lang.ref.*;
43  
44  /**
45   * The Swing JEditorPane text component supports different kinds
46   * of content via a plug-in mechanism called an EditorKit.  Because
47   * HTML is a very popular format of content, some support is provided
48   * by default.  The default support is provided by this class, which
49   * supports HTML version 3.2 (with some extensions), and is migrating
50   * toward version 4.0.
51   * The <applet> tag is not supported, but some support is provided
52   * for the <object> tag.
53   * <p>
54   * There are several goals of the HTML EditorKit provided, that have
55   * an effect upon the way that HTML is modeled.  These
56   * have influenced its design in a substantial way.
57   * <dl>
58   * <dt>
59   * Support editing
60   * <dd>
61   * It might seem fairly obvious that a plug-in for JEditorPane
62   * should provide editing support, but that fact has several
63   * design considerations.  There are a substantial number of HTML
64   * documents that don't properly conform to an HTML specification.
65   * These must be normalized somewhat into a correct form if one
66   * is to edit them.  Additionally, users don't like to be presented
67   * with an excessive amount of structure editing, so using traditional
68   * text editing gestures is preferred over using the HTML structure
69   * exactly as defined in the HTML document.
70   * <p>
71   * The modeling of HTML is provided by the class <code>HTMLDocument</code>.
72   * Its documentation describes the details of how the HTML is modeled.
73   * The editing support leverages heavily off of the text package.
74   *
75   * <dt>
76   * Extendable/Scalable
77   * <dd>
78   * To maximize the usefulness of this kit, a great deal of effort
79   * has gone into making it extendable.  These are some of the
80   * features.
81   * <ol>
82   *   <li>
83   *   The parser is replaceable.  The default parser is the Hot Java
84   *   parser which is DTD based.  A different DTD can be used, or an
85   *   entirely different parser can be used.  To change the parser,
86   *   reimplement the getParser method.  The default parser is
87   *   dynamically loaded when first asked for, so the class files
88   *   will never be loaded if an alternative parser is used.  The
89   *   default parser is in a separate package called parser below
90   *   this package.
91   *   <li>
92   *   The parser drives the ParserCallback, which is provided by
93   *   HTMLDocument.  To change the callback, subclass HTMLDocument
94   *   and reimplement the createDefaultDocument method to return
95   *   document that produces a different reader.  The reader controls
96   *   how the document is structured.  Although the Document provides
97   *   HTML support by default, there is nothing preventing support of
98   *   non-HTML tags that result in alternative element structures.
99   *   <li>
100  *   The default view of the models are provided as a hierarchy of
101  *   View implementations, so one can easily customize how a particular
102  *   element is displayed or add capabilities for new kinds of elements
103  *   by providing new View implementations.  The default set of views
104  *   are provided by the <code>HTMLFactory</code> class.  This can
105  *   be easily changed by subclassing or replacing the HTMLFactory
106  *   and reimplementing the getViewFactory method to return the alternative
107  *   factory.
108  *   <li>
109  *   The View implementations work primarily off of CSS attributes,
110  *   which are kept in the views.  This makes it possible to have
111  *   multiple views mapped over the same model that appear substantially
112  *   different.  This can be especially useful for printing.  For
113  *   most HTML attributes, the HTML attributes are converted to CSS
114  *   attributes for display.  This helps make the View implementations
115  *   more general purpose
116  * </ol>
117  *
118  * <dt>
119  * Asynchronous Loading
120  * <dd>
121  * Larger documents involve a lot of parsing and take some time
122  * to load.  By default, this kit produces documents that will be
123  * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
124  * This is controlled by a property on the document.  The method
125  * {@link #createDefaultDocument createDefaultDocument} can
126  * be overriden to change this.  The batching of work is done
127  * by the <code>HTMLDocument.HTMLReader</code> class.  The actual
128  * work is done by the <code>DefaultStyledDocument</code> and
129  * <code>AbstractDocument</code> classes in the text package.
130  *
131  * <dt>
132  * Customization from current LAF
133  * <dd>
134  * HTML provides a well known set of features without exactly
135  * specifying the display characteristics.  Swing has a theme
136  * mechanism for its look-and-feel implementations.  It is desirable
137  * for the look-and-feel to feed display characteristics into the
138  * HTML views.  An user with poor vision for example would want
139  * high contrast and larger than typical fonts.
140  * <p>
141  * The support for this is provided by the <code>StyleSheet</code>
142  * class.  The presentation of the HTML can be heavily influenced
143  * by the setting of the StyleSheet property on the EditorKit.
144  *
145  * <dt>
146  * Not lossy
147  * <dd>
148  * An EditorKit has the ability to be read and save documents.
149  * It is generally the most pleasing to users if there is no loss
150  * of data between the two operation.  The policy of the HTMLEditorKit
151  * will be to store things not recognized or not necessarily visible
152  * so they can be subsequently written out.  The model of the HTML document
153  * should therefore contain all information discovered while reading the
154  * document.  This is constrained in some ways by the need to support
155  * editing (i.e. incorrect documents sometimes must be normalized).
156  * The guiding principle is that information shouldn't be lost, but
157  * some might be synthesized to produce a more correct model or it might
158  * be rearranged.
159  * </dl>
160  *
161  * @author  Timothy Prinzing
162  */
163 public class HTMLEditorKit extends StyledEditorKit implements Accessible {
164 
165     private JEditorPane theEditor;
166 
167     /**
168      * Constructs an HTMLEditorKit, creates a StyleContext,
169      * and loads the style sheet.
170      */
171     public HTMLEditorKit() {
172 
173     }
174 
175     /**
176      * Get the MIME type of the data that this
177      * kit represents support for.  This kit supports
178      * the type <code>text/html</code>.
179      *
180      * @return the type
181      */
182     public String getContentType() {
183         return "text/html";
184     }
185 
186     /**
187      * Fetch a factory that is suitable for producing
188      * views of any models that are produced by this
189      * kit.
190      *
191      * @return the factory
192      */
193     public ViewFactory getViewFactory() {
194         return defaultFactory;
195     }
196 
197     /**
198      * Create an uninitialized text storage model
199      * that is appropriate for this type of editor.
200      *
201      * @return the model
202      */
203     public Document createDefaultDocument() {
204         StyleSheet styles = getStyleSheet();
205         StyleSheet ss = new StyleSheet();
206 
207         ss.addStyleSheet(styles);
208 
209         HTMLDocument doc = new HTMLDocument(ss);
210         doc.setParser(getParser());
211         doc.setAsynchronousLoadPriority(4);
212         doc.setTokenThreshold(100);
213         return doc;
214     }
215 
216     /**
217      * Try to get an HTML parser from the document.  If no parser is set for
218      * the document, return the editor kit's default parser.  It is an error
219      * if no parser could be obtained from the editor kit.
220      */
221     private Parser ensureParser(HTMLDocument doc) throws IOException {
222         Parser p = doc.getParser();
223         if (p == null) {
224             p = getParser();
225         }
226         if (p == null) {
227             throw new IOException("Can't load parser");
228         }
229         return p;
230     }
231 
232     /**
233      * Inserts content from the given stream. If <code>doc</code> is
234      * an instance of HTMLDocument, this will read
235      * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
236      * the body Element, if you do not insert into the body an exception will
237      * be thrown. When inserting into a non-empty document all tags outside
238      * of the body (head, title) will be dropped.
239      *
240      * @param in  the stream to read from
241      * @param doc the destination for the insertion
242      * @param pos the location in the document to place the
243      *   content
244      * @exception IOException on any I/O error
245      * @exception BadLocationException if pos represents an invalid
246      *   location within the document
247      * @exception RuntimeException (will eventually be a BadLocationException)
248      *            if pos is invalid
249      */
250     public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
251 
252         if (doc instanceof HTMLDocument) {
253             HTMLDocument hdoc = (HTMLDocument) doc;
254             if (pos > doc.getLength()) {
255                 throw new BadLocationException("Invalid location", pos);
256             }
257 
258             Parser p = ensureParser(hdoc);
259             ParserCallback receiver = hdoc.getReader(pos);
260             Boolean ignoreCharset = (Boolean)doc.getProperty("IgnoreCharsetDirective");
261             p.parse(in, receiver, (ignoreCharset == null) ? false : ignoreCharset.booleanValue());
262             receiver.flush();
263         } else {
264             super.read(in, doc, pos);
265         }
266     }
267 
268     /**
269      * Inserts HTML into an existing document.
270      *
271      * @param doc       the document to insert into
272      * @param offset    the offset to insert HTML at
273      * @param popDepth  the number of ElementSpec.EndTagTypes to generate before
274      *        inserting
275      * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
276      *        of ElementSpec.JoinNextDirection that should be generated
277      *        before inserting, but after the end tags have been generated
278      * @param insertTag the first tag to start inserting into document
279      * @exception RuntimeException (will eventually be a BadLocationException)
280      *            if pos is invalid
281      */
282     public void insertHTML(HTMLDocument doc, int offset, String html,
283                            int popDepth, int pushDepth,
284                            HTML.Tag insertTag) throws
285                        BadLocationException, IOException {
286         if (offset > doc.getLength()) {
287             throw new BadLocationException("Invalid location", offset);
288         }
289 
290         Parser p = ensureParser(doc);
291         ParserCallback receiver = doc.getReader(offset, popDepth, pushDepth,
292                                                 insertTag);
293         Boolean ignoreCharset = (Boolean)doc.getProperty
294                                 ("IgnoreCharsetDirective");
295         p.parse(new StringReader(html), receiver, (ignoreCharset == null) ?
296                 false : ignoreCharset.booleanValue());
297         receiver.flush();
298     }
299 
300     /**
301      * Write content from a document to the given stream
302      * in a format appropriate for this kind of content handler.
303      *
304      * @param out  the stream to write to
305      * @param doc  the source for the write
306      * @param pos  the location in the document to fetch the
307      *   content
308      * @param len  the amount to write out
309      * @exception IOException on any I/O error
310      * @exception BadLocationException if pos represents an invalid
311      *   location within the document
312      */
313     public void write(Writer out, Document doc, int pos, int len)
314         throws IOException, BadLocationException {
315 
316         if (doc instanceof HTMLDocument) {
317             HTMLWriter w = new HTMLWriter(out, (HTMLDocument)doc, pos, len);
318             w.write();
319         } else if (doc instanceof StyledDocument) {
320             MinimalHTMLWriter w = new MinimalHTMLWriter(out, (StyledDocument)doc, pos, len);
321             w.write();
322         } else {
323             super.write(out, doc, pos, len);
324         }
325     }
326 
327     /**
328      * Called when the kit is being installed into the
329      * a JEditorPane.
330      *
331      * @param c the JEditorPane
332      */
333     public void install(JEditorPane c) {
334         c.addMouseListener(linkHandler);
335         c.addMouseMotionListener(linkHandler);
336         c.addCaretListener(nextLinkAction);
337         super.install(c);
338         theEditor = c;
339     }
340 
341     /**
342      * Called when the kit is being removed from the
343      * JEditorPane.  This is used to unregister any
344      * listeners that were attached.
345      *
346      * @param c the JEditorPane
347      */
348     public void deinstall(JEditorPane c) {
349         c.removeMouseListener(linkHandler);
350         c.removeMouseMotionListener(linkHandler);
351         c.removeCaretListener(nextLinkAction);
352         super.deinstall(c);
353         theEditor = null;
354     }
355 
356     /**
357      * Default Cascading Style Sheet file that sets
358      * up the tag views.
359      */
360     public static final String DEFAULT_CSS = "default.css";
361 
362     /**
363      * Set the set of styles to be used to render the various
364      * HTML elements.  These styles are specified in terms of
365      * CSS specifications.  Each document produced by the kit
366      * will have a copy of the sheet which it can add the
367      * document specific styles to.  By default, the StyleSheet
368      * specified is shared by all HTMLEditorKit instances.
369      * This should be reimplemented to provide a finer granularity
370      * if desired.
371      */
372     public void setStyleSheet(StyleSheet s) {
373         if (s == null) {
374             AppContext.getAppContext().remove(DEFAULT_STYLES_KEY);
375         } else {
376             AppContext.getAppContext().put(DEFAULT_STYLES_KEY, s);
377         }
378     }
379 
380     /**
381      * Get the set of styles currently being used to render the
382      * HTML elements.  By default the resource specified by
383      * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
384      * instances.
385      */
386     public StyleSheet getStyleSheet() {
387         AppContext appContext = AppContext.getAppContext();
388         StyleSheet defaultStyles = (StyleSheet) appContext.get(DEFAULT_STYLES_KEY);
389 
390         if (defaultStyles == null) {
391             defaultStyles = new StyleSheet();
392             appContext.put(DEFAULT_STYLES_KEY, defaultStyles);
393             try {
394                 InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
395                 Reader r = new BufferedReader(
396                         new InputStreamReader(is, "ISO-8859-1"));
397                 defaultStyles.loadRules(r, null);
398                 r.close();
399             } catch (Throwable e) {
400                 // on error we simply have no styles... the html
401                 // will look mighty wrong but still function.
402             }
403         }
404         return defaultStyles;
405     }
406 
407     /**
408      * Fetch a resource relative to the HTMLEditorKit classfile.
409      * If this is called on 1.2 the loading will occur under the
410      * protection of a doPrivileged call to allow the HTMLEditorKit
411      * to function when used in an applet.
412      *
413      * @param name the name of the resource, relative to the
414      *  HTMLEditorKit class
415      * @return a stream representing the resource
416      */
417     static InputStream getResourceAsStream(String name) {
418         try {
419             return ResourceLoader.getResourceAsStream(name);
420         } catch (Throwable e) {
421             // If the class doesn't exist or we have some other
422             // problem we just try to call getResourceAsStream directly.
423             return HTMLEditorKit.class.getResourceAsStream(name);
424         }
425     }
426 
427     /**
428      * Fetches the command list for the editor.  This is
429      * the list of commands supported by the superclass
430      * augmented by the collection of commands defined
431      * locally for style operations.
432      *
433      * @return the command list
434      */
435     public Action[] getActions() {
436         return TextAction.augmentList(super.getActions(), this.defaultActions);
437     }
438 
439     /**
440      * Copies the key/values in <code>element</code>s AttributeSet into
441      * <code>set</code>. This does not copy component, icon, or element
442      * names attributes. Subclasses may wish to refine what is and what
443      * isn't copied here. But be sure to first remove all the attributes that
444      * are in <code>set</code>.<p>
445      * This is called anytime the caret moves over a different location.
446      *
447      */
448     protected void createInputAttributes(Element element,
449                                          MutableAttributeSet set) {
450         set.removeAttributes(set);
451         set.addAttributes(element.getAttributes());
452         set.removeAttribute(StyleConstants.ComposedTextAttribute);
453 
454         Object o = set.getAttribute(StyleConstants.NameAttribute);
455         if (o instanceof HTML.Tag) {
456             HTML.Tag tag = (HTML.Tag)o;
457             // PENDING: we need a better way to express what shouldn't be
458             // copied when editing...
459             if(tag == HTML.Tag.IMG) {
460                 // Remove the related image attributes, src, width, height
461                 set.removeAttribute(HTML.Attribute.SRC);
462                 set.removeAttribute(HTML.Attribute.HEIGHT);
463                 set.removeAttribute(HTML.Attribute.WIDTH);
464                 set.addAttribute(StyleConstants.NameAttribute,
465                                  HTML.Tag.CONTENT);
466             }
467             else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
468                 // Don't copy HRs or BRs either.
469                 set.addAttribute(StyleConstants.NameAttribute,
470                                  HTML.Tag.CONTENT);
471             }
472             else if (tag == HTML.Tag.COMMENT) {
473                 // Don't copy COMMENTs either
474                 set.addAttribute(StyleConstants.NameAttribute,
475                                  HTML.Tag.CONTENT);
476                 set.removeAttribute(HTML.Attribute.COMMENT);
477             }
478             else if (tag == HTML.Tag.INPUT) {
479                 // or INPUT either
480                 set.addAttribute(StyleConstants.NameAttribute,
481                                  HTML.Tag.CONTENT);
482                 set.removeAttribute(HTML.Tag.INPUT);
483             }
484             else if (tag instanceof HTML.UnknownTag) {
485                 // Don't copy unknowns either:(
486                 set.addAttribute(StyleConstants.NameAttribute,
487                                  HTML.Tag.CONTENT);
488                 set.removeAttribute(HTML.Attribute.ENDTAG);
489             }
490         }
491     }
492 
493     /**
494      * Gets the input attributes used for the styled
495      * editing actions.
496      *
497      * @return the attribute set
498      */
499     public MutableAttributeSet getInputAttributes() {
500         if (input == null) {
501             input = getStyleSheet().addStyle(null, null);
502         }
503         return input;
504     }
505 
506     /**
507      * Sets the default cursor.
508      *
509      * @since 1.3
510      */
511     public void setDefaultCursor(Cursor cursor) {
512         defaultCursor = cursor;
513     }
514 
515     /**
516      * Returns the default cursor.
517      *
518      * @since 1.3
519      */
520     public Cursor getDefaultCursor() {
521         return defaultCursor;
522     }
523 
524     /**
525      * Sets the cursor to use over links.
526      *
527      * @since 1.3
528      */
529     public void setLinkCursor(Cursor cursor) {
530         linkCursor = cursor;
531     }
532 
533     /**
534      * Returns the cursor to use over hyper links.
535      * @since 1.3
536      */
537     public Cursor getLinkCursor() {
538         return linkCursor;
539     }
540 
541     /**
542      * Indicates whether an html form submission is processed automatically
543      * or only <code>FormSubmitEvent</code> is fired.
544      *
545      * @return true  if html form submission is processed automatically,
546      *         false otherwise.
547      *
548      * @see #setAutoFormSubmission
549      * @since 1.5
550      */
551     public boolean isAutoFormSubmission() {
552         return isAutoFormSubmission;
553     }
554 
555     /**
556      * Specifies if an html form submission is processed
557      * automatically or only <code>FormSubmitEvent</code> is fired.
558      * By default it is set to true.
559      *
560      * @see #isAutoFormSubmission()
561      * @see FormSubmitEvent
562      * @since 1.5
563      */
564     public void setAutoFormSubmission(boolean isAuto) {
565         isAutoFormSubmission = isAuto;
566     }
567 
568     /**
569      * Creates a copy of the editor kit.
570      *
571      * @return the copy
572      */
573     public Object clone() {
574         HTMLEditorKit o = (HTMLEditorKit)super.clone();
575         if (o != null) {
576             o.input = null;
577             o.linkHandler = new LinkController();
578         }
579         return o;
580     }
581 
582     /**
583      * Fetch the parser to use for reading HTML streams.
584      * This can be reimplemented to provide a different
585      * parser.  The default implementation is loaded dynamically
586      * to avoid the overhead of loading the default parser if
587      * it's not used.  The default parser is the HotJava parser
588      * using an HTML 3.2 DTD.
589      */
590     protected Parser getParser() {
591         if (defaultParser == null) {
592             try {
593                 Class c = Class.forName("javax.swing.text.html.parser.ParserDelegator");
594                 defaultParser = (Parser) c.newInstance();
595             } catch (Throwable e) {
596             }
597         }
598         return defaultParser;
599     }
600 
601     // ----- Accessibility support -----
602     private AccessibleContext accessibleContext;
603 
604     /**
605      * returns the AccessibleContext associated with this editor kit
606      *
607      * @return the AccessibleContext associated with this editor kit
608      * @since 1.4
609      */
610     public AccessibleContext getAccessibleContext() {
611         if (theEditor == null) {
612             return null;
613         }
614         if (accessibleContext == null) {
615             AccessibleHTML a = new AccessibleHTML(theEditor);
616             accessibleContext = a.getAccessibleContext();
617         }
618         return accessibleContext;
619     }
620 
621     // --- variables ------------------------------------------
622 
623     private static final Cursor MoveCursor = Cursor.getPredefinedCursor
624                                     (Cursor.HAND_CURSOR);
625     private static final Cursor DefaultCursor = Cursor.getPredefinedCursor
626                                     (Cursor.DEFAULT_CURSOR);
627 
628     /** Shared factory for creating HTML Views. */
629     private static final ViewFactory defaultFactory = new HTMLFactory();
630 
631     MutableAttributeSet input;
632     private static final Object DEFAULT_STYLES_KEY = new Object();
633     private LinkController linkHandler = new LinkController();
634     private static Parser defaultParser = null;
635     private Cursor defaultCursor = DefaultCursor;
636     private Cursor linkCursor = MoveCursor;
637     private boolean isAutoFormSubmission = true;
638 
639     /**
640      * Class to watch the associated component and fire
641      * hyperlink events on it when appropriate.
642      */
643     public static class LinkController extends MouseAdapter implements MouseMotionListener, Serializable {
644         private Element curElem = null;
645         /**
646          * If true, the current element (curElem) represents an image.
647          */
648         private boolean curElemImage = false;
649         private String href = null;
650         /** This is used by viewToModel to avoid allocing a new array each
651          * time. */
652         private transient Position.Bias[] bias = new Position.Bias[1];
653         /**
654          * Current offset.
655          */
656         private int curOffset;
657 
658         /**
659          * Called for a mouse click event.
660          * If the component is read-only (ie a browser) then
661          * the clicked event is used to drive an attempt to
662          * follow the reference specified by a link.
663          *
664          * @param e the mouse event
665          * @see MouseListener#mouseClicked
666          */
667         public void mouseClicked(MouseEvent e) {
668             JEditorPane editor = (JEditorPane) e.getSource();
669 
670             if (! editor.isEditable() && editor.isEnabled() &&
671                     SwingUtilities.isLeftMouseButton(e)) {
672                 Point pt = new Point(e.getX(), e.getY());
673                 int pos = editor.viewToModel(pt);
674                 if (pos >= 0) {
675                     activateLink(pos, editor, e);
676                 }
677             }
678         }
679 
680         // ignore the drags
681         public void mouseDragged(MouseEvent e) {
682         }
683 
684         // track the moving of the mouse.
685         public void mouseMoved(MouseEvent e) {
686             JEditorPane editor = (JEditorPane) e.getSource();
687             if (!editor.isEnabled()) {
688                 return;
689             }
690 
691             HTMLEditorKit kit = (HTMLEditorKit)editor.getEditorKit();
692             boolean adjustCursor = true;
693             Cursor newCursor = kit.getDefaultCursor();
694             if (!editor.isEditable()) {
695                 Point pt = new Point(e.getX(), e.getY());
696                 int pos = editor.getUI().viewToModel(editor, pt, bias);
697                 if (bias[0] == Position.Bias.Backward && pos > 0) {
698                     pos--;
699                 }
700                 if (pos >= 0 &&(editor.getDocument() instanceof HTMLDocument)){
701                     HTMLDocument hdoc = (HTMLDocument)editor.getDocument();
702                     Element elem = hdoc.getCharacterElement(pos);
703                     if (!doesElementContainLocation(editor, elem, pos,
704                                                     e.getX(), e.getY())) {
705                         elem = null;
706                     }
707                     if (curElem != elem || curElemImage) {
708                         Element lastElem = curElem;
709                         curElem = elem;
710                         String href = null;
711                         curElemImage = false;
712                         if (elem != null) {
713                             AttributeSet a = elem.getAttributes();
714                             AttributeSet anchor = (AttributeSet)a.
715                                                    getAttribute(HTML.Tag.A);
716                             if (anchor == null) {
717                                 curElemImage = (a.getAttribute(StyleConstants.
718                                             NameAttribute) == HTML.Tag.IMG);
719                                 if (curElemImage) {
720                                     href = getMapHREF(editor, hdoc, elem, a,
721                                                       pos, e.getX(), e.getY());
722                                 }
723                             }
724                             else {
725                                 href = (String)anchor.getAttribute
726                                     (HTML.Attribute.HREF);
727                             }
728                         }
729 
730                         if (href != this.href) {
731                             // reference changed, fire event(s)
732                             fireEvents(editor, hdoc, href, lastElem, e);
733                             this.href = href;
734                             if (href != null) {
735                                 newCursor = kit.getLinkCursor();
736                             }
737                         }
738                         else {
739                             adjustCursor = false;
740                         }
741                     }
742                     else {
743                         adjustCursor = false;
744                     }
745                     curOffset = pos;
746                 }
747             }
748             if (adjustCursor && editor.getCursor() != newCursor) {
749                 editor.setCursor(newCursor);
750             }
751         }
752 
753         /**
754          * Returns a string anchor if the passed in element has a
755          * USEMAP that contains the passed in location.
756          */
757         private String getMapHREF(JEditorPane html, HTMLDocument hdoc,
758                                   Element elem, AttributeSet attr, int offset,
759                                   int x, int y) {
760             Object useMap = attr.getAttribute(HTML.Attribute.USEMAP);
761             if (useMap != null && (useMap instanceof String)) {
762                 Map m = hdoc.getMap((String)useMap);
763                 if (m != null && offset < hdoc.getLength()) {
764                     Rectangle bounds;
765                     TextUI ui = html.getUI();
766                     try {
767                         Shape lBounds = ui.modelToView(html, offset,
768                                                    Position.Bias.Forward);
769                         Shape rBounds = ui.modelToView(html, offset + 1,
770                                                    Position.Bias.Backward);
771                         bounds = lBounds.getBounds();
772                         bounds.add((rBounds instanceof Rectangle) ?
773                                     (Rectangle)rBounds : rBounds.getBounds());
774                     } catch (BadLocationException ble) {
775                         bounds = null;
776                     }
777                     if (bounds != null) {
778                         AttributeSet area = m.getArea(x - bounds.x,
779                                                       y - bounds.y,
780                                                       bounds.width,
781                                                       bounds.height);
782                         if (area != null) {
783                             return (String)area.getAttribute(HTML.Attribute.
784                                                              HREF);
785                         }
786                     }
787                 }
788             }
789             return null;
790         }
791 
792         /**
793          * Returns true if the View representing <code>e</code> contains
794          * the location <code>x</code>, <code>y</code>. <code>offset</code>
795          * gives the offset into the Document to check for.
796          */
797         private boolean doesElementContainLocation(JEditorPane editor,
798                                                    Element e, int offset,
799                                                    int x, int y) {
800             if (e != null && offset > 0 && e.getStartOffset() == offset) {
801                 try {
802                     TextUI ui = editor.getUI();
803                     Shape s1 = ui.modelToView(editor, offset,
804                                               Position.Bias.Forward);
805                     if (s1 == null) {
806                         return false;
807                     }
808                     Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle)s1 :
809                                     s1.getBounds();
810                     Shape s2 = ui.modelToView(editor, e.getEndOffset(),
811                                               Position.Bias.Backward);
812                     if (s2 != null) {
813                         Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle)s2 :
814                                     s2.getBounds();
815                         r1.add(r2);
816                     }
817                     return r1.contains(x, y);
818                 } catch (BadLocationException ble) {
819                 }
820             }
821             return true;
822         }
823 
824         /**
825          * Calls linkActivated on the associated JEditorPane
826          * if the given position represents a link.<p>This is implemented
827          * to forward to the method with the same name, but with the following
828          * args both == -1.
829          *
830          * @param pos the position
831          * @param editor the editor pane
832          */
833         protected void activateLink(int pos, JEditorPane editor) {
834             activateLink(pos, editor, null);
835         }
836 
837         /**
838          * Calls linkActivated on the associated JEditorPane
839          * if the given position represents a link. If this was the result
840          * of a mouse click, <code>x</code> and
841          * <code>y</code> will give the location of the mouse, otherwise
842          * they will be {@literal <} 0.
843          *
844          * @param pos the position
845          * @param html the editor pane
846          */
847         void activateLink(int pos, JEditorPane html, MouseEvent mouseEvent) {
848             Document doc = html.getDocument();
849             if (doc instanceof HTMLDocument) {
850                 HTMLDocument hdoc = (HTMLDocument) doc;
851                 Element e = hdoc.getCharacterElement(pos);
852                 AttributeSet a = e.getAttributes();
853                 AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A);
854                 HyperlinkEvent linkEvent = null;
855                 String description;
856                 int x = -1;
857                 int y = -1;
858 
859                 if (mouseEvent != null) {
860                     x = mouseEvent.getX();
861                     y = mouseEvent.getY();
862                 }
863 
864                 if (anchor == null) {
865                     href = getMapHREF(html, hdoc, e, a, pos, x, y);
866                 }
867                 else {
868                     href = (String)anchor.getAttribute(HTML.Attribute.HREF);
869                 }
870 
871                 if (href != null) {
872                     linkEvent = createHyperlinkEvent(html, hdoc, href, anchor,
873                                                      e, mouseEvent);
874                 }
875                 if (linkEvent != null) {
876                     html.fireHyperlinkUpdate(linkEvent);
877                 }
878             }
879         }
880 
881         /**
882          * Creates and returns a new instance of HyperlinkEvent. If
883          * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
884          * will be created.
885          */
886         HyperlinkEvent createHyperlinkEvent(JEditorPane html,
887                                             HTMLDocument hdoc, String href,
888                                             AttributeSet anchor,
889                                             Element element,
890                                             MouseEvent mouseEvent) {
891             URL u;
892             try {
893                 URL base = hdoc.getBase();
894                 u = new URL(base, href);
895                 // Following is a workaround for 1.2, in which
896                 // new URL("file://...", "#...") causes the filename to
897                 // be lost.
898                 if (href != null && "file".equals(u.getProtocol()) &&
899                     href.startsWith("#")) {
900                     String baseFile = base.getFile();
901                     String newFile = u.getFile();
902                     if (baseFile != null && newFile != null &&
903                         !newFile.startsWith(baseFile)) {
904                         u = new URL(base, baseFile + href);
905                     }
906                 }
907             } catch (MalformedURLException m) {
908                 u = null;
909             }
910             HyperlinkEvent linkEvent;
911 
912             if (!hdoc.isFrameDocument()) {
913                 linkEvent = new HyperlinkEvent(
914                         html, HyperlinkEvent.EventType.ACTIVATED, u, href,
915                         element, mouseEvent);
916             } else {
917                 String target = (anchor != null) ?
918                     (String)anchor.getAttribute(HTML.Attribute.TARGET) : null;
919                 if ((target == null) || (target.equals(""))) {
920                     target = hdoc.getBaseTarget();
921                 }
922                 if ((target == null) || (target.equals(""))) {
923                     target = "_self";
924                 }
925                     linkEvent = new HTMLFrameHyperlinkEvent(
926                         html, HyperlinkEvent.EventType.ACTIVATED, u, href,
927                         element, mouseEvent, target);
928             }
929             return linkEvent;
930         }
931 
932         void fireEvents(JEditorPane editor, HTMLDocument doc, String href,
933                         Element lastElem, MouseEvent mouseEvent) {
934             if (this.href != null) {
935                 // fire an exited event on the old link
936                 URL u;
937                 try {
938                     u = new URL(doc.getBase(), this.href);
939                 } catch (MalformedURLException m) {
940                     u = null;
941                 }
942                 HyperlinkEvent exit = new HyperlinkEvent(editor,
943                                  HyperlinkEvent.EventType.EXITED, u, this.href,
944                                  lastElem, mouseEvent);
945                 editor.fireHyperlinkUpdate(exit);
946             }
947             if (href != null) {
948                 // fire an entered event on the new link
949                 URL u;
950                 try {
951                     u = new URL(doc.getBase(), href);
952                 } catch (MalformedURLException m) {
953                     u = null;
954                 }
955                 HyperlinkEvent entered = new HyperlinkEvent(editor,
956                                             HyperlinkEvent.EventType.ENTERED,
957                                             u, href, curElem, mouseEvent);
958                 editor.fireHyperlinkUpdate(entered);
959             }
960         }
961     }
962 
963     /**
964      * Interface to be supported by the parser.  This enables
965      * providing a different parser while reusing some of the
966      * implementation provided by this editor kit.
967      */
968     public static abstract class Parser {
969         /**
970          * Parse the given stream and drive the given callback
971          * with the results of the parse.  This method should
972          * be implemented to be thread-safe.
973          */
974         public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
975 
976     }
977 
978     /**
979      * The result of parsing drives these callback methods.
980      * The open and close actions should be balanced.  The
981      * <code>flush</code> method will be the last method
982      * called, to give the receiver a chance to flush any
983      * pending data into the document.
984      * <p>Refer to DocumentParser, the default parser used, for further
985      * information on the contents of the AttributeSets, the positions, and
986      * other info.
987      *
988      * @see javax.swing.text.html.parser.DocumentParser
989      */
990     public static class ParserCallback {
991         /**
992          * This is passed as an attribute in the attributeset to indicate
993          * the element is implied eg, the string '&lt;&gt;foo&lt;\t&gt;'
994          * contains an implied html element and an implied body element.
995          *
996          * @since 1.3
997          */
998         public static final Object IMPLIED = "_implied_";
999 
1000 
1001         public void flush() throws BadLocationException {
1002         }
1003 
1004         public void handleText(char[] data, int pos) {
1005         }
1006 
1007         public void handleComment(char[] data, int pos) {
1008         }
1009 
1010         public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
1011         }
1012 
1013         public void handleEndTag(HTML.Tag t, int pos) {
1014         }
1015 
1016         public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
1017         }
1018 
1019         public void handleError(String errorMsg, int pos){
1020         }
1021 
1022         /**
1023          * This is invoked after the stream has been parsed, but before
1024          * <code>flush</code>. <code>eol</code> will be one of \n, \r
1025          * or \r\n, which ever is encountered the most in parsing the
1026          * stream.
1027          *
1028          * @since 1.3
1029          */
1030         public void handleEndOfLineString(String eol) {
1031         }
1032     }
1033 
1034     /**
1035      * A factory to build views for HTML.  The following
1036      * table describes what this factory will build by
1037      * default.
1038      *
1039      * <table summary="Describes the tag and view created by this factory by default">
1040      * <tr>
1041      * <th align=left>Tag<th align=left>View created
1042      * </tr><tr>
1043      * <td>HTML.Tag.CONTENT<td>InlineView
1044      * </tr><tr>
1045      * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView
1046      * </tr><tr>
1047      * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView
1048      * </tr><tr>
1049      * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView
1050      * </tr><tr>
1051      * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView
1052      * </tr><tr>
1053      * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView
1054      * </tr><tr>
1055      * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView
1056      * </tr><tr>
1057      * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView
1058      * </tr><tr>
1059      * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView
1060      * </tr><tr>
1061      * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView
1062      * </tr><tr>
1063      * <td>HTML.Tag.MENU<td>ListView
1064      * </tr><tr>
1065      * <td>HTML.Tag.DIR<td>ListView
1066      * </tr><tr>
1067      * <td>HTML.Tag.UL<td>ListView
1068      * </tr><tr>
1069      * <td>HTML.Tag.OL<td>ListView
1070      * </tr><tr>
1071      * <td>HTML.Tag.LI<td>BlockView
1072      * </tr><tr>
1073      * <td>HTML.Tag.DL<td>BlockView
1074      * </tr><tr>
1075      * <td>HTML.Tag.DD<td>BlockView
1076      * </tr><tr>
1077      * <td>HTML.Tag.BODY<td>BlockView
1078      * </tr><tr>
1079      * <td>HTML.Tag.HTML<td>BlockView
1080      * </tr><tr>
1081      * <td>HTML.Tag.CENTER<td>BlockView
1082      * </tr><tr>
1083      * <td>HTML.Tag.DIV<td>BlockView
1084      * </tr><tr>
1085      * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1086      * </tr><tr>
1087      * <td>HTML.Tag.PRE<td>BlockView
1088      * </tr><tr>
1089      * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1090      * </tr><tr>
1091      * <td>HTML.Tag.PRE<td>BlockView
1092      * </tr><tr>
1093      * <td>HTML.Tag.IMG<td>ImageView
1094      * </tr><tr>
1095      * <td>HTML.Tag.HR<td>HRuleView
1096      * </tr><tr>
1097      * <td>HTML.Tag.BR<td>BRView
1098      * </tr><tr>
1099      * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView
1100      * </tr><tr>
1101      * <td>HTML.Tag.INPUT<td>FormView
1102      * </tr><tr>
1103      * <td>HTML.Tag.SELECT<td>FormView
1104      * </tr><tr>
1105      * <td>HTML.Tag.TEXTAREA<td>FormView
1106      * </tr><tr>
1107      * <td>HTML.Tag.OBJECT<td>ObjectView
1108      * </tr><tr>
1109      * <td>HTML.Tag.FRAMESET<td>FrameSetView
1110      * </tr><tr>
1111      * <td>HTML.Tag.FRAME<td>FrameView
1112      * </tr>
1113      * </table>
1114      */
1115     public static class HTMLFactory implements ViewFactory {
1116 
1117         /**
1118          * Creates a view from an element.
1119          *
1120          * @param elem the element
1121          * @return the view
1122          */
1123         public View create(Element elem) {
1124             AttributeSet attrs = elem.getAttributes();
1125             Object elementName =
1126                 attrs.getAttribute(AbstractDocument.ElementNameAttribute);
1127             Object o = (elementName != null) ?
1128                 null : attrs.getAttribute(StyleConstants.NameAttribute);
1129             if (o instanceof HTML.Tag) {
1130                 HTML.Tag kind = (HTML.Tag) o;
1131                 if (kind == HTML.Tag.CONTENT) {
1132                     return new InlineView(elem);
1133                 } else if (kind == HTML.Tag.IMPLIED) {
1134                     String ws = (String) elem.getAttributes().getAttribute(
1135                         CSS.Attribute.WHITE_SPACE);
1136                     if ((ws != null) && ws.equals("pre")) {
1137                         return new LineView(elem);
1138                     }
1139                     return new javax.swing.text.html.ParagraphView(elem);
1140                 } else if ((kind == HTML.Tag.P) ||
1141                            (kind == HTML.Tag.H1) ||
1142                            (kind == HTML.Tag.H2) ||
1143                            (kind == HTML.Tag.H3) ||
1144                            (kind == HTML.Tag.H4) ||
1145                            (kind == HTML.Tag.H5) ||
1146                            (kind == HTML.Tag.H6) ||
1147                            (kind == HTML.Tag.DT)) {
1148                     // paragraph
1149                     return new javax.swing.text.html.ParagraphView(elem);
1150                 } else if ((kind == HTML.Tag.MENU) ||
1151                            (kind == HTML.Tag.DIR) ||
1152                            (kind == HTML.Tag.UL)   ||
1153                            (kind == HTML.Tag.OL)) {
1154                     return new ListView(elem);
1155                 } else if (kind == HTML.Tag.BODY) {
1156                     return new BodyBlockView(elem);
1157                 } else if (kind == HTML.Tag.HTML) {
1158                     return new BlockView(elem, View.Y_AXIS);
1159                 } else if ((kind == HTML.Tag.LI) ||
1160                            (kind == HTML.Tag.CENTER) ||
1161                            (kind == HTML.Tag.DL) ||
1162                            (kind == HTML.Tag.DD) ||
1163                            (kind == HTML.Tag.DIV) ||
1164                            (kind == HTML.Tag.BLOCKQUOTE) ||
1165                            (kind == HTML.Tag.PRE) ||
1166                            (kind == HTML.Tag.FORM)) {
1167                     // vertical box
1168                     return new BlockView(elem, View.Y_AXIS);
1169                 } else if (kind == HTML.Tag.NOFRAMES) {
1170                     return new NoFramesView(elem, View.Y_AXIS);
1171                 } else if (kind==HTML.Tag.IMG) {
1172                     return new ImageView(elem);
1173                 } else if (kind == HTML.Tag.ISINDEX) {
1174                     return new IsindexView(elem);
1175                 } else if (kind == HTML.Tag.HR) {
1176                     return new HRuleView(elem);
1177                 } else if (kind == HTML.Tag.BR) {
1178                     return new BRView(elem);
1179                 } else if (kind == HTML.Tag.TABLE) {
1180                     return new javax.swing.text.html.TableView(elem);
1181                 } else if ((kind == HTML.Tag.INPUT) ||
1182                            (kind == HTML.Tag.SELECT) ||
1183                            (kind == HTML.Tag.TEXTAREA)) {
1184                     return new FormView(elem);
1185                 } else if (kind == HTML.Tag.OBJECT) {
1186                     return new ObjectView(elem);
1187                 } else if (kind == HTML.Tag.FRAMESET) {
1188                      if (elem.getAttributes().isDefined(HTML.Attribute.ROWS)) {
1189                          return new FrameSetView(elem, View.Y_AXIS);
1190                      } else if (elem.getAttributes().isDefined(HTML.Attribute.COLS)) {
1191                          return new FrameSetView(elem, View.X_AXIS);
1192                      }
1193                      throw new RuntimeException("Can't build a"  + kind + ", " + elem + ":" +
1194                                      "no ROWS or COLS defined.");
1195                 } else if (kind == HTML.Tag.FRAME) {
1196                     return new FrameView(elem);
1197                 } else if (kind instanceof HTML.UnknownTag) {
1198                     return new HiddenTagView(elem);
1199                 } else if (kind == HTML.Tag.COMMENT) {
1200                     return new CommentView(elem);
1201                 } else if (kind == HTML.Tag.HEAD) {
1202                     // Make the head never visible, and never load its
1203                     // children. For Cursor positioning,
1204                     // getNextVisualPositionFrom is overriden to always return
1205                     // the end offset of the element.
1206                     return new BlockView(elem, View.X_AXIS) {
1207                         public float getPreferredSpan(int axis) {
1208                             return 0;
1209                         }
1210                         public float getMinimumSpan(int axis) {
1211                             return 0;
1212                         }
1213                         public float getMaximumSpan(int axis) {
1214                             return 0;
1215                         }
1216                         protected void loadChildren(ViewFactory f) {
1217                         }
1218                         public Shape modelToView(int pos, Shape a,
1219                                Position.Bias b) throws BadLocationException {
1220                             return a;
1221                         }
1222                         public int getNextVisualPositionFrom(int pos,
1223                                      Position.Bias b, Shape a,
1224                                      int direction, Position.Bias[] biasRet) {
1225                             return getElement().getEndOffset();
1226                         }
1227                     };
1228                 } else if ((kind == HTML.Tag.TITLE) ||
1229                            (kind == HTML.Tag.META) ||
1230                            (kind == HTML.Tag.LINK) ||
1231                            (kind == HTML.Tag.STYLE) ||
1232                            (kind == HTML.Tag.SCRIPT) ||
1233                            (kind == HTML.Tag.AREA) ||
1234                            (kind == HTML.Tag.MAP) ||
1235                            (kind == HTML.Tag.PARAM) ||
1236                            (kind == HTML.Tag.APPLET)) {
1237                     return new HiddenTagView(elem);
1238                 }
1239             }
1240             // If we get here, it's either an element we don't know about
1241             // or something from StyledDocument that doesn't have a mapping to HTML.
1242             String nm = (elementName != null) ? (String)elementName :
1243                                                 elem.getName();
1244             if (nm != null) {
1245                 if (nm.equals(AbstractDocument.ContentElementName)) {
1246                     return new LabelView(elem);
1247                 } else if (nm.equals(AbstractDocument.ParagraphElementName)) {
1248                     return new ParagraphView(elem);
1249                 } else if (nm.equals(AbstractDocument.SectionElementName)) {
1250                     return new BoxView(elem, View.Y_AXIS);
1251                 } else if (nm.equals(StyleConstants.ComponentElementName)) {
1252                     return new ComponentView(elem);
1253                 } else if (nm.equals(StyleConstants.IconElementName)) {
1254                     return new IconView(elem);
1255                 }
1256             }
1257 
1258             // default to text display
1259             return new LabelView(elem);
1260         }
1261 
1262         static class BodyBlockView extends BlockView implements ComponentListener {
1263             public BodyBlockView(Element elem) {
1264                 super(elem,View.Y_AXIS);
1265             }
1266             // reimplement major axis requirements to indicate that the
1267             // block is flexible for the body element... so that it can
1268             // be stretched to fill the background properly.
1269             protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
1270                 r = super.calculateMajorAxisRequirements(axis, r);
1271                 r.maximum = Integer.MAX_VALUE;
1272                 return r;
1273             }
1274 
1275             protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
1276                 Container container = getContainer();
1277                 Container parentContainer;
1278                 if (container != null
1279                     && (container instanceof javax.swing.JEditorPane)
1280                     && (parentContainer = container.getParent()) != null
1281                     && (parentContainer instanceof javax.swing.JViewport)) {
1282                     JViewport viewPort = (JViewport)parentContainer;
1283                     if (cachedViewPort != null) {
1284                         JViewport cachedObject = cachedViewPort.get();
1285                         if (cachedObject != null) {
1286                             if (cachedObject != viewPort) {
1287                                 cachedObject.removeComponentListener(this);
1288                             }
1289                         } else {
1290                             cachedViewPort = null;
1291                         }
1292                     }
1293                     if (cachedViewPort == null) {
1294                         viewPort.addComponentListener(this);
1295                         cachedViewPort = new WeakReference<JViewport>(viewPort);
1296                     }
1297 
1298                     componentVisibleWidth = viewPort.getExtentSize().width;
1299                     if (componentVisibleWidth > 0) {
1300                     Insets insets = container.getInsets();
1301                     viewVisibleWidth = componentVisibleWidth - insets.left - getLeftInset();
1302                     //try to use viewVisibleWidth if it is smaller than targetSpan
1303                     targetSpan = Math.min(targetSpan, viewVisibleWidth);
1304                     }
1305                 } else {
1306                     if (cachedViewPort != null) {
1307                         JViewport cachedObject = cachedViewPort.get();
1308                         if (cachedObject != null) {
1309                             cachedObject.removeComponentListener(this);
1310                         }
1311                         cachedViewPort = null;
1312                     }
1313                 }
1314                 super.layoutMinorAxis(targetSpan, axis, offsets, spans);
1315             }
1316 
1317             public void setParent(View parent) {
1318                 //if parent == null unregister component listener
1319                 if (parent == null) {
1320                     if (cachedViewPort != null) {
1321                         Object cachedObject;
1322                         if ((cachedObject = cachedViewPort.get()) != null) {
1323                             ((JComponent)cachedObject).removeComponentListener(this);
1324                         }
1325                         cachedViewPort = null;
1326                     }
1327                 }
1328                 super.setParent(parent);
1329             }
1330 
1331             public void componentResized(ComponentEvent e) {
1332                 if ( !(e.getSource() instanceof JViewport) ) {
1333                     return;
1334                 }
1335                 JViewport viewPort = (JViewport)e.getSource();
1336                 if (componentVisibleWidth != viewPort.getExtentSize().width) {
1337                     Document doc = getDocument();
1338                     if (doc instanceof AbstractDocument) {
1339                         AbstractDocument document = (AbstractDocument)getDocument();
1340                         document.readLock();
1341                         try {
1342                             layoutChanged(X_AXIS);
1343                             preferenceChanged(null, true, true);
1344                         } finally {
1345                             document.readUnlock();
1346                         }
1347 
1348                     }
1349                 }
1350             }
1351             public void componentHidden(ComponentEvent e) {
1352             }
1353             public void componentMoved(ComponentEvent e) {
1354             }
1355             public void componentShown(ComponentEvent e) {
1356             }
1357             /*
1358              * we keep weak reference to viewPort if and only if BodyBoxView is listening for ComponentEvents
1359              * only in that case cachedViewPort is not equal to null.
1360              * we need to keep this reference in order to remove BodyBoxView from viewPort listeners.
1361              *
1362              */
1363             private Reference<JViewport> cachedViewPort = null;
1364             private boolean isListening = false;
1365             private int viewVisibleWidth = Integer.MAX_VALUE;
1366             private int componentVisibleWidth = Integer.MAX_VALUE;
1367         }
1368 
1369     }
1370 
1371     // --- Action implementations ------------------------------
1372 
1373 /** The bold action identifier
1374 */
1375     public static final String  BOLD_ACTION = "html-bold-action";
1376 /** The italic action identifier
1377 */
1378     public static final String  ITALIC_ACTION = "html-italic-action";
1379 /** The paragraph left indent action identifier
1380 */
1381     public static final String  PARA_INDENT_LEFT = "html-para-indent-left";
1382 /** The paragraph right indent action identifier
1383 */
1384     public static final String  PARA_INDENT_RIGHT = "html-para-indent-right";
1385 /** The  font size increase to next value action identifier
1386 */
1387     public static final String  FONT_CHANGE_BIGGER = "html-font-bigger";
1388 /** The font size decrease to next value action identifier
1389 */
1390     public static final String  FONT_CHANGE_SMALLER = "html-font-smaller";
1391 /** The Color choice action identifier
1392      The color is passed as an argument
1393 */
1394     public static final String  COLOR_ACTION = "html-color-action";
1395 /** The logical style choice action identifier
1396      The logical style is passed in as an argument
1397 */
1398     public static final String  LOGICAL_STYLE_ACTION = "html-logical-style-action";
1399     /**
1400      * Align images at the top.
1401      */
1402     public static final String  IMG_ALIGN_TOP = "html-image-align-top";
1403 
1404     /**
1405      * Align images in the middle.
1406      */
1407     public static final String  IMG_ALIGN_MIDDLE = "html-image-align-middle";
1408 
1409     /**
1410      * Align images at the bottom.
1411      */
1412     public static final String  IMG_ALIGN_BOTTOM = "html-image-align-bottom";
1413 
1414     /**
1415      * Align images at the border.
1416      */
1417     public static final String  IMG_BORDER = "html-image-border";
1418 
1419 
1420     /** HTML used when inserting tables. */
1421     private static final String INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>";
1422 
1423     /** HTML used when inserting unordered lists. */
1424     private static final String INSERT_UL_HTML = "<ul><li></li></ul>";
1425 
1426     /** HTML used when inserting ordered lists. */
1427     private static final String INSERT_OL_HTML = "<ol><li></li></ol>";
1428 
1429     /** HTML used when inserting hr. */
1430     private static final String INSERT_HR_HTML = "<hr>";
1431 
1432     /** HTML used when inserting pre. */
1433     private static final String INSERT_PRE_HTML = "<pre></pre>";
1434 
1435     private static final NavigateLinkAction nextLinkAction =
1436         new NavigateLinkAction("next-link-action");
1437 
1438     private static final NavigateLinkAction previousLinkAction =
1439         new NavigateLinkAction("previous-link-action");
1440 
1441     private static final ActivateLinkAction activateLinkAction =
1442         new ActivateLinkAction("activate-link-action");
1443 
1444     private static final Action[] defaultActions = {
1445         new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML,
1446                                  HTML.Tag.BODY, HTML.Tag.TABLE),
1447         new InsertHTMLTextAction("InsertTableRow", INSERT_TABLE_HTML,
1448                                  HTML.Tag.TABLE, HTML.Tag.TR,
1449                                  HTML.Tag.BODY, HTML.Tag.TABLE),
1450         new InsertHTMLTextAction("InsertTableDataCell", INSERT_TABLE_HTML,
1451                                  HTML.Tag.TR, HTML.Tag.TD,
1452                                  HTML.Tag.BODY, HTML.Tag.TABLE),
1453         new InsertHTMLTextAction("InsertUnorderedList", INSERT_UL_HTML,
1454                                  HTML.Tag.BODY, HTML.Tag.UL),
1455         new InsertHTMLTextAction("InsertUnorderedListItem", INSERT_UL_HTML,
1456                                  HTML.Tag.UL, HTML.Tag.LI,
1457                                  HTML.Tag.BODY, HTML.Tag.UL),
1458         new InsertHTMLTextAction("InsertOrderedList", INSERT_OL_HTML,
1459                                  HTML.Tag.BODY, HTML.Tag.OL),
1460         new InsertHTMLTextAction("InsertOrderedListItem", INSERT_OL_HTML,
1461                                  HTML.Tag.OL, HTML.Tag.LI,
1462                                  HTML.Tag.BODY, HTML.Tag.OL),
1463         new InsertHRAction(),
1464         new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML,
1465                                  HTML.Tag.BODY, HTML.Tag.PRE),
1466         nextLinkAction, previousLinkAction, activateLinkAction,
1467 
1468         new BeginAction(beginAction, false),
1469         new BeginAction(selectionBeginAction, true)
1470     };
1471 
1472     // link navigation support
1473     private boolean foundLink = false;
1474     private int prevHypertextOffset = -1;
1475     private Object linkNavigationTag;
1476 
1477 
1478     /**
1479      * An abstract Action providing some convenience methods that may
1480      * be useful in inserting HTML into an existing document.
1481      * <p>NOTE: None of the convenience methods obtain a lock on the
1482      * document. If you have another thread modifying the text these
1483      * methods may have inconsistent behavior, or return the wrong thing.
1484      */
1485     public static abstract class HTMLTextAction extends StyledTextAction {
1486         public HTMLTextAction(String name) {
1487             super(name);
1488         }
1489 
1490         /**
1491          * @return HTMLDocument of <code>e</code>.
1492          */
1493         protected HTMLDocument getHTMLDocument(JEditorPane e) {
1494             Document d = e.getDocument();
1495             if (d instanceof HTMLDocument) {
1496                 return (HTMLDocument) d;
1497             }
1498             throw new IllegalArgumentException("document must be HTMLDocument");
1499         }
1500 
1501         /**
1502          * @return HTMLEditorKit for <code>e</code>.
1503          */
1504         protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
1505             EditorKit k = e.getEditorKit();
1506             if (k instanceof HTMLEditorKit) {
1507                 return (HTMLEditorKit) k;
1508             }
1509             throw new IllegalArgumentException("EditorKit must be HTMLEditorKit");
1510         }
1511 
1512         /**
1513          * Returns an array of the Elements that contain <code>offset</code>.
1514          * The first elements corresponds to the root.
1515          */
1516         protected Element[] getElementsAt(HTMLDocument doc, int offset) {
1517             return getElementsAt(doc.getDefaultRootElement(), offset, 0);
1518         }
1519 
1520         /**
1521          * Recursive method used by getElementsAt.
1522          */
1523         private Element[] getElementsAt(Element parent, int offset,
1524                                         int depth) {
1525             if (parent.isLeaf()) {
1526                 Element[] retValue = new Element[depth + 1];
1527                 retValue[depth] = parent;
1528                 return retValue;
1529             }
1530             Element[] retValue = getElementsAt(parent.getElement
1531                           (parent.getElementIndex(offset)), offset, depth + 1);
1532             retValue[depth] = parent;
1533             return retValue;
1534         }
1535 
1536         /**
1537          * Returns number of elements, starting at the deepest leaf, needed
1538          * to get to an element representing <code>tag</code>. This will
1539          * return -1 if no elements is found representing <code>tag</code>,
1540          * or 0 if the parent of the leaf at <code>offset</code> represents
1541          * <code>tag</code>.
1542          */
1543         protected int elementCountToTag(HTMLDocument doc, int offset,
1544                                         HTML.Tag tag) {
1545             int depth = -1;
1546             Element e = doc.getCharacterElement(offset);
1547             while (e != null && e.getAttributes().getAttribute
1548                    (StyleConstants.NameAttribute) != tag) {
1549                 e = e.getParentElement();
1550                 depth++;
1551             }
1552             if (e == null) {
1553                 return -1;
1554             }
1555             return depth;
1556         }
1557 
1558         /**
1559          * Returns the deepest element at <code>offset</code> matching
1560          * <code>tag</code>.
1561          */
1562         protected Element findElementMatchingTag(HTMLDocument doc, int offset,
1563                                                  HTML.Tag tag) {
1564             Element e = doc.getDefaultRootElement();
1565             Element lastMatch = null;
1566             while (e != null) {
1567                 if (e.getAttributes().getAttribute
1568                    (StyleConstants.NameAttribute) == tag) {
1569                     lastMatch = e;
1570                 }
1571                 e = e.getElement(e.getElementIndex(offset));
1572             }
1573             return lastMatch;
1574         }
1575     }
1576 
1577 
1578     /**
1579      * InsertHTMLTextAction can be used to insert an arbitrary string of HTML
1580      * into an existing HTML document. At least two HTML.Tags need to be
1581      * supplied. The first Tag, parentTag, identifies the parent in
1582      * the document to add the elements to. The second tag, addTag,
1583      * identifies the first tag that should be added to the document as
1584      * seen in the HTML string. One important thing to remember, is that
1585      * the parser is going to generate all the appropriate tags, even if
1586      * they aren't in the HTML string passed in.<p>
1587      * For example, lets say you wanted to create an action to insert
1588      * a table into the body. The parentTag would be HTML.Tag.BODY,
1589      * addTag would be HTML.Tag.TABLE, and the string could be something
1590      * like &lt;table&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;.
1591      * <p>There is also an option to supply an alternate parentTag and
1592      * addTag. These will be checked for if there is no parentTag at
1593      * offset.
1594      */
1595     public static class InsertHTMLTextAction extends HTMLTextAction {
1596         public InsertHTMLTextAction(String name, String html,
1597                                     HTML.Tag parentTag, HTML.Tag addTag) {
1598             this(name, html, parentTag, addTag, null, null);
1599         }
1600 
1601         public InsertHTMLTextAction(String name, String html,
1602                                     HTML.Tag parentTag,
1603                                     HTML.Tag addTag,
1604                                     HTML.Tag alternateParentTag,
1605                                     HTML.Tag alternateAddTag) {
1606             this(name, html, parentTag, addTag, alternateParentTag,
1607                  alternateAddTag, true);
1608         }
1609 
1610         /* public */
1611         InsertHTMLTextAction(String name, String html,
1612                                     HTML.Tag parentTag,
1613                                     HTML.Tag addTag,
1614                                     HTML.Tag alternateParentTag,
1615                                     HTML.Tag alternateAddTag,
1616                                     boolean adjustSelection) {
1617             super(name);
1618             this.html = html;
1619             this.parentTag = parentTag;
1620             this.addTag = addTag;
1621             this.alternateParentTag = alternateParentTag;
1622             this.alternateAddTag = alternateAddTag;
1623             this.adjustSelection = adjustSelection;
1624         }
1625 
1626         /**
1627          * A cover for HTMLEditorKit.insertHTML. If an exception it
1628          * thrown it is wrapped in a RuntimeException and thrown.
1629          */
1630         protected void insertHTML(JEditorPane editor, HTMLDocument doc,
1631                                   int offset, String html, int popDepth,
1632                                   int pushDepth, HTML.Tag addTag) {
1633             try {
1634                 getHTMLEditorKit(editor).insertHTML(doc, offset, html,
1635                                                     popDepth, pushDepth,
1636                                                     addTag);
1637             } catch (IOException ioe) {
1638                 throw new RuntimeException("Unable to insert: " + ioe);
1639             } catch (BadLocationException ble) {
1640                 throw new RuntimeException("Unable to insert: " + ble);
1641             }
1642         }
1643 
1644         /**
1645          * This is invoked when inserting at a boundary. It determines
1646          * the number of pops, and then the number of pushes that need
1647          * to be performed, and then invokes insertHTML.
1648          * @since 1.3
1649          */
1650         protected void insertAtBoundary(JEditorPane editor, HTMLDocument doc,
1651                                         int offset, Element insertElement,
1652                                         String html, HTML.Tag parentTag,
1653                                         HTML.Tag addTag) {
1654             insertAtBoundry(editor, doc, offset, insertElement, html,
1655                             parentTag, addTag);
1656         }
1657 
1658         /**
1659          * This is invoked when inserting at a boundary. It determines
1660          * the number of pops, and then the number of pushes that need
1661          * to be performed, and then invokes insertHTML.
1662          * @deprecated As of Java 2 platform v1.3, use insertAtBoundary
1663          */
1664         @Deprecated
1665         protected void insertAtBoundry(JEditorPane editor, HTMLDocument doc,
1666                                        int offset, Element insertElement,
1667                                        String html, HTML.Tag parentTag,
1668                                        HTML.Tag addTag) {
1669             // Find the common parent.
1670             Element e;
1671             Element commonParent;
1672             boolean isFirst = (offset == 0);
1673 
1674             if (offset > 0 || insertElement == null) {
1675                 e = doc.getDefaultRootElement();
1676                 while (e != null && e.getStartOffset() != offset &&
1677                        !e.isLeaf()) {
1678                     e = e.getElement(e.getElementIndex(offset));
1679                 }
1680                 commonParent = (e != null) ? e.getParentElement() : null;
1681             }
1682             else {
1683                 // If inserting at the origin, the common parent is the
1684                 // insertElement.
1685                 commonParent = insertElement;
1686             }
1687             if (commonParent != null) {
1688                 // Determine how many pops to do.
1689                 int pops = 0;
1690                 int pushes = 0;
1691                 if (isFirst && insertElement != null) {
1692                     e = commonParent;
1693                     while (e != null && !e.isLeaf()) {
1694                         e = e.getElement(e.getElementIndex(offset));
1695                         pops++;
1696                     }
1697                 }
1698                 else {
1699                     e = commonParent;
1700                     offset--;
1701                     while (e != null && !e.isLeaf()) {
1702                         e = e.getElement(e.getElementIndex(offset));
1703                         pops++;
1704                     }
1705 
1706                     // And how many pushes
1707                     e = commonParent;
1708                     offset++;
1709                     while (e != null && e != insertElement) {
1710                         e = e.getElement(e.getElementIndex(offset));
1711                         pushes++;
1712                     }
1713                 }
1714                 pops = Math.max(0, pops - 1);
1715 
1716                 // And insert!
1717                 insertHTML(editor, doc, offset, html, pops, pushes, addTag);
1718             }
1719         }
1720 
1721         /**
1722          * If there is an Element with name <code>tag</code> at
1723          * <code>offset</code>, this will invoke either insertAtBoundary
1724          * or <code>insertHTML</code>. This returns true if there is
1725          * a match, and one of the inserts is invoked.
1726          */
1727         /*protected*/
1728         boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
1729                               int offset, HTML.Tag tag, HTML.Tag addTag) {
1730             Element e = findElementMatchingTag(doc, offset, tag);
1731             if (e != null && e.getStartOffset() == offset) {
1732                 insertAtBoundary(editor, doc, offset, e, html,
1733                                  tag, addTag);
1734                 return true;
1735             }
1736             else if (offset > 0) {
1737                 int depth = elementCountToTag(doc, offset - 1, tag);
1738                 if (depth != -1) {
1739                     insertHTML(editor, doc, offset, html, depth, 0, addTag);
1740                     return true;
1741                 }
1742             }
1743             return false;
1744         }
1745 
1746         /**
1747          * Called after an insertion to adjust the selection.
1748          */
1749         /* protected */
1750         void adjustSelection(JEditorPane pane, HTMLDocument doc,
1751                              int startOffset, int oldLength) {
1752             int newLength = doc.getLength();
1753             if (newLength != oldLength && startOffset < newLength) {
1754                 if (startOffset > 0) {
1755                     String text;
1756                     try {
1757                         text = doc.getText(startOffset - 1, 1);
1758                     } catch (BadLocationException ble) {
1759                         text = null;
1760                     }
1761                     if (text != null && text.length() > 0 &&
1762                         text.charAt(0) == '\n') {
1763                         pane.select(startOffset, startOffset);
1764                     }
1765                     else {
1766                         pane.select(startOffset + 1, startOffset + 1);
1767                     }
1768                 }
1769                 else {
1770                     pane.select(1, 1);
1771                 }
1772             }
1773         }
1774 
1775         /**
1776          * Inserts the HTML into the document.
1777          *
1778          * @param ae the event
1779          */
1780         public void actionPerformed(ActionEvent ae) {
1781             JEditorPane editor = getEditor(ae);
1782             if (editor != null) {
1783                 HTMLDocument doc = getHTMLDocument(editor);
1784                 int offset = editor.getSelectionStart();
1785                 int length = doc.getLength();
1786                 boolean inserted;
1787                 // Try first choice
1788                 if (!insertIntoTag(editor, doc, offset, parentTag, addTag) &&
1789                     alternateParentTag != null) {
1790                     // Then alternate.
1791                     inserted = insertIntoTag(editor, doc, offset,
1792                                              alternateParentTag,
1793                                              alternateAddTag);
1794                 }
1795                 else {
1796                     inserted = true;
1797                 }
1798                 if (adjustSelection && inserted) {
1799                     adjustSelection(editor, doc, offset, length);
1800                 }
1801             }
1802         }
1803 
1804         /** HTML to insert. */
1805         protected String html;
1806         /** Tag to check for in the document. */
1807         protected HTML.Tag parentTag;
1808         /** Tag in HTML to start adding tags from. */
1809         protected HTML.Tag addTag;
1810         /** Alternate Tag to check for in the document if parentTag is
1811          * not found. */
1812         protected HTML.Tag alternateParentTag;
1813         /** Alternate tag in HTML to start adding tags from if parentTag
1814          * is not found and alternateParentTag is found. */
1815         protected HTML.Tag alternateAddTag;
1816         /** True indicates the selection should be adjusted after an insert. */
1817         boolean adjustSelection;
1818     }
1819 
1820 
1821     /**
1822      * InsertHRAction is special, at actionPerformed time it will determine
1823      * the parent HTML.Tag based on the paragraph element at the selection
1824      * start.
1825      */
1826     static class InsertHRAction extends InsertHTMLTextAction {
1827         InsertHRAction() {
1828             super("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null, null,
1829                   false);
1830         }
1831 
1832         /**
1833          * Inserts the HTML into the document.
1834          *
1835          * @param ae the event
1836          */
1837         public void actionPerformed(ActionEvent ae) {
1838             JEditorPane editor = getEditor(ae);
1839             if (editor != null) {
1840                 HTMLDocument doc = getHTMLDocument(editor);
1841                 int offset = editor.getSelectionStart();
1842                 Element paragraph = doc.getParagraphElement(offset);
1843                 if (paragraph.getParentElement() != null) {
1844                     parentTag = (HTML.Tag)paragraph.getParentElement().
1845                                   getAttributes().getAttribute
1846                                   (StyleConstants.NameAttribute);
1847                     super.actionPerformed(ae);
1848                 }
1849             }
1850         }
1851 
1852     }
1853 
1854     /*
1855      * Returns the object in an AttributeSet matching a key
1856      */
1857     static private Object getAttrValue(AttributeSet attr, HTML.Attribute key) {
1858         Enumeration names = attr.getAttributeNames();
1859         while (names.hasMoreElements()) {
1860             Object nextKey = names.nextElement();
1861             Object nextVal = attr.getAttribute(nextKey);
1862             if (nextVal instanceof AttributeSet) {
1863                 Object value = getAttrValue((AttributeSet)nextVal, key);
1864                 if (value != null) {
1865                     return value;
1866                 }
1867             } else if (nextKey == key) {
1868                 return nextVal;
1869             }
1870         }
1871         return null;
1872     }
1873 
1874     /*
1875      * Action to move the focus on the next or previous hypertext link
1876      * or object. TODO: This method relies on support from the
1877      * javax.accessibility package.  The text package should support
1878      * keyboard navigation of text elements directly.
1879      */
1880     static class NavigateLinkAction extends TextAction implements CaretListener {
1881 
1882         private static final FocusHighlightPainter focusPainter =
1883             new FocusHighlightPainter(null);
1884         private final boolean focusBack;
1885 
1886         /*
1887          * Create this action with the appropriate identifier.
1888          */
1889         public NavigateLinkAction(String actionName) {
1890             super(actionName);
1891             focusBack = "previous-link-action".equals(actionName);
1892         }
1893 
1894         /**
1895          * Called when the caret position is updated.
1896          *
1897          * @param e the caret event
1898          */
1899         public void caretUpdate(CaretEvent e) {
1900             Object src = e.getSource();
1901             if (src instanceof JTextComponent) {
1902                 JTextComponent comp = (JTextComponent) src;
1903                 HTMLEditorKit kit = getHTMLEditorKit(comp);
1904                 if (kit != null && kit.foundLink) {
1905                     kit.foundLink = false;
1906                     // TODO: The AccessibleContext for the editor should register
1907                     // as a listener for CaretEvents and forward the events to
1908                     // assistive technologies listening for such events.
1909                     comp.getAccessibleContext().firePropertyChange(
1910                         AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET,
1911                         Integer.valueOf(kit.prevHypertextOffset),
1912                         Integer.valueOf(e.getDot()));
1913                 }
1914             }
1915         }
1916 
1917         /*
1918          * The operation to perform when this action is triggered.
1919          */
1920         public void actionPerformed(ActionEvent e) {
1921             JTextComponent comp = getTextComponent(e);
1922             if (comp == null || comp.isEditable()) {
1923                 return;
1924             }
1925 
1926             Document doc = comp.getDocument();
1927             HTMLEditorKit kit = getHTMLEditorKit(comp);
1928             if (doc == null || kit == null) {
1929                 return;
1930             }
1931 
1932             // TODO: Should start successive iterations from the
1933             // current caret position.
1934             ElementIterator ei = new ElementIterator(doc);
1935             int currentOffset = comp.getCaretPosition();
1936             int prevStartOffset = -1;
1937             int prevEndOffset = -1;
1938 
1939             // highlight the next link or object after the current caret position
1940             Element nextElement;
1941             while ((nextElement = ei.next()) != null) {
1942                 String name = nextElement.getName();
1943                 AttributeSet attr = nextElement.getAttributes();
1944 
1945                 Object href = getAttrValue(attr, HTML.Attribute.HREF);
1946                 if (!(name.equals(HTML.Tag.OBJECT.toString())) && href == null) {
1947                     continue;
1948                 }
1949 
1950                 int elementOffset = nextElement.getStartOffset();
1951                 if (focusBack) {
1952                     if (elementOffset >= currentOffset &&
1953                         prevStartOffset >= 0) {
1954 
1955                         kit.foundLink = true;
1956                         comp.setCaretPosition(prevStartOffset);
1957                         moveCaretPosition(comp, kit, prevStartOffset,
1958                                           prevEndOffset);
1959                         kit.prevHypertextOffset = prevStartOffset;
1960                         return;
1961                     }
1962                 } else { // focus forward
1963                     if (elementOffset > currentOffset) {
1964 
1965                         kit.foundLink = true;
1966                         comp.setCaretPosition(elementOffset);
1967                         moveCaretPosition(comp, kit, elementOffset,
1968                                           nextElement.getEndOffset());
1969                         kit.prevHypertextOffset = elementOffset;
1970                         return;
1971                     }
1972                 }
1973                 prevStartOffset = nextElement.getStartOffset();
1974                 prevEndOffset = nextElement.getEndOffset();
1975             }
1976             if (focusBack && prevStartOffset >= 0) {
1977                 kit.foundLink = true;
1978                 comp.setCaretPosition(prevStartOffset);
1979                 moveCaretPosition(comp, kit, prevStartOffset, prevEndOffset);
1980                 kit.prevHypertextOffset = prevStartOffset;
1981             }
1982         }
1983 
1984         /*
1985          * Moves the caret from mark to dot
1986          */
1987         private void moveCaretPosition(JTextComponent comp, HTMLEditorKit kit,
1988                                        int mark, int dot) {
1989             Highlighter h = comp.getHighlighter();
1990             if (h != null) {
1991                 int p0 = Math.min(dot, mark);
1992                 int p1 = Math.max(dot, mark);
1993                 try {
1994                     if (kit.linkNavigationTag != null) {
1995                         h.changeHighlight(kit.linkNavigationTag, p0, p1);
1996                     } else {
1997                         kit.linkNavigationTag =
1998                                 h.addHighlight(p0, p1, focusPainter);
1999                     }
2000                 } catch (BadLocationException e) {
2001                 }
2002             }
2003         }
2004 
2005         private HTMLEditorKit getHTMLEditorKit(JTextComponent comp) {
2006             if (comp instanceof JEditorPane) {
2007                 EditorKit kit = ((JEditorPane) comp).getEditorKit();
2008                 if (kit instanceof HTMLEditorKit) {
2009                     return (HTMLEditorKit) kit;
2010                 }
2011             }
2012             return null;
2013         }
2014 
2015         /**
2016          * A highlight painter that draws a one-pixel border around
2017          * the highlighted area.
2018          */
2019         static class FocusHighlightPainter extends
2020             DefaultHighlighter.DefaultHighlightPainter {
2021 
2022             FocusHighlightPainter(Color color) {
2023                 super(color);
2024             }
2025 
2026             /**
2027              * Paints a portion of a highlight.
2028              *
2029              * @param g the graphics context
2030              * @param offs0 the starting model offset &ge; 0
2031              * @param offs1 the ending model offset &ge; offs1
2032              * @param bounds the bounding box of the view, which is not
2033              *        necessarily the region to paint.
2034              * @param c the editor
2035              * @param view View painting for
2036              * @return region in which drawing occurred
2037              */
2038             public Shape paintLayer(Graphics g, int offs0, int offs1,
2039                                     Shape bounds, JTextComponent c, View view) {
2040 
2041                 Color color = getColor();
2042 
2043                 if (color == null) {
2044                     g.setColor(c.getSelectionColor());
2045                 }
2046                 else {
2047                     g.setColor(color);
2048                 }
2049                 if (offs0 == view.getStartOffset() &&
2050                     offs1 == view.getEndOffset()) {
2051                     // Contained in view, can just use bounds.
2052                     Rectangle alloc;
2053                     if (bounds instanceof Rectangle) {
2054                         alloc = (Rectangle)bounds;
2055                     }
2056                     else {
2057                         alloc = bounds.getBounds();
2058                     }
2059                     g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height);
2060                     return alloc;
2061                 }
2062                 else {
2063                     // Should only render part of View.
2064                     try {
2065                         // --- determine locations ---
2066                         Shape shape = view.modelToView(offs0, Position.Bias.Forward,
2067                                                        offs1,Position.Bias.Backward,
2068                                                        bounds);
2069                         Rectangle r = (shape instanceof Rectangle) ?
2070                             (Rectangle)shape : shape.getBounds();
2071                         g.drawRect(r.x, r.y, r.width - 1, r.height);
2072                         return r;
2073                     } catch (BadLocationException e) {
2074                         // can't render
2075                     }
2076                 }
2077                 // Only if exception
2078                 return null;
2079             }
2080         }
2081     }
2082 
2083     /*
2084      * Action to activate the hypertext link that has focus.
2085      * TODO: This method relies on support from the
2086      * javax.accessibility package.  The text package should support
2087      * keyboard navigation of text elements directly.
2088      */
2089     static class ActivateLinkAction extends TextAction {
2090 
2091         /**
2092          * Create this action with the appropriate identifier.
2093          */
2094         public ActivateLinkAction(String actionName) {
2095             super(actionName);
2096         }
2097 
2098         /*
2099          * activates the hyperlink at offset
2100          */
2101         private void activateLink(String href, HTMLDocument doc,
2102                                   JEditorPane editor, int offset) {
2103             try {
2104                 URL page =
2105                     (URL)doc.getProperty(Document.StreamDescriptionProperty);
2106                 URL url = new URL(page, href);
2107                 HyperlinkEvent linkEvent = new HyperlinkEvent
2108                     (editor, HyperlinkEvent.EventType.
2109                      ACTIVATED, url, url.toExternalForm(),
2110                      doc.getCharacterElement(offset));
2111                 editor.fireHyperlinkUpdate(linkEvent);
2112             } catch (MalformedURLException m) {
2113             }
2114         }
2115 
2116         /*
2117          * Invokes default action on the object in an element
2118          */
2119         private void doObjectAction(JEditorPane editor, Element elem) {
2120             View view = getView(editor, elem);
2121             if (view != null && view instanceof ObjectView) {
2122                 Component comp = ((ObjectView)view).getComponent();
2123                 if (comp != null && comp instanceof Accessible) {
2124                     AccessibleContext ac = comp.getAccessibleContext();
2125                     if (ac != null) {
2126                         AccessibleAction aa = ac.getAccessibleAction();
2127                         if (aa != null) {
2128                             aa.doAccessibleAction(0);
2129                         }
2130                     }
2131                 }
2132             }
2133         }
2134 
2135         /*
2136          * Returns the root view for a document
2137          */
2138         private View getRootView(JEditorPane editor) {
2139             return editor.getUI().getRootView(editor);
2140         }
2141 
2142         /*
2143          * Returns a view associated with an element
2144          */
2145         private View getView(JEditorPane editor, Element elem) {
2146             Object lock = lock(editor);
2147             try {
2148                 View rootView = getRootView(editor);
2149                 int start = elem.getStartOffset();
2150                 if (rootView != null) {
2151                     return getView(rootView, elem, start);
2152                 }
2153                 return null;
2154             } finally {
2155                 unlock(lock);
2156             }
2157         }
2158 
2159         private View getView(View parent, Element elem, int start) {
2160             if (parent.getElement() == elem) {
2161                 return parent;
2162             }
2163             int index = parent.getViewIndex(start, Position.Bias.Forward);
2164 
2165             if (index != -1 && index < parent.getViewCount()) {
2166                 return getView(parent.getView(index), elem, start);
2167             }
2168             return null;
2169         }
2170 
2171         /*
2172          * If possible acquires a lock on the Document.  If a lock has been
2173          * obtained a key will be retured that should be passed to
2174          * <code>unlock</code>.
2175          */
2176         private Object lock(JEditorPane editor) {
2177             Document document = editor.getDocument();
2178 
2179             if (document instanceof AbstractDocument) {
2180                 ((AbstractDocument)document).readLock();
2181                 return document;
2182             }
2183             return null;
2184         }
2185 
2186         /*
2187          * Releases a lock previously obtained via <code>lock</code>.
2188          */
2189         private void unlock(Object key) {
2190             if (key != null) {
2191                 ((AbstractDocument)key).readUnlock();
2192             }
2193         }
2194 
2195         /*
2196          * The operation to perform when this action is triggered.
2197          */
2198         public void actionPerformed(ActionEvent e) {
2199 
2200             JTextComponent c = getTextComponent(e);
2201             if (c.isEditable() || !(c instanceof JEditorPane)) {
2202                 return;
2203             }
2204             JEditorPane editor = (JEditorPane)c;
2205 
2206             Document d = editor.getDocument();
2207             if (d == null || !(d instanceof HTMLDocument)) {
2208                 return;
2209             }
2210             HTMLDocument doc = (HTMLDocument)d;
2211 
2212             ElementIterator ei = new ElementIterator(doc);
2213             int currentOffset = editor.getCaretPosition();
2214 
2215             // invoke the next link or object action
2216             String urlString = null;
2217             String objString = null;
2218             Element currentElement;
2219             while ((currentElement = ei.next()) != null) {
2220                 String name = currentElement.getName();
2221                 AttributeSet attr = currentElement.getAttributes();
2222 
2223                 Object href = getAttrValue(attr, HTML.Attribute.HREF);
2224                 if (href != null) {
2225                     if (currentOffset >= currentElement.getStartOffset() &&
2226                         currentOffset <= currentElement.getEndOffset()) {
2227 
2228                         activateLink((String)href, doc, editor, currentOffset);
2229                         return;
2230                     }
2231                 } else if (name.equals(HTML.Tag.OBJECT.toString())) {
2232                     Object obj = getAttrValue(attr, HTML.Attribute.CLASSID);
2233                     if (obj != null) {
2234                         if (currentOffset >= currentElement.getStartOffset() &&
2235                             currentOffset <= currentElement.getEndOffset()) {
2236 
2237                             doObjectAction(editor, currentElement);
2238                             return;
2239                         }
2240                     }
2241                 }
2242             }
2243         }
2244     }
2245 
2246     private static int getBodyElementStart(JTextComponent comp) {
2247         Element rootElement = comp.getDocument().getRootElements()[0];
2248         for (int i = 0; i < rootElement.getElementCount(); i++) {
2249             Element currElement = rootElement.getElement(i);
2250             if("body".equals(currElement.getName())) {
2251                 return currElement.getStartOffset();
2252             }
2253         }
2254         return 0;
2255     }
2256 
2257     /*
2258      * Move the caret to the beginning of the document.
2259      * @see DefaultEditorKit#beginAction
2260      * @see HTMLEditorKit#getActions
2261      */
2262 
2263     static class BeginAction extends TextAction {
2264 
2265         /* Create this object with the appropriate identifier. */
2266         BeginAction(String nm, boolean select) {
2267             super(nm);
2268             this.select = select;
2269         }
2270 
2271         /** The operation to perform when this action is triggered. */
2272         public void actionPerformed(ActionEvent e) {
2273             JTextComponent target = getTextComponent(e);
2274             int bodyStart = getBodyElementStart(target);
2275 
2276             if (target != null) {
2277                 if (select) {
2278                     target.moveCaretPosition(bodyStart);
2279                 } else {
2280                     target.setCaretPosition(bodyStart);
2281                 }
2282             }
2283         }
2284 
2285         private boolean select;
2286     }
2287 }