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