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

Save This Page
Home » openjdk-7 » javax » swing »