| /* HTMLEditorKit.java -- | |
| Copyright (C) 2005 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package javax.swing.text.html; | |
| import java.awt.event.ActionEvent; | |
| import java.awt.event.MouseAdapter; | |
| import java.awt.event.MouseEvent; | |
| import java.awt.event.MouseMotionListener; | |
| import java.awt.Cursor; | |
| import java.awt.Point; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.Reader; | |
| import java.io.Serializable; | |
| import java.io.StringReader; | |
| import java.io.Writer; | |
| import java.net.MalformedURLException; | |
| import java.net.URL; | |
| import javax.accessibility.Accessible; | |
| import javax.accessibility.AccessibleContext; | |
| import javax.swing.Action; | |
| import javax.swing.JEditorPane; | |
| import javax.swing.SwingUtilities; | |
| import javax.swing.event.HyperlinkEvent; | |
| import javax.swing.text.AttributeSet; | |
| import javax.swing.text.BadLocationException; | |
| import javax.swing.text.Document; | |
| import javax.swing.text.EditorKit; | |
| import javax.swing.text.Element; | |
| import javax.swing.text.MutableAttributeSet; | |
| import javax.swing.text.StyleConstants; | |
| import javax.swing.text.StyledDocument; | |
| import javax.swing.text.StyledEditorKit; | |
| import javax.swing.text.TextAction; | |
| import javax.swing.text.View; | |
| import javax.swing.text.ViewFactory; | |
| import javax.swing.text.html.parser.ParserDelegator; | |
| /* Move these imports here after javax.swing.text.html to make it compile | |
| with jikes. */ | |
| import gnu.javax.swing.text.html.parser.GnuParserDelegator; | |
| import gnu.javax.swing.text.html.parser.HTML_401F; | |
| /** | |
| * @author Lillian Angel (langel at redhat dot com) | |
| */ | |
| public class HTMLEditorKit | |
| extends StyledEditorKit | |
| implements Serializable, Cloneable, Accessible | |
| { | |
| /** | |
| * Fires the hyperlink events on the associated component | |
| * when needed. | |
| */ | |
| public static class LinkController | |
| extends MouseAdapter | |
| implements MouseMotionListener, Serializable | |
| { | |
| /** | |
| * The element of the last anchor tag. | |
| */ | |
| private Element lastAnchorElement; | |
| /** | |
| * Constructor | |
| */ | |
| public LinkController() | |
| { | |
| super(); | |
| } | |
| /** | |
| * Dispatched when the mouse is clicked. If the component | |
| * is read-only, then the clicked event is used to drive an | |
| * attempt to follow the reference specified by a link | |
| * | |
| * @param e - the mouse event | |
| */ | |
| public void mouseClicked(MouseEvent e) | |
| { | |
| JEditorPane editor = (JEditorPane) e.getSource(); | |
| if (! editor.isEditable() && SwingUtilities.isLeftMouseButton(e)) | |
| { | |
| Point loc = e.getPoint(); | |
| int pos = editor.viewToModel(loc); | |
| if (pos >= 0) | |
| activateLink(pos, editor, e.getX(), e.getY()); | |
| } | |
| } | |
| /** | |
| * Dispatched when the mouse is dragged on a component. | |
| * | |
| * @param e - the mouse event. | |
| */ | |
| public void mouseDragged(MouseEvent e) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Dispatched when the mouse cursor has moved into the component. | |
| * | |
| * @param e - the mouse event. | |
| */ | |
| public void mouseMoved(MouseEvent e) | |
| { | |
| JEditorPane editor = (JEditorPane) e.getSource(); | |
| HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit(); | |
| if (! editor.isEditable()) | |
| { | |
| Document doc = editor.getDocument(); | |
| if (doc instanceof HTMLDocument) | |
| { | |
| Cursor newCursor = kit.getDefaultCursor(); | |
| HTMLDocument htmlDoc = (HTMLDocument) doc; | |
| Point loc = e.getPoint(); | |
| int pos = editor.viewToModel(loc); | |
| Element el = htmlDoc.getCharacterElement(pos); | |
| if (pos < el.getStartOffset() || pos >= el.getEndOffset()) | |
| el = null; | |
| if (el != null) | |
| { | |
| AttributeSet aAtts = (AttributeSet) | |
| el.getAttributes().getAttribute(HTML.Tag.A); | |
| if (aAtts != null) | |
| { | |
| if (el != lastAnchorElement) | |
| { | |
| if (lastAnchorElement != null) | |
| htmlDoc.updateSpecialClass(lastAnchorElement, | |
| HTML.Attribute.DYNAMIC_CLASS, | |
| null); | |
| lastAnchorElement = el; | |
| htmlDoc.updateSpecialClass(el, | |
| HTML.Attribute.DYNAMIC_CLASS, | |
| "hover"); | |
| } | |
| newCursor = kit.getLinkCursor(); | |
| } | |
| else | |
| { | |
| if (lastAnchorElement != null) | |
| htmlDoc.updateSpecialClass(lastAnchorElement, | |
| HTML.Attribute.DYNAMIC_CLASS, | |
| null); | |
| lastAnchorElement = null; | |
| } | |
| } | |
| else | |
| { | |
| if (lastAnchorElement != null) | |
| htmlDoc.updateSpecialClass(lastAnchorElement, | |
| HTML.Attribute.DYNAMIC_CLASS, | |
| null); | |
| lastAnchorElement = null; | |
| } | |
| if (editor.getCursor() != newCursor) | |
| { | |
| editor.setCursor(newCursor); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * If the given position represents a link, then linkActivated is called | |
| * on the JEditorPane. | |
| * | |
| * @param pos the position | |
| * @param editor the editor pane | |
| */ | |
| protected void activateLink(int pos, JEditorPane editor) | |
| { | |
| activateLink(pos, editor); | |
| } | |
| private void activateLink(int pos, JEditorPane editor, int x, int y) | |
| { | |
| // TODO: This is here for future extension for mapped links support. | |
| // For the time beeing we implement simple hyperlinks. | |
| Document doc = editor.getDocument(); | |
| if (doc instanceof HTMLDocument) | |
| { | |
| HTMLDocument htmlDoc = (HTMLDocument) doc; | |
| Element el = htmlDoc.getCharacterElement(pos); | |
| AttributeSet atts = el.getAttributes(); | |
| AttributeSet anchorAtts = | |
| (AttributeSet) atts.getAttribute(HTML.Tag.A); | |
| String href = null; | |
| if (anchorAtts != null) | |
| { | |
| href = (String) anchorAtts.getAttribute(HTML.Attribute.HREF); | |
| htmlDoc.updateSpecialClass(el, HTML.Attribute.PSEUDO_CLASS, | |
| "visited"); | |
| } | |
| else | |
| { | |
| // TODO: Implement link maps here. | |
| } | |
| HyperlinkEvent event = null; | |
| if (href != null) | |
| event = createHyperlinkEvent(editor, htmlDoc, href, | |
| anchorAtts, el); | |
| if (event != null) | |
| editor.fireHyperlinkUpdate(event); | |
| } | |
| } | |
| /** | |
| * Creates a HyperlinkEvent for the specified href and anchor if | |
| * possible. If for some reason this won't work, return null. | |
| * | |
| * @param editor the editor | |
| * @param doc the document | |
| * @param href the href link | |
| * @param anchor the anchor | |
| * @param el the element | |
| * | |
| * @return the hyperlink event, or <code>null</code> if we couldn't | |
| * create one | |
| */ | |
| private HyperlinkEvent createHyperlinkEvent(JEditorPane editor, | |
| HTMLDocument doc, | |
| String href, | |
| AttributeSet anchor, | |
| Element el) | |
| { | |
| URL url; | |
| try | |
| { | |
| URL base = doc.getBase(); | |
| url = new URL(base, href); | |
| } | |
| catch (MalformedURLException ex) | |
| { | |
| url = null; | |
| } | |
| HyperlinkEvent ev; | |
| if (doc.isFrameDocument()) | |
| { | |
| String target = null; | |
| if (anchor != null) | |
| target = (String) anchor.getAttribute(HTML.Attribute.TARGET); | |
| if (target == null || target.equals("")) | |
| target = doc.getBaseTarget(); | |
| if (target == null || target.equals("")) | |
| target = "_self"; | |
| ev = new HTMLFrameHyperlinkEvent(editor, | |
| HyperlinkEvent.EventType.ACTIVATED, | |
| url, href, el, target); | |
| } | |
| else | |
| { | |
| ev = new HyperlinkEvent(editor, HyperlinkEvent.EventType.ACTIVATED, | |
| url, href, el); | |
| } | |
| return ev; | |
| } | |
| } | |
| /** | |
| * This class is used to insert a string of HTML into an existing | |
| * document. At least 2 HTML.Tags need to be supplied. The first Tag (parentTag) | |
| * identifies the parent in the document to add the elements to. The second, (addTag), | |
| * identifies that the first tag should be added to the document as seen in the string. | |
| * The parser will generate all appropriate (opening/closing tags_ even if they are not | |
| * in the HTML string passed in. | |
| */ | |
| public static class InsertHTMLTextAction | |
| extends HTMLTextAction | |
| { | |
| /** | |
| * Tag in HTML to start adding tags from. | |
| */ | |
| protected HTML.Tag addTag; | |
| /** | |
| * Alternate tag in HTML to start adding tags from if parentTag is | |
| * not found and alternateParentTag is not found. | |
| */ | |
| protected HTML.Tag alternateAddTag; | |
| /** | |
| * Alternate tag to check if parentTag is not found. | |
| */ | |
| protected HTML.Tag alternateParentTag; | |
| /** | |
| * HTML to insert. | |
| */ | |
| protected String html; | |
| /** | |
| * Tag to check for in the document. | |
| */ | |
| protected HTML.Tag parentTag; | |
| /** | |
| * Initializes all fields. | |
| * | |
| * @param name - the name of the document. | |
| * @param html - the html to insert | |
| * @param parentTag - the parent tag to check for | |
| * @param addTag - the tag to start adding from | |
| */ | |
| public InsertHTMLTextAction(String name, String html, | |
| HTML.Tag parentTag, HTML.Tag addTag) | |
| { | |
| this(name, html, parentTag, addTag, null, null); | |
| } | |
| /** | |
| * Initializes all fields and calls super | |
| * | |
| * @param name - the name of the document. | |
| * @param html - the html to insert | |
| * @param parentTag - the parent tag to check for | |
| * @param addTag - the tag to start adding from | |
| * @param alternateParentTag - the alternate parent tag | |
| * @param alternateAddTag - the alternate add tag | |
| */ | |
| public InsertHTMLTextAction(String name, String html, HTML.Tag parentTag, | |
| HTML.Tag addTag, HTML.Tag alternateParentTag, | |
| HTML.Tag alternateAddTag) | |
| { | |
| super(name); | |
| // Fields are for easy access when the action is applied to an actual | |
| // document. | |
| this.html = html; | |
| this.parentTag = parentTag; | |
| this.addTag = addTag; | |
| this.alternateParentTag = alternateParentTag; | |
| this.alternateAddTag = alternateAddTag; | |
| } | |
| /** | |
| * HTMLEditorKit.insertHTML is called. If an exception is | |
| * thrown, it is wrapped in a RuntimeException and thrown. | |
| * | |
| * @param editor - the editor to use to get the editorkit | |
| * @param doc - | |
| * the Document to insert the HTML into. | |
| * @param offset - | |
| * where to begin inserting the HTML. | |
| * @param html - | |
| * the String to insert | |
| * @param popDepth - | |
| * the number of ElementSpec.EndTagTypes to generate before | |
| * inserting | |
| * @param pushDepth - | |
| * the number of ElementSpec.StartTagTypes with a direction of | |
| * ElementSpec.JoinNextDirection that should be generated before | |
| * @param addTag - | |
| * the first tag to start inserting into document | |
| */ | |
| protected void insertHTML(JEditorPane editor, HTMLDocument doc, int offset, | |
| String html, int popDepth, int pushDepth, | |
| HTML.Tag addTag) | |
| { | |
| try | |
| { | |
| super.getHTMLEditorKit(editor).insertHTML(doc, offset, html, | |
| popDepth, pushDepth, addTag); | |
| } | |
| catch (IOException e) | |
| { | |
| throw (RuntimeException) new RuntimeException("Parser is null.").initCause(e); | |
| } | |
| catch (BadLocationException ex) | |
| { | |
| throw (RuntimeException) new RuntimeException("BadLocationException: " | |
| + offset).initCause(ex); | |
| } | |
| } | |
| /** | |
| * Invoked when inserting at a boundary. Determines the number of pops, | |
| * and then the number of pushes that need to be performed. The it calls | |
| * insertHTML. | |
| * | |
| * @param editor - | |
| * the editor to use to get the editorkit | |
| * @param doc - | |
| * the Document to insert the HTML into. | |
| * @param offset - | |
| * where to begin inserting the HTML. | |
| * @param insertElement - | |
| * the element to insert | |
| * @param html - | |
| * the html to insert | |
| * @param parentTag - | |
| * the parent tag | |
| * @param addTag - | |
| * the first tag | |
| */ | |
| protected void insertAtBoundary(JEditorPane editor, | |
| HTMLDocument doc, int offset, | |
| Element insertElement, | |
| String html, HTML.Tag parentTag, | |
| HTML.Tag addTag) | |
| { | |
| insertAtBoundry(editor, doc, offset, insertElement, | |
| html, parentTag, addTag); | |
| } | |
| /** | |
| * Invoked when inserting at a boundary. Determines the number of pops, | |
| * and then the number of pushes that need to be performed. The it calls | |
| * insertHTML. | |
| * | |
| * @param editor - the editor to use to get the editorkit | |
| * @param doc - | |
| * the Document to insert the HTML into. | |
| * @param offset - | |
| * where to begin inserting the HTML. | |
| * @param insertElement - the element to insert | |
| * @param html - the html to insert | |
| * @param parentTag - the parent tag | |
| * @param addTag - the first tag | |
| * | |
| * @deprecated as of v1.3, use insertAtBoundary | |
| */ | |
| protected void insertAtBoundry(JEditorPane editor, | |
| HTMLDocument doc, | |
| int offset, Element insertElement, | |
| String html, HTML.Tag parentTag, | |
| HTML.Tag addTag) | |
| { | |
| Element parent = insertElement; | |
| Element el; | |
| // Find common parent element. | |
| if (offset > 0 || insertElement == null) | |
| { | |
| el = doc.getDefaultRootElement(); | |
| while (el != null && el.getStartOffset() != offset | |
| && ! el.isLeaf()) | |
| el = el.getElement(el.getElementIndex(offset)); | |
| parent = el != null ? el.getParentElement() : null; | |
| } | |
| if (parent != null) | |
| { | |
| int pops = 0; | |
| int pushes = 0; | |
| if (offset == 0 && insertElement != null) | |
| { | |
| el = parent; | |
| while (el != null && ! el.isLeaf()) | |
| { | |
| el = el.getElement(el.getElementIndex(offset)); | |
| pops++; | |
| } | |
| } | |
| else | |
| { | |
| el = parent; | |
| offset--; | |
| while (el != null && ! el.isLeaf()) | |
| { | |
| el = el.getElement(el.getElementIndex(offset)); | |
| pops++; | |
| } | |
| el = parent; | |
| offset++; | |
| while (el != null && el != insertElement) | |
| { | |
| el = el.getElement(el.getElementIndex(offset)); | |
| pushes++; | |
| } | |
| } | |
| pops = Math.max(0, pops - 1); | |
| insertHTML(editor, doc, offset, html, pops, pushes, addTag); | |
| } | |
| } | |
| /** | |
| * Inserts the HTML. | |
| * | |
| * @param ae - the action performed | |
| */ | |
| public void actionPerformed(ActionEvent ae) | |
| { | |
| JEditorPane source = getEditor(ae); | |
| if (source != null) | |
| { | |
| HTMLDocument d = getHTMLDocument(source); | |
| int offset = source.getSelectionStart(); | |
| int length = d.getLength(); | |
| boolean inserted = true; | |
| if (! tryInsert(source, d, offset, parentTag, addTag)) | |
| { | |
| inserted = tryInsert(source, d, offset, alternateParentTag, | |
| alternateAddTag); | |
| } | |
| if (inserted) | |
| adjustSelection(source, d, offset, length); | |
| } | |
| } | |
| /** | |
| * Tries to insert the html chunk to the specified <code>addTag</code>. | |
| * | |
| * @param pane the editor | |
| * @param doc the document | |
| * @param offset the offset at which to insert | |
| * @param tag the tag at which to insert | |
| * @param addTag the add tag | |
| * | |
| * @return <code>true</code> when the html has been inserted successfully, | |
| * <code>false</code> otherwise | |
| */ | |
| private boolean tryInsert(JEditorPane pane, HTMLDocument doc, int offset, | |
| HTML.Tag tag, HTML.Tag addTag) | |
| { | |
| boolean inserted = false; | |
| Element el = findElementMatchingTag(doc, offset, tag); | |
| if (el != null && el.getStartOffset() == offset) | |
| { | |
| insertAtBoundary(pane, doc, offset, el, html, tag, addTag); | |
| inserted = true; | |
| } | |
| else if (offset > 0) | |
| { | |
| int depth = elementCountToTag(doc, offset - 1, tag); | |
| if (depth != -1) | |
| { | |
| insertHTML(pane, doc, offset, html, depth, 0, addTag); | |
| inserted = true; | |
| } | |
| } | |
| return inserted; | |
| } | |
| /** | |
| * Adjusts the selection after an insertion has been performed. | |
| * | |
| * @param pane the editor pane | |
| * @param doc the document | |
| * @param offset the insert offset | |
| * @param oldLen the old document length | |
| */ | |
| private void adjustSelection(JEditorPane pane, HTMLDocument doc, | |
| int offset, int oldLen) | |
| { | |
| int newLen = doc.getLength(); | |
| if (newLen != oldLen && offset < newLen) | |
| { | |
| if (offset > 0) | |
| { | |
| String text; | |
| try | |
| { | |
| text = doc.getText(offset - 1, 1); | |
| } | |
| catch (BadLocationException ex) | |
| { | |
| text = null; | |
| } | |
| if (text != null && text.length() > 0 | |
| && text.charAt(0) == '\n') | |
| { | |
| pane.select(offset, offset); | |
| } | |
| else | |
| { | |
| pane.select(offset + 1, offset + 1); | |
| } | |
| } | |
| else | |
| { | |
| pane.select(1, 1); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Abstract Action class that helps inserting HTML into an existing document. | |
| */ | |
| public abstract static class HTMLTextAction | |
| extends StyledEditorKit.StyledTextAction | |
| { | |
| /** | |
| * Constructor | |
| */ | |
| public HTMLTextAction(String name) | |
| { | |
| super(name); | |
| } | |
| /** | |
| * Gets the HTMLDocument from the JEditorPane. | |
| * | |
| * @param e - the editor pane | |
| * @return the html document. | |
| */ | |
| protected HTMLDocument getHTMLDocument(JEditorPane e) | |
| { | |
| Document d = e.getDocument(); | |
| if (d instanceof HTMLDocument) | |
| return (HTMLDocument) d; | |
| throw new IllegalArgumentException("Document is not a HTMLDocument."); | |
| } | |
| /** | |
| * Gets the HTMLEditorKit | |
| * | |
| * @param e - the JEditorPane to get the HTMLEditorKit from. | |
| * @return the HTMLEditorKit | |
| */ | |
| protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) | |
| { | |
| EditorKit d = e.getEditorKit(); | |
| if (d instanceof HTMLEditorKit) | |
| return (HTMLEditorKit) d; | |
| throw new IllegalArgumentException("EditorKit is not a HTMLEditorKit."); | |
| } | |
| /** | |
| * Returns an array of Elements that contain the offset. | |
| * The first elements corresponds to the roots of the doc. | |
| * | |
| * @param doc - the document to get the Elements from. | |
| * @param offset - the offset the Elements must contain | |
| * @return an array of all the elements containing the offset. | |
| */ | |
| protected Element[] getElementsAt(HTMLDocument doc, | |
| int offset) | |
| { | |
| return getElementsAt(doc.getDefaultRootElement(), offset, 0); | |
| } | |
| /** | |
| * Helper function to get all elements using recursion. | |
| */ | |
| private Element[] getElementsAt(Element root, int offset, int depth) | |
| { | |
| Element[] elements = null; | |
| if (root != null) | |
| { | |
| if (root.isLeaf()) | |
| { | |
| elements = new Element[depth + 1]; | |
| elements[depth] = root; | |
| return elements; | |
| } | |
| elements = getElementsAt(root.getElement(root.getElementIndex(offset)), | |
| offset, depth + 1); | |
| elements[depth] = root; | |
| } | |
| return elements; | |
| } | |
| /** | |
| * Returns the number of elements, starting at the deepest point, needed | |
| * to get an element representing tag. -1 if no elements are found, 0 if | |
| * the parent of the leaf at offset represents the tag. | |
| * | |
| * @param doc - | |
| * the document to search | |
| * @param offset - | |
| * the offset to check | |
| * @param tag - | |
| * the tag to look for | |
| * @return - the number of elements needed to get an element representing | |
| * tag. | |
| */ | |
| protected int elementCountToTag(HTMLDocument doc, | |
| int offset, HTML.Tag tag) | |
| { | |
| Element root = doc.getDefaultRootElement(); | |
| int num = -1; | |
| Element next = root.getElement(root.getElementIndex(offset)); | |
| while (!next.isLeaf()) | |
| { | |
| num++; | |
| if (next.getAttributes(). | |
| getAttribute(StyleConstants.NameAttribute).equals(tag)) | |
| return num; | |
| next = next.getElement(next.getElementIndex(offset)); | |
| } | |
| return num; | |
| } | |
| /** | |
| * Gets the deepest element at offset with the | |
| * matching tag. | |
| * | |
| * @param doc - the document to search | |
| * @param offset - the offset to check for | |
| * @param tag - the tag to match | |
| * @return - the element that is found, null if not found. | |
| */ | |
| protected Element findElementMatchingTag(HTMLDocument doc, | |
| int offset, HTML.Tag tag) | |
| { | |
| Element element = doc.getDefaultRootElement(); | |
| Element tagElement = null; | |
| while (element != null) | |
| { | |
| Object otag = element.getAttributes().getAttribute( | |
| StyleConstants.NameAttribute); | |
| if (otag instanceof HTML.Tag && otag.equals(tag)) | |
| tagElement = element; | |
| element = element.getElement(element.getElementIndex(offset)); | |
| } | |
| return tagElement; | |
| } | |
| } | |
| /** | |
| * A {@link ViewFactory} that is able to create {@link View}s for | |
| * the <code>Element</code>s that are supported. | |
| */ | |
| public static class HTMLFactory | |
| implements ViewFactory | |
| { | |
| /** | |
| * Constructor | |
| */ | |
| public HTMLFactory() | |
| { | |
| // Do Nothing here. | |
| } | |
| /** | |
| * Creates a {@link View} for the specified <code>Element</code>. | |
| * | |
| * @param element the <code>Element</code> to create a <code>View</code> | |
| * for | |
| * @return the <code>View</code> for the specified <code>Element</code> | |
| * or <code>null</code> if the type of <code>element</code> is | |
| * not supported | |
| */ | |
| public View create(Element element) | |
| { | |
| View view = null; | |
| Object attr = | |
| element.getAttributes().getAttribute(StyleConstants.NameAttribute); | |
| if (attr instanceof HTML.Tag) | |
| { | |
| HTML.Tag tag = (HTML.Tag) attr; | |
| if (tag == HTML.Tag.IMPLIED || tag == HTML.Tag.P | |
| || tag == HTML.Tag.H1 || tag == HTML.Tag.H2 | |
| || tag == HTML.Tag.H3 || tag == HTML.Tag.H4 | |
| || tag == HTML.Tag.H5 || tag == HTML.Tag.H6 | |
| || tag == HTML.Tag.DT) | |
| view = new ParagraphView(element); | |
| else if (tag == HTML.Tag.LI || tag == HTML.Tag.DL | |
| || tag == HTML.Tag.DD || tag == HTML.Tag.BODY | |
| || tag == HTML.Tag.HTML || tag == HTML.Tag.CENTER | |
| || tag == HTML.Tag.DIV | |
| || tag == HTML.Tag.BLOCKQUOTE | |
| || tag == HTML.Tag.PRE | |
| || tag == HTML.Tag.FORM | |
| // Misplaced TD and TH tags get mapped as vertical block. | |
| // Note that correctly placed tags get mapped in TableView. | |
| || tag == HTML.Tag.TD || tag == HTML.Tag.TH) | |
| view = new BlockView(element, View.Y_AXIS); | |
| else if (tag == HTML.Tag.TR) | |
| // Misplaced TR tags get mapped as horizontal blocks. | |
| // Note that correctly placed tags get mapped in TableView. | |
| view = new BlockView(element, View.X_AXIS); | |
| else if (tag == HTML.Tag.IMG) | |
| view = new ImageView(element); | |
| else if (tag == HTML.Tag.CONTENT) | |
| view = new InlineView(element); | |
| else if (tag == HTML.Tag.HEAD) | |
| view = new NullView(element); | |
| else if (tag == HTML.Tag.TABLE) | |
| view = new javax.swing.text.html.TableView(element); | |
| else if (tag == HTML.Tag.HR) | |
| view = new HRuleView(element); | |
| else if (tag == HTML.Tag.BR) | |
| view = new BRView(element); | |
| else if (tag == HTML.Tag.INPUT || tag == HTML.Tag.SELECT | |
| || tag == HTML.Tag.TEXTAREA) | |
| view = new FormView(element); | |
| else if (tag == HTML.Tag.MENU || tag == HTML.Tag.DIR | |
| || tag == HTML.Tag.UL || tag == HTML.Tag.OL) | |
| view = new ListView(element); | |
| else if (tag == HTML.Tag.FRAMESET) | |
| view = new FrameSetView(element); | |
| else if (tag == HTML.Tag.FRAME) | |
| view = new FrameView(element); | |
| else if (tag == HTML.Tag.OBJECT) | |
| view = new ObjectView(element); | |
| } | |
| if (view == null) | |
| { | |
| view = new NullView(element); | |
| } | |
| return view; | |
| } | |
| } | |
| /** | |
| * The abstract HTML parser declaration. | |
| */ | |
| public abstract static class Parser | |
| { | |
| /** | |
| * Parse the HTML text, calling various methods of the provided callback | |
| * in response to the occurence of the corresponding HTML constructions. | |
| * @param reader The reader to read the source HTML from. | |
| * @param callback The callback to receive information about the parsed | |
| * HTML structures | |
| * @param ignoreCharSet If true, the parser ignores all charset information | |
| * that may be present in HTML documents. | |
| * @throws IOException, normally if the reader throws one. | |
| */ | |
| public abstract void parse(Reader reader, ParserCallback callback, | |
| boolean ignoreCharSet) throws IOException; | |
| } | |
| /** | |
| * The "hook" that receives all information about the HTML document | |
| * structure while parsing it. The methods are invoked by parser | |
| * and should be normally overridden. | |
| */ | |
| public static class ParserCallback | |
| { | |
| /** | |
| * If the tag does not occurs in the html stream directly, but | |
| * is supposed by parser, the tag attribute set contains this additional | |
| * attribute, having value Boolean.True. | |
| */ | |
| public static final Object IMPLIED = "_implied_"; | |
| /** | |
| * Constructor | |
| */ | |
| public ParserCallback() | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * The parser calls this method after it finishes parsing the document. | |
| */ | |
| public void flush() throws BadLocationException | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Handle HTML comment, present in the given position. | |
| * @param comment the comment | |
| * @position the position of the comment in the text being parsed. | |
| */ | |
| public void handleComment(char[] comment, int position) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Notifies about the character sequences, used to separate lines in | |
| * this document. The parser calls this method after it finishes | |
| * parsing the document, but before flush(). | |
| * @param end_of_line The "end of line sequence", one of: \r or \n or \r\n. | |
| */ | |
| public void handleEndOfLineString(String end_of_line) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * The method is called when the HTML closing tag ((like </table>) | |
| * is found or if the parser concludes that the one should be present | |
| * in the current position. | |
| * @param tag The tag being handled | |
| * @param position the tag position in the text being parsed. | |
| */ | |
| public void handleEndTag(HTML.Tag tag, int position) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Handle the error. | |
| * @param message The message, explaining the error. | |
| * @param position The starting position of the fragment that has caused | |
| * the error in the html document being parsed. | |
| */ | |
| public void handleError(String message, int position) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Handle the tag with no content, like <br>. The method is | |
| * called for the elements that, in accordance with the current DTD, | |
| * has an empty content. | |
| * @param tag The tag being handled. | |
| * @param position The tag position in the text being parsed. | |
| */ | |
| public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes, | |
| int position) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * The method is called when the HTML opening tag ((like <table>) | |
| * is found or if the parser concludes that the one should be present | |
| * in the current position. | |
| * @param tag The tag being handled | |
| * @param position The tag position in the text being parsed | |
| */ | |
| public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes, | |
| int position) | |
| { | |
| // Nothing to do here. | |
| } | |
| /** | |
| * Handle the text section. | |
| * @param text A section text. | |
| * @param position The text position in the HTML document text being parsed. | |
| */ | |
| public void handleText(char[] text, int position) | |
| { | |
| // Nothing to do here. | |
| } | |
| } | |
| /** | |
| * Use serialVersionUID (v1.4) for interoperability. | |
| */ | |
| private static final long serialVersionUID = 8751997116710384592L; | |
| /** | |
| * Default cascading stylesheed file ("default.css"). | |
| */ | |
| public static final String DEFAULT_CSS = "default.css"; | |
| /** | |
| * The <b>bold</b> action identifier. | |
| */ | |
| public static final String BOLD_ACTION = "html-bold-action"; | |
| /** | |
| * The <i>italic</i> action identifier. | |
| */ | |
| public static final String ITALIC_ACTION = "html-italic-action"; | |
| /** | |
| * The <font color="#FF0000">color</font> action indentifier | |
| * (passing the color as an argument). | |
| */ | |
| public static final String COLOR_ACTION = "html-color-action"; | |
| /** | |
| * The <font size="+1">increase</font> font action identifier. | |
| */ | |
| public static final String FONT_CHANGE_BIGGER = "html-font-bigger"; | |
| /** | |
| * The <font size="-1">decrease</font> font action identifier. | |
| */ | |
| public static final String FONT_CHANGE_SMALLER = "html-font-smaller"; | |
| /** | |
| * Align images at the bottom. | |
| */ | |
| public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom"; | |
| /** | |
| * Align images at the middle. | |
| */ | |
| public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle"; | |
| /** | |
| * Align images at the top. | |
| */ | |
| public static final String IMG_ALIGN_TOP = "html-image-align-top"; | |
| /** | |
| * Align images at the border. | |
| */ | |
| public static final String IMG_BORDER = "html-image-border"; | |
| /** | |
| * The "logical style" action identifier, passing that style as parameter. | |
| */ | |
| public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action"; | |
| /** | |
| * The "ident paragraph left" action. | |
| */ | |
| public static final String PARA_INDENT_LEFT = "html-para-indent-left"; | |
| /** | |
| * The "ident paragraph right" action. | |
| */ | |
| public static final String PARA_INDENT_RIGHT = "html-para-indent-right"; | |
| /** | |
| * Actions for HTML | |
| */ | |
| private static final Action[] defaultActions = | |
| { | |
| new InsertHTMLTextAction("InsertTable", | |
| "<table border=1><tr><td></td></tr></table>", | |
| HTML.Tag.BODY, HTML.Tag.TABLE), | |
| new InsertHTMLTextAction("InsertTableRow", | |
| "<table border=1><tr><td></td></tr></table>", | |
| HTML.Tag.TABLE, HTML.Tag.TR, | |
| HTML.Tag.BODY, HTML.Tag.TABLE), | |
| new InsertHTMLTextAction("InsertTableCell", | |
| "<table border=1><tr><td></td></tr></table>", | |
| HTML.Tag.TR, HTML.Tag.TD, | |
| HTML.Tag.BODY, HTML.Tag.TABLE), | |
| new InsertHTMLTextAction("InsertUnorderedList", | |
| "<ul><li></li></ul>", | |
| HTML.Tag.BODY, HTML.Tag.UL), | |
| new InsertHTMLTextAction("InsertUnorderedListItem", | |
| "<ul><li></li></ul>", | |
| HTML.Tag.UL, HTML.Tag.LI, | |
| HTML.Tag.BODY, HTML.Tag.UL), | |
| new InsertHTMLTextAction("InsertOrderedList", | |
| "<ol><li></li></ol>", | |
| HTML.Tag.BODY, HTML.Tag.OL), | |
| new InsertHTMLTextAction("InsertOrderedListItem", | |
| "<ol><li></li></ol>", | |
| HTML.Tag.OL, HTML.Tag.LI, | |
| HTML.Tag.BODY, HTML.Tag.OL), | |
| new InsertHTMLTextAction("InsertPre", | |
| "<pre></pre>", HTML.Tag.BODY, HTML.Tag.PRE) | |
| // TODO: The reference impl has an InsertHRAction too. | |
| }; | |
| /** | |
| * The current style sheet. | |
| */ | |
| private StyleSheet styleSheet; | |
| /** | |
| * The ViewFactory for HTMLFactory. | |
| */ | |
| HTMLFactory viewFactory; | |
| /** | |
| * The Cursor for links. | |
| */ | |
| Cursor linkCursor; | |
| /** | |
| * The default cursor. | |
| */ | |
| Cursor defaultCursor; | |
| /** | |
| * The parser. | |
| */ | |
| Parser parser; | |
| /** | |
| * The mouse listener used for links. | |
| */ | |
| private LinkController linkController; | |
| /** The content type */ | |
| String contentType = "text/html"; | |
| /** The input attributes defined by default.css */ | |
| MutableAttributeSet inputAttributes; | |
| /** The editor pane used. */ | |
| JEditorPane editorPane; | |
| /** | |
| * Whether or not the editor kit handles form submissions. | |
| * | |
| * @see #isAutoFormSubmission() | |
| * @see #setAutoFormSubmission(boolean) | |
| */ | |
| private boolean autoFormSubmission; | |
| /** | |
| * Constructs an HTMLEditorKit, creates a StyleContext, and loads the style sheet. | |
| */ | |
| public HTMLEditorKit() | |
| { | |
| linkController = new LinkController(); | |
| autoFormSubmission = true; | |
| } | |
| /** | |
| * Gets a factory suitable for producing views of any | |
| * models that are produced by this kit. | |
| * | |
| * @return the view factory suitable for producing views. | |
| */ | |
| public ViewFactory getViewFactory() | |
| { | |
| if (viewFactory == null) | |
| viewFactory = new HTMLFactory(); | |
| return viewFactory; | |
| } | |
| /** | |
| * Create a text storage model for this type of editor. | |
| * | |
| * @return the model | |
| */ | |
| public Document createDefaultDocument() | |
| { | |
| // Protect the shared stylesheet. | |
| StyleSheet styleSheet = getStyleSheet(); | |
| StyleSheet ss = new StyleSheet(); | |
| ss.addStyleSheet(styleSheet); | |
| HTMLDocument document = new HTMLDocument(ss); | |
| document.setParser(getParser()); | |
| document.setAsynchronousLoadPriority(4); | |
| document.setTokenThreshold(100); | |
| return document; | |
| } | |
| /** | |
| * Get the parser that this editor kit uses for reading HTML streams. This | |
| * method can be overridden to use the alternative parser. | |
| * | |
| * @return the HTML parser (by default, {@link ParserDelegator}). | |
| */ | |
| protected Parser getParser() | |
| { | |
| if (parser == null) | |
| { | |
| parser = new GnuParserDelegator(HTML_401F.getInstance()); | |
| } | |
| return parser; | |
| } | |
| /** | |
| * Inserts HTML into an existing document. | |
| * | |
| * @param doc - the Document to insert the HTML into. | |
| * @param offset - where to begin inserting the HTML. | |
| * @param html - the String to insert | |
| * @param popDepth - the number of ElementSpec.EndTagTypes | |
| * to generate before inserting | |
| * @param pushDepth - the number of ElementSpec.StartTagTypes | |
| * with a direction of ElementSpec.JoinNextDirection that | |
| * should be generated before | |
| * @param insertTag - the first tag to start inserting into document | |
| * @throws IOException - on any I/O error | |
| * @throws BadLocationException - if pos represents an invalid location | |
| * within the document | |
| */ | |
| public void insertHTML(HTMLDocument doc, int offset, String html, | |
| int popDepth, int pushDepth, HTML.Tag insertTag) | |
| throws BadLocationException, IOException | |
| { | |
| Parser parser = getParser(); | |
| if (offset < 0 || offset > doc.getLength()) | |
| throw new BadLocationException("Bad location", offset); | |
| if (parser == null) | |
| throw new IOException("Parser is null."); | |
| ParserCallback pc = doc.getReader(offset, popDepth, pushDepth, insertTag); | |
| // FIXME: What should ignoreCharSet be set to? | |
| // parser.parse inserts html into the buffer | |
| parser.parse(new StringReader(html), pc, false); | |
| pc.flush(); | |
| } | |
| /** | |
| * Inserts content from the given stream. Inserting HTML into a non-empty | |
| * document must be inside the body Element, if you do not insert into | |
| * the body an exception will be thrown. When inserting into a non-empty | |
| * document all tags outside of the body (head, title) will be dropped. | |
| * | |
| * @param in - the stream to read from | |
| * @param doc - the destination for the insertion | |
| * @param pos - the location in the document to place the content | |
| * @throws IOException - on any I/O error | |
| * @throws BadLocationException - if pos represents an invalid location | |
| * within the document | |
| */ | |
| public void read(Reader in, Document doc, int pos) throws IOException, | |
| BadLocationException | |
| { | |
| if (doc instanceof HTMLDocument) | |
| { | |
| Parser parser = getParser(); | |
| if (pos < 0 || pos > doc.getLength()) | |
| throw new BadLocationException("Bad location", pos); | |
| if (parser == null) | |
| throw new IOException("Parser is null."); | |
| HTMLDocument hd = ((HTMLDocument) doc); | |
| if (editorPane != null) | |
| hd.setBase(editorPane.getPage()); | |
| ParserCallback pc = hd.getReader(pos); | |
| // FIXME: What should ignoreCharSet be set to? | |
| // parser.parse inserts html into the buffer | |
| parser.parse(in, pc, false); | |
| pc.flush(); | |
| } | |
| else | |
| // read in DefaultEditorKit is called. | |
| // the string is inserted in the document as usual. | |
| super.read(in, doc, pos); | |
| } | |
| /** | |
| * Writes content from a document to the given stream in | |
| * an appropriate format. | |
| * | |
| * @param out - the stream to write to | |
| * @param doc - the source for the write | |
| * @param pos - the location in the document to get the content. | |
| * @param len - the amount to write out | |
| * @throws IOException - on any I/O error | |
| * @throws BadLocationException - if pos represents an invalid location | |
| * within the document | |
| */ | |
| public void write(Writer out, Document doc, int pos, int len) | |
| throws IOException, BadLocationException | |
| { | |
| if (doc instanceof HTMLDocument) | |
| { | |
| HTMLWriter writer = new HTMLWriter(out, (HTMLDocument) doc, pos, len); | |
| writer.write(); | |
| } | |
| else if (doc instanceof StyledDocument) | |
| { | |
| MinimalHTMLWriter writer = new MinimalHTMLWriter(out, | |
| (StyledDocument) doc, | |
| pos, len); | |
| writer.write(); | |
| } | |
| else | |
| super.write(out, doc, pos, len); | |
| } | |
| /** | |
| * Gets the content type that the kit supports. | |
| * This kit supports the type text/html. | |
| * | |
| * @returns the content type supported. | |
| */ | |
| public String getContentType() | |
| { | |
| return contentType; | |
| } | |
| /** | |
| * Creates a copy of the editor kit. | |
| * | |
| * @return a copy of this. | |
| */ | |
| public Object clone() | |
| { | |
| // FIXME: Need to clone all fields | |
| HTMLEditorKit copy = (HTMLEditorKit) super.clone(); | |
| copy.linkController = new LinkController(); | |
| return copy; | |
| } | |
| /** | |
| * Copies the key/values in elements AttributeSet into set. | |
| * This does not copy component, icon, or element names attributes. | |
| * This is called anytime the caret moves over a different location. | |
| * | |
| * @param element - the element to create the input attributes for. | |
| * @param set - the set to copy the values into. | |
| */ | |
| protected void createInputAttributes(Element element, | |
| MutableAttributeSet set) | |
| { | |
| set.removeAttributes(set); | |
| set.addAttributes(element.getAttributes()); | |
| // FIXME: Not fully implemented. | |
| } | |
| /** | |
| * Called when this is installed into the JEditorPane. | |
| * | |
| * @param c - the JEditorPane installed into. | |
| */ | |
| public void install(JEditorPane c) | |
| { | |
| super.install(c); | |
| c.addMouseListener(linkController); | |
| c.addMouseMotionListener(linkController); | |
| editorPane = c; | |
| } | |
| /** | |
| * Called when the this is removed from the JEditorPane. | |
| * It unregisters any listeners. | |
| * | |
| * @param c - the JEditorPane being removed from. | |
| */ | |
| public void deinstall(JEditorPane c) | |
| { | |
| super.deinstall(c); | |
| c.removeMouseListener(linkController); | |
| c.removeMouseMotionListener(linkController); | |
| editorPane = null; | |
| } | |
| /** | |
| * Gets the AccessibleContext associated with this. | |
| * | |
| * @return the AccessibleContext for this. | |
| */ | |
| public AccessibleContext getAccessibleContext() | |
| { | |
| // FIXME: Should return an instance of | |
| // javax.swing.text.html.AccessibleHTML$RootHTMLAccessibleContext | |
| // Not implemented yet. | |
| return null; | |
| } | |
| /** | |
| * Gets the action list. This list is supported by the superclass | |
| * augmented by the collection of actions defined locally for style | |
| * operations. | |
| * | |
| * @return an array of all the actions | |
| */ | |
| public Action[] getActions() | |
| { | |
| return TextAction.augmentList(super.getActions(), defaultActions); | |
| } | |
| /** | |
| * Returns the default cursor. | |
| * | |
| * @return the default cursor | |
| */ | |
| public Cursor getDefaultCursor() | |
| { | |
| if (defaultCursor == null) | |
| defaultCursor = Cursor.getDefaultCursor(); | |
| return defaultCursor; | |
| } | |
| /** | |
| * Returns the cursor for links. | |
| * | |
| * @return the cursor for links. | |
| */ | |
| public Cursor getLinkCursor() | |
| { | |
| if (linkCursor == null) | |
| linkCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); | |
| return linkCursor; | |
| } | |
| /** | |
| * Sets the Cursor for links. | |
| * | |
| * @param cursor - the new cursor for links. | |
| */ | |
| public void setLinkCursor(Cursor cursor) | |
| { | |
| linkCursor = cursor; | |
| } | |
| /** | |
| * Sets the default cursor. | |
| * | |
| * @param cursor - the new default cursor. | |
| */ | |
| public void setDefaultCursor(Cursor cursor) | |
| { | |
| defaultCursor = cursor; | |
| } | |
| /** | |
| * Gets the input attributes used for the styled editing actions. | |
| * | |
| * @return the attribute set | |
| */ | |
| public MutableAttributeSet getInputAttributes() | |
| { | |
| return inputAttributes; | |
| } | |
| /** | |
| * Get the set of styles currently being used to render the HTML elements. | |
| * By default the resource specified by DEFAULT_CSS gets loaded, and is | |
| * shared by all HTMLEditorKit instances. | |
| * | |
| * @return the style sheet. | |
| */ | |
| public StyleSheet getStyleSheet() | |
| { | |
| if (styleSheet == null) | |
| { | |
| try | |
| { | |
| styleSheet = new StyleSheet(); | |
| Class<?> c = HTMLEditorKit.class; | |
| InputStream in = c.getResourceAsStream(DEFAULT_CSS); | |
| InputStreamReader r = new InputStreamReader(in); | |
| styleSheet.loadRules(r, null); | |
| r.close(); | |
| } | |
| catch (IOException ex) | |
| { | |
| throw new RuntimeException("No style available.", ex); | |
| } | |
| } | |
| return styleSheet; | |
| } | |
| /** | |
| * Set the set of styles to be used to render the various HTML elements. | |
| * These styles are specified in terms of CSS specifications. Each document | |
| * produced by the kit will have a copy of the sheet which it can add the | |
| * document specific styles to. By default, the StyleSheet specified is shared | |
| * by all HTMLEditorKit instances. | |
| * | |
| * @param s - the new style sheet | |
| */ | |
| public void setStyleSheet(StyleSheet s) | |
| { | |
| styleSheet = s; | |
| } | |
| /** | |
| * Returns <code>true</code> when forms should be automatically submitted | |
| * by the editor kit. Set this to <code>false</code> when you want to | |
| * intercept form submission. In this case you'd want to listen for | |
| * hyperlink events on the document and handle FormSubmitEvents specially. | |
| * | |
| * The default is <code>true</code>. | |
| * | |
| * @return <code>true</code> when forms should be automatically submitted | |
| * by the editor kit, <code>false</code> otherwise | |
| * | |
| * @since 1.5 | |
| * | |
| * @see #setAutoFormSubmission(boolean) | |
| * @see FormSubmitEvent | |
| */ | |
| public boolean isAutoFormSubmission() | |
| { | |
| return autoFormSubmission; | |
| } | |
| /** | |
| * Sets whether or not the editor kit should automatically submit forms. | |
| * | |
| * @param auto <code>true</code> when the editor kit should handle form | |
| * submission, <code>false</code> otherwise | |
| * | |
| * @since 1.5 | |
| * | |
| * @see #isAutoFormSubmission() | |
| */ | |
| public void setAutoFormSubmission(boolean auto) | |
| { | |
| autoFormSubmission = auto; | |
| } | |
| } |