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