< prev index next >

src/java.desktop/share/classes/javax/swing/text/html/HTMLEditorKit.java

Print this page




  51  * The &lt;applet&gt; tag is not supported, but some support is provided
  52  * for the &lt;object&gt; tag.
  53  * <p>
  54  * There are several goals of the HTML EditorKit provided, that have
  55  * an effect upon the way that HTML is modeled.  These
  56  * have influenced its design in a substantial way.
  57  * <dl>
  58  * <dt>
  59  * Support editing
  60  * <dd>
  61  * It might seem fairly obvious that a plug-in for JEditorPane
  62  * should provide editing support, but that fact has several
  63  * design considerations.  There are a substantial number of HTML
  64  * documents that don't properly conform to an HTML specification.
  65  * These must be normalized somewhat into a correct form if one
  66  * is to edit them.  Additionally, users don't like to be presented
  67  * with an excessive amount of structure editing, so using traditional
  68  * text editing gestures is preferred over using the HTML structure
  69  * exactly as defined in the HTML document.
  70  * <p>
  71  * The modeling of HTML is provided by the class <code>HTMLDocument</code>.
  72  * Its documentation describes the details of how the HTML is modeled.
  73  * The editing support leverages heavily off of the text package.
  74  *
  75  * <dt>
  76  * Extendable/Scalable
  77  * <dd>
  78  * To maximize the usefulness of this kit, a great deal of effort
  79  * has gone into making it extendable.  These are some of the
  80  * features.
  81  * <ol>
  82  *   <li>
  83  *   The parser is replaceable.  The default parser is the Hot Java
  84  *   parser which is DTD based.  A different DTD can be used, or an
  85  *   entirely different parser can be used.  To change the parser,
  86  *   reimplement the getParser method.  The default parser is
  87  *   dynamically loaded when first asked for, so the class files
  88  *   will never be loaded if an alternative parser is used.  The
  89  *   default parser is in a separate package called parser below
  90  *   this package.
  91  *   <li>
  92  *   The parser drives the ParserCallback, which is provided by
  93  *   HTMLDocument.  To change the callback, subclass HTMLDocument
  94  *   and reimplement the createDefaultDocument method to return
  95  *   document that produces a different reader.  The reader controls
  96  *   how the document is structured.  Although the Document provides
  97  *   HTML support by default, there is nothing preventing support of
  98  *   non-HTML tags that result in alternative element structures.
  99  *   <li>
 100  *   The default view of the models are provided as a hierarchy of
 101  *   View implementations, so one can easily customize how a particular
 102  *   element is displayed or add capabilities for new kinds of elements
 103  *   by providing new View implementations.  The default set of views
 104  *   are provided by the <code>HTMLFactory</code> class.  This can
 105  *   be easily changed by subclassing or replacing the HTMLFactory
 106  *   and reimplementing the getViewFactory method to return the alternative
 107  *   factory.
 108  *   <li>
 109  *   The View implementations work primarily off of CSS attributes,
 110  *   which are kept in the views.  This makes it possible to have
 111  *   multiple views mapped over the same model that appear substantially
 112  *   different.  This can be especially useful for printing.  For
 113  *   most HTML attributes, the HTML attributes are converted to CSS
 114  *   attributes for display.  This helps make the View implementations
 115  *   more general purpose
 116  * </ol>
 117  *
 118  * <dt>
 119  * Asynchronous Loading
 120  * <dd>
 121  * Larger documents involve a lot of parsing and take some time
 122  * to load.  By default, this kit produces documents that will be
 123  * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
 124  * This is controlled by a property on the document.  The method
 125  * {@link #createDefaultDocument createDefaultDocument} can
 126  * be overriden to change this.  The batching of work is done
 127  * by the <code>HTMLDocument.HTMLReader</code> class.  The actual
 128  * work is done by the <code>DefaultStyledDocument</code> and
 129  * <code>AbstractDocument</code> classes in the text package.
 130  *
 131  * <dt>
 132  * Customization from current LAF
 133  * <dd>
 134  * HTML provides a well known set of features without exactly
 135  * specifying the display characteristics.  Swing has a theme
 136  * mechanism for its look-and-feel implementations.  It is desirable
 137  * for the look-and-feel to feed display characteristics into the
 138  * HTML views.  An user with poor vision for example would want
 139  * high contrast and larger than typical fonts.
 140  * <p>
 141  * The support for this is provided by the <code>StyleSheet</code>
 142  * class.  The presentation of the HTML can be heavily influenced
 143  * by the setting of the StyleSheet property on the EditorKit.
 144  *
 145  * <dt>
 146  * Not lossy
 147  * <dd>
 148  * An EditorKit has the ability to be read and save documents.
 149  * It is generally the most pleasing to users if there is no loss
 150  * of data between the two operation.  The policy of the HTMLEditorKit
 151  * will be to store things not recognized or not necessarily visible
 152  * so they can be subsequently written out.  The model of the HTML document
 153  * should therefore contain all information discovered while reading the
 154  * document.  This is constrained in some ways by the need to support
 155  * editing (i.e. incorrect documents sometimes must be normalized).
 156  * The guiding principle is that information shouldn't be lost, but
 157  * some might be synthesized to produce a more correct model or it might
 158  * be rearranged.
 159  * </dl>
 160  *
 161  * @author  Timothy Prinzing
 162  */
 163 @SuppressWarnings("serial") // Same-version serialization only
 164 public class HTMLEditorKit extends StyledEditorKit implements Accessible {
 165 
 166     private JEditorPane theEditor;
 167 
 168     /**
 169      * Constructs an HTMLEditorKit, creates a StyleContext,
 170      * and loads the style sheet.
 171      */
 172     public HTMLEditorKit() {
 173 
 174     }
 175 
 176     /**
 177      * Get the MIME type of the data that this
 178      * kit represents support for.  This kit supports
 179      * the type <code>text/html</code>.
 180      *
 181      * @return the type
 182      */
 183     public String getContentType() {
 184         return "text/html";
 185     }
 186 
 187     /**
 188      * Fetch a factory that is suitable for producing
 189      * views of any models that are produced by this
 190      * kit.
 191      *
 192      * @return the factory
 193      */
 194     public ViewFactory getViewFactory() {
 195         return defaultFactory;
 196     }
 197 
 198     /**
 199      * Create an uninitialized text storage model


 214         return doc;
 215     }
 216 
 217     /**
 218      * Try to get an HTML parser from the document.  If no parser is set for
 219      * the document, return the editor kit's default parser.  It is an error
 220      * if no parser could be obtained from the editor kit.
 221      */
 222     private Parser ensureParser(HTMLDocument doc) throws IOException {
 223         Parser p = doc.getParser();
 224         if (p == null) {
 225             p = getParser();
 226         }
 227         if (p == null) {
 228             throw new IOException("Can't load parser");
 229         }
 230         return p;
 231     }
 232 
 233     /**
 234      * Inserts content from the given stream. If <code>doc</code> is
 235      * an instance of HTMLDocument, this will read
 236      * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
 237      * the body Element, if you do not insert into the body an exception will
 238      * be thrown. When inserting into a non-empty document all tags outside
 239      * of the body (head, title) will be dropped.
 240      *
 241      * @param in the stream to read from
 242      * @param doc the destination for the insertion
 243      * @param pos the location in the document to place the
 244      *   content
 245      * @exception IOException on any I/O error
 246      * @exception BadLocationException if pos represents an invalid
 247      *   location within the document
 248      * @exception RuntimeException (will eventually be a BadLocationException)
 249      *            if pos is invalid
 250      */
 251     public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
 252 
 253         if (doc instanceof HTMLDocument) {
 254             HTMLDocument hdoc = (HTMLDocument) doc;


 428                 new PrivilegedAction<InputStream>() {
 429                     public InputStream run() {
 430                         return HTMLEditorKit.class.getResourceAsStream(name);
 431                     }
 432                 });
 433     }
 434 
 435     /**
 436      * Fetches the command list for the editor.  This is
 437      * the list of commands supported by the superclass
 438      * augmented by the collection of commands defined
 439      * locally for style operations.
 440      *
 441      * @return the command list
 442      */
 443     public Action[] getActions() {
 444         return TextAction.augmentList(super.getActions(), defaultActions);
 445     }
 446 
 447     /**
 448      * Copies the key/values in <code>element</code>s AttributeSet into
 449      * <code>set</code>. This does not copy component, icon, or element
 450      * names attributes. Subclasses may wish to refine what is and what
 451      * isn't copied here. But be sure to first remove all the attributes that
 452      * are in <code>set</code>.<p>
 453      * This is called anytime the caret moves over a different location.
 454      *
 455      */
 456     protected void createInputAttributes(Element element,
 457                                          MutableAttributeSet set) {
 458         set.removeAttributes(set);
 459         set.addAttributes(element.getAttributes());
 460         set.removeAttribute(StyleConstants.ComposedTextAttribute);
 461 
 462         Object o = set.getAttribute(StyleConstants.NameAttribute);
 463         if (o instanceof HTML.Tag) {
 464             HTML.Tag tag = (HTML.Tag)o;
 465             // PENDING: we need a better way to express what shouldn't be
 466             // copied when editing...
 467             if(tag == HTML.Tag.IMG) {
 468                 // Remove the related image attributes, src, width, height
 469                 set.removeAttribute(HTML.Attribute.SRC);
 470                 set.removeAttribute(HTML.Attribute.HEIGHT);
 471                 set.removeAttribute(HTML.Attribute.WIDTH);
 472                 set.addAttribute(StyleConstants.NameAttribute,


 540      *
 541      * @since 1.3
 542      */
 543     public void setLinkCursor(Cursor cursor) {
 544         linkCursor = cursor;
 545     }
 546 
 547     /**
 548      * Returns the cursor to use over hyper links.
 549      *
 550      * @return the cursor
 551      *
 552      * @since 1.3
 553      */
 554     public Cursor getLinkCursor() {
 555         return linkCursor;
 556     }
 557 
 558     /**
 559      * Indicates whether an html form submission is processed automatically
 560      * or only <code>FormSubmitEvent</code> is fired.
 561      *
 562      * @return true  if html form submission is processed automatically,
 563      *         false otherwise.
 564      *
 565      * @see #setAutoFormSubmission
 566      * @since 1.5
 567      */
 568     public boolean isAutoFormSubmission() {
 569         return isAutoFormSubmission;
 570     }
 571 
 572     /**
 573      * Specifies if an html form submission is processed
 574      * automatically or only <code>FormSubmitEvent</code> is fired.
 575      * By default it is set to true.
 576      *
 577      * @param isAuto if {@code true}, html form submission is processed automatically.
 578      *
 579      * @see #isAutoFormSubmission()
 580      * @see FormSubmitEvent
 581      * @since 1.5
 582      */
 583     public void setAutoFormSubmission(boolean isAuto) {
 584         isAutoFormSubmission = isAuto;
 585     }
 586 
 587     /**
 588      * Creates a copy of the editor kit.
 589      *
 590      * @return the copy
 591      */
 592     public Object clone() {
 593         HTMLEditorKit o = (HTMLEditorKit)super.clone();
 594         if (o != null) {


 795                                     (Rectangle)rBounds : rBounds.getBounds());
 796                     } catch (BadLocationException ble) {
 797                         bounds = null;
 798                     }
 799                     if (bounds != null) {
 800                         AttributeSet area = m.getArea(x - bounds.x,
 801                                                       y - bounds.y,
 802                                                       bounds.width,
 803                                                       bounds.height);
 804                         if (area != null) {
 805                             return (String)area.getAttribute(HTML.Attribute.
 806                                                              HREF);
 807                         }
 808                     }
 809                 }
 810             }
 811             return null;
 812         }
 813 
 814         /**
 815          * Returns true if the View representing <code>e</code> contains
 816          * the location <code>x</code>, <code>y</code>. <code>offset</code>
 817          * gives the offset into the Document to check for.
 818          */
 819         private boolean doesElementContainLocation(JEditorPane editor,
 820                                                    Element e, int offset,
 821                                                    int x, int y) {
 822             if (e != null && offset > 0 && e.getStartOffset() == offset) {
 823                 try {
 824                     TextUI ui = editor.getUI();
 825                     Shape s1 = ui.modelToView(editor, offset,
 826                                               Position.Bias.Forward);
 827                     if (s1 == null) {
 828                         return false;
 829                     }
 830                     Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle)s1 :
 831                                     s1.getBounds();
 832                     Shape s2 = ui.modelToView(editor, e.getEndOffset(),
 833                                               Position.Bias.Backward);
 834                     if (s2 != null) {
 835                         Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle)s2 :
 836                                     s2.getBounds();


 842             }
 843             return true;
 844         }
 845 
 846         /**
 847          * Calls linkActivated on the associated JEditorPane
 848          * if the given position represents a link.<p>This is implemented
 849          * to forward to the method with the same name, but with the following
 850          * args both == -1.
 851          *
 852          * @param pos the position
 853          * @param editor the editor pane
 854          */
 855         protected void activateLink(int pos, JEditorPane editor) {
 856             activateLink(pos, editor, null);
 857         }
 858 
 859         /**
 860          * Calls linkActivated on the associated JEditorPane
 861          * if the given position represents a link. If this was the result
 862          * of a mouse click, <code>x</code> and
 863          * <code>y</code> will give the location of the mouse, otherwise
 864          * they will be {@literal <} 0.
 865          *
 866          * @param pos the position
 867          * @param html the editor pane
 868          */
 869         void activateLink(int pos, JEditorPane html, MouseEvent mouseEvent) {
 870             Document doc = html.getDocument();
 871             if (doc instanceof HTMLDocument) {
 872                 HTMLDocument hdoc = (HTMLDocument) doc;
 873                 Element e = hdoc.getCharacterElement(pos);
 874                 AttributeSet a = e.getAttributes();
 875                 AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A);
 876                 HyperlinkEvent linkEvent = null;
 877                 String description;
 878                 int x = -1;
 879                 int y = -1;
 880 
 881                 if (mouseEvent != null) {
 882                     x = mouseEvent.getX();
 883                     y = mouseEvent.getY();


 885 
 886                 if (anchor == null) {
 887                     href = getMapHREF(html, hdoc, e, a, pos, x, y);
 888                 }
 889                 else {
 890                     href = (String)anchor.getAttribute(HTML.Attribute.HREF);
 891                 }
 892 
 893                 if (href != null) {
 894                     linkEvent = createHyperlinkEvent(html, hdoc, href, anchor,
 895                                                      e, mouseEvent);
 896                 }
 897                 if (linkEvent != null) {
 898                     html.fireHyperlinkUpdate(linkEvent);
 899                 }
 900             }
 901         }
 902 
 903         /**
 904          * Creates and returns a new instance of HyperlinkEvent. If
 905          * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
 906          * will be created.
 907          */
 908         HyperlinkEvent createHyperlinkEvent(JEditorPane html,
 909                                             HTMLDocument hdoc, String href,
 910                                             AttributeSet anchor,
 911                                             Element element,
 912                                             MouseEvent mouseEvent) {
 913             URL u;
 914             try {
 915                 URL base = hdoc.getBase();
 916                 u = new URL(base, href);
 917                 // Following is a workaround for 1.2, in which
 918                 // new URL("file://...", "#...") causes the filename to
 919                 // be lost.
 920                 if (href != null && "file".equals(u.getProtocol()) &&
 921                     href.startsWith("#")) {
 922                     String baseFile = base.getFile();
 923                     String newFile = u.getFile();
 924                     if (baseFile != null && newFile != null &&
 925                         !newFile.startsWith(baseFile)) {


 988      * implementation provided by this editor kit.
 989      */
 990     public abstract static class Parser {
 991         /**
 992          * Parse the given stream and drive the given callback
 993          * with the results of the parse.  This method should
 994          * be implemented to be thread-safe.
 995          *
 996          * @param r a reader
 997          * @param cb a parser callback
 998          * @param ignoreCharSet if {@code true} charset is ignoring
 999          * @throws IOException if an I/O exception occurs
1000          */
1001         public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
1002 
1003     }
1004 
1005     /**
1006      * The result of parsing drives these callback methods.
1007      * The open and close actions should be balanced.  The
1008      * <code>flush</code> method will be the last method
1009      * called, to give the receiver a chance to flush any
1010      * pending data into the document.
1011      * <p>Refer to DocumentParser, the default parser used, for further
1012      * information on the contents of the AttributeSets, the positions, and
1013      * other info.
1014      *
1015      * @see javax.swing.text.html.parser.DocumentParser
1016      */
1017     public static class ParserCallback {
1018         /**
1019          * This is passed as an attribute in the attributeset to indicate
1020          * the element is implied eg, the string '&lt;&gt;foo&lt;\t&gt;'
1021          * contains an implied html element and an implied body element.
1022          *
1023          * @since 1.3
1024          */
1025         public static final Object IMPLIED = "_implied_";
1026 
1027         /**
1028          * The last method called on the reader. It allows


1083          *
1084          * @param t an HTML tag
1085          * @param a a set of attributes
1086          * @param pos a position
1087          */
1088         public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
1089         }
1090 
1091         /**
1092          * Callback from the parser. Route to the appropriate
1093          * handler for the error.
1094          *
1095          * @param errorMsg a error message
1096          * @param pos a position
1097          */
1098         public void handleError(String errorMsg, int pos) {
1099         }
1100 
1101         /**
1102          * This is invoked after the stream has been parsed, but before
1103          * <code>flush</code>. <code>eol</code> will be one of \n, \r
1104          * or \r\n, which ever is encountered the most in parsing the
1105          * stream.
1106          *
1107          * @param eol value of eol
1108          *
1109          * @since 1.3
1110          */
1111         public void handleEndOfLineString(String eol) {
1112         }
1113     }
1114 
1115     /**
1116      * A factory to build views for HTML.  The following
1117      * table describes what this factory will build by
1118      * default.
1119      *
1120      * <table summary="Describes the tag and view created by this factory by default">
1121      * <tr>
1122      * <th align=left>Tag<th align=left>View created
1123      * </tr><tr>


1560      * An abstract Action providing some convenience methods that may
1561      * be useful in inserting HTML into an existing document.
1562      * <p>NOTE: None of the convenience methods obtain a lock on the
1563      * document. If you have another thread modifying the text these
1564      * methods may have inconsistent behavior, or return the wrong thing.
1565      */
1566     @SuppressWarnings("serial") // Superclass is not serializable across versions
1567     public abstract static class HTMLTextAction extends StyledTextAction {
1568 
1569         /**
1570          * Creates a new HTMLTextAction from a string action name.
1571          *
1572          * @param name the name of the action
1573          */
1574         public HTMLTextAction(String name) {
1575             super(name);
1576         }
1577 
1578         /**
1579          * @param e the JEditorPane
1580          * @return HTMLDocument of <code>e</code>.
1581          */
1582         protected HTMLDocument getHTMLDocument(JEditorPane e) {
1583             Document d = e.getDocument();
1584             if (d instanceof HTMLDocument) {
1585                 return (HTMLDocument) d;
1586             }
1587             throw new IllegalArgumentException("document must be HTMLDocument");
1588         }
1589 
1590         /**
1591          * @param e the JEditorPane
1592          * @return HTMLEditorKit for <code>e</code>.
1593          */
1594         protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
1595             EditorKit k = e.getEditorKit();
1596             if (k instanceof HTMLEditorKit) {
1597                 return (HTMLEditorKit) k;
1598             }
1599             throw new IllegalArgumentException("EditorKit must be HTMLEditorKit");
1600         }
1601 
1602         /**
1603          * Returns an array of the Elements that contain <code>offset</code>.
1604          * The first elements corresponds to the root.
1605          *
1606          * @param doc an instance of HTMLDocument
1607          * @param offset value of offset
1608          * @return an array of the Elements that contain <code>offset</code>
1609          */
1610         protected Element[] getElementsAt(HTMLDocument doc, int offset) {
1611             return getElementsAt(doc.getDefaultRootElement(), offset, 0);
1612         }
1613 
1614         /**
1615          * Recursive method used by getElementsAt.
1616          */
1617         private Element[] getElementsAt(Element parent, int offset,
1618                                         int depth) {
1619             if (parent.isLeaf()) {
1620                 Element[] retValue = new Element[depth + 1];
1621                 retValue[depth] = parent;
1622                 return retValue;
1623             }
1624             Element[] retValue = getElementsAt(parent.getElement
1625                           (parent.getElementIndex(offset)), offset, depth + 1);
1626             retValue[depth] = parent;
1627             return retValue;
1628         }
1629 
1630         /**
1631          * Returns number of elements, starting at the deepest leaf, needed
1632          * to get to an element representing <code>tag</code>. This will
1633          * return -1 if no elements is found representing <code>tag</code>,
1634          * or 0 if the parent of the leaf at <code>offset</code> represents
1635          * <code>tag</code>.
1636          *
1637          * @param doc an instance of HTMLDocument
1638          * @param offset an offset to start from
1639          * @param tag tag to represent
1640          * @return number of elements
1641          */
1642         protected int elementCountToTag(HTMLDocument doc, int offset,
1643                                         HTML.Tag tag) {
1644             int depth = -1;
1645             Element e = doc.getCharacterElement(offset);
1646             while (e != null && e.getAttributes().getAttribute
1647                    (StyleConstants.NameAttribute) != tag) {
1648                 e = e.getParentElement();
1649                 depth++;
1650             }
1651             if (e == null) {
1652                 return -1;
1653             }
1654             return depth;
1655         }
1656 
1657         /**
1658          * Returns the deepest element at <code>offset</code> matching
1659          * <code>tag</code>.
1660          *
1661          * @param doc an instance of HTMLDocument
1662          * @param offset the specified offset &gt;= 0
1663          * @param tag an instance of HTML.Tag
1664          *
1665          * @return the deepest element
1666          */
1667         protected Element findElementMatchingTag(HTMLDocument doc, int offset,
1668                                                  HTML.Tag tag) {
1669             Element e = doc.getDefaultRootElement();
1670             Element lastMatch = null;
1671             while (e != null) {
1672                 if (e.getAttributes().getAttribute
1673                    (StyleConstants.NameAttribute) == tag) {
1674                     lastMatch = e;
1675                 }
1676                 e = e.getElement(e.getElementIndex(offset));
1677             }
1678             return lastMatch;
1679         }


1856                         e = e.getElement(e.getElementIndex(offset));
1857                         pops++;
1858                     }
1859 
1860                     // And how many pushes
1861                     e = commonParent;
1862                     offset++;
1863                     while (e != null && e != insertElement) {
1864                         e = e.getElement(e.getElementIndex(offset));
1865                         pushes++;
1866                     }
1867                 }
1868                 pops = Math.max(0, pops - 1);
1869 
1870                 // And insert!
1871                 insertHTML(editor, doc, offset, html, pops, pushes, addTag);
1872             }
1873         }
1874 
1875         /**
1876          * If there is an Element with name <code>tag</code> at
1877          * <code>offset</code>, this will invoke either insertAtBoundary
1878          * or <code>insertHTML</code>. This returns true if there is
1879          * a match, and one of the inserts is invoked.
1880          */
1881         /*protected*/
1882         boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
1883                               int offset, HTML.Tag tag, HTML.Tag addTag) {
1884             Element e = findElementMatchingTag(doc, offset, tag);
1885             if (e != null && e.getStartOffset() == offset) {
1886                 insertAtBoundary(editor, doc, offset, e, html,
1887                                  tag, addTag);
1888                 return true;
1889             }
1890             else if (offset > 0) {
1891                 int depth = elementCountToTag(doc, offset - 1, tag);
1892                 if (depth != -1) {
1893                     insertHTML(editor, doc, offset, html, depth, 0, addTag);
1894                     return true;
1895                 }
1896             }
1897             return false;
1898         }


2311             } finally {
2312                 unlock(lock);
2313             }
2314         }
2315 
2316         private View getView(View parent, Element elem, int start) {
2317             if (parent.getElement() == elem) {
2318                 return parent;
2319             }
2320             int index = parent.getViewIndex(start, Position.Bias.Forward);
2321 
2322             if (index != -1 && index < parent.getViewCount()) {
2323                 return getView(parent.getView(index), elem, start);
2324             }
2325             return null;
2326         }
2327 
2328         /*
2329          * If possible acquires a lock on the Document.  If a lock has been
2330          * obtained a key will be retured that should be passed to
2331          * <code>unlock</code>.
2332          */
2333         private Object lock(JEditorPane editor) {
2334             Document document = editor.getDocument();
2335 
2336             if (document instanceof AbstractDocument) {
2337                 ((AbstractDocument)document).readLock();
2338                 return document;
2339             }
2340             return null;
2341         }
2342 
2343         /*
2344          * Releases a lock previously obtained via <code>lock</code>.
2345          */
2346         private void unlock(Object key) {
2347             if (key != null) {
2348                 ((AbstractDocument)key).readUnlock();
2349             }
2350         }
2351 
2352         /*
2353          * The operation to perform when this action is triggered.
2354          */
2355         public void actionPerformed(ActionEvent e) {
2356 
2357             JTextComponent c = getTextComponent(e);
2358             if (c.isEditable() || !(c instanceof JEditorPane)) {
2359                 return;
2360             }
2361             JEditorPane editor = (JEditorPane)c;
2362 
2363             Document d = editor.getDocument();
2364             if (d == null || !(d instanceof HTMLDocument)) {




  51  * The &lt;applet&gt; tag is not supported, but some support is provided
  52  * for the &lt;object&gt; tag.
  53  * <p>
  54  * There are several goals of the HTML EditorKit provided, that have
  55  * an effect upon the way that HTML is modeled.  These
  56  * have influenced its design in a substantial way.
  57  * <dl>
  58  * <dt>
  59  * Support editing
  60  * <dd>
  61  * It might seem fairly obvious that a plug-in for JEditorPane
  62  * should provide editing support, but that fact has several
  63  * design considerations.  There are a substantial number of HTML
  64  * documents that don't properly conform to an HTML specification.
  65  * These must be normalized somewhat into a correct form if one
  66  * is to edit them.  Additionally, users don't like to be presented
  67  * with an excessive amount of structure editing, so using traditional
  68  * text editing gestures is preferred over using the HTML structure
  69  * exactly as defined in the HTML document.
  70  * <p>
  71  * The modeling of HTML is provided by the class {@code HTMLDocument}.
  72  * Its documentation describes the details of how the HTML is modeled.
  73  * The editing support leverages heavily off of the text package.
  74  *
  75  * <dt>
  76  * Extendable/Scalable
  77  * <dd>
  78  * To maximize the usefulness of this kit, a great deal of effort
  79  * has gone into making it extendable.  These are some of the
  80  * features.
  81  * <ol>
  82  *   <li>
  83  *   The parser is replaceable.  The default parser is the Hot Java
  84  *   parser which is DTD based.  A different DTD can be used, or an
  85  *   entirely different parser can be used.  To change the parser,
  86  *   reimplement the getParser method.  The default parser is
  87  *   dynamically loaded when first asked for, so the class files
  88  *   will never be loaded if an alternative parser is used.  The
  89  *   default parser is in a separate package called parser below
  90  *   this package.
  91  *   <li>
  92  *   The parser drives the ParserCallback, which is provided by
  93  *   HTMLDocument.  To change the callback, subclass HTMLDocument
  94  *   and reimplement the createDefaultDocument method to return
  95  *   document that produces a different reader.  The reader controls
  96  *   how the document is structured.  Although the Document provides
  97  *   HTML support by default, there is nothing preventing support of
  98  *   non-HTML tags that result in alternative element structures.
  99  *   <li>
 100  *   The default view of the models are provided as a hierarchy of
 101  *   View implementations, so one can easily customize how a particular
 102  *   element is displayed or add capabilities for new kinds of elements
 103  *   by providing new View implementations.  The default set of views
 104  *   are provided by the {@code HTMLFactory} class.  This can
 105  *   be easily changed by subclassing or replacing the HTMLFactory
 106  *   and reimplementing the getViewFactory method to return the alternative
 107  *   factory.
 108  *   <li>
 109  *   The View implementations work primarily off of CSS attributes,
 110  *   which are kept in the views.  This makes it possible to have
 111  *   multiple views mapped over the same model that appear substantially
 112  *   different.  This can be especially useful for printing.  For
 113  *   most HTML attributes, the HTML attributes are converted to CSS
 114  *   attributes for display.  This helps make the View implementations
 115  *   more general purpose
 116  * </ol>
 117  *
 118  * <dt>
 119  * Asynchronous Loading
 120  * <dd>
 121  * Larger documents involve a lot of parsing and take some time
 122  * to load.  By default, this kit produces documents that will be
 123  * loaded asynchronously if loaded using {@code JEditorPane.setPage}.
 124  * This is controlled by a property on the document.  The method
 125  * {@link #createDefaultDocument createDefaultDocument} can
 126  * be overriden to change this.  The batching of work is done
 127  * by the {@code HTMLDocument.HTMLReader} class.  The actual
 128  * work is done by the {@code DefaultStyledDocument} and
 129  * {@code AbstractDocument} classes in the text package.
 130  *
 131  * <dt>
 132  * Customization from current LAF
 133  * <dd>
 134  * HTML provides a well known set of features without exactly
 135  * specifying the display characteristics.  Swing has a theme
 136  * mechanism for its look-and-feel implementations.  It is desirable
 137  * for the look-and-feel to feed display characteristics into the
 138  * HTML views.  An user with poor vision for example would want
 139  * high contrast and larger than typical fonts.
 140  * <p>
 141  * The support for this is provided by the {@code StyleSheet}
 142  * class.  The presentation of the HTML can be heavily influenced
 143  * by the setting of the StyleSheet property on the EditorKit.
 144  *
 145  * <dt>
 146  * Not lossy
 147  * <dd>
 148  * An EditorKit has the ability to be read and save documents.
 149  * It is generally the most pleasing to users if there is no loss
 150  * of data between the two operation.  The policy of the HTMLEditorKit
 151  * will be to store things not recognized or not necessarily visible
 152  * so they can be subsequently written out.  The model of the HTML document
 153  * should therefore contain all information discovered while reading the
 154  * document.  This is constrained in some ways by the need to support
 155  * editing (i.e. incorrect documents sometimes must be normalized).
 156  * The guiding principle is that information shouldn't be lost, but
 157  * some might be synthesized to produce a more correct model or it might
 158  * be rearranged.
 159  * </dl>
 160  *
 161  * @author  Timothy Prinzing
 162  */
 163 @SuppressWarnings("serial") // Same-version serialization only
 164 public class HTMLEditorKit extends StyledEditorKit implements Accessible {
 165 
 166     private JEditorPane theEditor;
 167 
 168     /**
 169      * Constructs an HTMLEditorKit, creates a StyleContext,
 170      * and loads the style sheet.
 171      */
 172     public HTMLEditorKit() {
 173 
 174     }
 175 
 176     /**
 177      * Get the MIME type of the data that this
 178      * kit represents support for.  This kit supports
 179      * the type {@code text/html}.
 180      *
 181      * @return the type
 182      */
 183     public String getContentType() {
 184         return "text/html";
 185     }
 186 
 187     /**
 188      * Fetch a factory that is suitable for producing
 189      * views of any models that are produced by this
 190      * kit.
 191      *
 192      * @return the factory
 193      */
 194     public ViewFactory getViewFactory() {
 195         return defaultFactory;
 196     }
 197 
 198     /**
 199      * Create an uninitialized text storage model


 214         return doc;
 215     }
 216 
 217     /**
 218      * Try to get an HTML parser from the document.  If no parser is set for
 219      * the document, return the editor kit's default parser.  It is an error
 220      * if no parser could be obtained from the editor kit.
 221      */
 222     private Parser ensureParser(HTMLDocument doc) throws IOException {
 223         Parser p = doc.getParser();
 224         if (p == null) {
 225             p = getParser();
 226         }
 227         if (p == null) {
 228             throw new IOException("Can't load parser");
 229         }
 230         return p;
 231     }
 232 
 233     /**
 234      * Inserts content from the given stream. If {@code doc} is
 235      * an instance of HTMLDocument, this will read
 236      * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
 237      * the body Element, if you do not insert into the body an exception will
 238      * be thrown. When inserting into a non-empty document all tags outside
 239      * of the body (head, title) will be dropped.
 240      *
 241      * @param in the stream to read from
 242      * @param doc the destination for the insertion
 243      * @param pos the location in the document to place the
 244      *   content
 245      * @exception IOException on any I/O error
 246      * @exception BadLocationException if pos represents an invalid
 247      *   location within the document
 248      * @exception RuntimeException (will eventually be a BadLocationException)
 249      *            if pos is invalid
 250      */
 251     public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
 252 
 253         if (doc instanceof HTMLDocument) {
 254             HTMLDocument hdoc = (HTMLDocument) doc;


 428                 new PrivilegedAction<InputStream>() {
 429                     public InputStream run() {
 430                         return HTMLEditorKit.class.getResourceAsStream(name);
 431                     }
 432                 });
 433     }
 434 
 435     /**
 436      * Fetches the command list for the editor.  This is
 437      * the list of commands supported by the superclass
 438      * augmented by the collection of commands defined
 439      * locally for style operations.
 440      *
 441      * @return the command list
 442      */
 443     public Action[] getActions() {
 444         return TextAction.augmentList(super.getActions(), defaultActions);
 445     }
 446 
 447     /**
 448      * Copies the key/values in {@code element}s AttributeSet into
 449      * {@code set}. This does not copy component, icon, or element
 450      * names attributes. Subclasses may wish to refine what is and what
 451      * isn't copied here. But be sure to first remove all the attributes that
 452      * are in {@code set}.<p>
 453      * This is called anytime the caret moves over a different location.
 454      *
 455      */
 456     protected void createInputAttributes(Element element,
 457                                          MutableAttributeSet set) {
 458         set.removeAttributes(set);
 459         set.addAttributes(element.getAttributes());
 460         set.removeAttribute(StyleConstants.ComposedTextAttribute);
 461 
 462         Object o = set.getAttribute(StyleConstants.NameAttribute);
 463         if (o instanceof HTML.Tag) {
 464             HTML.Tag tag = (HTML.Tag)o;
 465             // PENDING: we need a better way to express what shouldn't be
 466             // copied when editing...
 467             if(tag == HTML.Tag.IMG) {
 468                 // Remove the related image attributes, src, width, height
 469                 set.removeAttribute(HTML.Attribute.SRC);
 470                 set.removeAttribute(HTML.Attribute.HEIGHT);
 471                 set.removeAttribute(HTML.Attribute.WIDTH);
 472                 set.addAttribute(StyleConstants.NameAttribute,


 540      *
 541      * @since 1.3
 542      */
 543     public void setLinkCursor(Cursor cursor) {
 544         linkCursor = cursor;
 545     }
 546 
 547     /**
 548      * Returns the cursor to use over hyper links.
 549      *
 550      * @return the cursor
 551      *
 552      * @since 1.3
 553      */
 554     public Cursor getLinkCursor() {
 555         return linkCursor;
 556     }
 557 
 558     /**
 559      * Indicates whether an html form submission is processed automatically
 560      * or only {@code FormSubmitEvent} is fired.
 561      *
 562      * @return true  if html form submission is processed automatically,
 563      *         false otherwise.
 564      *
 565      * @see #setAutoFormSubmission
 566      * @since 1.5
 567      */
 568     public boolean isAutoFormSubmission() {
 569         return isAutoFormSubmission;
 570     }
 571 
 572     /**
 573      * Specifies if an html form submission is processed
 574      * automatically or only {@code FormSubmitEvent} is fired.
 575      * By default it is set to true.
 576      *
 577      * @param isAuto if {@code true}, html form submission is processed automatically.
 578      *
 579      * @see #isAutoFormSubmission()
 580      * @see FormSubmitEvent
 581      * @since 1.5
 582      */
 583     public void setAutoFormSubmission(boolean isAuto) {
 584         isAutoFormSubmission = isAuto;
 585     }
 586 
 587     /**
 588      * Creates a copy of the editor kit.
 589      *
 590      * @return the copy
 591      */
 592     public Object clone() {
 593         HTMLEditorKit o = (HTMLEditorKit)super.clone();
 594         if (o != null) {


 795                                     (Rectangle)rBounds : rBounds.getBounds());
 796                     } catch (BadLocationException ble) {
 797                         bounds = null;
 798                     }
 799                     if (bounds != null) {
 800                         AttributeSet area = m.getArea(x - bounds.x,
 801                                                       y - bounds.y,
 802                                                       bounds.width,
 803                                                       bounds.height);
 804                         if (area != null) {
 805                             return (String)area.getAttribute(HTML.Attribute.
 806                                                              HREF);
 807                         }
 808                     }
 809                 }
 810             }
 811             return null;
 812         }
 813 
 814         /**
 815          * Returns true if the View representing {@code e} contains
 816          * the location {@code x}, {@code y}. {@code offset}
 817          * gives the offset into the Document to check for.
 818          */
 819         private boolean doesElementContainLocation(JEditorPane editor,
 820                                                    Element e, int offset,
 821                                                    int x, int y) {
 822             if (e != null && offset > 0 && e.getStartOffset() == offset) {
 823                 try {
 824                     TextUI ui = editor.getUI();
 825                     Shape s1 = ui.modelToView(editor, offset,
 826                                               Position.Bias.Forward);
 827                     if (s1 == null) {
 828                         return false;
 829                     }
 830                     Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle)s1 :
 831                                     s1.getBounds();
 832                     Shape s2 = ui.modelToView(editor, e.getEndOffset(),
 833                                               Position.Bias.Backward);
 834                     if (s2 != null) {
 835                         Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle)s2 :
 836                                     s2.getBounds();


 842             }
 843             return true;
 844         }
 845 
 846         /**
 847          * Calls linkActivated on the associated JEditorPane
 848          * if the given position represents a link.<p>This is implemented
 849          * to forward to the method with the same name, but with the following
 850          * args both == -1.
 851          *
 852          * @param pos the position
 853          * @param editor the editor pane
 854          */
 855         protected void activateLink(int pos, JEditorPane editor) {
 856             activateLink(pos, editor, null);
 857         }
 858 
 859         /**
 860          * Calls linkActivated on the associated JEditorPane
 861          * if the given position represents a link. If this was the result
 862          * of a mouse click, {@code x} and
 863          * {@code y} will give the location of the mouse, otherwise
 864          * they will be {@literal <} 0.
 865          *
 866          * @param pos the position
 867          * @param html the editor pane
 868          */
 869         void activateLink(int pos, JEditorPane html, MouseEvent mouseEvent) {
 870             Document doc = html.getDocument();
 871             if (doc instanceof HTMLDocument) {
 872                 HTMLDocument hdoc = (HTMLDocument) doc;
 873                 Element e = hdoc.getCharacterElement(pos);
 874                 AttributeSet a = e.getAttributes();
 875                 AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A);
 876                 HyperlinkEvent linkEvent = null;
 877                 String description;
 878                 int x = -1;
 879                 int y = -1;
 880 
 881                 if (mouseEvent != null) {
 882                     x = mouseEvent.getX();
 883                     y = mouseEvent.getY();


 885 
 886                 if (anchor == null) {
 887                     href = getMapHREF(html, hdoc, e, a, pos, x, y);
 888                 }
 889                 else {
 890                     href = (String)anchor.getAttribute(HTML.Attribute.HREF);
 891                 }
 892 
 893                 if (href != null) {
 894                     linkEvent = createHyperlinkEvent(html, hdoc, href, anchor,
 895                                                      e, mouseEvent);
 896                 }
 897                 if (linkEvent != null) {
 898                     html.fireHyperlinkUpdate(linkEvent);
 899                 }
 900             }
 901         }
 902 
 903         /**
 904          * Creates and returns a new instance of HyperlinkEvent. If
 905          * {@code hdoc} is a frame document a HTMLFrameHyperlinkEvent
 906          * will be created.
 907          */
 908         HyperlinkEvent createHyperlinkEvent(JEditorPane html,
 909                                             HTMLDocument hdoc, String href,
 910                                             AttributeSet anchor,
 911                                             Element element,
 912                                             MouseEvent mouseEvent) {
 913             URL u;
 914             try {
 915                 URL base = hdoc.getBase();
 916                 u = new URL(base, href);
 917                 // Following is a workaround for 1.2, in which
 918                 // new URL("file://...", "#...") causes the filename to
 919                 // be lost.
 920                 if (href != null && "file".equals(u.getProtocol()) &&
 921                     href.startsWith("#")) {
 922                     String baseFile = base.getFile();
 923                     String newFile = u.getFile();
 924                     if (baseFile != null && newFile != null &&
 925                         !newFile.startsWith(baseFile)) {


 988      * implementation provided by this editor kit.
 989      */
 990     public abstract static class Parser {
 991         /**
 992          * Parse the given stream and drive the given callback
 993          * with the results of the parse.  This method should
 994          * be implemented to be thread-safe.
 995          *
 996          * @param r a reader
 997          * @param cb a parser callback
 998          * @param ignoreCharSet if {@code true} charset is ignoring
 999          * @throws IOException if an I/O exception occurs
1000          */
1001         public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
1002 
1003     }
1004 
1005     /**
1006      * The result of parsing drives these callback methods.
1007      * The open and close actions should be balanced.  The
1008      * {@code flush} method will be the last method
1009      * called, to give the receiver a chance to flush any
1010      * pending data into the document.
1011      * <p>Refer to DocumentParser, the default parser used, for further
1012      * information on the contents of the AttributeSets, the positions, and
1013      * other info.
1014      *
1015      * @see javax.swing.text.html.parser.DocumentParser
1016      */
1017     public static class ParserCallback {
1018         /**
1019          * This is passed as an attribute in the attributeset to indicate
1020          * the element is implied eg, the string '&lt;&gt;foo&lt;\t&gt;'
1021          * contains an implied html element and an implied body element.
1022          *
1023          * @since 1.3
1024          */
1025         public static final Object IMPLIED = "_implied_";
1026 
1027         /**
1028          * The last method called on the reader. It allows


1083          *
1084          * @param t an HTML tag
1085          * @param a a set of attributes
1086          * @param pos a position
1087          */
1088         public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
1089         }
1090 
1091         /**
1092          * Callback from the parser. Route to the appropriate
1093          * handler for the error.
1094          *
1095          * @param errorMsg a error message
1096          * @param pos a position
1097          */
1098         public void handleError(String errorMsg, int pos) {
1099         }
1100 
1101         /**
1102          * This is invoked after the stream has been parsed, but before
1103          * {@code flush}. {@code eol} will be one of \n, \r
1104          * or \r\n, which ever is encountered the most in parsing the
1105          * stream.
1106          *
1107          * @param eol value of eol
1108          *
1109          * @since 1.3
1110          */
1111         public void handleEndOfLineString(String eol) {
1112         }
1113     }
1114 
1115     /**
1116      * A factory to build views for HTML.  The following
1117      * table describes what this factory will build by
1118      * default.
1119      *
1120      * <table summary="Describes the tag and view created by this factory by default">
1121      * <tr>
1122      * <th align=left>Tag<th align=left>View created
1123      * </tr><tr>


1560      * An abstract Action providing some convenience methods that may
1561      * be useful in inserting HTML into an existing document.
1562      * <p>NOTE: None of the convenience methods obtain a lock on the
1563      * document. If you have another thread modifying the text these
1564      * methods may have inconsistent behavior, or return the wrong thing.
1565      */
1566     @SuppressWarnings("serial") // Superclass is not serializable across versions
1567     public abstract static class HTMLTextAction extends StyledTextAction {
1568 
1569         /**
1570          * Creates a new HTMLTextAction from a string action name.
1571          *
1572          * @param name the name of the action
1573          */
1574         public HTMLTextAction(String name) {
1575             super(name);
1576         }
1577 
1578         /**
1579          * @param e the JEditorPane
1580          * @return HTMLDocument of {@code e}.
1581          */
1582         protected HTMLDocument getHTMLDocument(JEditorPane e) {
1583             Document d = e.getDocument();
1584             if (d instanceof HTMLDocument) {
1585                 return (HTMLDocument) d;
1586             }
1587             throw new IllegalArgumentException("document must be HTMLDocument");
1588         }
1589 
1590         /**
1591          * @param e the JEditorPane
1592          * @return HTMLEditorKit for {@code e}.
1593          */
1594         protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
1595             EditorKit k = e.getEditorKit();
1596             if (k instanceof HTMLEditorKit) {
1597                 return (HTMLEditorKit) k;
1598             }
1599             throw new IllegalArgumentException("EditorKit must be HTMLEditorKit");
1600         }
1601 
1602         /**
1603          * Returns an array of the Elements that contain {@code offset}.
1604          * The first elements corresponds to the root.
1605          *
1606          * @param doc an instance of HTMLDocument
1607          * @param offset value of offset
1608          * @return an array of the Elements that contain {@code offset}
1609          */
1610         protected Element[] getElementsAt(HTMLDocument doc, int offset) {
1611             return getElementsAt(doc.getDefaultRootElement(), offset, 0);
1612         }
1613 
1614         /**
1615          * Recursive method used by getElementsAt.
1616          */
1617         private Element[] getElementsAt(Element parent, int offset,
1618                                         int depth) {
1619             if (parent.isLeaf()) {
1620                 Element[] retValue = new Element[depth + 1];
1621                 retValue[depth] = parent;
1622                 return retValue;
1623             }
1624             Element[] retValue = getElementsAt(parent.getElement
1625                           (parent.getElementIndex(offset)), offset, depth + 1);
1626             retValue[depth] = parent;
1627             return retValue;
1628         }
1629 
1630         /**
1631          * Returns number of elements, starting at the deepest leaf, needed
1632          * to get to an element representing {@code tag}. This will
1633          * return -1 if no elements is found representing {@code tag},
1634          * or 0 if the parent of the leaf at {@code offset} represents
1635          * {@code tag}.
1636          *
1637          * @param doc an instance of HTMLDocument
1638          * @param offset an offset to start from
1639          * @param tag tag to represent
1640          * @return number of elements
1641          */
1642         protected int elementCountToTag(HTMLDocument doc, int offset,
1643                                         HTML.Tag tag) {
1644             int depth = -1;
1645             Element e = doc.getCharacterElement(offset);
1646             while (e != null && e.getAttributes().getAttribute
1647                    (StyleConstants.NameAttribute) != tag) {
1648                 e = e.getParentElement();
1649                 depth++;
1650             }
1651             if (e == null) {
1652                 return -1;
1653             }
1654             return depth;
1655         }
1656 
1657         /**
1658          * Returns the deepest element at {@code offset} matching
1659          * {@code tag}.
1660          *
1661          * @param doc an instance of HTMLDocument
1662          * @param offset the specified offset &gt;= 0
1663          * @param tag an instance of HTML.Tag
1664          *
1665          * @return the deepest element
1666          */
1667         protected Element findElementMatchingTag(HTMLDocument doc, int offset,
1668                                                  HTML.Tag tag) {
1669             Element e = doc.getDefaultRootElement();
1670             Element lastMatch = null;
1671             while (e != null) {
1672                 if (e.getAttributes().getAttribute
1673                    (StyleConstants.NameAttribute) == tag) {
1674                     lastMatch = e;
1675                 }
1676                 e = e.getElement(e.getElementIndex(offset));
1677             }
1678             return lastMatch;
1679         }


1856                         e = e.getElement(e.getElementIndex(offset));
1857                         pops++;
1858                     }
1859 
1860                     // And how many pushes
1861                     e = commonParent;
1862                     offset++;
1863                     while (e != null && e != insertElement) {
1864                         e = e.getElement(e.getElementIndex(offset));
1865                         pushes++;
1866                     }
1867                 }
1868                 pops = Math.max(0, pops - 1);
1869 
1870                 // And insert!
1871                 insertHTML(editor, doc, offset, html, pops, pushes, addTag);
1872             }
1873         }
1874 
1875         /**
1876          * If there is an Element with name {@code tag} at
1877          * {@code offset}, this will invoke either insertAtBoundary
1878          * or {@code insertHTML}. This returns true if there is
1879          * a match, and one of the inserts is invoked.
1880          */
1881         /*protected*/
1882         boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
1883                               int offset, HTML.Tag tag, HTML.Tag addTag) {
1884             Element e = findElementMatchingTag(doc, offset, tag);
1885             if (e != null && e.getStartOffset() == offset) {
1886                 insertAtBoundary(editor, doc, offset, e, html,
1887                                  tag, addTag);
1888                 return true;
1889             }
1890             else if (offset > 0) {
1891                 int depth = elementCountToTag(doc, offset - 1, tag);
1892                 if (depth != -1) {
1893                     insertHTML(editor, doc, offset, html, depth, 0, addTag);
1894                     return true;
1895                 }
1896             }
1897             return false;
1898         }


2311             } finally {
2312                 unlock(lock);
2313             }
2314         }
2315 
2316         private View getView(View parent, Element elem, int start) {
2317             if (parent.getElement() == elem) {
2318                 return parent;
2319             }
2320             int index = parent.getViewIndex(start, Position.Bias.Forward);
2321 
2322             if (index != -1 && index < parent.getViewCount()) {
2323                 return getView(parent.getView(index), elem, start);
2324             }
2325             return null;
2326         }
2327 
2328         /*
2329          * If possible acquires a lock on the Document.  If a lock has been
2330          * obtained a key will be retured that should be passed to
2331          * {@code unlock}.
2332          */
2333         private Object lock(JEditorPane editor) {
2334             Document document = editor.getDocument();
2335 
2336             if (document instanceof AbstractDocument) {
2337                 ((AbstractDocument)document).readLock();
2338                 return document;
2339             }
2340             return null;
2341         }
2342 
2343         /*
2344          * Releases a lock previously obtained via {@code lock}.
2345          */
2346         private void unlock(Object key) {
2347             if (key != null) {
2348                 ((AbstractDocument)key).readUnlock();
2349             }
2350         }
2351 
2352         /*
2353          * The operation to perform when this action is triggered.
2354          */
2355         public void actionPerformed(ActionEvent e) {
2356 
2357             JTextComponent c = getTextComponent(e);
2358             if (c.isEditable() || !(c instanceof JEditorPane)) {
2359                 return;
2360             }
2361             JEditorPane editor = (JEditorPane)c;
2362 
2363             Document d = editor.getDocument();
2364             if (d == null || !(d instanceof HTMLDocument)) {


< prev index next >