0001 /*
0002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing.text.html;
0026
0027 import java.lang.reflect.Method;
0028 import java.awt.*;
0029 import java.awt.event.*;
0030 import java.io.*;
0031 import java.net.MalformedURLException;
0032 import java.net.URL;
0033 import javax.swing.text.*;
0034 import javax.swing.*;
0035 import javax.swing.border.*;
0036 import javax.swing.event.*;
0037 import javax.swing.plaf.TextUI;
0038 import java.util.*;
0039 import javax.accessibility.*;
0040 import java.lang.ref.*;
0041
0042 /**
0043 * The Swing JEditorPane text component supports different kinds
0044 * of content via a plug-in mechanism called an EditorKit. Because
0045 * HTML is a very popular format of content, some support is provided
0046 * by default. The default support is provided by this class, which
0047 * supports HTML version 3.2 (with some extensions), and is migrating
0048 * toward version 4.0.
0049 * The <applet> tag is not supported, but some support is provided
0050 * for the <object> tag.
0051 * <p>
0052 * There are several goals of the HTML EditorKit provided, that have
0053 * an effect upon the way that HTML is modeled. These
0054 * have influenced its design in a substantial way.
0055 * <dl>
0056 * <p>
0057 * <dt>
0058 * Support editing
0059 * <dd>
0060 * It might seem fairly obvious that a plug-in for JEditorPane
0061 * should provide editing support, but that fact has several
0062 * design considerations. There are a substantial number of HTML
0063 * documents that don't properly conform to an HTML specification.
0064 * These must be normalized somewhat into a correct form if one
0065 * is to edit them. Additionally, users don't like to be presented
0066 * with an excessive amount of structure editing, so using traditional
0067 * text editing gestures is preferred over using the HTML structure
0068 * exactly as defined in the HTML document.
0069 * <p>
0070 * The modeling of HTML is provided by the class <code>HTMLDocument</code>.
0071 * Its documention describes the details of how the HTML is modeled.
0072 * The editing support leverages heavily off of the text package.
0073 * <p>
0074 * <dt>
0075 * Extendable/Scalable
0076 * <dd>
0077 * To maximize the usefulness of this kit, a great deal of effort
0078 * has gone into making it extendable. These are some of the
0079 * features.
0080 * <ol>
0081 * <li>
0082 * The parser is replacable. The default parser is the Hot Java
0083 * parser which is DTD based. A different DTD can be used, or an
0084 * entirely different parser can be used. To change the parser,
0085 * reimplement the getParser method. The default parser is
0086 * dynamically loaded when first asked for, so the class files
0087 * will never be loaded if an alternative parser is used. The
0088 * default parser is in a separate package called parser below
0089 * this package.
0090 * <li>
0091 * The parser drives the ParserCallback, which is provided by
0092 * HTMLDocument. To change the callback, subclass HTMLDocument
0093 * and reimplement the createDefaultDocument method to return
0094 * document that produces a different reader. The reader controls
0095 * how the document is structured. Although the Document provides
0096 * HTML support by default, there is nothing preventing support of
0097 * non-HTML tags that result in alternative element structures.
0098 * <li>
0099 * The default view of the models are provided as a hierarchy of
0100 * View implementations, so one can easily customize how a particular
0101 * element is displayed or add capabilities for new kinds of elements
0102 * by providing new View implementations. The default set of views
0103 * are provided by the <code>HTMLFactory</code> class. This can
0104 * be easily changed by subclassing or replacing the HTMLFactory
0105 * and reimplementing the getViewFactory method to return the alternative
0106 * factory.
0107 * <li>
0108 * The View implementations work primarily off of CSS attributes,
0109 * which are kept in the views. This makes it possible to have
0110 * multiple views mapped over the same model that appear substantially
0111 * different. This can be especially useful for printing. For
0112 * most HTML attributes, the HTML attributes are converted to CSS
0113 * attributes for display. This helps make the View implementations
0114 * more general purpose
0115 * </ol>
0116 * <p>
0117 * <dt>
0118 * Asynchronous Loading
0119 * <dd>
0120 * Larger documents involve a lot of parsing and take some time
0121 * to load. By default, this kit produces documents that will be
0122 * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
0123 * This is controlled by a property on the document. The method
0124 * <a href="#createDefaultDocument">createDefaultDocument</a> can
0125 * be overriden to change this. The batching of work is done
0126 * by the <code>HTMLDocument.HTMLReader</code> class. The actual
0127 * work is done by the <code>DefaultStyledDocument</code> and
0128 * <code>AbstractDocument</code> classes in the text package.
0129 * <p>
0130 * <dt>
0131 * Customization from current LAF
0132 * <dd>
0133 * HTML provides a well known set of features without exactly
0134 * specifying the display characteristics. Swing has a theme
0135 * mechanism for its look-and-feel implementations. It is desirable
0136 * for the look-and-feel to feed display characteristics into the
0137 * HTML views. An user with poor vision for example would want
0138 * high contrast and larger than typical fonts.
0139 * <p>
0140 * The support for this is provided by the <code>StyleSheet</code>
0141 * class. The presentation of the HTML can be heavily influenced
0142 * by the setting of the StyleSheet property on the EditorKit.
0143 * <p>
0144 * <dt>
0145 * Not lossy
0146 * <dd>
0147 * An EditorKit has the ability to be read and save documents.
0148 * It is generally the most pleasing to users if there is no loss
0149 * of data between the two operation. The policy of the HTMLEditorKit
0150 * will be to store things not recognized or not necessarily visible
0151 * so they can be subsequently written out. The model of the HTML document
0152 * should therefore contain all information discovered while reading the
0153 * document. This is constrained in some ways by the need to support
0154 * editing (i.e. incorrect documents sometimes must be normalized).
0155 * The guiding principle is that information shouldn't be lost, but
0156 * some might be synthesized to produce a more correct model or it might
0157 * be rearranged.
0158 * </dl>
0159 *
0160 * @author Timothy Prinzing
0161 * @version 1.142 05/05/07
0162 */
0163 public class HTMLEditorKit extends StyledEditorKit implements
0164 Accessible {
0165
0166 private JEditorPane theEditor;
0167
0168 /**
0169 * Constructs an HTMLEditorKit, creates a StyleContext,
0170 * and loads the style sheet.
0171 */
0172 public HTMLEditorKit() {
0173
0174 }
0175
0176 /**
0177 * Get the MIME type of the data that this
0178 * kit represents support for. This kit supports
0179 * the type <code>text/html</code>.
0180 *
0181 * @return the type
0182 */
0183 public String getContentType() {
0184 return "text/html";
0185 }
0186
0187 /**
0188 * Fetch a factory that is suitable for producing
0189 * views of any models that are produced by this
0190 * kit.
0191 *
0192 * @return the factory
0193 */
0194 public ViewFactory getViewFactory() {
0195 return defaultFactory;
0196 }
0197
0198 /**
0199 * Create an uninitialized text storage model
0200 * that is appropriate for this type of editor.
0201 *
0202 * @return the model
0203 */
0204 public Document createDefaultDocument() {
0205 StyleSheet styles = getStyleSheet();
0206 StyleSheet ss = new StyleSheet();
0207
0208 ss.addStyleSheet(styles);
0209
0210 HTMLDocument doc = new HTMLDocument(ss);
0211 doc.setParser(getParser());
0212 doc.setAsynchronousLoadPriority(4);
0213 doc.setTokenThreshold(100);
0214 return doc;
0215 }
0216
0217 /**
0218 * Try to get an HTML parser from the document. If no parser is set for
0219 * the document, return the editor kit's default parser. It is an error
0220 * if no parser could be obtained from the editor kit.
0221 */
0222 private Parser ensureParser(HTMLDocument doc) throws IOException {
0223 Parser p = doc.getParser();
0224 if (p == null) {
0225 p = getParser();
0226 }
0227 if (p == null) {
0228 throw new IOException("Can't load parser");
0229 }
0230 return p;
0231 }
0232
0233 /**
0234 * Inserts content from the given stream. If <code>doc</code> is
0235 * an instance of HTMLDocument, this will read
0236 * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
0237 * the body Element, if you do not insert into the body an exception will
0238 * be thrown. When inserting into a non-empty document all tags outside
0239 * of the body (head, title) will be dropped.
0240 *
0241 * @param in the stream to read from
0242 * @param doc the destination for the insertion
0243 * @param pos the location in the document to place the
0244 * content
0245 * @exception IOException on any I/O error
0246 * @exception BadLocationException if pos represents an invalid
0247 * location within the document
0248 * @exception RuntimeException (will eventually be a BadLocationException)
0249 * if pos is invalid
0250 */
0251 public void read(Reader in, Document doc, int pos)
0252 throws IOException, BadLocationException {
0253
0254 if (doc instanceof HTMLDocument) {
0255 HTMLDocument hdoc = (HTMLDocument) doc;
0256 if (pos > doc.getLength()) {
0257 throw new BadLocationException("Invalid location", pos);
0258 }
0259
0260 Parser p = ensureParser(hdoc);
0261 ParserCallback receiver = hdoc.getReader(pos);
0262 Boolean ignoreCharset = (Boolean) doc
0263 .getProperty("IgnoreCharsetDirective");
0264 p.parse(in, receiver, (ignoreCharset == null) ? false
0265 : ignoreCharset.booleanValue());
0266 receiver.flush();
0267 } else {
0268 super .read(in, doc, pos);
0269 }
0270 }
0271
0272 /**
0273 * Inserts HTML into an existing document.
0274 *
0275 * @param doc the document to insert into
0276 * @param offset the offset to insert HTML at
0277 * @param popDepth the number of ElementSpec.EndTagTypes to generate before
0278 * inserting
0279 * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
0280 * of ElementSpec.JoinNextDirection that should be generated
0281 * before inserting, but after the end tags have been generated
0282 * @param insertTag the first tag to start inserting into document
0283 * @exception RuntimeException (will eventually be a BadLocationException)
0284 * if pos is invalid
0285 */
0286 public void insertHTML(HTMLDocument doc, int offset, String html,
0287 int popDepth, int pushDepth, HTML.Tag insertTag)
0288 throws BadLocationException, IOException {
0289 if (offset > doc.getLength()) {
0290 throw new BadLocationException("Invalid location", offset);
0291 }
0292
0293 Parser p = ensureParser(doc);
0294 ParserCallback receiver = doc.getReader(offset, popDepth,
0295 pushDepth, insertTag);
0296 Boolean ignoreCharset = (Boolean) doc
0297 .getProperty("IgnoreCharsetDirective");
0298 p.parse(new StringReader(html), receiver,
0299 (ignoreCharset == null) ? false : ignoreCharset
0300 .booleanValue());
0301 receiver.flush();
0302 }
0303
0304 /**
0305 * Write content from a document to the given stream
0306 * in a format appropriate for this kind of content handler.
0307 *
0308 * @param out the stream to write to
0309 * @param doc the source for the write
0310 * @param pos the location in the document to fetch the
0311 * content
0312 * @param len the amount to write out
0313 * @exception IOException on any I/O error
0314 * @exception BadLocationException if pos represents an invalid
0315 * location within the document
0316 */
0317 public void write(Writer out, Document doc, int pos, int len)
0318 throws IOException, BadLocationException {
0319
0320 if (doc instanceof HTMLDocument) {
0321 HTMLWriter w = new HTMLWriter(out, (HTMLDocument) doc, pos,
0322 len);
0323 w.write();
0324 } else if (doc instanceof StyledDocument) {
0325 MinimalHTMLWriter w = new MinimalHTMLWriter(out,
0326 (StyledDocument) doc, pos, len);
0327 w.write();
0328 } else {
0329 super .write(out, doc, pos, len);
0330 }
0331 }
0332
0333 /**
0334 * Called when the kit is being installed into the
0335 * a JEditorPane.
0336 *
0337 * @param c the JEditorPane
0338 */
0339 public void install(JEditorPane c) {
0340 c.addMouseListener(linkHandler);
0341 c.addMouseMotionListener(linkHandler);
0342 c.addCaretListener(nextLinkAction);
0343 super .install(c);
0344 theEditor = c;
0345 }
0346
0347 /**
0348 * Called when the kit is being removed from the
0349 * JEditorPane. This is used to unregister any
0350 * listeners that were attached.
0351 *
0352 * @param c the JEditorPane
0353 */
0354 public void deinstall(JEditorPane c) {
0355 c.removeMouseListener(linkHandler);
0356 c.removeMouseMotionListener(linkHandler);
0357 c.removeCaretListener(nextLinkAction);
0358 super .deinstall(c);
0359 theEditor = null;
0360 }
0361
0362 /**
0363 * Default Cascading Style Sheet file that sets
0364 * up the tag views.
0365 */
0366 public static final String DEFAULT_CSS = "default.css";
0367
0368 /**
0369 * Set the set of styles to be used to render the various
0370 * HTML elements. These styles are specified in terms of
0371 * CSS specifications. Each document produced by the kit
0372 * will have a copy of the sheet which it can add the
0373 * document specific styles to. By default, the StyleSheet
0374 * specified is shared by all HTMLEditorKit instances.
0375 * This should be reimplemented to provide a finer granularity
0376 * if desired.
0377 */
0378 public void setStyleSheet(StyleSheet s) {
0379 defaultStyles = s;
0380 }
0381
0382 /**
0383 * Get the set of styles currently being used to render the
0384 * HTML elements. By default the resource specified by
0385 * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
0386 * instances.
0387 */
0388 public StyleSheet getStyleSheet() {
0389 if (defaultStyles == null) {
0390 defaultStyles = new StyleSheet();
0391 try {
0392 InputStream is = HTMLEditorKit
0393 .getResourceAsStream(DEFAULT_CSS);
0394 Reader r = new BufferedReader(new InputStreamReader(is,
0395 "ISO-8859-1"));
0396 defaultStyles.loadRules(r, null);
0397 r.close();
0398 } catch (Throwable e) {
0399 // on error we simply have no styles... the html
0400 // will look mighty wrong but still function.
0401 }
0402 }
0403 return defaultStyles;
0404 }
0405
0406 /**
0407 * Fetch a resource relative to the HTMLEditorKit classfile.
0408 * If this is called on 1.2 the loading will occur under the
0409 * protection of a doPrivileged call to allow the HTMLEditorKit
0410 * to function when used in an applet.
0411 *
0412 * @param name the name of the resource, relative to the
0413 * HTMLEditorKit class
0414 * @return a stream representing the resource
0415 */
0416 static InputStream getResourceAsStream(String name) {
0417 try {
0418 return ResourceLoader.getResourceAsStream(name);
0419 } catch (Throwable e) {
0420 // If the class doesn't exist or we have some other
0421 // problem we just try to call getResourceAsStream directly.
0422 return HTMLEditorKit.class.getResourceAsStream(name);
0423 }
0424 }
0425
0426 /**
0427 * Fetches the command list for the editor. This is
0428 * the list of commands supported by the superclass
0429 * augmented by the collection of commands defined
0430 * locally for style operations.
0431 *
0432 * @return the command list
0433 */
0434 public Action[] getActions() {
0435 return TextAction.augmentList(super .getActions(),
0436 this .defaultActions);
0437 }
0438
0439 /**
0440 * Copies the key/values in <code>element</code>s AttributeSet into
0441 * <code>set</code>. This does not copy component, icon, or element
0442 * names attributes. Subclasses may wish to refine what is and what
0443 * isn't copied here. But be sure to first remove all the attributes that
0444 * are in <code>set</code>.<p>
0445 * This is called anytime the caret moves over a different location.
0446 *
0447 */
0448 protected void createInputAttributes(Element element,
0449 MutableAttributeSet set) {
0450 set.removeAttributes(set);
0451 set.addAttributes(element.getAttributes());
0452 set.removeAttribute(StyleConstants.ComposedTextAttribute);
0453
0454 Object o = set.getAttribute(StyleConstants.NameAttribute);
0455 if (o instanceof HTML.Tag) {
0456 HTML.Tag tag = (HTML.Tag) o;
0457 // PENDING: we need a better way to express what shouldn't be
0458 // copied when editing...
0459 if (tag == HTML.Tag.IMG) {
0460 // Remove the related image attributes, src, width, height
0461 set.removeAttribute(HTML.Attribute.SRC);
0462 set.removeAttribute(HTML.Attribute.HEIGHT);
0463 set.removeAttribute(HTML.Attribute.WIDTH);
0464 set.addAttribute(StyleConstants.NameAttribute,
0465 HTML.Tag.CONTENT);
0466 } else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
0467 // Don't copy HRs or BRs either.
0468 set.addAttribute(StyleConstants.NameAttribute,
0469 HTML.Tag.CONTENT);
0470 } else if (tag == HTML.Tag.COMMENT) {
0471 // Don't copy COMMENTs either
0472 set.addAttribute(StyleConstants.NameAttribute,
0473 HTML.Tag.CONTENT);
0474 set.removeAttribute(HTML.Attribute.COMMENT);
0475 } else if (tag == HTML.Tag.INPUT) {
0476 // or INPUT either
0477 set.addAttribute(StyleConstants.NameAttribute,
0478 HTML.Tag.CONTENT);
0479 set.removeAttribute(HTML.Tag.INPUT);
0480 } else if (tag instanceof HTML.UnknownTag) {
0481 // Don't copy unknowns either:(
0482 set.addAttribute(StyleConstants.NameAttribute,
0483 HTML.Tag.CONTENT);
0484 set.removeAttribute(HTML.Attribute.ENDTAG);
0485 }
0486 }
0487 }
0488
0489 /**
0490 * Gets the input attributes used for the styled
0491 * editing actions.
0492 *
0493 * @return the attribute set
0494 */
0495 public MutableAttributeSet getInputAttributes() {
0496 if (input == null) {
0497 input = getStyleSheet().addStyle(null, null);
0498 }
0499 return input;
0500 }
0501
0502 /**
0503 * Sets the default cursor.
0504 *
0505 * @since 1.3
0506 */
0507 public void setDefaultCursor(Cursor cursor) {
0508 defaultCursor = cursor;
0509 }
0510
0511 /**
0512 * Returns the default cursor.
0513 *
0514 * @since 1.3
0515 */
0516 public Cursor getDefaultCursor() {
0517 return defaultCursor;
0518 }
0519
0520 /**
0521 * Sets the cursor to use over links.
0522 *
0523 * @since 1.3
0524 */
0525 public void setLinkCursor(Cursor cursor) {
0526 linkCursor = cursor;
0527 }
0528
0529 /**
0530 * Returns the cursor to use over hyper links.
0531 * @since 1.3
0532 */
0533 public Cursor getLinkCursor() {
0534 return linkCursor;
0535 }
0536
0537 /**
0538 * Indicates whether an html form submission is processed automatically
0539 * or only <code>FormSubmitEvent</code> is fired.
0540 *
0541 * @return true if html form submission is processed automatically,
0542 * false otherwise.
0543 *
0544 * @see #setAutoFormSubmission
0545 * @since 1.5
0546 */
0547 public boolean isAutoFormSubmission() {
0548 return isAutoFormSubmission;
0549 }
0550
0551 /**
0552 * Specifies if an html form submission is processed
0553 * automatically or only <code>FormSubmitEvent</code> is fired.
0554 * By default it is set to true.
0555 *
0556 * @see #isAutoFormSubmission
0557 * @see FormSubmitEvent
0558 * @since 1.5
0559 */
0560 public void setAutoFormSubmission(boolean isAuto) {
0561 isAutoFormSubmission = isAuto;
0562 }
0563
0564 /**
0565 * Creates a copy of the editor kit.
0566 *
0567 * @return the copy
0568 */
0569 public Object clone() {
0570 HTMLEditorKit o = (HTMLEditorKit) super .clone();
0571 if (o != null) {
0572 o.input = null;
0573 o.linkHandler = new LinkController();
0574 }
0575 return o;
0576 }
0577
0578 /**
0579 * Fetch the parser to use for reading HTML streams.
0580 * This can be reimplemented to provide a different
0581 * parser. The default implementation is loaded dynamically
0582 * to avoid the overhead of loading the default parser if
0583 * it's not used. The default parser is the HotJava parser
0584 * using an HTML 3.2 DTD.
0585 */
0586 protected Parser getParser() {
0587 if (defaultParser == null) {
0588 try {
0589 Class c = Class
0590 .forName("javax.swing.text.html.parser.ParserDelegator");
0591 defaultParser = (Parser) c.newInstance();
0592 } catch (Throwable e) {
0593 }
0594 }
0595 return defaultParser;
0596 }
0597
0598 // ----- Accessibility support -----
0599 private AccessibleContext accessibleContext;
0600
0601 /**
0602 * returns the AccessibleContext associated with this editor kit
0603 *
0604 * @return the AccessibleContext associated with this editor kit
0605 * @since 1.4
0606 */
0607 public AccessibleContext getAccessibleContext() {
0608 if (theEditor == null) {
0609 return null;
0610 }
0611 if (accessibleContext == null) {
0612 AccessibleHTML a = new AccessibleHTML(theEditor);
0613 accessibleContext = a.getAccessibleContext();
0614 }
0615 return accessibleContext;
0616 }
0617
0618 // --- variables ------------------------------------------
0619
0620 private static final Cursor MoveCursor = Cursor
0621 .getPredefinedCursor(Cursor.HAND_CURSOR);
0622 private static final Cursor DefaultCursor = Cursor
0623 .getPredefinedCursor(Cursor.DEFAULT_CURSOR);
0624
0625 /** Shared factory for creating HTML Views. */
0626 private static final ViewFactory defaultFactory = new HTMLFactory();
0627
0628 MutableAttributeSet input;
0629 private static StyleSheet defaultStyles = null;
0630 private LinkController linkHandler = new LinkController();
0631 private static Parser defaultParser = null;
0632 private Cursor defaultCursor = DefaultCursor;
0633 private Cursor linkCursor = MoveCursor;
0634 private boolean isAutoFormSubmission = true;
0635
0636 /**
0637 * Class to watch the associated component and fire
0638 * hyperlink events on it when appropriate.
0639 */
0640 public static class LinkController extends MouseAdapter implements
0641 MouseMotionListener, Serializable {
0642 private Element curElem = null;
0643 /**
0644 * If true, the current element (curElem) represents an image.
0645 */
0646 private boolean curElemImage = false;
0647 private String href = null;
0648 /** This is used by viewToModel to avoid allocing a new array each
0649 * time. */
0650 private transient Position.Bias[] bias = new Position.Bias[1];
0651 /**
0652 * Current offset.
0653 */
0654 private int curOffset;
0655
0656 /**
0657 * Called for a mouse click event.
0658 * If the component is read-only (ie a browser) then
0659 * the clicked event is used to drive an attempt to
0660 * follow the reference specified by a link.
0661 *
0662 * @param e the mouse event
0663 * @see MouseListener#mouseClicked
0664 */
0665 public void mouseClicked(MouseEvent e) {
0666 JEditorPane editor = (JEditorPane) e.getSource();
0667
0668 if (!editor.isEditable() && editor.isEnabled()
0669 && SwingUtilities.isLeftMouseButton(e)) {
0670 Point pt = new Point(e.getX(), e.getY());
0671 int pos = editor.viewToModel(pt);
0672 if (pos >= 0) {
0673 activateLink(pos, editor, e.getX(), e.getY());
0674 }
0675 }
0676 }
0677
0678 // ignore the drags
0679 public void mouseDragged(MouseEvent e) {
0680 }
0681
0682 // track the moving of the mouse.
0683 public void mouseMoved(MouseEvent e) {
0684 JEditorPane editor = (JEditorPane) e.getSource();
0685 if (!editor.isEnabled()) {
0686 return;
0687 }
0688
0689 HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit();
0690 boolean adjustCursor = true;
0691 Cursor newCursor = kit.getDefaultCursor();
0692 if (!editor.isEditable()) {
0693 Point pt = new Point(e.getX(), e.getY());
0694 int pos = editor.getUI().viewToModel(editor, pt, bias);
0695 if (bias[0] == Position.Bias.Backward && pos > 0) {
0696 pos--;
0697 }
0698 if (pos >= 0
0699 && (editor.getDocument() instanceof HTMLDocument)) {
0700 HTMLDocument hdoc = (HTMLDocument) editor
0701 .getDocument();
0702 Element elem = hdoc.getCharacterElement(pos);
0703 if (!doesElementContainLocation(editor, elem, pos,
0704 e.getX(), e.getY())) {
0705 elem = null;
0706 }
0707 if (curElem != elem || curElemImage) {
0708 Element lastElem = curElem;
0709 curElem = elem;
0710 String href = null;
0711 curElemImage = false;
0712 if (elem != null) {
0713 AttributeSet a = elem.getAttributes();
0714 AttributeSet anchor = (AttributeSet) a
0715 .getAttribute(HTML.Tag.A);
0716 if (anchor == null) {
0717 curElemImage = (a
0718 .getAttribute(StyleConstants.NameAttribute) == HTML.Tag.IMG);
0719 if (curElemImage) {
0720 href = getMapHREF(editor, hdoc,
0721 elem, a, pos, e.getX(), e
0722 .getY());
0723 }
0724 } else {
0725 href = (String) anchor
0726 .getAttribute(HTML.Attribute.HREF);
0727 }
0728 }
0729
0730 if (href != this .href) {
0731 // reference changed, fire event(s)
0732 fireEvents(editor, hdoc, href, lastElem);
0733 this .href = href;
0734 if (href != null) {
0735 newCursor = kit.getLinkCursor();
0736 }
0737 } else {
0738 adjustCursor = false;
0739 }
0740 } else {
0741 adjustCursor = false;
0742 }
0743 curOffset = pos;
0744 }
0745 }
0746 if (adjustCursor && editor.getCursor() != newCursor) {
0747 editor.setCursor(newCursor);
0748 }
0749 }
0750
0751 /**
0752 * Returns a string anchor if the passed in element has a
0753 * USEMAP that contains the passed in location.
0754 */
0755 private String getMapHREF(JEditorPane html, HTMLDocument hdoc,
0756 Element elem, AttributeSet attr, int offset, int x,
0757 int y) {
0758 Object useMap = attr.getAttribute(HTML.Attribute.USEMAP);
0759 if (useMap != null && (useMap instanceof String)) {
0760 Map m = hdoc.getMap((String) useMap);
0761 if (m != null && offset < hdoc.getLength()) {
0762 Rectangle bounds;
0763 TextUI ui = html.getUI();
0764 try {
0765 Shape lBounds = ui.modelToView(html, offset,
0766 Position.Bias.Forward);
0767 Shape rBounds = ui.modelToView(html,
0768 offset + 1, Position.Bias.Backward);
0769 bounds = lBounds.getBounds();
0770 bounds
0771 .add((rBounds instanceof Rectangle) ? (Rectangle) rBounds
0772 : rBounds.getBounds());
0773 } catch (BadLocationException ble) {
0774 bounds = null;
0775 }
0776 if (bounds != null) {
0777 AttributeSet area = m
0778 .getArea(x - bounds.x, y - bounds.y,
0779 bounds.width, bounds.height);
0780 if (area != null) {
0781 return (String) area
0782 .getAttribute(HTML.Attribute.HREF);
0783 }
0784 }
0785 }
0786 }
0787 return null;
0788 }
0789
0790 /**
0791 * Returns true if the View representing <code>e</code> contains
0792 * the location <code>x</code>, <code>y</code>. <code>offset</code>
0793 * gives the offset into the Document to check for.
0794 */
0795 private boolean doesElementContainLocation(JEditorPane editor,
0796 Element e, int offset, int x, int y) {
0797 if (e != null && offset > 0 && e.getStartOffset() == offset) {
0798 try {
0799 TextUI ui = editor.getUI();
0800 Shape s1 = ui.modelToView(editor, offset,
0801 Position.Bias.Forward);
0802 if (s1 == null) {
0803 return false;
0804 }
0805 Rectangle r1 = (s1 instanceof Rectangle) ? (Rectangle) s1
0806 : s1.getBounds();
0807 Shape s2 = ui.modelToView(editor, e.getEndOffset(),
0808 Position.Bias.Backward);
0809 if (s2 != null) {
0810 Rectangle r2 = (s2 instanceof Rectangle) ? (Rectangle) s2
0811 : s2.getBounds();
0812 r1.add(r2);
0813 }
0814 return r1.contains(x, y);
0815 } catch (BadLocationException ble) {
0816 }
0817 }
0818 return true;
0819 }
0820
0821 /**
0822 * Calls linkActivated on the associated JEditorPane
0823 * if the given position represents a link.<p>This is implemented
0824 * to forward to the method with the same name, but with the following
0825 * args both == -1.
0826 *
0827 * @param pos the position
0828 * @param editor the editor pane
0829 */
0830 protected void activateLink(int pos, JEditorPane editor) {
0831 activateLink(pos, editor, -1, -1);
0832 }
0833
0834 /**
0835 * Calls linkActivated on the associated JEditorPane
0836 * if the given position represents a link. If this was the result
0837 * of a mouse click, <code>x</code> and
0838 * <code>y</code> will give the location of the mouse, otherwise
0839 * they will be < 0.
0840 *
0841 * @param pos the position
0842 * @param html the editor pane
0843 */
0844 void activateLink(int pos, JEditorPane html, int x, int y) {
0845 Document doc = html.getDocument();
0846 if (doc instanceof HTMLDocument) {
0847 HTMLDocument hdoc = (HTMLDocument) doc;
0848 Element e = hdoc.getCharacterElement(pos);
0849 AttributeSet a = e.getAttributes();
0850 AttributeSet anchor = (AttributeSet) a
0851 .getAttribute(HTML.Tag.A);
0852 HyperlinkEvent linkEvent = null;
0853 String description;
0854
0855 if (anchor == null) {
0856 href = getMapHREF(html, hdoc, e, a, pos, x, y);
0857 } else {
0858 href = (String) anchor
0859 .getAttribute(HTML.Attribute.HREF);
0860 }
0861
0862 if (href != null) {
0863 linkEvent = createHyperlinkEvent(html, hdoc, href,
0864 anchor, e);
0865 }
0866 if (linkEvent != null) {
0867 html.fireHyperlinkUpdate(linkEvent);
0868 }
0869 }
0870 }
0871
0872 /**
0873 * Creates and returns a new instance of HyperlinkEvent. If
0874 * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
0875 * will be created.
0876 */
0877 HyperlinkEvent createHyperlinkEvent(JEditorPane html,
0878 HTMLDocument hdoc, String href, AttributeSet anchor,
0879 Element element) {
0880 URL u;
0881 try {
0882 URL base = hdoc.getBase();
0883 u = new URL(base, href);
0884 // Following is a workaround for 1.2, in which
0885 // new URL("file://...", "#...") causes the filename to
0886 // be lost.
0887 if (href != null && "file".equals(u.getProtocol())
0888 && href.startsWith("#")) {
0889 String baseFile = base.getFile();
0890 String newFile = u.getFile();
0891 if (baseFile != null && newFile != null
0892 && !newFile.startsWith(baseFile)) {
0893 u = new URL(base, baseFile + href);
0894 }
0895 }
0896 } catch (MalformedURLException m) {
0897 u = null;
0898 }
0899 HyperlinkEvent linkEvent = null;
0900
0901 if (!hdoc.isFrameDocument()) {
0902 linkEvent = new HyperlinkEvent(html,
0903 HyperlinkEvent.EventType.ACTIVATED, u, href,
0904 element);
0905 } else {
0906 String target = (anchor != null) ? (String) anchor
0907 .getAttribute(HTML.Attribute.TARGET) : null;
0908 if ((target == null) || (target.equals(""))) {
0909 target = hdoc.getBaseTarget();
0910 }
0911 if ((target == null) || (target.equals(""))) {
0912 target = "_self";
0913 }
0914 linkEvent = new HTMLFrameHyperlinkEvent(html,
0915 HyperlinkEvent.EventType.ACTIVATED, u, href,
0916 element, target);
0917 }
0918 return linkEvent;
0919 }
0920
0921 void fireEvents(JEditorPane editor, HTMLDocument doc,
0922 String href, Element lastElem) {
0923 if (this .href != null) {
0924 // fire an exited event on the old link
0925 URL u;
0926 try {
0927 u = new URL(doc.getBase(), this .href);
0928 } catch (MalformedURLException m) {
0929 u = null;
0930 }
0931 HyperlinkEvent exit = new HyperlinkEvent(editor,
0932 HyperlinkEvent.EventType.EXITED, u, this .href,
0933 lastElem);
0934 editor.fireHyperlinkUpdate(exit);
0935 }
0936 if (href != null) {
0937 // fire an entered event on the new link
0938 URL u;
0939 try {
0940 u = new URL(doc.getBase(), href);
0941 } catch (MalformedURLException m) {
0942 u = null;
0943 }
0944 HyperlinkEvent entered = new HyperlinkEvent(editor,
0945 HyperlinkEvent.EventType.ENTERED, u, href,
0946 curElem);
0947 editor.fireHyperlinkUpdate(entered);
0948 }
0949 }
0950 }
0951
0952 /**
0953 * Interface to be supported by the parser. This enables
0954 * providing a different parser while reusing some of the
0955 * implementation provided by this editor kit.
0956 */
0957 public static abstract class Parser {
0958 /**
0959 * Parse the given stream and drive the given callback
0960 * with the results of the parse. This method should
0961 * be implemented to be thread-safe.
0962 */
0963 public abstract void parse(Reader r, ParserCallback cb,
0964 boolean ignoreCharSet) throws IOException;
0965
0966 }
0967
0968 /**
0969 * The result of parsing drives these callback methods.
0970 * The open and close actions should be balanced. The
0971 * <code>flush</code> method will be the last method
0972 * called, to give the receiver a chance to flush any
0973 * pending data into the document.
0974 * <p>Refer to DocumentParser, the default parser used, for further
0975 * information on the contents of the AttributeSets, the positions, and
0976 * other info.
0977 *
0978 * @see javax.swing.text.html.parser.DocumentParser
0979 */
0980 public static class ParserCallback {
0981 /**
0982 * This is passed as an attribute in the attributeset to indicate
0983 * the element is implied eg, the string '<>foo<\t>'
0984 * contains an implied html element and an implied body element.
0985 *
0986 * @since 1.3
0987 */
0988 public static final Object IMPLIED = "_implied_";
0989
0990 public void flush() throws BadLocationException {
0991 }
0992
0993 public void handleText(char[] data, int pos) {
0994 }
0995
0996 public void handleComment(char[] data, int pos) {
0997 }
0998
0999 public void handleStartTag(HTML.Tag t, MutableAttributeSet a,
1000 int pos) {
1001 }
1002
1003 public void handleEndTag(HTML.Tag t, int pos) {
1004 }
1005
1006 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a,
1007 int pos) {
1008 }
1009
1010 public void handleError(String errorMsg, int pos) {
1011 }
1012
1013 /**
1014 * This is invoked after the stream has been parsed, but before
1015 * <code>flush</code>. <code>eol</code> will be one of \n, \r
1016 * or \r\n, which ever is encountered the most in parsing the
1017 * stream.
1018 *
1019 * @since 1.3
1020 */
1021 public void handleEndOfLineString(String eol) {
1022 }
1023 }
1024
1025 /**
1026 * A factory to build views for HTML. The following
1027 * table describes what this factory will build by
1028 * default.
1029 *
1030 * <table summary="Describes the tag and view created by this factory by default">
1031 * <tr>
1032 * <th align=left>Tag<th align=left>View created
1033 * </tr><tr>
1034 * <td>HTML.Tag.CONTENT<td>InlineView
1035 * </tr><tr>
1036 * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView
1037 * </tr><tr>
1038 * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView
1039 * </tr><tr>
1040 * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView
1041 * </tr><tr>
1042 * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView
1043 * </tr><tr>
1044 * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView
1045 * </tr><tr>
1046 * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView
1047 * </tr><tr>
1048 * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView
1049 * </tr><tr>
1050 * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView
1051 * </tr><tr>
1052 * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView
1053 * </tr><tr>
1054 * <td>HTML.Tag.MENU<td>ListView
1055 * </tr><tr>
1056 * <td>HTML.Tag.DIR<td>ListView
1057 * </tr><tr>
1058 * <td>HTML.Tag.UL<td>ListView
1059 * </tr><tr>
1060 * <td>HTML.Tag.OL<td>ListView
1061 * </tr><tr>
1062 * <td>HTML.Tag.LI<td>BlockView
1063 * </tr><tr>
1064 * <td>HTML.Tag.DL<td>BlockView
1065 * </tr><tr>
1066 * <td>HTML.Tag.DD<td>BlockView
1067 * </tr><tr>
1068 * <td>HTML.Tag.BODY<td>BlockView
1069 * </tr><tr>
1070 * <td>HTML.Tag.HTML<td>BlockView
1071 * </tr><tr>
1072 * <td>HTML.Tag.CENTER<td>BlockView
1073 * </tr><tr>
1074 * <td>HTML.Tag.DIV<td>BlockView
1075 * </tr><tr>
1076 * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1077 * </tr><tr>
1078 * <td>HTML.Tag.PRE<td>BlockView
1079 * </tr><tr>
1080 * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
1081 * </tr><tr>
1082 * <td>HTML.Tag.PRE<td>BlockView
1083 * </tr><tr>
1084 * <td>HTML.Tag.IMG<td>ImageView
1085 * </tr><tr>
1086 * <td>HTML.Tag.HR<td>HRuleView
1087 * </tr><tr>
1088 * <td>HTML.Tag.BR<td>BRView
1089 * </tr><tr>
1090 * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView
1091 * </tr><tr>
1092 * <td>HTML.Tag.INPUT<td>FormView
1093 * </tr><tr>
1094 * <td>HTML.Tag.SELECT<td>FormView
1095 * </tr><tr>
1096 * <td>HTML.Tag.TEXTAREA<td>FormView
1097 * </tr><tr>
1098 * <td>HTML.Tag.OBJECT<td>ObjectView
1099 * </tr><tr>
1100 * <td>HTML.Tag.FRAMESET<td>FrameSetView
1101 * </tr><tr>
1102 * <td>HTML.Tag.FRAME<td>FrameView
1103 * </tr>
1104 * </table>
1105 */
1106 public static class HTMLFactory implements ViewFactory {
1107
1108 /**
1109 * Creates a view from an element.
1110 *
1111 * @param elem the element
1112 * @return the view
1113 */
1114 public View create(Element elem) {
1115 AttributeSet attrs = elem.getAttributes();
1116 Object elementName = attrs
1117 .getAttribute(AbstractDocument.ElementNameAttribute);
1118 Object o = (elementName != null) ? null : attrs
1119 .getAttribute(StyleConstants.NameAttribute);
1120 if (o instanceof HTML.Tag) {
1121 HTML.Tag kind = (HTML.Tag) o;
1122 if (kind == HTML.Tag.CONTENT) {
1123 return new InlineView(elem);
1124 } else if (kind == HTML.Tag.IMPLIED) {
1125 String ws = (String) elem.getAttributes()
1126 .getAttribute(CSS.Attribute.WHITE_SPACE);
1127 if ((ws != null) && ws.equals("pre")) {
1128 return new LineView(elem);
1129 }
1130 return new javax.swing.text.html.ParagraphView(elem);
1131 } else if ((kind == HTML.Tag.P)
1132 || (kind == HTML.Tag.H1)
1133 || (kind == HTML.Tag.H2)
1134 || (kind == HTML.Tag.H3)
1135 || (kind == HTML.Tag.H4)
1136 || (kind == HTML.Tag.H5)
1137 || (kind == HTML.Tag.H6)
1138 || (kind == HTML.Tag.DT)) {
1139 // paragraph
1140 return new javax.swing.text.html.ParagraphView(elem);
1141 } else if ((kind == HTML.Tag.MENU)
1142 || (kind == HTML.Tag.DIR)
1143 || (kind == HTML.Tag.UL)
1144 || (kind == HTML.Tag.OL)) {
1145 return new ListView(elem);
1146 } else if (kind == HTML.Tag.BODY) {
1147 return new BodyBlockView(elem);
1148 } else if (kind == HTML.Tag.HTML) {
1149 return new BlockView(elem, View.Y_AXIS);
1150 } else if ((kind == HTML.Tag.LI)
1151 || (kind == HTML.Tag.CENTER)
1152 || (kind == HTML.Tag.DL)
1153 || (kind == HTML.Tag.DD)
1154 || (kind == HTML.Tag.DIV)
1155 || (kind == HTML.Tag.BLOCKQUOTE)
1156 || (kind == HTML.Tag.PRE)
1157 || (kind == HTML.Tag.FORM)) {
1158 // vertical box
1159 return new BlockView(elem, View.Y_AXIS);
1160 } else if (kind == HTML.Tag.NOFRAMES) {
1161 return new NoFramesView(elem, View.Y_AXIS);
1162 } else if (kind == HTML.Tag.IMG) {
1163 return new ImageView(elem);
1164 } else if (kind == HTML.Tag.ISINDEX) {
1165 return new IsindexView(elem);
1166 } else if (kind == HTML.Tag.HR) {
1167 return new HRuleView(elem);
1168 } else if (kind == HTML.Tag.BR) {
1169 return new BRView(elem);
1170 } else if (kind == HTML.Tag.TABLE) {
1171 return new javax.swing.text.html.TableView(elem);
1172 } else if ((kind == HTML.Tag.INPUT)
1173 || (kind == HTML.Tag.SELECT)
1174 || (kind == HTML.Tag.TEXTAREA)) {
1175 return new FormView(elem);
1176 } else if (kind == HTML.Tag.OBJECT) {
1177 return new ObjectView(elem);
1178 } else if (kind == HTML.Tag.FRAMESET) {
1179 if (elem.getAttributes().isDefined(
1180 HTML.Attribute.ROWS)) {
1181 return new FrameSetView(elem, View.Y_AXIS);
1182 } else if (elem.getAttributes().isDefined(
1183 HTML.Attribute.COLS)) {
1184 return new FrameSetView(elem, View.X_AXIS);
1185 }
1186 throw new RuntimeException("Can't build a" + kind
1187 + ", " + elem + ":"
1188 + "no ROWS or COLS defined.");
1189 } else if (kind == HTML.Tag.FRAME) {
1190 return new FrameView(elem);
1191 } else if (kind instanceof HTML.UnknownTag) {
1192 return new HiddenTagView(elem);
1193 } else if (kind == HTML.Tag.COMMENT) {
1194 return new CommentView(elem);
1195 } else if (kind == HTML.Tag.HEAD) {
1196 // Make the head never visible, and never load its
1197 // children. For Cursor positioning,
1198 // getNextVisualPositionFrom is overriden to always return
1199 // the end offset of the element.
1200 return new BlockView(elem, View.X_AXIS) {
1201 public float getPreferredSpan(int axis) {
1202 return 0;
1203 }
1204
1205 public float getMinimumSpan(int axis) {
1206 return 0;
1207 }
1208
1209 public float getMaximumSpan(int axis) {
1210 return 0;
1211 }
1212
1213 protected void loadChildren(ViewFactory f) {
1214 }
1215
1216 public Shape modelToView(int pos, Shape a,
1217 Position.Bias b)
1218 throws BadLocationException {
1219 return a;
1220 }
1221
1222 public int getNextVisualPositionFrom(int pos,
1223 Position.Bias b, Shape a,
1224 int direction, Position.Bias[] biasRet) {
1225 return getElement().getEndOffset();
1226 }
1227 };
1228 } else if ((kind == HTML.Tag.TITLE)
1229 || (kind == HTML.Tag.META)
1230 || (kind == HTML.Tag.LINK)
1231 || (kind == HTML.Tag.STYLE)
1232 || (kind == HTML.Tag.SCRIPT)
1233 || (kind == HTML.Tag.AREA)
1234 || (kind == HTML.Tag.MAP)
1235 || (kind == HTML.Tag.PARAM)
1236 || (kind == HTML.Tag.APPLET)) {
1237 return new HiddenTagView(elem);
1238 }
1239 }
1240 // If we get here, it's either an element we don't know about
1241 // or something from StyledDocument that doesn't have a mapping to HTML.
1242 String nm = (elementName != null) ? (String) elementName
1243 : elem.getName();
1244 if (nm != null) {
1245 if (nm.equals(AbstractDocument.ContentElementName)) {
1246 return new LabelView(elem);
1247 } else if (nm
1248 .equals(AbstractDocument.ParagraphElementName)) {
1249 return new ParagraphView(elem);
1250 } else if (nm
1251 .equals(AbstractDocument.SectionElementName)) {
1252 return new BoxView(elem, View.Y_AXIS);
1253 } else if (nm
1254 .equals(StyleConstants.ComponentElementName)) {
1255 return new ComponentView(elem);
1256 } else if (nm.equals(StyleConstants.IconElementName)) {
1257 return new IconView(elem);
1258 }
1259 }
1260
1261 // default to text display
1262 return new LabelView(elem);
1263 }
1264
1265 static class BodyBlockView extends BlockView implements
1266 ComponentListener {
1267 public BodyBlockView(Element elem) {
1268 super (elem, View.Y_AXIS);
1269 }
1270
1271 // reimplement major axis requirements to indicate that the
1272 // block is flexible for the body element... so that it can
1273 // be stretched to fill the background properly.
1274 protected SizeRequirements calculateMajorAxisRequirements(
1275 int axis, SizeRequirements r) {
1276 r = super .calculateMajorAxisRequirements(axis, r);
1277 r.maximum = Integer.MAX_VALUE;
1278 return r;
1279 }
1280
1281 protected void layoutMinorAxis(int targetSpan, int axis,
1282 int[] offsets, int[] spans) {
1283 Container container = getContainer();
1284 Container parentContainer;
1285 if (container != null
1286 && (container instanceof javax.swing.JEditorPane)
1287 && (parentContainer = container.getParent()) != null
1288 && (parentContainer instanceof javax.swing.JViewport)) {
1289 JViewport viewPort = (JViewport) parentContainer;
1290 Object cachedObject;
1291 if (cachedViewPort != null) {
1292 if ((cachedObject = cachedViewPort.get()) != null) {
1293 if (cachedObject != viewPort) {
1294 ((JComponent) cachedObject)
1295 .removeComponentListener(this );
1296 }
1297 } else {
1298 cachedViewPort = null;
1299 }
1300 }
1301 if (cachedViewPort == null) {
1302 viewPort.addComponentListener(this );
1303 cachedViewPort = new WeakReference(viewPort);
1304 }
1305
1306 componentVisibleWidth = viewPort.getExtentSize().width;
1307 if (componentVisibleWidth > 0) {
1308 Insets insets = container.getInsets();
1309 viewVisibleWidth = componentVisibleWidth
1310 - insets.left - getLeftInset();
1311 //try to use viewVisibleWidth if it is smaller than targetSpan
1312 targetSpan = Math.min(targetSpan,
1313 viewVisibleWidth);
1314 }
1315 } else {
1316 if (cachedViewPort != null) {
1317 Object cachedObject;
1318 if ((cachedObject = cachedViewPort.get()) != null) {
1319 ((JComponent) cachedObject)
1320 .removeComponentListener(this );
1321 }
1322 cachedViewPort = null;
1323 }
1324 }
1325 super .layoutMinorAxis(targetSpan, axis, offsets, spans);
1326 }
1327
1328 public void setParent(View parent) {
1329 //if parent == null unregister component listener
1330 if (parent == null) {
1331 if (cachedViewPort != null) {
1332 Object cachedObject;
1333 if ((cachedObject = cachedViewPort.get()) != null) {
1334 ((JComponent) cachedObject)
1335 .removeComponentListener(this );
1336 }
1337 cachedViewPort = null;
1338 }
1339 }
1340 super .setParent(parent);
1341 }
1342
1343 public void componentResized(ComponentEvent e) {
1344 if (!(e.getSource() instanceof JViewport)) {
1345 return;
1346 }
1347 JViewport viewPort = (JViewport) e.getSource();
1348 if (componentVisibleWidth != viewPort.getExtentSize().width) {
1349 Document doc = getDocument();
1350 if (doc instanceof AbstractDocument) {
1351 AbstractDocument document = (AbstractDocument) getDocument();
1352 document.readLock();
1353 try {
1354 layoutChanged(X_AXIS);
1355 preferenceChanged(null, true, true);
1356 } finally {
1357 document.readUnlock();
1358 }
1359
1360 }
1361 }
1362 }
1363
1364 public void componentHidden(ComponentEvent e) {
1365 }
1366
1367 public void componentMoved(ComponentEvent e) {
1368 }
1369
1370 public void componentShown(ComponentEvent e) {
1371 }
1372
1373 /*
1374 * we keep weak reference to viewPort if and only if BodyBoxView is listening for ComponentEvents
1375 * only in that case cachedViewPort is not equal to null.
1376 * we need to keep this reference in order to remove BodyBoxView from viewPort listeners.
1377 *
1378 */
1379 private Reference cachedViewPort = null;
1380 private boolean isListening = false;
1381 private int viewVisibleWidth = Integer.MAX_VALUE;
1382 private int componentVisibleWidth = Integer.MAX_VALUE;
1383 }
1384
1385 }
1386
1387 // --- Action implementations ------------------------------
1388
1389 /** The bold action identifier
1390 */
1391 public static final String BOLD_ACTION = "html-bold-action";
1392 /** The italic action identifier
1393 */
1394 public static final String ITALIC_ACTION = "html-italic-action";
1395 /** The paragraph left indent action identifier
1396 */
1397 public static final String PARA_INDENT_LEFT = "html-para-indent-left";
1398 /** The paragraph right indent action identifier
1399 */
1400 public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
1401 /** The font size increase to next value action identifier
1402 */
1403 public static final String FONT_CHANGE_BIGGER = "html-font-bigger";
1404 /** The font size decrease to next value action identifier
1405 */
1406 public static final String FONT_CHANGE_SMALLER = "html-font-smaller";
1407 /** The Color choice action identifier
1408 The color is passed as an argument
1409 */
1410 public static final String COLOR_ACTION = "html-color-action";
1411 /** The logical style choice action identifier
1412 The logical style is passed in as an argument
1413 */
1414 public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action";
1415 /**
1416 * Align images at the top.
1417 */
1418 public static final String IMG_ALIGN_TOP = "html-image-align-top";
1419
1420 /**
1421 * Align images in the middle.
1422 */
1423 public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle";
1424
1425 /**
1426 * Align images at the bottom.
1427 */
1428 public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom";
1429
1430 /**
1431 * Align images at the border.
1432 */
1433 public static final String IMG_BORDER = "html-image-border";
1434
1435 /** HTML used when inserting tables. */
1436 private static final String INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>";
1437
1438 /** HTML used when inserting unordered lists. */
1439 private static final String INSERT_UL_HTML = "<ul><li></li></ul>";
1440
1441 /** HTML used when inserting ordered lists. */
1442 private static final String INSERT_OL_HTML = "<ol><li></li></ol>";
1443
1444 /** HTML used when inserting hr. */
1445 private static final String INSERT_HR_HTML = "<hr>";
1446
1447 /** HTML used when inserting pre. */
1448 private static final String INSERT_PRE_HTML = "<pre></pre>";
1449
1450 private static NavigateLinkAction nextLinkAction = new NavigateLinkAction(
1451 "next-link-action");
1452
1453 private static NavigateLinkAction previousLinkAction = new NavigateLinkAction(
1454 "previous-link-action");
1455
1456 private static ActivateLinkAction activateLinkAction = new ActivateLinkAction(
1457 "activate-link-action");
1458
1459 private static final Action[] defaultActions = {
1460 new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML,
1461 HTML.Tag.BODY, HTML.Tag.TABLE),
1462 new InsertHTMLTextAction("InsertTableRow",
1463 INSERT_TABLE_HTML, HTML.Tag.TABLE, HTML.Tag.TR,
1464 HTML.Tag.BODY, HTML.Tag.TABLE),
1465 new InsertHTMLTextAction("InsertTableDataCell",
1466 INSERT_TABLE_HTML, HTML.Tag.TR, HTML.Tag.TD,
1467 HTML.Tag.BODY, HTML.Tag.TABLE),
1468 new InsertHTMLTextAction("InsertUnorderedList",
1469 INSERT_UL_HTML, HTML.Tag.BODY, HTML.Tag.UL),
1470 new InsertHTMLTextAction("InsertUnorderedListItem",
1471 INSERT_UL_HTML, HTML.Tag.UL, HTML.Tag.LI,
1472 HTML.Tag.BODY, HTML.Tag.UL),
1473 new InsertHTMLTextAction("InsertOrderedList",
1474 INSERT_OL_HTML, HTML.Tag.BODY, HTML.Tag.OL),
1475 new InsertHTMLTextAction("InsertOrderedListItem",
1476 INSERT_OL_HTML, HTML.Tag.OL, HTML.Tag.LI,
1477 HTML.Tag.BODY, HTML.Tag.OL),
1478 new InsertHRAction(),
1479 new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML,
1480 HTML.Tag.BODY, HTML.Tag.PRE), nextLinkAction,
1481 previousLinkAction, activateLinkAction,
1482
1483 new BeginAction(beginAction, false),
1484 new BeginAction(selectionBeginAction, true) };
1485
1486 /**
1487 * An abstract Action providing some convenience methods that may
1488 * be useful in inserting HTML into an existing document.
1489 * <p>NOTE: None of the convenience methods obtain a lock on the
1490 * document. If you have another thread modifying the text these
1491 * methods may have inconsistent behavior, or return the wrong thing.
1492 */
1493 public static abstract class HTMLTextAction extends
1494 StyledTextAction {
1495 public HTMLTextAction(String name) {
1496 super (name);
1497 }
1498
1499 /**
1500 * @return HTMLDocument of <code>e</code>.
1501 */
1502 protected HTMLDocument getHTMLDocument(JEditorPane e) {
1503 Document d = e.getDocument();
1504 if (d instanceof HTMLDocument) {
1505 return (HTMLDocument) d;
1506 }
1507 throw new IllegalArgumentException(
1508 "document must be HTMLDocument");
1509 }
1510
1511 /**
1512 * @return HTMLEditorKit for <code>e</code>.
1513 */
1514 protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
1515 EditorKit k = e.getEditorKit();
1516 if (k instanceof HTMLEditorKit) {
1517 return (HTMLEditorKit) k;
1518 }
1519 throw new IllegalArgumentException(
1520 "EditorKit must be HTMLEditorKit");
1521 }
1522
1523 /**
1524 * Returns an array of the Elements that contain <code>offset</code>.
1525 * The first elements corresponds to the root.
1526 */
1527 protected Element[] getElementsAt(HTMLDocument doc, int offset) {
1528 return getElementsAt(doc.getDefaultRootElement(), offset, 0);
1529 }
1530
1531 /**
1532 * Recursive method used by getElementsAt.
1533 */
1534 private Element[] getElementsAt(Element parent, int offset,
1535 int depth) {
1536 if (parent.isLeaf()) {
1537 Element[] retValue = new Element[depth + 1];
1538 retValue[depth] = parent;
1539 return retValue;
1540 }
1541 Element[] retValue = getElementsAt(parent.getElement(parent
1542 .getElementIndex(offset)), offset, depth + 1);
1543 retValue[depth] = parent;
1544 return retValue;
1545 }
1546
1547 /**
1548 * Returns number of elements, starting at the deepest leaf, needed
1549 * to get to an element representing <code>tag</code>. This will
1550 * return -1 if no elements is found representing <code>tag</code>,
1551 * or 0 if the parent of the leaf at <code>offset</code> represents
1552 * <code>tag</code>.
1553 */
1554 protected int elementCountToTag(HTMLDocument doc, int offset,
1555 HTML.Tag tag) {
1556 int depth = -1;
1557 Element e = doc.getCharacterElement(offset);
1558 while (e != null
1559 && e.getAttributes().getAttribute(
1560 StyleConstants.NameAttribute) != tag) {
1561 e = e.getParentElement();
1562 depth++;
1563 }
1564 if (e == null) {
1565 return -1;
1566 }
1567 return depth;
1568 }
1569
1570 /**
1571 * Returns the deepest element at <code>offset</code> matching
1572 * <code>tag</code>.
1573 */
1574 protected Element findElementMatchingTag(HTMLDocument doc,
1575 int offset, HTML.Tag tag) {
1576 Element e = doc.getDefaultRootElement();
1577 Element lastMatch = null;
1578 while (e != null) {
1579 if (e.getAttributes().getAttribute(
1580 StyleConstants.NameAttribute) == tag) {
1581 lastMatch = e;
1582 }
1583 e = e.getElement(e.getElementIndex(offset));
1584 }
1585 return lastMatch;
1586 }
1587 }
1588
1589 /**
1590 * InsertHTMLTextAction can be used to insert an arbitrary string of HTML
1591 * into an existing HTML document. At least two HTML.Tags need to be
1592 * supplied. The first Tag, parentTag, identifies the parent in
1593 * the document to add the elements to. The second tag, addTag,
1594 * identifies the first tag that should be added to the document as
1595 * seen in the HTML string. One important thing to remember, is that
1596 * the parser is going to generate all the appropriate tags, even if
1597 * they aren't in the HTML string passed in.<p>
1598 * For example, lets say you wanted to create an action to insert
1599 * a table into the body. The parentTag would be HTML.Tag.BODY,
1600 * addTag would be HTML.Tag.TABLE, and the string could be something
1601 * like <table><tr><td></td></tr></table>.
1602 * <p>There is also an option to supply an alternate parentTag and
1603 * addTag. These will be checked for if there is no parentTag at
1604 * offset.
1605 */
1606 public static class InsertHTMLTextAction extends HTMLTextAction {
1607 public InsertHTMLTextAction(String name, String html,
1608 HTML.Tag parentTag, HTML.Tag addTag) {
1609 this (name, html, parentTag, addTag, null, null);
1610 }
1611
1612 public InsertHTMLTextAction(String name, String html,
1613 HTML.Tag parentTag, HTML.Tag addTag,
1614 HTML.Tag alternateParentTag, HTML.Tag alternateAddTag) {
1615 this (name, html, parentTag, addTag, alternateParentTag,
1616 alternateAddTag, true);
1617 }
1618
1619 /* public */
1620 InsertHTMLTextAction(String name, String html,
1621 HTML.Tag parentTag, HTML.Tag addTag,
1622 HTML.Tag alternateParentTag, HTML.Tag alternateAddTag,
1623 boolean adjustSelection) {
1624 super (name);
1625 this .html = html;
1626 this .parentTag = parentTag;
1627 this .addTag = addTag;
1628 this .alternateParentTag = alternateParentTag;
1629 this .alternateAddTag = alternateAddTag;
1630 this .adjustSelection = adjustSelection;
1631 }
1632
1633 /**
1634 * A cover for HTMLEditorKit.insertHTML. If an exception it
1635 * thrown it is wrapped in a RuntimeException and thrown.
1636 */
1637 protected void insertHTML(JEditorPane editor, HTMLDocument doc,
1638 int offset, String html, int popDepth, int pushDepth,
1639 HTML.Tag addTag) {
1640 try {
1641 getHTMLEditorKit(editor).insertHTML(doc, offset, html,
1642 popDepth, pushDepth, addTag);
1643 } catch (IOException ioe) {
1644 throw new RuntimeException("Unable to insert: " + ioe);
1645 } catch (BadLocationException ble) {
1646 throw new RuntimeException("Unable to insert: " + ble);
1647 }
1648 }
1649
1650 /**
1651 * This is invoked when inserting at a boundary. It determines
1652 * the number of pops, and then the number of pushes that need
1653 * to be performed, and then invokes insertHTML.
1654 * @since 1.3
1655 */
1656 protected void insertAtBoundary(JEditorPane editor,
1657 HTMLDocument doc, int offset, Element insertElement,
1658 String html, HTML.Tag parentTag, HTML.Tag addTag) {
1659 insertAtBoundry(editor, doc, offset, insertElement, html,
1660 parentTag, addTag);
1661 }
1662
1663 /**
1664 * This is invoked when inserting at a boundary. It determines
1665 * the number of pops, and then the number of pushes that need
1666 * to be performed, and then invokes insertHTML.
1667 * @deprecated As of Java 2 platform v1.3, use insertAtBoundary
1668 */
1669 @Deprecated
1670 protected void insertAtBoundry(JEditorPane editor,
1671 HTMLDocument doc, int offset, Element insertElement,
1672 String html, HTML.Tag parentTag, HTML.Tag addTag) {
1673 // Find the common parent.
1674 Element e;
1675 Element commonParent;
1676 boolean isFirst = (offset == 0);
1677
1678 if (offset > 0 || insertElement == null) {
1679 e = doc.getDefaultRootElement();
1680 while (e != null && e.getStartOffset() != offset
1681 && !e.isLeaf()) {
1682 e = e.getElement(e.getElementIndex(offset));
1683 }
1684 commonParent = (e != null) ? e.getParentElement()
1685 : null;
1686 } else {
1687 // If inserting at the origin, the common parent is the
1688 // insertElement.
1689 commonParent = insertElement;
1690 }
1691 if (commonParent != null) {
1692 // Determine how many pops to do.
1693 int pops = 0;
1694 int pushes = 0;
1695 if (isFirst && insertElement != null) {
1696 e = commonParent;
1697 while (e != null && !e.isLeaf()) {
1698 e = e.getElement(e.getElementIndex(offset));
1699 pops++;
1700 }
1701 } else {
1702 e = commonParent;
1703 offset--;
1704 while (e != null && !e.isLeaf()) {
1705 e = e.getElement(e.getElementIndex(offset));
1706 pops++;
1707 }
1708
1709 // And how many pushes
1710 e = commonParent;
1711 offset++;
1712 while (e != null && e != insertElement) {
1713 e = e.getElement(e.getElementIndex(offset));
1714 pushes++;
1715 }
1716 }
1717 pops = Math.max(0, pops - 1);
1718
1719 // And insert!
1720 insertHTML(editor, doc, offset, html, pops, pushes,
1721 addTag);
1722 }
1723 }
1724
1725 /**
1726 * If there is an Element with name <code>tag</code> at
1727 * <code>offset</code>, this will invoke either insertAtBoundary
1728 * or <code>insertHTML</code>. This returns true if there is
1729 * a match, and one of the inserts is invoked.
1730 */
1731 /*protected*/
1732 boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
1733 int offset, HTML.Tag tag, HTML.Tag addTag) {
1734 Element e = findElementMatchingTag(doc, offset, tag);
1735 if (e != null && e.getStartOffset() == offset) {
1736 insertAtBoundary(editor, doc, offset, e, html, tag,
1737 addTag);
1738 return true;
1739 } else if (offset > 0) {
1740 int depth = elementCountToTag(doc, offset - 1, tag);
1741 if (depth != -1) {
1742 insertHTML(editor, doc, offset, html, depth, 0,
1743 addTag);
1744 return true;
1745 }
1746 }
1747 return false;
1748 }
1749
1750 /**
1751 * Called after an insertion to adjust the selection.
1752 */
1753 /* protected */
1754 void adjustSelection(JEditorPane pane, HTMLDocument doc,
1755 int startOffset, int oldLength) {
1756 int newLength = doc.getLength();
1757 if (newLength != oldLength && startOffset < newLength) {
1758 if (startOffset > 0) {
1759 String text;
1760 try {
1761 text = doc.getText(startOffset - 1, 1);
1762 } catch (BadLocationException ble) {
1763 text = null;
1764 }
1765 if (text != null && text.length() > 0
1766 && text.charAt(0) == '\n') {
1767 pane.select(startOffset, startOffset);
1768 } else {
1769 pane.select(startOffset + 1, startOffset + 1);
1770 }
1771 } else {
1772 pane.select(1, 1);
1773 }
1774 }
1775 }
1776
1777 /**
1778 * Inserts the HTML into the document.
1779 *
1780 * @param ae the event
1781 */
1782 public void actionPerformed(ActionEvent ae) {
1783 JEditorPane editor = getEditor(ae);
1784 if (editor != null) {
1785 HTMLDocument doc = getHTMLDocument(editor);
1786 int offset = editor.getSelectionStart();
1787 int length = doc.getLength();
1788 boolean inserted;
1789 // Try first choice
1790 if (!insertIntoTag(editor, doc, offset, parentTag,
1791 addTag)
1792 && alternateParentTag != null) {
1793 // Then alternate.
1794 inserted = insertIntoTag(editor, doc, offset,
1795 alternateParentTag, alternateAddTag);
1796 } else {
1797 inserted = true;
1798 }
1799 if (adjustSelection && inserted) {
1800 adjustSelection(editor, doc, offset, length);
1801 }
1802 }
1803 }
1804
1805 /** HTML to insert. */
1806 protected String html;
1807 /** Tag to check for in the document. */
1808 protected HTML.Tag parentTag;
1809 /** Tag in HTML to start adding tags from. */
1810 protected HTML.Tag addTag;
1811 /** Alternate Tag to check for in the document if parentTag is
1812 * not found. */
1813 protected HTML.Tag alternateParentTag;
1814 /** Alternate tag in HTML to start adding tags from if parentTag
1815 * is not found and alternateParentTag is found. */
1816 protected HTML.Tag alternateAddTag;
1817 /** True indicates the selection should be adjusted after an insert. */
1818 boolean adjustSelection;
1819 }
1820
1821 /**
1822 * InsertHRAction is special, at actionPerformed time it will determine
1823 * the parent HTML.Tag based on the paragraph element at the selection
1824 * start.
1825 */
1826 static class InsertHRAction extends InsertHTMLTextAction {
1827 InsertHRAction() {
1828 super ("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null,
1829 null, false);
1830 }
1831
1832 /**
1833 * Inserts the HTML into the document.
1834 *
1835 * @param ae the event
1836 */
1837 public void actionPerformed(ActionEvent ae) {
1838 JEditorPane editor = getEditor(ae);
1839 if (editor != null) {
1840 HTMLDocument doc = getHTMLDocument(editor);
1841 int offset = editor.getSelectionStart();
1842 Element paragraph = doc.getParagraphElement(offset);
1843 if (paragraph.getParentElement() != null) {
1844 parentTag = (HTML.Tag) paragraph.getParentElement()
1845 .getAttributes().getAttribute(
1846 StyleConstants.NameAttribute);
1847 super .actionPerformed(ae);
1848 }
1849 }
1850 }
1851
1852 }
1853
1854 /*
1855 * Returns the object in an AttributeSet matching a key
1856 */
1857 static private Object getAttrValue(AttributeSet attr,
1858 HTML.Attribute key) {
1859 Enumeration names = attr.getAttributeNames();
1860 while (names.hasMoreElements()) {
1861 Object nextKey = names.nextElement();
1862 Object nextVal = attr.getAttribute(nextKey);
1863 if (nextVal instanceof AttributeSet) {
1864 Object value = getAttrValue((AttributeSet) nextVal, key);
1865 if (value != null) {
1866 return value;
1867 }
1868 } else if (nextKey == key) {
1869 return nextVal;
1870 }
1871 }
1872 return null;
1873 }
1874
1875 /*
1876 * Action to move the focus on the next or previous hypertext link
1877 * or object. TODO: This method relies on support from the
1878 * javax.accessibility package. The text package should support
1879 * keyboard navigation of text elements directly.
1880 */
1881 static class NavigateLinkAction extends TextAction implements
1882 CaretListener {
1883
1884 private static int prevHypertextOffset = -1;
1885 private static boolean foundLink = false;
1886 private static FocusHighlightPainter focusPainter = new FocusHighlightPainter(
1887 null);
1888 private Object selectionTag;
1889 private boolean focusBack = false;
1890
1891 /*
1892 * Create this action with the appropriate identifier.
1893 */
1894 public NavigateLinkAction(String actionName) {
1895 super (actionName);
1896 if ("previous-link-action".equals(actionName)) {
1897 focusBack = true;
1898 }
1899 }
1900
1901 /**
1902 * Called when the caret position is updated.
1903 *
1904 * @param e the caret event
1905 */
1906 public void caretUpdate(CaretEvent e) {
1907 if (foundLink) {
1908 foundLink = false;
1909 // TODO: The AccessibleContext for the editor should register
1910 // as a listener for CaretEvents and forward the events to
1911 // assistive technologies listening for such events.
1912 Object src = e.getSource();
1913 if (src instanceof JTextComponent) {
1914 ((JTextComponent) src)
1915 .getAccessibleContext()
1916 .firePropertyChange(
1917 AccessibleContext.ACCESSIBLE_HYPERTEXT_OFFSET,
1918 new Integer(prevHypertextOffset),
1919 new Integer(e.getDot()));
1920 }
1921 }
1922 }
1923
1924 /*
1925 * The operation to perform when this action is triggered.
1926 */
1927 public void actionPerformed(ActionEvent e) {
1928 JTextComponent comp = getTextComponent(e);
1929 if (comp == null || comp.isEditable()) {
1930 return;
1931 }
1932 Document doc = comp.getDocument();
1933 if (doc == null) {
1934 return;
1935 }
1936 // TODO: Should start successive iterations from the
1937 // current caret position.
1938 ElementIterator ei = new ElementIterator(doc);
1939
1940 int currentOffset = comp.getCaretPosition();
1941 int prevStartOffset = -1;
1942 int prevEndOffset = -1;
1943
1944 // highlight the next link or object after the current caret position
1945 Element nextElement = null;
1946 while ((nextElement = ei.next()) != null) {
1947 String name = nextElement.getName();
1948 AttributeSet attr = nextElement.getAttributes();
1949
1950 Object href = getAttrValue(attr, HTML.Attribute.HREF);
1951 if (!(name.equals(HTML.Tag.OBJECT.toString()))
1952 && href == null) {
1953 continue;
1954 }
1955
1956 int elementOffset = nextElement.getStartOffset();
1957 if (focusBack) {
1958 if (elementOffset >= currentOffset
1959 && prevStartOffset >= 0) {
1960
1961 foundLink = true;
1962 comp.setCaretPosition(prevStartOffset);
1963 moveCaretPosition(comp, prevStartOffset,
1964 prevEndOffset);
1965 prevHypertextOffset = prevStartOffset;
1966 return;
1967 }
1968 } else { // focus forward
1969 if (elementOffset > currentOffset) {
1970
1971 foundLink = true;
1972 comp.setCaretPosition(elementOffset);
1973 moveCaretPosition(comp, elementOffset,
1974 nextElement.getEndOffset());
1975 prevHypertextOffset = elementOffset;
1976 return;
1977 }
1978 }
1979 prevStartOffset = nextElement.getStartOffset();
1980 prevEndOffset = nextElement.getEndOffset();
1981 }
1982 if (focusBack && prevStartOffset >= 0) {
1983 foundLink = true;
1984 comp.setCaretPosition(prevStartOffset);
1985 moveCaretPosition(comp, prevStartOffset, prevEndOffset);
1986 prevHypertextOffset = prevStartOffset;
1987 return;
1988 }
1989 }
1990
1991 /*
1992 * Moves the caret from mark to dot
1993 */
1994 private void moveCaretPosition(JTextComponent comp, int mark,
1995 int dot) {
1996 Highlighter h = comp.getHighlighter();
1997 if (h != null) {
1998 int p0 = Math.min(dot, mark);
1999 int p1 = Math.max(dot, mark);
2000 try {
2001 if (selectionTag != null) {
2002 h.changeHighlight(selectionTag, p0, p1);
2003 } else {
2004 Highlighter.HighlightPainter p = focusPainter;
2005 selectionTag = h.addHighlight(p0, p1, p);
2006 }
2007 } catch (BadLocationException e) {
2008 }
2009 }
2010 }
2011
2012 /**
2013 * A highlight painter that draws a one-pixel border around
2014 * the highlighted area.
2015 */
2016 static class FocusHighlightPainter extends
2017 DefaultHighlighter.DefaultHighlightPainter {
2018
2019 FocusHighlightPainter(Color color) {
2020 super (color);
2021 }
2022
2023 /**
2024 * Paints a portion of a highlight.
2025 *
2026 * @param g the graphics context
2027 * @param offs0 the starting model offset >= 0
2028 * @param offs1 the ending model offset >= offs1
2029 * @param bounds the bounding box of the view, which is not
2030 * necessarily the region to paint.
2031 * @param c the editor
2032 * @param view View painting for
2033 * @return region in which drawing occurred
2034 */
2035 public Shape paintLayer(Graphics g, int offs0, int offs1,
2036 Shape bounds, JTextComponent c, View view) {
2037
2038 Color color = getColor();
2039
2040 if (color == null) {
2041 g.setColor(c.getSelectionColor());
2042 } else {
2043 g.setColor(color);
2044 }
2045 if (offs0 == view.getStartOffset()
2046 && offs1 == view.getEndOffset()) {
2047 // Contained in view, can just use bounds.
2048 Rectangle alloc;
2049 if (bounds instanceof Rectangle) {
2050 alloc = (Rectangle) bounds;
2051 } else {
2052 alloc = bounds.getBounds();
2053 }
2054 g.drawRect(alloc.x, alloc.y, alloc.width - 1,
2055 alloc.height);
2056 return alloc;
2057 } else {
2058 // Should only render part of View.
2059 try {
2060 // --- determine locations ---
2061 Shape shape = view.modelToView(offs0,
2062 Position.Bias.Forward, offs1,
2063 Position.Bias.Backward, bounds);
2064 Rectangle r = (shape instanceof Rectangle) ? (Rectangle) shape
2065 : shape.getBounds();
2066 g.drawRect(r.x, r.y, r.width - 1, r.height);
2067 return r;
2068 } catch (BadLocationException e) {
2069 // can't render
2070 }
2071 }
2072 // Only if exception
2073 return null;
2074 }
2075 }
2076 }
2077
2078 /*
2079 * Action to activate the hypertext link that has focus.
2080 * TODO: This method relies on support from the
2081 * javax.accessibility package. The text package should support
2082 * keyboard navigation of text elements directly.
2083 */
2084 static class ActivateLinkAction extends TextAction {
2085
2086 /**
2087 * Create this action with the appropriate identifier.
2088 */
2089 public ActivateLinkAction(String actionName) {
2090 super (actionName);
2091 }
2092
2093 /*
2094 * activates the hyperlink at offset
2095 */
2096 private void activateLink(String href, HTMLDocument doc,
2097 JEditorPane editor, int offset) {
2098 try {
2099 URL page = (URL) doc
2100 .getProperty(Document.StreamDescriptionProperty);
2101 URL url = new URL(page, href);
2102 HyperlinkEvent linkEvent = new HyperlinkEvent(editor,
2103 HyperlinkEvent.EventType.ACTIVATED, url, url
2104 .toExternalForm(), doc
2105 .getCharacterElement(offset));
2106 editor.fireHyperlinkUpdate(linkEvent);
2107 } catch (MalformedURLException m) {
2108 }
2109 }
2110
2111 /*
2112 * Invokes default action on the object in an element
2113 */
2114 private void doObjectAction(JEditorPane editor, Element elem) {
2115 View view = getView(editor, elem);
2116 if (view != null && view instanceof ObjectView) {
2117 Component comp = ((ObjectView) view).getComponent();
2118 if (comp != null && comp instanceof Accessible) {
2119 AccessibleContext ac = ((Accessible) comp)
2120 .getAccessibleContext();
2121 if (ac != null) {
2122 AccessibleAction aa = ac.getAccessibleAction();
2123 if (aa != null) {
2124 aa.doAccessibleAction(0);
2125 }
2126 }
2127 }
2128 }
2129 }
2130
2131 /*
2132 * Returns the root view for a document
2133 */
2134 private View getRootView(JEditorPane editor) {
2135 return editor.getUI().getRootView(editor);
2136 }
2137
2138 /*
2139 * Returns a view associated with an element
2140 */
2141 private View getView(JEditorPane editor, Element elem) {
2142 Object lock = lock(editor);
2143 try {
2144 View rootView = getRootView(editor);
2145 int start = elem.getStartOffset();
2146 if (rootView != null) {
2147 return getView(rootView, elem, start);
2148 }
2149 return null;
2150 } finally {
2151 unlock(lock);
2152 }
2153 }
2154
2155 private View getView(View parent, Element elem, int start) {
2156 if (parent.getElement() == elem) {
2157 return parent;
2158 }
2159 int index = parent.getViewIndex(start,
2160 Position.Bias.Forward);
2161
2162 if (index != -1 && index < parent.getViewCount()) {
2163 return getView(parent.getView(index), elem, start);
2164 }
2165 return null;
2166 }
2167
2168 /*
2169 * If possible acquires a lock on the Document. If a lock has been
2170 * obtained a key will be retured that should be passed to
2171 * <code>unlock</code>.
2172 */
2173 private Object lock(JEditorPane editor) {
2174 Document document = editor.getDocument();
2175
2176 if (document instanceof AbstractDocument) {
2177 ((AbstractDocument) document).readLock();
2178 return document;
2179 }
2180 return null;
2181 }
2182
2183 /*
2184 * Releases a lock previously obtained via <code>lock</code>.
2185 */
2186 private void unlock(Object key) {
2187 if (key != null) {
2188 ((AbstractDocument) key).readUnlock();
2189 }
2190 }
2191
2192 /*
2193 * The operation to perform when this action is triggered.
2194 */
2195 public void actionPerformed(ActionEvent e) {
2196
2197 JTextComponent c = getTextComponent(e);
2198 if (c.isEditable() || !(c instanceof JEditorPane)) {
2199 return;
2200 }
2201 JEditorPane editor = (JEditorPane) c;
2202
2203 Document d = editor.getDocument();
2204 if (d == null || !(d instanceof HTMLDocument)) {
2205 return;
2206 }
2207 HTMLDocument doc = (HTMLDocument) d;
2208
2209 ElementIterator ei = new ElementIterator(doc);
2210 int currentOffset = editor.getCaretPosition();
2211
2212 // invoke the next link or object action
2213 String urlString = null;
2214 String objString = null;
2215 Element currentElement = null;
2216 while ((currentElement = ei.next()) != null) {
2217 String name = currentElement.getName();
2218 AttributeSet attr = currentElement.getAttributes();
2219
2220 Object href = getAttrValue(attr, HTML.Attribute.HREF);
2221 if (href != null) {
2222 if (currentOffset >= currentElement
2223 .getStartOffset()
2224 && currentOffset <= currentElement
2225 .getEndOffset()) {
2226
2227 activateLink((String) href, doc, editor,
2228 currentOffset);
2229 return;
2230 }
2231 } else if (name.equals(HTML.Tag.OBJECT.toString())) {
2232 Object obj = getAttrValue(attr,
2233 HTML.Attribute.CLASSID);
2234 if (obj != null) {
2235 if (currentOffset >= currentElement
2236 .getStartOffset()
2237 && currentOffset <= currentElement
2238 .getEndOffset()) {
2239
2240 doObjectAction(editor, currentElement);
2241 return;
2242 }
2243 }
2244 }
2245 }
2246 }
2247 }
2248
2249 private static int getBodyElementStart(JTextComponent comp) {
2250 Element rootElement = comp.getDocument().getRootElements()[0];
2251 for (int i = 0; i < rootElement.getElementCount(); i++) {
2252 Element currElement = rootElement.getElement(i);
2253 if ("body".equals(currElement.getName())) {
2254 return currElement.getStartOffset();
2255 }
2256 }
2257 return 0;
2258 }
2259
2260 /*
2261 * Move the caret to the beginning of the document.
2262 * @see DefaultEditorKit#beginAction
2263 * @see HTMLEditorKit#getActions
2264 */
2265
2266 static class BeginAction extends TextAction {
2267
2268 /* Create this object with the appropriate identifier. */
2269 BeginAction(String nm, boolean select) {
2270 super (nm);
2271 this .select = select;
2272 }
2273
2274 /** The operation to perform when this action is triggered. */
2275 public void actionPerformed(ActionEvent e) {
2276 JTextComponent target = getTextComponent(e);
2277 int bodyStart = getBodyElementStart(target);
2278
2279 if (target != null) {
2280 if (select) {
2281 target.moveCaretPosition(bodyStart);
2282 } else {
2283 target.setCaretPosition(bodyStart);
2284 }
2285 }
2286 }
2287
2288 private boolean select;
2289 }
2290 }
|