1. /*
  2. * @(#)HTMLEditorKit.java 1.98 00/07/20
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text.html;
  11. import java.lang.reflect.Method;
  12. import java.awt.*;
  13. import java.awt.event.*;
  14. import java.io.*;
  15. import java.net.MalformedURLException;
  16. import java.net.URL;
  17. import javax.swing.text.*;
  18. import javax.swing.*;
  19. import javax.swing.border.*;
  20. import javax.swing.event.*;
  21. import java.util.*;
  22. /**
  23. * The Swing JEditorPane text component supports different kinds
  24. * of content via a plug-in mechanism called an EditorKit. Because
  25. * HTML is a very popular format of content, some support is provided
  26. * by default. The default support is provided by this class, which
  27. * supports HTML version 3.2 (with some extensions), and is migrating
  28. * toward version 4.0.
  29. * The <applet> tag is not supported, but some support is provided
  30. * for the <object> tag.
  31. * <p>
  32. * There are several goals of the HTML EditorKit provided, that have
  33. * an effect upon the way that HTML is modeled. These
  34. * have influenced its design in a substantial way.
  35. * <dl>
  36. * <p>
  37. * <dt>
  38. * Support editing
  39. * <dd>
  40. * It might seem fairly obvious that a plug-in for JEditorPane
  41. * should provide editing support, but that fact has several
  42. * design considerations. There are a substantial number of HTML
  43. * documents that don't properly conform to an HTML specification.
  44. * These must be normalized somewhat into a correct form if one
  45. * is to edit them. Additionally, users don't like to be presented
  46. * with an excessive amount of structure editing, so using traditional
  47. * text editing gestures is preferred over using the HTML structure
  48. * exactly as defined in the HTML document.
  49. * <p>
  50. * The modeling of HTML is provided by the class <code>HTMLDocument</code>.
  51. * Its documention describes the details of how the HTML is modeled.
  52. * The editing support leverages heavily off of the text package.
  53. * <p>
  54. * <dt>
  55. * Extendable/Scalable
  56. * <dd>
  57. * To maximize the usefulness of this kit, a great deal of effort
  58. * has gone into making it extendable. These are some of the
  59. * features.
  60. * <ol>
  61. * <li>
  62. * The parser is replacable. The default parser is the Hot Java
  63. * parser which is DTD based. A different DTD can be used, or an
  64. * entirely different parser can be used. To change the parser,
  65. * reimplement the getParser method. The default parser is
  66. * dynamically loaded when first asked for, so the class files
  67. * will never be loaded if an alternative parser is used. The
  68. * default parser is in a seperate package called parser below
  69. * this package.
  70. * <li>
  71. * The parser drives the ParserCallback, which is provided by
  72. * HTMLDocument. To change the callback, subclass HTMLDocument
  73. * and reimplement the createDefaultDocument method to return
  74. * document that produces a different reader. The reader controls
  75. * how the document is structured. Although the Document provides
  76. * HTML support by default, there is nothing preventing support of
  77. * non-HTML tags that result in alternative element structures.
  78. * <li>
  79. * The default view of the models are provided as a hierarchy of
  80. * View implementations, so one can easily customize how a particular
  81. * element is displayed or add capabilities for new kinds of elements
  82. * by providing new View implementations. The default set of views
  83. * are provided by the <code>HTMLFactory</code> class. This can
  84. * be easily changed by subclassing or replacing the HTMLFactory
  85. * and reimplementing the getViewFactory method to return the alternative
  86. * factory.
  87. * <li>
  88. * The View implementations work primarily off of CSS attributes,
  89. * which are kept in the views. This makes it possible to have
  90. * multiple views mapped over the same model that appear substantially
  91. * different. This can be especially useful for printing. For
  92. * most HTML attributes, the HTML attributes are converted to CSS
  93. * attributes for display. This helps make the View implementations
  94. * more general purpose
  95. * </ol>
  96. * <p>
  97. * <dt>
  98. * Asynchronous Loading
  99. * <dd>
  100. * Larger documents involve a lot of parsing and take some time
  101. * to load. By default, this kit produces documents that will be
  102. * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
  103. * This is controlled by a property on the document. The method
  104. * <a href="#createDefaultDocument">createDefaultDocument</a> can
  105. * be overriden to change this. The batching of work is done
  106. * by the <code>HTMLDocument.HTMLReader</code> class. The actual
  107. * work is done by the <code>DefaultStyledDocument</code> and
  108. * <code>AbstractDocument</code> classes in the text package.
  109. * <p>
  110. * <dt>
  111. * Customization from current LAF
  112. * <dd>
  113. * HTML provides a well known set of features without exactly
  114. * specifying the display characteristics. Swing has a theme
  115. * mechanism for its look-and-feel implementations. It is desirable
  116. * for the look-and-feel to feed display characteristics into the
  117. * HTML views. An user with poor vision for example would want
  118. * high contrast and larger than typical fonts.
  119. * <p>
  120. * The support for this is provided by the <code>StyleSheet</code>
  121. * class. The presentation of the HTML can be heavily influenced
  122. * by the setting of the StyleSheet property on the EditorKit.
  123. * <p>
  124. * <dt>
  125. * Not lossy
  126. * <dd>
  127. * An EditorKit has the ability to be read and save documents.
  128. * It is generally the most pleasing to users if there is no loss
  129. * of data between the two operation. The policy of the HTMLEditorKit
  130. * will be to store things not recognized or not necessarily visible
  131. * so they can be subsequently written out. The model of the HTML document
  132. * should therefore contain all information discovered while reading the
  133. * document. This is constrained in some ways by the need to support
  134. * editing (i.e. incorrect documents sometimes must be normalized).
  135. * The guiding principle is that information shouldn't be lost, but
  136. * some might be synthesized to produce a more correct model or it might
  137. * be rearranged.
  138. * </dl>
  139. *
  140. * @author Timothy Prinzing
  141. * @version 1.98 07/20/00
  142. */
  143. public class HTMLEditorKit extends StyledEditorKit {
  144. /**
  145. * Constructs an HTMLEditorKit, creates a StyleContext,
  146. * and loads the style sheet.
  147. */
  148. public HTMLEditorKit() {
  149. }
  150. /**
  151. * Create a copy of the editor kit. This
  152. * allows an implementation to serve as a prototype
  153. * for others, so that they can be quickly created.
  154. *
  155. * @return the copy
  156. */
  157. public Object clone() {
  158. return new HTMLEditorKit();
  159. }
  160. /**
  161. * Get the MIME type of the data that this
  162. * kit represents support for. This kit supports
  163. * the type <code>text/html</code>.
  164. *
  165. * @return the type
  166. */
  167. public String getContentType() {
  168. return "text/html";
  169. }
  170. /**
  171. * Fetch a factory that is suitable for producing
  172. * views of any models that are produced by this
  173. * kit.
  174. *
  175. * @return the factory
  176. */
  177. public ViewFactory getViewFactory() {
  178. return defaultFactory;
  179. }
  180. /**
  181. * Create an uninitialized text storage model
  182. * that is appropriate for this type of editor.
  183. *
  184. * @return the model
  185. */
  186. public Document createDefaultDocument() {
  187. StyleSheet styles = getStyleSheet();
  188. StyleSheet ss = new StyleSheet();
  189. ss.addStyleSheet(styles);
  190. HTMLDocument doc = new HTMLDocument(ss);
  191. doc.setParser(getParser());
  192. doc.setAsynchronousLoadPriority(4);
  193. doc.setTokenThreshold(100);
  194. return doc;
  195. }
  196. /**
  197. * Inserts content from the given stream. If <code>doc</code> is
  198. * an instance of HTMLDocument, this will read
  199. * HTML 3.2 text. Inserting HTML into a non-empty document must be inside
  200. * the body Element, if you do not insert into the body an exception will
  201. * be thrown. When inserting into a non-empty document all tags outside
  202. * of the body (head, title) will be dropped.
  203. *
  204. * @param in the stream to read from
  205. * @param doc the destination for the insertion
  206. * @param pos the location in the document to place the
  207. * content
  208. * @exception IOException on any I/O error
  209. * @exception BadLocationException if pos represents an invalid
  210. * location within the document
  211. * @exception RuntimeException (will eventually be a BadLocationException)
  212. * if pos is invalid
  213. */
  214. public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
  215. if (doc instanceof HTMLDocument) {
  216. HTMLDocument hdoc = (HTMLDocument) doc;
  217. Parser p = getParser();
  218. if (p == null) {
  219. throw new IOException("Can't load parser");
  220. }
  221. if (pos > doc.getLength()) {
  222. throw new BadLocationException("Invalid location", pos);
  223. }
  224. ParserCallback receiver = hdoc.getReader(pos);
  225. Boolean ignoreCharset = (Boolean)doc.getProperty("IgnoreCharsetDirective");
  226. p.parse(in, receiver, (ignoreCharset == null) ? false : ignoreCharset.booleanValue());
  227. receiver.flush();
  228. } else {
  229. super.read(in, doc, pos);
  230. }
  231. }
  232. /**
  233. * Inserts HTML into an existing document.
  234. *
  235. * @param doc the document to insert into
  236. * @param offset the offset to insert HTML at
  237. * @param popDepth the number of ElementSpec.EndTagTypes to generate before
  238. * inserting
  239. * @param pushDepth the number of ElementSpec.StartTagTypes with a direction
  240. * of ElementSpec.JoinNextDirection that should be generated
  241. * before inserting, but after the end tags have been generated
  242. * @param insertTag the first tag to start inserting into document
  243. * @exception RuntimeException (will eventually be a BadLocationException)
  244. * if pos is invalid
  245. */
  246. public void insertHTML(HTMLDocument doc, int offset, String html,
  247. int popDepth, int pushDepth,
  248. HTML.Tag insertTag) throws
  249. BadLocationException, IOException {
  250. Parser p = getParser();
  251. if (p == null) {
  252. throw new IOException("Can't load parser");
  253. }
  254. if (offset > doc.getLength()) {
  255. throw new BadLocationException("Invalid location", offset);
  256. }
  257. ParserCallback receiver = doc.getReader(offset, popDepth, pushDepth,
  258. insertTag);
  259. Boolean ignoreCharset = (Boolean)doc.getProperty
  260. ("IgnoreCharsetDirective");
  261. p.parse(new StringReader(html), receiver, (ignoreCharset == null) ?
  262. false : ignoreCharset.booleanValue());
  263. receiver.flush();
  264. }
  265. /**
  266. * Write content from a document to the given stream
  267. * in a format appropriate for this kind of content handler.
  268. *
  269. * @param out the stream to write to
  270. * @param doc the source for the write
  271. * @param pos the location in the document to fetch the
  272. * content
  273. * @param len the amount to write out
  274. * @exception IOException on any I/O error
  275. * @exception BadLocationException if pos represents an invalid
  276. * location within the document
  277. */
  278. public void write(Writer out, Document doc, int pos, int len)
  279. throws IOException, BadLocationException {
  280. if (doc instanceof HTMLDocument) {
  281. HTMLWriter w = new HTMLWriter(out, (HTMLDocument)doc, pos, len);
  282. w.write();
  283. } else if (doc instanceof StyledDocument) {
  284. MinimalHTMLWriter w = new MinimalHTMLWriter(out, (StyledDocument)doc, pos, len);
  285. w.write();
  286. } else {
  287. super.write(out, doc, pos, len);
  288. }
  289. }
  290. /**
  291. * Called when the kit is being installed into the
  292. * a JEditorPane.
  293. *
  294. * @param c the JEditorPane
  295. */
  296. public void install(JEditorPane c) {
  297. c.addMouseListener(linkHandler);
  298. c.addMouseMotionListener(linkHandler);
  299. super.install(c);
  300. }
  301. /**
  302. * Called when the kit is being removed from the
  303. * JEditorPane. This is used to unregister any
  304. * listeners that were attached.
  305. *
  306. * @param c the JEditorPane
  307. */
  308. public void deinstall(JEditorPane c) {
  309. c.removeMouseListener(linkHandler);
  310. c.removeMouseMotionListener(linkHandler);
  311. super.deinstall(c);
  312. }
  313. /**
  314. * Default Cascading Style Sheet file that sets
  315. * up the tag views.
  316. */
  317. public static final String DEFAULT_CSS = "default.css";
  318. /**
  319. * Set the set of styles to be used to render the various
  320. * HTML elements. These styles are specified in terms of
  321. * CSS specifications. Each document produced by the kit
  322. * will have a copy of the sheet which it can add the
  323. * document specific styles to. By default, the StyleSheet
  324. * specified is shared by all HTMLEditorKit instances.
  325. * This should be reimplemented to provide a finer granularity
  326. * if desired.
  327. */
  328. public void setStyleSheet(StyleSheet s) {
  329. defaultStyles = s;
  330. }
  331. /**
  332. * Get the set of styles currently being used to render the
  333. * HTML elements. By default the resource specified by
  334. * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
  335. * instances.
  336. */
  337. public StyleSheet getStyleSheet() {
  338. if (defaultStyles == null) {
  339. defaultStyles = new StyleSheet();
  340. try {
  341. InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
  342. Reader r = new BufferedReader(new InputStreamReader(is));
  343. defaultStyles.loadRules(r, null);
  344. r.close();
  345. } catch (Throwable e) {
  346. // on error we simply have no styles... the html
  347. // will look mighty wrong but still function.
  348. }
  349. }
  350. return defaultStyles;
  351. }
  352. /**
  353. * Fetch a resource relative to the HTMLEditorKit classfile.
  354. * If this is called on 1.2 the loading will occur under the
  355. * protection of a doPrivileged call to allow the HTMLEditorKit
  356. * to function when used in an applet.
  357. *
  358. * @param name the name of the resource, relative to the
  359. * HTMLEditorKit class
  360. * @returns a stream representing the resource
  361. */
  362. static InputStream getResourceAsStream(String name) {
  363. try {
  364. Class klass;
  365. ClassLoader loader = HTMLEditorKit.class.getClassLoader();
  366. if (loader != null) {
  367. klass = loader.loadClass("javax.swing.text.html.ResourceLoader");
  368. } else {
  369. klass = Class.forName("javax.swing.text.html.ResourceLoader");
  370. }
  371. Class[] parameterTypes = { String.class };
  372. Method loadMethod = klass.getMethod("getResourceAsStream", parameterTypes);
  373. String[] args = { name };
  374. return (InputStream) loadMethod.invoke(null, args);
  375. } catch (Throwable e) {
  376. // If the class doesn't exist or we have some other
  377. // problem we just try to call getResourceAsStream directly.
  378. return HTMLEditorKit.class.getResourceAsStream(name);
  379. }
  380. }
  381. /**
  382. * Fetches the command list for the editor. This is
  383. * the list of commands supported by the superclass
  384. * augmented by the collection of commands defined
  385. * locally for style operations.
  386. *
  387. * @return the command list
  388. */
  389. public Action[] getActions() {
  390. return TextAction.augmentList(super.getActions(), this.defaultActions);
  391. }
  392. /**
  393. * Copies the key/values in <code>element</code>s AttributeSet into
  394. * <code>set</code>. This does not copy component, icon, or element
  395. * names attributes. Subclasses may wish to refine what is and what
  396. * isn't copied here. But be sure to first remove all the attributes that
  397. * are in <code>set</code>.<p>
  398. * This is called anytime the caret moves over a different location.
  399. *
  400. */
  401. protected void createInputAttributes(Element element,
  402. MutableAttributeSet set) {
  403. set.removeAttributes(set);
  404. set.addAttributes(element.getAttributes());
  405. set.removeAttribute(StyleConstants.ComposedTextAttribute);
  406. Object o = set.getAttribute(StyleConstants.NameAttribute);
  407. if (o instanceof HTML.Tag) {
  408. HTML.Tag tag = (HTML.Tag)o;
  409. // PENDING: we need a better way to express what shouldn't be
  410. // copied when editing...
  411. if(tag == HTML.Tag.IMG) {
  412. // Remove the related image attributes, src, width, height
  413. set.removeAttribute(HTML.Attribute.SRC);
  414. set.removeAttribute(HTML.Attribute.HEIGHT);
  415. set.removeAttribute(HTML.Attribute.WIDTH);
  416. set.addAttribute(StyleConstants.NameAttribute,
  417. HTML.Tag.CONTENT);
  418. }
  419. else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
  420. // Don't copy HR's or BR's either.
  421. set.addAttribute(StyleConstants.NameAttribute,
  422. HTML.Tag.CONTENT);
  423. }
  424. else if (tag == HTML.Tag.COMMENT) {
  425. // Don't copy COMMENTs either
  426. set.addAttribute(StyleConstants.NameAttribute,
  427. HTML.Tag.CONTENT);
  428. set.removeAttribute(HTML.Attribute.COMMENT);
  429. }
  430. else if (tag == HTML.Tag.INPUT) {
  431. // or INPUT either
  432. set.addAttribute(StyleConstants.NameAttribute,
  433. HTML.Tag.CONTENT);
  434. set.removeAttribute(HTML.Tag.INPUT);
  435. }
  436. else if (tag instanceof HTML.UnknownTag) {
  437. // Don't copy unknowns either:(
  438. set.addAttribute(StyleConstants.NameAttribute,
  439. HTML.Tag.CONTENT);
  440. set.removeAttribute(HTML.Attribute.ENDTAG);
  441. }
  442. }
  443. }
  444. /**
  445. * Gets the input attributes used for the styled
  446. * editing actions.
  447. *
  448. * @return the attribute set
  449. */
  450. public MutableAttributeSet getInputAttributes() {
  451. if (input == null) {
  452. input = getStyleSheet().addStyle(null, null);
  453. }
  454. return input;
  455. }
  456. /**
  457. * Sets the default cursor.
  458. *
  459. * @since 1.3
  460. */
  461. public void setDefaultCursor(Cursor cursor) {
  462. defaultCursor = cursor;
  463. }
  464. /**
  465. * Returns the default cursor.
  466. *
  467. * @since 1.3
  468. */
  469. public Cursor getDefaultCursor() {
  470. return defaultCursor;
  471. }
  472. /**
  473. * Sets the cursor to use over links.
  474. *
  475. * @since 1.3
  476. */
  477. public void setLinkCursor(Cursor cursor) {
  478. linkCursor = cursor;
  479. }
  480. /**
  481. * Returns the cursor to use over hyper links.
  482. */
  483. public Cursor getLinkCursor() {
  484. return linkCursor;
  485. }
  486. /**
  487. * Fetch the parser to use for reading HTML streams.
  488. * This can be reimplemented to provide a different
  489. * parser. The default implementation is loaded dynamically
  490. * to avoid the overhead of loading the default parser if
  491. * it's not used. The default parser is the HotJava parser
  492. * using an HTML 3.2 DTD.
  493. */
  494. protected Parser getParser() {
  495. if (defaultParser == null) {
  496. try {
  497. Class c = Class.forName("javax.swing.text.html.parser.ParserDelegator");
  498. defaultParser = (Parser) c.newInstance();
  499. } catch (Throwable e) {
  500. }
  501. }
  502. return defaultParser;
  503. }
  504. // --- variables ------------------------------------------
  505. private static final Cursor MoveCursor = Cursor.getPredefinedCursor
  506. (Cursor.HAND_CURSOR);
  507. private static final Cursor DefaultCursor = Cursor.getPredefinedCursor
  508. (Cursor.DEFAULT_CURSOR);
  509. /** Shared factory for creating HTML Views. */
  510. private static final ViewFactory defaultFactory = new HTMLFactory();
  511. MutableAttributeSet input;
  512. private static StyleSheet defaultStyles = null;
  513. private LinkController linkHandler = new LinkController();
  514. private static Parser defaultParser = null;
  515. private Cursor defaultCursor = DefaultCursor;
  516. private Cursor linkCursor = MoveCursor;
  517. /**
  518. * Class to watch the associated component and fire
  519. * hyperlink events on it when appropriate.
  520. */
  521. public static class LinkController extends MouseAdapter implements MouseMotionListener, Serializable {
  522. private Element curElem = null;
  523. private String href = null;
  524. /** This is used by viewToModel to avoid allocing a new array each
  525. * time. */
  526. private Position.Bias[] bias = new Position.Bias[1];
  527. /**
  528. * Called for a mouse click event.
  529. * If the component is read-only (ie a browser) then
  530. * the clicked event is used to drive an attempt to
  531. * follow the reference specified by a link.
  532. *
  533. * @param e the mouse event
  534. * @see MouseListener#mouseClicked
  535. */
  536. public void mouseClicked(MouseEvent e) {
  537. JEditorPane editor = (JEditorPane) e.getSource();
  538. if (! editor.isEditable()) {
  539. Point pt = new Point(e.getX(), e.getY());
  540. int pos = editor.viewToModel(pt);
  541. if (pos >= 0) {
  542. activateLink(pos, editor, e.getX(), e.getY());
  543. }
  544. }
  545. }
  546. // ignore the drags
  547. public void mouseDragged(MouseEvent e) {
  548. }
  549. // track the moving of the mouse.
  550. public void mouseMoved(MouseEvent e) {
  551. JEditorPane editor = (JEditorPane) e.getSource();
  552. HTMLEditorKit kit = (HTMLEditorKit)editor.getEditorKit();
  553. boolean adjustCursor = true;
  554. Cursor newCursor = kit.getDefaultCursor();
  555. if (!editor.isEditable()) {
  556. Point pt = new Point(e.getX(), e.getY());
  557. int pos = editor.getUI().viewToModel(editor, pt, bias);
  558. if (bias[0] == Position.Bias.Backward && pos > 0) {
  559. pos--;
  560. }
  561. if (pos >= 0) {
  562. Document doc = editor.getDocument();
  563. if (doc instanceof HTMLDocument) {
  564. HTMLDocument hdoc = (HTMLDocument) doc;
  565. Element elem = hdoc.getCharacterElement(pos);
  566. if (curElem != elem) {
  567. curElem = elem;
  568. AttributeSet a = elem.getAttributes();
  569. AttributeSet anchor = (AttributeSet) a.getAttribute(HTML.Tag.A);
  570. String href = (anchor != null) ?
  571. (String) anchor.getAttribute(HTML.Attribute.HREF)
  572. : null;
  573. if (href != this.href) {
  574. // reference changed, fire event(s)
  575. fireEvents(editor, hdoc, href);
  576. this.href = href;
  577. if (href != null) {
  578. newCursor = kit.getLinkCursor();
  579. }
  580. }
  581. }
  582. else {
  583. adjustCursor = false;
  584. }
  585. }
  586. }
  587. }
  588. if (adjustCursor && editor.getCursor() != newCursor) {
  589. editor.setCursor(newCursor);
  590. }
  591. }
  592. /**
  593. * Calls linkActivated on the associated JEditorPane
  594. * if the given position represents a link.<p>This is implemented
  595. * to forward to the method with the same name, but with the following
  596. * args both == -1.
  597. *
  598. * @param pos the position
  599. * @param html the editor pane
  600. */
  601. protected void activateLink(int pos, JEditorPane editor) {
  602. activateLink(pos, editor, -1, -1);
  603. }
  604. /**
  605. * Calls linkActivated on the associated JEditorPane
  606. * if the given position represents a link. If this was the result
  607. * of a mouse click, <code>x</code> and
  608. * <code>y</code> will give the location of the mouse, otherwise
  609. * they will be < 0.
  610. *
  611. * @param pos the position
  612. * @param html the editor pane
  613. */
  614. void activateLink(int pos, JEditorPane html, int x, int y) {
  615. Document doc = html.getDocument();
  616. if (doc instanceof HTMLDocument) {
  617. HTMLDocument hdoc = (HTMLDocument) doc;
  618. Element e = hdoc.getCharacterElement(pos);
  619. AttributeSet a = e.getAttributes();
  620. AttributeSet anchor = (AttributeSet) a.getAttribute(HTML.Tag.A);
  621. String href = (anchor != null) ?
  622. (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
  623. HyperlinkEvent linkEvent = null;
  624. if (href != null) {
  625. linkEvent = createHyperlinkEvent(html, hdoc, href,
  626. anchor);
  627. }
  628. else if (x >= 0 && y >= 0) {
  629. // Check for usemap.
  630. Object useMap = a.getAttribute(HTML.Attribute.USEMAP);
  631. if (useMap != null && (useMap instanceof String)) {
  632. Map m = hdoc.getMap((String)useMap);
  633. if (m != null) {
  634. Rectangle bounds;
  635. try {
  636. bounds = html.modelToView(pos);
  637. Rectangle rBounds = html.modelToView(pos + 1);
  638. if (bounds != null && rBounds != null) {
  639. bounds.union(rBounds);
  640. }
  641. } catch (BadLocationException ble) {
  642. bounds = null;
  643. }
  644. if (bounds != null) {
  645. AttributeSet area = m.getArea
  646. (x - bounds.x, y - bounds.y,
  647. bounds.width, bounds.height);
  648. if (area != null) {
  649. href = (String)area.getAttribute
  650. (HTML.Attribute.HREF);
  651. if (href != null) {
  652. linkEvent = createHyperlinkEvent(html,
  653. hdoc, href, anchor);
  654. }
  655. }
  656. }
  657. }
  658. }
  659. }
  660. if (linkEvent != null) {
  661. html.fireHyperlinkUpdate(linkEvent);
  662. }
  663. }
  664. }
  665. /**
  666. * Creates and returns a new instance of HyperlinkEvent. If
  667. * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
  668. * will be created.
  669. */
  670. HyperlinkEvent createHyperlinkEvent(JEditorPane html,
  671. HTMLDocument hdoc, String href,
  672. AttributeSet anchor) {
  673. URL u;
  674. try {
  675. URL base = hdoc.getBase();
  676. u = new URL(base, href);
  677. // Following is a workaround for 1.2, in which
  678. // new URL("file://...", "#...") causes the filename to
  679. // be lost.
  680. if (href != null && "file".equals(u.getProtocol()) &&
  681. href.startsWith("#")) {
  682. String baseFile = base.getFile();
  683. String newFile = u.getFile();
  684. if (baseFile != null && newFile != null &&
  685. !newFile.startsWith(baseFile)) {
  686. u = new URL(base, baseFile + href);
  687. }
  688. }
  689. } catch (MalformedURLException m) {
  690. u = null;
  691. }
  692. HyperlinkEvent linkEvent = null;
  693. if (!hdoc.isFrameDocument()) {
  694. linkEvent = new HyperlinkEvent(html, HyperlinkEvent.EventType.
  695. ACTIVATED, u, href);
  696. } else {
  697. String target = (anchor != null) ?
  698. (String)anchor.getAttribute(HTML.Attribute.TARGET) : null;
  699. if ((target == null) || (target.equals(""))) {
  700. target = "_self";
  701. }
  702. linkEvent = new HTMLFrameHyperlinkEvent(html, HyperlinkEvent.
  703. EventType.ACTIVATED, u, href, target);
  704. }
  705. return linkEvent;
  706. }
  707. void fireEvents(JEditorPane editor, HTMLDocument doc, String href) {
  708. if (this.href != null) {
  709. // fire an exited event on the old link
  710. URL u;
  711. try {
  712. u = new URL(doc.getBase(), this.href);
  713. } catch (MalformedURLException m) {
  714. u = null;
  715. }
  716. HyperlinkEvent exit = new HyperlinkEvent(editor,
  717. HyperlinkEvent.EventType.EXITED,
  718. u, this.href);
  719. editor.fireHyperlinkUpdate(exit);
  720. }
  721. if (href != null) {
  722. // fire an entered event on the new link
  723. URL u;
  724. try {
  725. u = new URL(doc.getBase(), href);
  726. } catch (MalformedURLException m) {
  727. u = null;
  728. }
  729. HyperlinkEvent entered = new HyperlinkEvent(editor,
  730. HyperlinkEvent.EventType.ENTERED,
  731. u, href);
  732. editor.fireHyperlinkUpdate(entered);
  733. }
  734. }
  735. }
  736. /**
  737. * Interface to be supported by the parser. This enables
  738. * providing a different parser while reusing some of the
  739. * implementation provided by this editor kit.
  740. */
  741. public static abstract class Parser {
  742. /**
  743. * Parse the given stream and drive the given callback
  744. * with the results of the parse. This method should
  745. * be implemented to be thread-safe.
  746. */
  747. public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
  748. }
  749. /**
  750. * The result of parsing drives these callback methods.
  751. * The open and close actions should be balanced. The
  752. * <code>flush</code> method will be the last method
  753. * called, to give the receiver a chance to flush any
  754. * pending data into the document.
  755. * <p>Refer to DocumentParser, the default parser used, for further
  756. * information on the contents of the AttributeSets, the positions, and
  757. * other info.
  758. *
  759. * @see javax.swing.text.html.parser.DocumentParser
  760. */
  761. public static class ParserCallback {
  762. /**
  763. * This is passed as an attribute in the attributeset to indicate
  764. * the element is implied eg, the string '<>foo<\t>'
  765. * contains an implied html element and an implied body element.
  766. *
  767. * @since 1.3
  768. */
  769. public static final Object IMPLIED = "_implied_";
  770. public void flush() throws BadLocationException {
  771. }
  772. public void handleText(char[] data, int pos) {
  773. }
  774. public void handleComment(char[] data, int pos) {
  775. }
  776. public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
  777. }
  778. public void handleEndTag(HTML.Tag t, int pos) {
  779. }
  780. public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
  781. }
  782. public void handleError(String errorMsg, int pos){
  783. }
  784. /**
  785. * This is invoked after the stream has been parsed, but before
  786. * <code>flush</code>. <code>eol</code> will be one of \n, \r
  787. * or \r\n, which ever is encountered the most in parsing the
  788. * stream.
  789. *
  790. * @since 1.3
  791. */
  792. public void handleEndOfLineString(String eol) {
  793. }
  794. }
  795. /**
  796. * A factory to build views for HTML. The following
  797. * table describes what this factory will build by
  798. * default.
  799. *
  800. * <table>
  801. * <tr>
  802. * <th align=left>Tag<th align=left>View created
  803. * </tr><tr>
  804. * <td>HTML.Tag.CONTENT<td>InlineView
  805. * </tr><tr>
  806. * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView
  807. * </tr><tr>
  808. * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView
  809. * </tr><tr>
  810. * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView
  811. * </tr><tr>
  812. * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView
  813. * </tr><tr>
  814. * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView
  815. * </tr><tr>
  816. * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView
  817. * </tr><tr>
  818. * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView
  819. * </tr><tr>
  820. * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView
  821. * </tr><tr>
  822. * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView
  823. * </tr><tr>
  824. * <td>HTML.Tag.MENU<td>ListView
  825. * </tr><tr>
  826. * <td>HTML.Tag.DIR<td>ListView
  827. * </tr><tr>
  828. * <td>HTML.Tag.UL<td>ListView
  829. * </tr><tr>
  830. * <td>HTML.Tag.OL<td>ListView
  831. * </tr><tr>
  832. * <td>HTML.Tag.LI<td>BlockView
  833. * </tr><tr>
  834. * <td>HTML.Tag.DL<td>BlockView
  835. * </tr><tr>
  836. * <td>HTML.Tag.DD<td>BlockView
  837. * </tr><tr>
  838. * <td>HTML.Tag.BODY<td>BlockView
  839. * </tr><tr>
  840. * <td>HTML.Tag.HTML<td>BlockView
  841. * </tr><tr>
  842. * <td>HTML.Tag.CENTER<td>BlockView
  843. * </tr><tr>
  844. * <td>HTML.Tag.DIV<td>BlockView
  845. * </tr><tr>
  846. * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
  847. * </tr><tr>
  848. * <td>HTML.Tag.PRE<td>BlockView
  849. * </tr><tr>
  850. * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
  851. * </tr><tr>
  852. * <td>HTML.Tag.PRE<td>BlockView
  853. * </tr><tr>
  854. * <td>HTML.Tag.IMG<td>ImageView
  855. * </tr><tr>
  856. * <td>HTML.Tag.HR<td>HRuleView
  857. * </tr><tr>
  858. * <td>HTML.Tag.BR<td>BRView
  859. * </tr><tr>
  860. * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView
  861. * </tr><tr>
  862. * <td>HTML.Tag.INPUT<td>FormView
  863. * </tr><tr>
  864. * <td>HTML.Tag.SELECT<td>FormView
  865. * </tr><tr>
  866. * <td>HTML.Tag.TEXTAREA<td>FormView
  867. * </tr><tr>
  868. * <td>HTML.Tag.OBJECT<td>ObjectView
  869. * </tr><tr>
  870. * <td>HTML.Tag.FRAMESET<td>FrameSetView
  871. * </tr><tr>
  872. * <td>HTML.Tag.FRAME<td>FrameView
  873. * </tr>
  874. * </table>
  875. */
  876. public static class HTMLFactory implements ViewFactory {
  877. /**
  878. * Creates a view from an element.
  879. *
  880. * @param elem the element
  881. * @return the view
  882. */
  883. public View create(Element elem) {
  884. Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
  885. if (o instanceof HTML.Tag) {
  886. HTML.Tag kind = (HTML.Tag) o;
  887. if (kind == HTML.Tag.CONTENT) {
  888. return new InlineView(elem);
  889. } else if (kind == HTML.Tag.IMPLIED) {
  890. String ws = (String) elem.getAttributes().getAttribute(
  891. CSS.Attribute.WHITE_SPACE);
  892. if ((ws != null) && ws.equals("pre")) {
  893. return new LineView(elem);
  894. }
  895. return new javax.swing.text.html.ParagraphView(elem);
  896. } else if ((kind == HTML.Tag.P) ||
  897. (kind == HTML.Tag.H1) ||
  898. (kind == HTML.Tag.H2) ||
  899. (kind == HTML.Tag.H3) ||
  900. (kind == HTML.Tag.H4) ||
  901. (kind == HTML.Tag.H5) ||
  902. (kind == HTML.Tag.H6) ||
  903. (kind == HTML.Tag.DT)) {
  904. // paragraph
  905. return new javax.swing.text.html.ParagraphView(elem);
  906. } else if ((kind == HTML.Tag.MENU) ||
  907. (kind == HTML.Tag.DIR) ||
  908. (kind == HTML.Tag.UL) ||
  909. (kind == HTML.Tag.OL)) {
  910. return new ListView(elem);
  911. } else if (kind == HTML.Tag.BODY) {
  912. // reimplement major axis requirements to indicate that the
  913. // block is flexible for the body element... so that it can
  914. // be stretched to fill the background properly.
  915. return new BlockView(elem, View.Y_AXIS) {
  916. protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
  917. r = super.calculateMajorAxisRequirements(axis, r);
  918. r.maximum = Integer.MAX_VALUE;
  919. return r;
  920. }
  921. };
  922. } else if (kind == HTML.Tag.HTML) {
  923. return new BlockView(elem, View.Y_AXIS);
  924. } else if ((kind == HTML.Tag.LI) ||
  925. (kind == HTML.Tag.CENTER) ||
  926. (kind == HTML.Tag.DL) ||
  927. (kind == HTML.Tag.DD) ||
  928. (kind == HTML.Tag.DIV) ||
  929. (kind == HTML.Tag.BLOCKQUOTE) ||
  930. (kind == HTML.Tag.PRE)) {
  931. // vertical box
  932. return new BlockView(elem, View.Y_AXIS);
  933. } else if (kind == HTML.Tag.NOFRAMES) {
  934. return new NoFramesView(elem, View.Y_AXIS);
  935. } else if (kind==HTML.Tag.IMG) {
  936. return new ImageView(elem);
  937. } else if (kind == HTML.Tag.ISINDEX) {
  938. return new IsindexView(elem);
  939. } else if (kind == HTML.Tag.HR) {
  940. return new HRuleView(elem);
  941. } else if (kind == HTML.Tag.BR) {
  942. return new BRView(elem);
  943. } else if (kind == HTML.Tag.TABLE) {
  944. return new javax.swing.text.html.TableView(elem);
  945. } else if ((kind == HTML.Tag.INPUT) ||
  946. (kind == HTML.Tag.SELECT) ||
  947. (kind == HTML.Tag.TEXTAREA)) {
  948. return new FormView(elem);
  949. } else if (kind == HTML.Tag.OBJECT) {
  950. return new ObjectView(elem);
  951. } else if (kind == HTML.Tag.FRAMESET) {
  952. if (elem.getAttributes().isDefined(HTML.Attribute.ROWS)) {
  953. return new FrameSetView(elem, View.Y_AXIS);
  954. } else if (elem.getAttributes().isDefined(HTML.Attribute.COLS)) {
  955. return new FrameSetView(elem, View.X_AXIS);
  956. }
  957. throw new Error("Can't build a" + kind + ", " + elem + ":" +
  958. "no ROWS or COLS defined.");
  959. } else if (kind == HTML.Tag.FRAME) {
  960. return new FrameView(elem);
  961. } else if (kind instanceof HTML.UnknownTag) {
  962. return new HiddenTagView(elem);
  963. } else if (kind == HTML.Tag.COMMENT) {
  964. return new CommentView(elem);
  965. } else if (kind == HTML.Tag.HEAD) {
  966. // Make the head never visible, and never load its
  967. // children. For Cursor positioning,
  968. // getNextVisualPositionFrom is overriden to always return
  969. // the end offset of the element.
  970. return new BlockView(elem, View.X_AXIS) {
  971. public float getPreferredSpan(int axis) {
  972. return 0;
  973. }
  974. public float getMinimumSpan(int axis) {
  975. return 0;
  976. }
  977. public float getMaximumSpan(int axis) {
  978. return 0;
  979. }
  980. protected void loadChildren(ViewFactory f) {
  981. }
  982. public int getNextVisualPositionFrom(int pos,
  983. Position.Bias b, Shape a,
  984. int direction, Position.Bias[] biasRet) {
  985. return getElement().getEndOffset();
  986. }
  987. };
  988. } else if ((kind == HTML.Tag.TITLE) ||
  989. (kind == HTML.Tag.META) ||
  990. (kind == HTML.Tag.LINK) ||
  991. (kind == HTML.Tag.STYLE) ||
  992. (kind == HTML.Tag.SCRIPT) ||
  993. (kind == HTML.Tag.AREA) ||
  994. (kind == HTML.Tag.MAP) ||
  995. (kind == HTML.Tag.PARAM) ||
  996. (kind == HTML.Tag.APPLET)) {
  997. return new HiddenTagView(elem);
  998. }
  999. }
  1000. // If we get here, it's either an element we don't know about
  1001. // or something from StyledDocument that doesn't have a mapping to HTML.
  1002. String nm = elem.getName();
  1003. if (nm != null) {
  1004. if (nm.equals(AbstractDocument.ContentElementName)) {
  1005. return new LabelView(elem);
  1006. } else if (nm.equals(AbstractDocument.ParagraphElementName)) {
  1007. return new ParagraphView(elem);
  1008. } else if (nm.equals(AbstractDocument.SectionElementName)) {
  1009. return new BoxView(elem, View.Y_AXIS);
  1010. } else if (nm.equals(StyleConstants.ComponentElementName)) {
  1011. return new ComponentView(elem);
  1012. } else if (nm.equals(StyleConstants.IconElementName)) {
  1013. return new IconView(elem);
  1014. }
  1015. }
  1016. // default to text display
  1017. return new LabelView(elem);
  1018. }
  1019. }
  1020. // --- Action implementations ------------------------------
  1021. /** The bold action identifier
  1022. */
  1023. public static final String BOLD_ACTION = "html-bold-action";
  1024. /** The italic action identifier
  1025. */
  1026. public static final String ITALIC_ACTION = "html-italic-action";
  1027. /** The paragraph left indent action identifier
  1028. */
  1029. public static final String PARA_INDENT_LEFT = "html-para-indent-left";
  1030. /** The paragraph right indent action identifier
  1031. */
  1032. public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
  1033. /** The font size increase to next value action identifier
  1034. */
  1035. public static final String FONT_CHANGE_BIGGER = "html-font-bigger";
  1036. /** The font size decrease to next value action identifier
  1037. */
  1038. public static final String FONT_CHANGE_SMALLER = "html-font-smaller";
  1039. /** The Color choice action identifier
  1040. The color is passed as an argument
  1041. */
  1042. public static final String COLOR_ACTION = "html-color-action";
  1043. /** The logical style choice action identifier
  1044. The logical style is passed in as an argument
  1045. */
  1046. public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action";
  1047. /**
  1048. * Align images at the top.
  1049. */
  1050. public static final String IMG_ALIGN_TOP = "html-image-align-top";
  1051. /**
  1052. * Align images in the middle.
  1053. */
  1054. public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle";
  1055. /**
  1056. * Align images at the bottom.
  1057. */
  1058. public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom";
  1059. /**
  1060. * Align images at the border.
  1061. */
  1062. public static final String IMG_BORDER = "html-image-border";
  1063. /** HTML used when inserting tables. */
  1064. private static final String INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>";
  1065. /** HTML used when inserting unordered lists. */
  1066. private static final String INSERT_UL_HTML = "<ul><li></li></ul>";
  1067. /** HTML used when inserting ordered lists. */
  1068. private static final String INSERT_OL_HTML = "<ol><li></li></ol>";
  1069. /** HTML used when inserting hr. */
  1070. private static final String INSERT_HR_HTML = "<hr>";
  1071. /** HTML used when inserting pre. */
  1072. private static final String INSERT_PRE_HTML = "<pre></pre>";
  1073. private static final Action[] defaultActions = {
  1074. new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML,
  1075. HTML.Tag.BODY, HTML.Tag.TABLE),
  1076. new InsertHTMLTextAction("InsertTableRow", INSERT_TABLE_HTML,
  1077. HTML.Tag.TABLE, HTML.Tag.TR,
  1078. HTML.Tag.BODY, HTML.Tag.TABLE),
  1079. new InsertHTMLTextAction("InsertTableDataCell", INSERT_TABLE_HTML,
  1080. HTML.Tag.TR, HTML.Tag.TD,
  1081. HTML.Tag.BODY, HTML.Tag.TABLE),
  1082. new InsertHTMLTextAction("InsertUnorderedList", INSERT_UL_HTML,
  1083. HTML.Tag.BODY, HTML.Tag.UL),
  1084. new InsertHTMLTextAction("InsertUnorderedListItem", INSERT_UL_HTML,
  1085. HTML.Tag.UL, HTML.Tag.LI,
  1086. HTML.Tag.BODY, HTML.Tag.UL),
  1087. new InsertHTMLTextAction("InsertOrderedList", INSERT_OL_HTML,
  1088. HTML.Tag.BODY, HTML.Tag.OL),
  1089. new InsertHTMLTextAction("InsertOrderedListItem", INSERT_OL_HTML,
  1090. HTML.Tag.OL, HTML.Tag.LI,
  1091. HTML.Tag.BODY, HTML.Tag.OL),
  1092. new InsertHRAction(),
  1093. new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML,
  1094. HTML.Tag.BODY, HTML.Tag.PRE),
  1095. new NavigateLinkAction("next-link-action"),
  1096. new NavigateLinkAction("previous-link-action"),
  1097. new ActivateLinkAction("activate-link-action")
  1098. };
  1099. /**
  1100. * An abstract Action providing some convenience methods that may
  1101. * be useful in inserting HTML into an existing document.
  1102. * <p>NOTE: None of the convenience methods obtain a lock on the
  1103. * document. If you have another thread modifying the text these
  1104. * methods may have inconsistant behavior, or return the wrong thing.
  1105. */
  1106. public static abstract class HTMLTextAction extends StyledTextAction {
  1107. public HTMLTextAction(String name) {
  1108. super(name);
  1109. }
  1110. /**
  1111. * @return HTMLDocument of <code>e</code>.
  1112. */
  1113. protected HTMLDocument getHTMLDocument(JEditorPane e) {
  1114. Document d = e.getDocument();
  1115. if (d instanceof HTMLDocument) {
  1116. return (HTMLDocument) d;
  1117. }
  1118. throw new IllegalArgumentException("document must be HTMLDocument");
  1119. }
  1120. /**
  1121. * @return HTMLEditorKit for <code>e</code>.
  1122. */
  1123. protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
  1124. EditorKit k = e.getEditorKit();
  1125. if (k instanceof HTMLEditorKit) {
  1126. return (HTMLEditorKit) k;
  1127. }
  1128. throw new IllegalArgumentException("EditorKit must be HTMLEditorKit");
  1129. }
  1130. /**
  1131. * Returns an array of the Elements that contain <code>offset</code>.
  1132. * The first elements corresponds to the root.
  1133. */
  1134. protected Element[] getElementsAt(HTMLDocument doc, int offset) {
  1135. return getElementsAt(doc.getDefaultRootElement(), offset, 0);
  1136. }
  1137. /**
  1138. * Recursive method used by getElementsAt.
  1139. */
  1140. private Element[] getElementsAt(Element parent, int offset,
  1141. int depth) {
  1142. if (parent.isLeaf()) {
  1143. Element[] retValue = new Element[depth + 1];
  1144. retValue[depth] = parent;
  1145. return retValue;
  1146. }
  1147. Element[] retValue = getElementsAt(parent.getElement
  1148. (parent.getElementIndex(offset)), offset, depth + 1);
  1149. retValue[depth] = parent;
  1150. return retValue;
  1151. }
  1152. /**
  1153. * Returns number of elements, starting at the deepest leaf, needed
  1154. * to get to an element representing <code>tag</code>. This will
  1155. * return -1 if no elements is found representing <code>tag</code>,
  1156. * or 0 if the parent of the leaf at <code>offset</code> represents
  1157. * <code>tag</code>.
  1158. */
  1159. protected int elementCountToTag(HTMLDocument doc, int offset,
  1160. HTML.Tag tag) {
  1161. int depth = -1;
  1162. Element e = doc.getCharacterElement(offset);
  1163. while (e != null && e.getAttributes().getAttribute
  1164. (StyleConstants.NameAttribute) != tag) {
  1165. e = e.getParentElement();
  1166. depth++;
  1167. }
  1168. if (e == null) {
  1169. return -1;
  1170. }
  1171. return depth;
  1172. }
  1173. /**
  1174. * Returns the deepest element at <code>offset</code> matching
  1175. * <code>tag</code>.
  1176. */
  1177. protected Element findElementMatchingTag(HTMLDocument doc, int offset,
  1178. HTML.Tag tag) {
  1179. Element e = doc.getDefaultRootElement();
  1180. Element lastMatch = null;
  1181. while (e != null) {
  1182. if (e.getAttributes().getAttribute
  1183. (StyleConstants.NameAttribute) == tag) {
  1184. lastMatch = e;
  1185. }
  1186. e = e.getElement(e.getElementIndex(offset));
  1187. }
  1188. return lastMatch;
  1189. }
  1190. }
  1191. /**
  1192. * InsertHTMLTextAction can be used to insert an arbitrary string of HTML
  1193. * into an existing HTML document. At least two HTML.Tags need to be
  1194. * supplied. The first Tag, parentTag, identifies the parent in
  1195. * the document to add the elements to. The second tag, addTag,
  1196. * identifies the first tag that should be added to the document as
  1197. * seen in the HTML string. One important thing to remember, is that
  1198. * the parser is going to generate all the appropriate tags, even if
  1199. * they aren't in the HTML string passed in.<p>
  1200. * For example, lets say you wanted to create an action to insert
  1201. * a table into the body. The parentTag would be HTML.Tag.BODY,
  1202. * addTag would be HTML.Tag.TABLE, and the string could be something
  1203. * like <table><tr><td></td></tr></table>.
  1204. * <p>There is also an option to supply an alternate parentTag and
  1205. * addTag. These will be checked for if there is no parentTag at
  1206. * offset.
  1207. */
  1208. public static class InsertHTMLTextAction extends HTMLTextAction {
  1209. public InsertHTMLTextAction(String name, String html,
  1210. HTML.Tag parentTag, HTML.Tag addTag) {
  1211. this(name, html, parentTag, addTag, null, null);
  1212. }
  1213. public InsertHTMLTextAction(String name, String html,
  1214. HTML.Tag parentTag,
  1215. HTML.Tag addTag,
  1216. HTML.Tag alternateParentTag,
  1217. HTML.Tag alternateAddTag) {
  1218. this(name, html, parentTag, addTag, alternateParentTag,
  1219. alternateAddTag, true);
  1220. }
  1221. /* public */
  1222. InsertHTMLTextAction(String name, String html,
  1223. HTML.Tag parentTag,
  1224. HTML.Tag addTag,
  1225. HTML.Tag alternateParentTag,
  1226. HTML.Tag alternateAddTag,
  1227. boolean adjustSelection) {
  1228. super(name);
  1229. this.html = html;
  1230. this.parentTag = parentTag;
  1231. this.addTag = addTag;
  1232. this.alternateParentTag = alternateParentTag;
  1233. this.alternateAddTag = alternateAddTag;
  1234. this.adjustSelection = adjustSelection;
  1235. }
  1236. /**
  1237. * A cover for HTMLEditorKit.insertHTML. If an exception it
  1238. * thrown it is wrapped in a RuntimeException and thrown.
  1239. */
  1240. protected void insertHTML(JEditorPane editor, HTMLDocument doc,
  1241. int offset, String html, int popDepth,
  1242. int pushDepth, HTML.Tag addTag) {
  1243. try {
  1244. getHTMLEditorKit(editor).insertHTML(doc, offset, html,
  1245. popDepth, pushDepth,
  1246. addTag);
  1247. } catch (IOException ioe) {
  1248. throw new RuntimeException("Unable to insert: " + ioe);
  1249. } catch (BadLocationException ble) {
  1250. throw new RuntimeException("Unable to insert: " + ble);
  1251. }
  1252. }
  1253. /**
  1254. * This is invoked when inserting at a boundary. It determines
  1255. * the number of pops, and then the number of pushes that need
  1256. * to be performed, and then invokes insertHTML.
  1257. * @since 1.3
  1258. */
  1259. protected void insertAtBoundary(JEditorPane editor, HTMLDocument doc,
  1260. int offset, Element insertElement,
  1261. String html, HTML.Tag parentTag,
  1262. HTML.Tag addTag) {
  1263. insertAtBoundry(editor, doc, offset, insertElement, html,
  1264. parentTag, addTag);
  1265. }
  1266. /**
  1267. * This is invoked when inserting at a boundary. It determines
  1268. * the number of pops, and then the number of pushes that need
  1269. * to be performed, and then invokes insertHTML.
  1270. * @deprecated As of Java 2 platform v1.3, use insertAtBoundary
  1271. */
  1272. protected void insertAtBoundry(JEditorPane editor, HTMLDocument doc,
  1273. int offset, Element insertElement,
  1274. String html, HTML.Tag parentTag,
  1275. HTML.Tag addTag) {
  1276. // Find the common parent.
  1277. Element e;
  1278. Element commonParent;
  1279. boolean isFirst = (offset == 0);
  1280. if (offset > 0 || insertElement == null) {
  1281. e = doc.getDefaultRootElement();
  1282. while (e != null && e.getStartOffset() != offset &&
  1283. !e.isLeaf()) {
  1284. e = e.getElement(e.getElementIndex(offset));
  1285. }
  1286. commonParent = (e != null) ? e.getParentElement() : null;
  1287. }
  1288. else {
  1289. // If inserting at the origin, the common parent is the
  1290. // insertElement.
  1291. commonParent = insertElement;
  1292. }
  1293. if (commonParent != null) {
  1294. // Determine how many pops to do.
  1295. int pops = 0;
  1296. int pushes = 0;
  1297. if (isFirst && insertElement != null) {
  1298. e = commonParent;
  1299. while (e != null && !e.isLeaf()) {
  1300. e = e.getElement(e.getElementIndex(offset));
  1301. pops++;
  1302. }
  1303. }
  1304. else {
  1305. e = commonParent;
  1306. offset--;
  1307. while (e != null && !e.isLeaf()) {
  1308. e = e.getElement(e.getElementIndex(offset));
  1309. pops++;
  1310. }
  1311. // And how many pushes
  1312. e = commonParent;
  1313. offset++;
  1314. while (e != null && e != insertElement) {
  1315. e = e.getElement(e.getElementIndex(offset));
  1316. pushes++;
  1317. }
  1318. }
  1319. pops = Math.max(0, pops - 1);
  1320. // And insert!
  1321. insertHTML(editor, doc, offset, html, pops, pushes, addTag);
  1322. }
  1323. }
  1324. /**
  1325. * If there is an Element with name <code>tag</code> at
  1326. * <code>offset</code>, this will invoke either insertAtBoundary
  1327. * or <code>insertHTML</code>. This returns true if there is
  1328. * a match, and one of the inserts is invoked.
  1329. */
  1330. /*protected*/
  1331. boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
  1332. int offset, HTML.Tag tag, HTML.Tag addTag) {
  1333. Element e = findElementMatchingTag(doc, offset, tag);
  1334. if (e != null && e.getStartOffset() == offset) {
  1335. insertAtBoundary(editor, doc, offset, e, html,
  1336. tag, addTag);
  1337. return true;
  1338. }
  1339. else if (offset > 0) {
  1340. int depth = elementCountToTag(doc, offset - 1, tag);
  1341. if (depth != -1) {
  1342. insertHTML(editor, doc, offset, html, depth, 0, addTag);
  1343. return true;
  1344. }
  1345. }
  1346. return false;
  1347. }
  1348. /**
  1349. * Called after an insertion to adjust the selection.
  1350. */
  1351. /* protected */
  1352. void adjustSelection(JEditorPane pane, HTMLDocument doc,
  1353. int startOffset, int oldLength) {
  1354. int newLength = doc.getLength();
  1355. if (newLength != oldLength && startOffset < newLength) {
  1356. if (startOffset > 0) {
  1357. String text;
  1358. try {
  1359. text = doc.getText(startOffset - 1, 1);
  1360. } catch (BadLocationException ble) {
  1361. text = null;
  1362. }
  1363. if (text != null && text.length() > 0 &&
  1364. text.charAt(0) == '\n') {
  1365. pane.select(startOffset, startOffset);
  1366. }
  1367. else {
  1368. pane.select(startOffset + 1, startOffset + 1);
  1369. }
  1370. }
  1371. else {
  1372. pane.select(1, 1);
  1373. }
  1374. }
  1375. }
  1376. /**
  1377. * Inserts the HTML into the document.
  1378. *
  1379. * @param ae the event
  1380. */
  1381. public void actionPerformed(ActionEvent ae) {
  1382. JEditorPane editor = getEditor(ae);
  1383. if (editor != null) {
  1384. HTMLDocument doc = getHTMLDocument(editor);
  1385. int offset = editor.getSelectionStart();
  1386. int length = doc.getLength();
  1387. boolean inserted;
  1388. // Try first choice
  1389. if (!insertIntoTag(editor, doc, offset, parentTag, addTag) &&
  1390. alternateParentTag != null) {
  1391. // Then alternate.
  1392. inserted = insertIntoTag(editor, doc, offset,
  1393. alternateParentTag,
  1394. alternateAddTag);
  1395. }
  1396. else {
  1397. inserted = true;
  1398. }
  1399. if (adjustSelection && inserted) {
  1400. adjustSelection(editor, doc, offset, length);
  1401. }
  1402. }
  1403. }
  1404. /** HTML to insert. */
  1405. protected String html;
  1406. /** Tag to check for in the document. */
  1407. protected HTML.Tag parentTag;
  1408. /** Tag in HTML to start adding tags from. */
  1409. protected HTML.Tag addTag;
  1410. /** Alternate Tag to check for in the document if parentTag is
  1411. * not found. */
  1412. protected HTML.Tag alternateParentTag;
  1413. /** Alternate tag in HTML to start adding tags from if parentTag
  1414. * is not found and alternateParentTag is found. */
  1415. protected HTML.Tag alternateAddTag;
  1416. /** True indicates the selection should be adjusted after an insert. */
  1417. boolean adjustSelection;
  1418. }
  1419. /**
  1420. * InsertHRAction is special, at actionPerformed time it will determine
  1421. * the parent HTML.Tag based on the paragraph element at the selection
  1422. * start.
  1423. */
  1424. static class InsertHRAction extends InsertHTMLTextAction {
  1425. InsertHRAction() {
  1426. super("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null, null,
  1427. false);
  1428. }
  1429. /**
  1430. * Inserts the HTML into the document.
  1431. *
  1432. * @param ae the event
  1433. */
  1434. public void actionPerformed(ActionEvent ae) {
  1435. JEditorPane editor = getEditor(ae);
  1436. if (editor != null) {
  1437. HTMLDocument doc = getHTMLDocument(editor);
  1438. int offset = editor.getSelectionStart();
  1439. Element paragraph = doc.getParagraphElement(offset);
  1440. if (paragraph.getParentElement() != null) {
  1441. parentTag = (HTML.Tag)paragraph.getParentElement().
  1442. getAttributes().getAttribute
  1443. (StyleConstants.NameAttribute);
  1444. super.actionPerformed(ae);
  1445. }
  1446. }
  1447. }
  1448. }
  1449. /*
  1450. * Action to move the focus on the next or previous hypertext link
  1451. */
  1452. static class NavigateLinkAction extends TextAction {
  1453. FocusHighlightPainter focusPainter = new FocusHighlightPainter(null);
  1454. Object selectionTag;
  1455. boolean focusBack = false;
  1456. /**
  1457. * Create this action with the appropriate identifier.
  1458. */
  1459. public NavigateLinkAction(String actionName) {
  1460. super(actionName);
  1461. if ("previous-link-action".equals(actionName)) {
  1462. focusBack = true;
  1463. }
  1464. }
  1465. /** The operation to perform when this action is triggered. */
  1466. public void actionPerformed(ActionEvent e) {
  1467. JTextComponent component = getTextComponent(e);
  1468. if (component.isEditable()) {
  1469. return;
  1470. }
  1471. // highlight the next link after the curent caret position
  1472. if (component != null) {
  1473. Document doc = component.getDocument();
  1474. if (doc != null) {
  1475. HTMLDocument hdoc = (HTMLDocument) doc;
  1476. HTMLDocument.Iterator ei = hdoc.getIterator(HTML.Tag.A);
  1477. int currentOffset = component.getCaretPosition();
  1478. int prevStartOffset = 0;
  1479. int prevEndOffset = 0;
  1480. while (ei.isValid()) {
  1481. AttributeSet anchor = ei.getAttributes();
  1482. if (anchor != null) {
  1483. String href = (String)anchor.getAttribute(HTML.Attribute.HREF);
  1484. if (href != null) {
  1485. int startOffset = ei.getStartOffset();
  1486. if (focusBack) {
  1487. if (startOffset >= currentOffset) {
  1488. component.setCaretPosition(prevStartOffset);
  1489. moveCaretPosition(component, prevStartOffset,
  1490. prevEndOffset);
  1491. return;
  1492. }
  1493. } else { // focus forward
  1494. if (startOffset > currentOffset) {
  1495. component.setCaretPosition(startOffset);
  1496. moveCaretPosition(component, startOffset,
  1497. ei.getEndOffset());
  1498. return;
  1499. }
  1500. }
  1501. }
  1502. }
  1503. prevStartOffset = ei.getStartOffset();
  1504. prevEndOffset = ei.getEndOffset();
  1505. ei.next();
  1506. }
  1507. }
  1508. }
  1509. }
  1510. /*
  1511. * Moves the caret from mark to dot
  1512. */
  1513. private void moveCaretPosition(JTextComponent component, int mark, int dot) {
  1514. Highlighter h = component.getHighlighter();
  1515. if (h != null) {
  1516. int p0 = Math.min(dot, mark);
  1517. int p1 = Math.max(dot, mark);
  1518. try {
  1519. if (selectionTag != null) {
  1520. h.changeHighlight(selectionTag, p0, p1);
  1521. } else {
  1522. Highlighter.HighlightPainter p = focusPainter;
  1523. selectionTag = h.addHighlight(p0, p1, p);
  1524. }
  1525. } catch (BadLocationException e) {
  1526. // XXX
  1527. }
  1528. }
  1529. }
  1530. /**
  1531. * A highlight painter that draws a one-pixel border around
  1532. * the highlighted area.
  1533. */
  1534. class FocusHighlightPainter extends
  1535. DefaultHighlighter.DefaultHighlightPainter {
  1536. FocusHighlightPainter(Color color) {
  1537. super(color);
  1538. }
  1539. /**
  1540. * Paints a portion of a highlight.
  1541. *
  1542. * @param g the graphics context
  1543. * @param offs0 the starting model offset >= 0
  1544. * @param offs1 the ending model offset >= offs1
  1545. * @param bounds the bounding box of the view, which is not
  1546. * necessarily the region to paint.
  1547. * @param c the editor
  1548. * @param view View painting for
  1549. * @return region drawing occured in
  1550. */
  1551. public Shape paintLayer(Graphics g, int offs0, int offs1,
  1552. Shape bounds, JTextComponent c, View view) {
  1553. Color color = getColor();
  1554. if (color == null) {
  1555. g.setColor(c.getSelectionColor());
  1556. }
  1557. else {
  1558. g.setColor(color);
  1559. }
  1560. if (offs0 == view.getStartOffset() &&
  1561. offs1 == view.getEndOffset()) {
  1562. // Contained in view, can just use bounds.
  1563. Rectangle alloc;
  1564. if (bounds instanceof Rectangle) {
  1565. alloc = (Rectangle)bounds;
  1566. }
  1567. else {
  1568. alloc = bounds.getBounds();
  1569. }
  1570. g.drawRect(alloc.x, alloc.y, alloc.width - 1, alloc.height);
  1571. return alloc;
  1572. }
  1573. else {
  1574. // Should only render part of View.
  1575. try {
  1576. // --- determine locations ---
  1577. Shape shape = view.modelToView(offs0, Position.Bias.Forward,
  1578. offs1,Position.Bias.Backward,
  1579. bounds);
  1580. Rectangle r = (shape instanceof Rectangle) ?
  1581. (Rectangle)shape : shape.getBounds();
  1582. g.drawRect(r.x, r.y, r.width - 1, r.height);
  1583. return r;
  1584. } catch (BadLocationException e) {
  1585. // can't render
  1586. }
  1587. }
  1588. // Only if exception
  1589. return null;
  1590. }
  1591. }
  1592. }
  1593. /*
  1594. * Action to activate the hypertext link that has focus.
  1595. */
  1596. static class ActivateLinkAction extends TextAction {
  1597. /**
  1598. * Create this action with the appropriate identifier.
  1599. */
  1600. public ActivateLinkAction(String actionName) {
  1601. super(actionName);
  1602. }
  1603. /** The operation to perform when this action is triggered. */
  1604. public void actionPerformed(ActionEvent e) {
  1605. JTextComponent c = getTextComponent(e);
  1606. if (!(c instanceof JEditorPane)) {
  1607. return;
  1608. }
  1609. JEditorPane component = (JEditorPane)c;
  1610. if (component.isEditable()) {
  1611. return;
  1612. }
  1613. // activate the hypertext link at the current caret position
  1614. if (component != null) {
  1615. Document doc = component.getDocument();
  1616. if (doc != null) {
  1617. HTMLDocument hdoc = (HTMLDocument) doc;
  1618. HTMLDocument.Iterator ei = hdoc.getIterator(HTML.Tag.A);
  1619. int currentOffset = component.getCaretPosition();
  1620. String urlString = null;
  1621. while (ei.isValid()) {
  1622. AttributeSet anchor = ei.getAttributes();
  1623. if (anchor != null) {
  1624. String href =
  1625. (String)anchor.getAttribute(HTML.Attribute.HREF);
  1626. if (href != null) {
  1627. if (currentOffset >= ei.getStartOffset() &&
  1628. currentOffset <= ei.getEndOffset()) {
  1629. urlString = href;
  1630. break;
  1631. }
  1632. }
  1633. }
  1634. ei.next();
  1635. }
  1636. if (urlString != null) {
  1637. try {
  1638. URL page =
  1639. (URL)doc.getProperty(Document.StreamDescriptionProperty);
  1640. URL url = new URL(page, urlString);
  1641. HyperlinkEvent linkEvent = new HyperlinkEvent(component,
  1642. HyperlinkEvent.EventType.ACTIVATED, url);
  1643. component.fireHyperlinkUpdate(linkEvent);
  1644. } catch (MalformedURLException m) {
  1645. }
  1646. }
  1647. }
  1648. }
  1649. }
  1650. }
  1651. }