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