1. /*
  2. * @(#)HTMLEditorKit.java 1.83 01/11/29
  3. *
  4. * Copyright 2002 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 java.util.*;
  19. /**
  20. * The Swing JEditorPane text component supports different kinds
  21. * of content via a plug-in mechanism called an EditorKit. Because
  22. * HTML is a very popular format of content, some support is provided
  23. * by default. The default support is provided by this class, which
  24. * supports HTML version 3.2 (with some extensions), and is migrating
  25. * toward version 4.0.
  26. * The <applet> tag is not supported, but some support is provided
  27. * for the <object> tag.
  28. * <p>
  29. * There are several goals of the HTML EditorKit provided, that have
  30. * an effect upon the way that html is modeled. These
  31. * have influenced its design in a substantial way.
  32. * <dl>
  33. * <p>
  34. * <dt>
  35. * Support editing
  36. * <dd>
  37. * It might seem fairly obvious that a plug-in for JEditorPane
  38. * should provide editing support, but that fact has several
  39. * design considerations. There are a substantial number of html
  40. * documents that don't properly conform to an html specification.
  41. * These must be normalized somewhat into a correct form if one
  42. * is to edit them. Additionally, users don't like to be presented
  43. * with an excessive amount of structure editing, so using traditional
  44. * text editing gestures is preferred over using the html structure
  45. * exactly as defined in the html document.
  46. * <p>
  47. * The modeling of html is provided by the class <code>HTMLDocument</code>.
  48. * It's documention describes the details of how the html is modeled.
  49. * The editing support leverages heavily off of the text package.
  50. * <p>
  51. * <dt>
  52. * Extendable/Scalable
  53. * <dd>
  54. * To maximize the usefulness of this kit, a great deal of effort
  55. * has gone into making it extendable. These are some of the
  56. * features.
  57. * <ol>
  58. * <li>
  59. * The parser is replacable. The default parser is the Hot Java
  60. * parser which is DTD based. A different DTD can be used, or an
  61. * entirely different parser can be used. To change the parser,
  62. * reimplement the getParser method. The default parser is
  63. * dynamically loaded when first asked for, so the class files
  64. * will never be loaded if an alternative parser is used. The
  65. * default parser is in a seperate package called parser below
  66. * this package.
  67. * <li>
  68. * The parser drives the ParserCallback, which is provided by
  69. * HTMLDocument. To change the callback, subclass HTMLDocument
  70. * and reimplement the createDefaultDocument method to return
  71. * document that produces a different reader. The reader controls
  72. * how the document is structured. Although the Document provides
  73. * HTML support by default, there is nothing preventing support of
  74. * non-html tags that result in alternative element structures.
  75. * <li>
  76. * The default view of the models are provided as a hierarchy of
  77. * View implementations, so one can easily customize how a particular
  78. * element is displayed or add capabilities for new kinds of elements
  79. * by providing new View implementations. The default set of views
  80. * are provided by the <code>HTMLFactory</code> class. This can
  81. * be easily changed by subclassing or replacing the HTMLFactory
  82. * and reimplementing the getViewFactory method to return the alternative
  83. * factory.
  84. * <li>
  85. * The View implementations work primarily off of CSS attributes,
  86. * which are kept in the views. This makes it possible to have
  87. * multiple views mapped over the same model that appear substantially
  88. * different. This can be especially useful for printing. For
  89. * most html attributes, the html attributes are converted to css
  90. * attributes for display. This helps make the View implementations
  91. * more general purpose
  92. * </ol>
  93. * <p>
  94. * <dt>
  95. * Asynchronous Loading
  96. * <dd>
  97. * Larger documents involve a lot of parsing and take some time
  98. * to load. By default, this kit produces documents that will be
  99. * loaded asynchronously if loaded using <code>JEditorPane.setPage</code>.
  100. * This is controlled by a property on the document. The method
  101. * <a href="#createDefaultDocument">createDefaultDocument</a> can
  102. * be overriden to change this. The batching of work is done
  103. * by the <code>HTMLDocument.HTMLReader</code> class. The actual
  104. * work is done by the <code>DefaultStyledDocument</code> and
  105. * <code>AbstractDocument</code> classes in the text package.
  106. * <p>
  107. * <dt>
  108. * Customization from current LAF
  109. * <dd>
  110. * HTML provides a well known set of features without exactly
  111. * specifying the display characteristics. Swing has a theme
  112. * mechanism for its look-and-feel implementations. It is desirable
  113. * for the look-and-feel to feed display characteristics into the
  114. * HTML views. An user with poor vision for example would want
  115. * high contrast and larger than typical fonts.
  116. * <p>
  117. * The support for this is provided by the <code>StyleSheet</code>
  118. * class. The presentation of the HTML can be heavily influenced
  119. * by the setting of the StyleSheet property on the EditorKit.
  120. * <p>
  121. * <dt>
  122. * Not lossy
  123. * <dd>
  124. * An EditorKit has the ability to be read and save documents.
  125. * It is generally the most pleasing to users if there is no loss
  126. * of data between the two operation. The policy of the HTMLEditorKit
  127. * will be to store things not recognized or not necessarily visible
  128. * so they can be subsequently written out. The model of the html document
  129. * should therefore contain all information discovered while reading the
  130. * document. This is constrained in some ways by the need to support
  131. * editing (i.e. incorrect documents sometimes must be normalized).
  132. * The guiding principle is that information shouldn't be lost, but
  133. * some might be synthesized to produce a more correct model or it might
  134. * be rearranged.
  135. * </dl>
  136. *
  137. * @author Timothy Prinzing
  138. * @version 1.83, 11/29/01
  139. */
  140. public class HTMLEditorKit extends StyledEditorKit {
  141. /**
  142. * Constructs an HTMLEditorKit, creates a StyleContext,
  143. * and loads the style sheet.
  144. */
  145. public HTMLEditorKit() {
  146. }
  147. /**
  148. * Create a copy of the editor kit. This
  149. * allows an implementation to serve as a prototype
  150. * for others, so that they can be quickly created.
  151. *
  152. * @return the copy
  153. */
  154. public Object clone() {
  155. return new HTMLEditorKit();
  156. }
  157. /**
  158. * Get the MIME type of the data that this
  159. * kit represents support for. This kit supports
  160. * the type <code>text/html</code>.
  161. *
  162. * @return the type
  163. */
  164. public String getContentType() {
  165. return "text/html";
  166. }
  167. /**
  168. * Fetch a factory that is suitable for producing
  169. * views of any models that are produced by this
  170. * kit.
  171. *
  172. * @return the factory
  173. */
  174. public ViewFactory getViewFactory() {
  175. return new HTMLFactory();
  176. }
  177. /**
  178. * Create an uninitialized text storage model
  179. * that is appropriate for this type of editor.
  180. *
  181. * @return the model
  182. */
  183. public Document createDefaultDocument() {
  184. StyleSheet styles = getStyleSheet();
  185. StyleSheet ss = new StyleSheet();
  186. ss.addStyleSheet(styles);
  187. HTMLDocument doc = new HTMLDocument(ss);
  188. doc.putProperty(HTMLDocument.PARSER_PROPERTY, getParser());
  189. doc.setAsynchronousLoadPriority(4);
  190. doc.setTokenThreshold(100);
  191. return doc;
  192. }
  193. /**
  194. * Inserts content from the given stream. If <code>doc</code> is
  195. * an instance of HTMLDocument, this will read
  196. * html 3.2 text. Inserting html into a non-empty document must be inside
  197. * the body Element, if you do not insert into the body an exception will
  198. * be thrown. When inserting into a non-empty document all tags outside
  199. * of the body (head, title) will be dropped.
  200. *
  201. * @param in The stream to read from
  202. * @param doc The destination for the insertion.
  203. * @param pos The location in the document to place the
  204. * content.
  205. * @exception IOException on any I/O error
  206. * @exception BadLocationException if pos represents an invalid
  207. * location within the document.
  208. * @exception RuntimeException (will eventually be a BadLocationException)
  209. * if pos is invalid.
  210. */
  211. public void read(Reader in, Document doc, int pos) throws IOException, BadLocationException {
  212. if (doc instanceof HTMLDocument) {
  213. HTMLDocument hdoc = (HTMLDocument) doc;
  214. Parser p = getParser();
  215. if (p == null) {
  216. throw new IOException("Can't load parser");
  217. }
  218. if (pos > doc.getLength()) {
  219. throw new BadLocationException("Invalid location", pos);
  220. }
  221. ParserCallback receiver = hdoc.getReader(pos);
  222. Boolean ignoreCharset = (Boolean)doc.getProperty("IgnoreCharsetDirective");
  223. p.parse(in, receiver, (ignoreCharset == null) ? false : ignoreCharset.booleanValue());
  224. receiver.flush();
  225. } else {
  226. super.read(in, doc, pos);
  227. }
  228. }
  229. /**
  230. * Inserts HTML into an existing document.
  231. *
  232. * @param doc Document to insert into.
  233. * @param offset offset to insert HTML at
  234. * @param popDepth number of ElementSpec.EndTagType to generate before
  235. * inserting.
  236. * @param pushDepth number of ElementSpec.StartTagType with a direction
  237. * of ElementSpec.JoinNextDirection that should be generated
  238. * before inserting, but after the end tags have been generated.
  239. * @param insertTag first tag to start inserting into document.
  240. * @exception RuntimeException (will eventually be a BadLocationException)
  241. * if pos is invalid.
  242. */
  243. public void insertHTML(HTMLDocument doc, int offset, String html,
  244. int popDepth, int pushDepth,
  245. HTML.Tag insertTag) throws
  246. BadLocationException, IOException {
  247. Parser p = getParser();
  248. if (p == null) {
  249. throw new IOException("Can't load parser");
  250. }
  251. if (offset > doc.getLength()) {
  252. throw new BadLocationException("Invalid location", offset);
  253. }
  254. ParserCallback receiver = doc.getReader(offset, popDepth, pushDepth,
  255. insertTag);
  256. Boolean ignoreCharset = (Boolean)doc.getProperty
  257. ("IgnoreCharsetDirective");
  258. p.parse(new StringReader(html), receiver, (ignoreCharset == null) ?
  259. false : ignoreCharset.booleanValue());
  260. receiver.flush();
  261. }
  262. /**
  263. * Write content from a document to the given stream
  264. * in a format appropriate for this kind of content handler.
  265. *
  266. * @param out The stream to write to
  267. * @param doc The source for the write.
  268. * @param pos The location in the document to fetch the
  269. * content.
  270. * @param len The amount to write out.
  271. * @exception IOException on any I/O error
  272. * @exception BadLocationException if pos represents an invalid
  273. * location within the document.
  274. */
  275. public void write(Writer out, Document doc, int pos, int len)
  276. throws IOException, BadLocationException {
  277. if (doc instanceof HTMLDocument) {
  278. HTMLWriter w = new HTMLWriter(out, (HTMLDocument)doc, pos, len);
  279. w.write();
  280. } else if (doc instanceof StyledDocument) {
  281. MinimalHTMLWriter w = new MinimalHTMLWriter(out, (StyledDocument)doc, pos, len);
  282. w.write();
  283. } else {
  284. super.write(out, doc, pos, len);
  285. }
  286. }
  287. /**
  288. * Called when the kit is being installed into the
  289. * a JEditorPane.
  290. *
  291. * @param c the JEditorPane
  292. */
  293. public void install(JEditorPane c) {
  294. c.addMouseListener(linkHandler);
  295. c.addMouseMotionListener(tmpHandler);
  296. super.install(c);
  297. }
  298. /**
  299. * Called when the kit is being removed from the
  300. * JEditorPane. This is used to unregister any
  301. * listeners that were attached.
  302. *
  303. * @param c the JEditorPane
  304. */
  305. public void deinstall(JEditorPane c) {
  306. c.removeMouseListener(linkHandler);
  307. c.removeMouseMotionListener(tmpHandler);
  308. super.deinstall(c);
  309. }
  310. /**
  311. * Default Cascading Style Sheet file that sets
  312. * up the tag views.
  313. */
  314. public static final String DEFAULT_CSS = "default.css";
  315. /**
  316. * Set the set of styles to be used to render the various
  317. * html elements. These styles are specified in terms of
  318. * css specifications. Each document produced by the kit
  319. * will have a copy of the sheet which it can add the
  320. * document specific styles to. By default, the StyleSheet
  321. * specified is shared by all HTMLEditorKit instances.
  322. * This should be reimplemented to provide a finer granularity
  323. * if desired.
  324. */
  325. public void setStyleSheet(StyleSheet s) {
  326. defaultStyles = s;
  327. }
  328. /**
  329. * Get the set of styles currently being used to render the
  330. * html elements. By default the resource specified by
  331. * DEFAULT_CSS gets loaded, and is shared by all HTMLEditorKit
  332. * instances.
  333. */
  334. public StyleSheet getStyleSheet() {
  335. if (defaultStyles == null) {
  336. defaultStyles = new StyleSheet();
  337. try {
  338. InputStream is = HTMLEditorKit.getResourceAsStream(DEFAULT_CSS);
  339. Reader r = new BufferedReader(new InputStreamReader(is));
  340. defaultStyles.loadRules(r, null);
  341. r.close();
  342. } catch (Throwable e) {
  343. // on error we simply have no styles... the html
  344. // will look mighty wrong but still function.
  345. }
  346. }
  347. return defaultStyles;
  348. }
  349. /**
  350. * Fetch a resource relative to the HTMLEditorKit classfile.
  351. * If this is called on 1.2 the loading will occur under the
  352. * protection of a doPrivileged call to allow the HTMLEditorKit
  353. * to function when used in an applet.
  354. *
  355. * @param name the name of the resource, relative to the
  356. * HTMLEditorKit class.
  357. * @returns a stream representing the resource
  358. */
  359. static InputStream getResourceAsStream(String name) {
  360. try {
  361. Class klass;
  362. ClassLoader loader = HTMLEditorKit.class.getClassLoader();
  363. if (loader != null) {
  364. klass = loader.loadClass("javax.swing.text.html.ResourceLoader");
  365. } else {
  366. klass = Class.forName("javax.swing.text.html.ResourceLoader");
  367. }
  368. Class[] parameterTypes = { String.class };
  369. Method loadMethod = klass.getMethod("getResourceAsStream", parameterTypes);
  370. String[] args = { name };
  371. return (InputStream) loadMethod.invoke(null, args);
  372. } catch (Throwable e) {
  373. // If the class doesn't exist or we have some other
  374. // problem we just try to call getResourceAsStream directly.
  375. return HTMLEditorKit.class.getResourceAsStream(name);
  376. }
  377. }
  378. /**
  379. * Fetches the command list for the editor. This is
  380. * the list of commands supported by the superclass
  381. * augmented by the collection of commands defined
  382. * locally for style operations.
  383. *
  384. * @return the command list
  385. */
  386. public Action[] getActions() {
  387. return TextAction.augmentList(super.getActions(), this.defaultActions);
  388. }
  389. /**
  390. * Copies the key/values in <code>element</code>s AttributeSet into
  391. * <code>set</code>. This does not copy component, icon, or element
  392. * names attributes. Subclasses may wish to refine what is and what
  393. * isn't copied here. But be sure to first remove all the attributes that
  394. * are in <code>set</code>.<p>
  395. * This is called anytime the caret moves over a different location.
  396. *
  397. */
  398. protected void createInputAttributes(Element element,
  399. MutableAttributeSet set) {
  400. set.removeAttributes(set);
  401. set.addAttributes(element.getAttributes());
  402. set.removeAttribute(StyleConstants.ComposedTextAttribute);
  403. Object o = set.getAttribute(StyleConstants.NameAttribute);
  404. if (o instanceof HTML.Tag) {
  405. HTML.Tag tag = (HTML.Tag)o;
  406. // PENDING: we need a better way to express what shouldn't be
  407. // copied when editing...
  408. if(tag == HTML.Tag.IMG) {
  409. // Remove the related image attributes, src, width, height
  410. set.removeAttribute(HTML.Attribute.SRC);
  411. set.removeAttribute(HTML.Attribute.HEIGHT);
  412. set.removeAttribute(HTML.Attribute.WIDTH);
  413. set.addAttribute(StyleConstants.NameAttribute,
  414. HTML.Tag.CONTENT);
  415. }
  416. else if (tag == HTML.Tag.HR || tag == HTML.Tag.BR) {
  417. // Don't copy HR's or BR's either.
  418. set.addAttribute(StyleConstants.NameAttribute,
  419. HTML.Tag.CONTENT);
  420. }
  421. else if (tag == HTML.Tag.COMMENT) {
  422. // Don't copy COMMENTs either
  423. set.addAttribute(StyleConstants.NameAttribute,
  424. HTML.Tag.CONTENT);
  425. set.removeAttribute(HTML.Attribute.COMMENT);
  426. }
  427. else if (tag instanceof HTML.UnknownTag) {
  428. // Don't copy unknowns either:(
  429. set.addAttribute(StyleConstants.NameAttribute,
  430. HTML.Tag.CONTENT);
  431. set.removeAttribute(HTML.Attribute.ENDTAG);
  432. }
  433. }
  434. }
  435. /**
  436. * Gets the input attributes used for the styled
  437. * editing actions.
  438. *
  439. * @return the attribute set
  440. */
  441. public MutableAttributeSet getInputAttributes() {
  442. if (input == null) {
  443. input = getStyleSheet().addStyle(null, null);
  444. }
  445. return input;
  446. }
  447. /**
  448. * Fetch the parser to use for reading html streams.
  449. * This can be reimplemented to provide a different
  450. * parser. The default implementation is loaded dynamically
  451. * to avoid the overhead of loading the default parser if
  452. * it's not used. The default parser is the HotJava parser
  453. * using an html 3.2 dtd.
  454. */
  455. protected Parser getParser() {
  456. if (defaultParser == null) {
  457. try {
  458. Class c = Class.forName("javax.swing.text.html.parser.ParserDelegator");
  459. defaultParser = (Parser) c.newInstance();
  460. } catch (Throwable e) {
  461. }
  462. }
  463. return defaultParser;
  464. }
  465. // --- variables ------------------------------------------
  466. MutableAttributeSet input;
  467. private static StyleSheet defaultStyles = null;
  468. private MouseListener linkHandler = new LinkController();
  469. private static Parser defaultParser = null;
  470. private MouseMotionListener tmpHandler = new TemporaryHandler();
  471. /**
  472. * Class to generate hyperlink enter and exit events. This
  473. * should be part of the LinkController class.
  474. * PENDING(prinz) MOVE THIS WHEN API CHANGES ARE ALLOWED!!!!
  475. */
  476. static class TemporaryHandler implements MouseMotionListener {
  477. private Element curElem = null;
  478. private String href = null;
  479. // ignore the drags
  480. public void mouseDragged(MouseEvent e) {
  481. }
  482. // track the moving of the mouse.
  483. public void mouseMoved(MouseEvent e) {
  484. JEditorPane editor = (JEditorPane) e.getSource();
  485. if (!editor.isEditable()) {
  486. Point pt = new Point(e.getX(), e.getY());
  487. int pos = editor.viewToModel(pt);
  488. if (pos >= 0) {
  489. Document doc = editor.getDocument();
  490. if (doc instanceof HTMLDocument) {
  491. HTMLDocument hdoc = (HTMLDocument) doc;
  492. Element elem = hdoc.getCharacterElement(pos);
  493. if (curElem != elem) {
  494. curElem = elem;
  495. AttributeSet a = elem.getAttributes();
  496. AttributeSet anchor = (AttributeSet) a.getAttribute(HTML.Tag.A);
  497. String href = (anchor != null) ?
  498. (String) anchor.getAttribute(HTML.Attribute.HREF)
  499. : null;
  500. if (href != this.href) {
  501. // reference changed, fire event(s)
  502. fireEvents(editor, hdoc, href);
  503. this.href = href;
  504. }
  505. }
  506. }
  507. }
  508. }
  509. }
  510. void fireEvents(JEditorPane editor, HTMLDocument doc, String href) {
  511. if (this.href != null) {
  512. // fire an exited event on the old link
  513. URL u;
  514. try {
  515. u = new URL(doc.getBase(), this.href);
  516. } catch (MalformedURLException m) {
  517. u = null;
  518. }
  519. HyperlinkEvent exit = new HyperlinkEvent(editor,
  520. HyperlinkEvent.EventType.EXITED,
  521. u, this.href);
  522. editor.fireHyperlinkUpdate(exit);
  523. }
  524. if (href != null) {
  525. // fire an entered event on the new link
  526. URL u;
  527. try {
  528. u = new URL(doc.getBase(), href);
  529. } catch (MalformedURLException m) {
  530. u = null;
  531. }
  532. HyperlinkEvent entered = new HyperlinkEvent(editor,
  533. HyperlinkEvent.EventType.ENTERED,
  534. u, href);
  535. editor.fireHyperlinkUpdate(entered);
  536. }
  537. }
  538. }
  539. /**
  540. * Class to watch the associated component and fire
  541. * hyperlink events on it when appropriate.
  542. */
  543. public static class LinkController extends MouseAdapter implements Serializable {
  544. /**
  545. * Called for a mouse click event.
  546. * If the component is read-only (ie a browser) then
  547. * the clicked event is used to drive an attempt to
  548. * follow the reference specified by a link.
  549. *
  550. * @param e the mouse event
  551. * @see MouseListener#mouseClicked
  552. */
  553. public void mouseClicked(MouseEvent e) {
  554. JEditorPane editor = (JEditorPane) e.getSource();
  555. if (! editor.isEditable()) {
  556. Point pt = new Point(e.getX(), e.getY());
  557. int pos = editor.viewToModel(pt);
  558. if (pos >= 0) {
  559. activateLink(pos, editor, e.getX(), e.getY());
  560. }
  561. }
  562. }
  563. /**
  564. * Calls linkActivated on the associated JEditorPane
  565. * if the given position represents a link.<p>This is implemented
  566. * to forward to the method with the same name, but with the following
  567. * args both == -1.
  568. *
  569. * @param pos the position
  570. * @param html the editor pane
  571. */
  572. protected void activateLink(int pos, JEditorPane editor) {
  573. activateLink(pos, editor, -1, -1);
  574. }
  575. /**
  576. * Calls linkActivated on the associated JEditorPane
  577. * if the given position represents a link. If this was the result
  578. * of a mouse click, <code>x</code> and
  579. * <code>y</code> will give the location of the mouse, otherwise
  580. * they will be < 0.
  581. *
  582. * @param pos the position
  583. * @param html the editor pane
  584. */
  585. void activateLink(int pos, JEditorPane html, int x, int y) {
  586. Document doc = html.getDocument();
  587. if (doc instanceof HTMLDocument) {
  588. HTMLDocument hdoc = (HTMLDocument) doc;
  589. Element e = hdoc.getCharacterElement(pos);
  590. AttributeSet a = e.getAttributes();
  591. AttributeSet anchor = (AttributeSet) a.getAttribute(HTML.Tag.A);
  592. String href = (anchor != null) ?
  593. (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
  594. HyperlinkEvent linkEvent = null;
  595. if (href != null) {
  596. linkEvent = createHyperlinkEvent(html, hdoc, href,
  597. anchor);
  598. }
  599. else if (x >= 0 && y >= 0) {
  600. // Check for usemap.
  601. Object useMap = a.getAttribute(HTML.Attribute.USEMAP);
  602. if (useMap != null && (useMap instanceof String)) {
  603. Map m = hdoc.getMap((String)useMap);
  604. if (m != null) {
  605. Rectangle bounds;
  606. try {
  607. bounds = html.modelToView(pos);
  608. Rectangle rBounds = html.modelToView(pos + 1);
  609. if (bounds != null && rBounds != null) {
  610. bounds.union(rBounds);
  611. }
  612. } catch (BadLocationException ble) {
  613. bounds = null;
  614. }
  615. if (bounds != null) {
  616. AttributeSet area = m.getArea
  617. (x - bounds.x, y - bounds.y,
  618. bounds.width, bounds.height);
  619. if (area != null) {
  620. href = (String)area.getAttribute
  621. (HTML.Attribute.HREF);
  622. if (href != null) {
  623. linkEvent = createHyperlinkEvent(html,
  624. hdoc, href, anchor);
  625. }
  626. }
  627. }
  628. }
  629. }
  630. }
  631. if (linkEvent != null) {
  632. html.fireHyperlinkUpdate(linkEvent);
  633. }
  634. }
  635. }
  636. /**
  637. * Creates and returns a new instance of HyperlinkEvent. If
  638. * <code>hdoc</code> is a frame document a HTMLFrameHyperlinkEvent
  639. * will be created.
  640. */
  641. HyperlinkEvent createHyperlinkEvent(JEditorPane html,
  642. HTMLDocument hdoc, String href,
  643. AttributeSet anchor) {
  644. URL u;
  645. try {
  646. URL base = hdoc.getBase();
  647. u = new URL(base, href);
  648. // Following is a workaround for 1.2, in which
  649. // new URL("file://...", "#...") causes the filename to
  650. // be lost.
  651. if (href != null && "file".equals(u.getProtocol()) &&
  652. href.startsWith("#")) {
  653. String baseFile = base.getFile();
  654. String newFile = u.getFile();
  655. if (baseFile != null && newFile != null &&
  656. !newFile.startsWith(baseFile)) {
  657. u = new URL(base, baseFile + href);
  658. }
  659. }
  660. } catch (MalformedURLException m) {
  661. u = null;
  662. }
  663. HyperlinkEvent linkEvent = null;
  664. if (!hdoc.isFrameDocument()) {
  665. linkEvent = new HyperlinkEvent(html, HyperlinkEvent.EventType.
  666. ACTIVATED, u, href);
  667. } else {
  668. String target = (anchor != null) ?
  669. (String)anchor.getAttribute(HTML.Attribute.TARGET) : null;
  670. if ((target == null) || (target.equals(""))) {
  671. target = "_self";
  672. }
  673. linkEvent = new HTMLFrameHyperlinkEvent(html, HyperlinkEvent.
  674. EventType.ACTIVATED, u, href, target);
  675. }
  676. return linkEvent;
  677. }
  678. }
  679. /**
  680. * Interface to be supported by the parser. This enables
  681. * providing a different parser while reusing some of the
  682. * implementation provided by this editor kit.
  683. */
  684. public static abstract class Parser {
  685. /**
  686. * Parse the given stream and drive the given callback
  687. * with the results of the parse. This method should
  688. * be implemented to be thread-safe.
  689. */
  690. public abstract void parse(Reader r, ParserCallback cb, boolean ignoreCharSet) throws IOException;
  691. }
  692. /**
  693. * The result of parsing drives these callback methods.
  694. * The open and close actions should be balanced. The
  695. * <code>flush</code> method will be the last method
  696. * called, to give the receiver a chance to flush any
  697. * pending data into the document.
  698. */
  699. public static class ParserCallback {
  700. public void flush() throws BadLocationException {
  701. }
  702. public void handleText(char[] data, int pos) {
  703. }
  704. public void handleComment(char[] data, int pos) {
  705. }
  706. public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
  707. }
  708. public void handleEndTag(HTML.Tag t, int pos) {
  709. }
  710. public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
  711. }
  712. public void handleError(String errorMsg, int pos){
  713. }
  714. }
  715. /**
  716. * A factory to build views for html. The following
  717. * table describes what this factory will build by
  718. * default.
  719. *
  720. * <table>
  721. * <th>Tag<th>View created
  722. * <td>HTML.Tag.CONTENT<td>InlineView
  723. * <td>HTML.Tag.IMPLIED<td>javax.swing.text.html.ParagraphView
  724. * <td>HTML.Tag.P<td>javax.swing.text.html.ParagraphView
  725. * <td>HTML.Tag.H1<td>javax.swing.text.html.ParagraphView
  726. * <td>HTML.Tag.H2<td>javax.swing.text.html.ParagraphView
  727. * <td>HTML.Tag.H3<td>javax.swing.text.html.ParagraphView
  728. * <td>HTML.Tag.H4<td>javax.swing.text.html.ParagraphView
  729. * <td>HTML.Tag.H5<td>javax.swing.text.html.ParagraphView
  730. * <td>HTML.Tag.H6<td>javax.swing.text.html.ParagraphView
  731. * <td>HTML.Tag.DT<td>javax.swing.text.html.ParagraphView
  732. * <td>HTML.Tag.MENU<td>ListView
  733. * <td>HTML.Tag.DIR<td>ListView
  734. * <td>HTML.Tag.UL<td>ListView
  735. * <td>HTML.Tag.OL<td>ListView
  736. * <td>HTML.Tag.LI<td>BlockView
  737. * <td>HTML.Tag.DL<td>BlockView
  738. * <td>HTML.Tag.DD<td>BlockView
  739. * <td>HTML.Tag.BODY<td>BlockView
  740. * <td>HTML.Tag.HTML<td>BlockView
  741. * <td>HTML.Tag.CENTER<td>BlockView
  742. * <td>HTML.Tag.DIV<td>BlockView
  743. * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
  744. * <td>HTML.Tag.PRE<td>BlockView
  745. * <td>HTML.Tag.BLOCKQUOTE<td>BlockView
  746. * <td>HTML.Tag.PRE<td>BlockView
  747. * <td>HTML.Tag.IMG<td>ImageView
  748. * <td>HTML.Tag.HR<td>HRuleView
  749. * <td>HTML.Tag.BR<td>BRView
  750. * <td>HTML.Tag.TABLE<td>javax.swing.text.html.TableView
  751. * <td>HTML.Tag.INPUT<td>FormView
  752. * <td>HTML.Tag.SELECT<td>FormView
  753. * <td>HTML.Tag.TEXTAREA<td>FormView
  754. * <td>HTML.Tag.OBJECT<td>ObjectView
  755. * <td>HTML.Tag.FRAMESET<td>FrameSetView
  756. * <td>HTML.Tag.FRAME<td>FrameView
  757. * </table>
  758. */
  759. public static class HTMLFactory implements ViewFactory {
  760. /**
  761. * Creates a view from an element.
  762. *
  763. * @param elem the element
  764. * @return the view
  765. */
  766. public View create(Element elem) {
  767. Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
  768. if (o instanceof HTML.Tag) {
  769. HTML.Tag kind = (HTML.Tag) o;
  770. if (kind == HTML.Tag.CONTENT) {
  771. return new InlineView(elem);
  772. } else if (kind == HTML.Tag.IMPLIED) {
  773. String ws = (String) elem.getAttributes().getAttribute(
  774. CSS.Attribute.WHITE_SPACE);
  775. if ((ws != null) && ws.equals("pre")) {
  776. return new LineView(elem);
  777. }
  778. return new javax.swing.text.html.ParagraphView(elem);
  779. } else if ((kind == HTML.Tag.P) ||
  780. (kind == HTML.Tag.H1) ||
  781. (kind == HTML.Tag.H2) ||
  782. (kind == HTML.Tag.H3) ||
  783. (kind == HTML.Tag.H4) ||
  784. (kind == HTML.Tag.H5) ||
  785. (kind == HTML.Tag.H6) ||
  786. (kind == HTML.Tag.DT)) {
  787. // paragraph
  788. return new javax.swing.text.html.ParagraphView(elem);
  789. } else if ((kind == HTML.Tag.MENU) ||
  790. (kind == HTML.Tag.DIR) ||
  791. (kind == HTML.Tag.UL) ||
  792. (kind == HTML.Tag.OL)) {
  793. return new ListView(elem);
  794. } else if (kind == HTML.Tag.BODY) {
  795. // reimplement major axis requirements to indicate that the
  796. // block is flexible for the body element... so that it can
  797. // be stretched to fill the background properly.
  798. return new BlockView(elem, View.Y_AXIS) {
  799. protected SizeRequirements calculateMajorAxisRequirements(int axis, SizeRequirements r) {
  800. r = super.calculateMajorAxisRequirements(axis, r);
  801. r.maximum = Integer.MAX_VALUE;
  802. return r;
  803. }
  804. };
  805. } else if ((kind == HTML.Tag.LI) ||
  806. (kind == HTML.Tag.CENTER) ||
  807. (kind == HTML.Tag.DL) ||
  808. (kind == HTML.Tag.DD) ||
  809. (kind == HTML.Tag.HTML) ||
  810. (kind == HTML.Tag.DIV) ||
  811. (kind == HTML.Tag.BLOCKQUOTE) ||
  812. (kind == HTML.Tag.PRE)) {
  813. // vertical box
  814. return new BlockView(elem, View.Y_AXIS);
  815. } else if (kind == HTML.Tag.NOFRAMES) {
  816. return new NoFramesView(elem, View.Y_AXIS);
  817. } else if ((kind == HTML.Tag.TH) ||
  818. (kind == HTML.Tag.TD)) {
  819. return new javax.swing.text.html.TableView.CellView(elem);
  820. } else if (kind==HTML.Tag.IMG) {
  821. return new ImageView(elem);
  822. } else if (kind == HTML.Tag.ISINDEX) {
  823. return new IsindexView(elem);
  824. } else if (kind == HTML.Tag.HR) {
  825. return new HRuleView(elem);
  826. } else if (kind == HTML.Tag.BR) {
  827. return new BRView(elem);
  828. } else if (kind == HTML.Tag.TABLE) {
  829. return new javax.swing.text.html.TableView(elem);
  830. } else if ((kind == HTML.Tag.INPUT) ||
  831. (kind == HTML.Tag.SELECT) ||
  832. (kind == HTML.Tag.TEXTAREA)) {
  833. return new FormView(elem);
  834. } else if (kind == HTML.Tag.OBJECT) {
  835. return new ObjectView(elem);
  836. } else if (kind == HTML.Tag.FRAMESET) {
  837. if (elem.getAttributes().isDefined(HTML.Attribute.ROWS)) {
  838. return new FrameSetView(elem, View.Y_AXIS);
  839. } else if (elem.getAttributes().isDefined(HTML.Attribute.COLS)) {
  840. return new FrameSetView(elem, View.X_AXIS);
  841. }
  842. throw new Error("Can't build a" + kind + ", " + elem + ":" +
  843. "no ROWS or COLS defined.");
  844. } else if (kind == HTML.Tag.FRAME) {
  845. return new FrameView(elem);
  846. } else if (kind instanceof HTML.UnknownTag) {
  847. return new HiddenTagView(elem);
  848. } else if (kind == HTML.Tag.COMMENT) {
  849. return new CommentView(elem);
  850. } else if ((kind == HTML.Tag.HEAD) ||
  851. (kind == HTML.Tag.TITLE) ||
  852. (kind == HTML.Tag.META) ||
  853. (kind == HTML.Tag.LINK) ||
  854. (kind == HTML.Tag.STYLE) ||
  855. (kind == HTML.Tag.SCRIPT) ||
  856. (kind == HTML.Tag.AREA) ||
  857. (kind == HTML.Tag.MAP) ||
  858. (kind == HTML.Tag.PARAM) ||
  859. (kind == HTML.Tag.APPLET)) {
  860. return new HiddenTagView(elem);
  861. }
  862. // don't know how to build this....
  863. throw new Error("Can't build a " + kind + ", " + elem);
  864. }
  865. // don't know how to build this....
  866. throw new Error("Can't build a " + elem);
  867. }
  868. }
  869. // --- Action implementations ------------------------------
  870. /** The bold action identifier
  871. */
  872. public static final String BOLD_ACTION = "html-bold-action";
  873. /** The italic action identifier
  874. */
  875. public static final String ITALIC_ACTION = "html-italic-action";
  876. /** The paragraph left indent action identifier
  877. */
  878. public static final String PARA_INDENT_LEFT = "html-para-indent-left";
  879. /** The paragraph right indent action identifier
  880. */
  881. public static final String PARA_INDENT_RIGHT = "html-para-indent-right";
  882. /** The font size increase to next value action identifier
  883. */
  884. public static final String FONT_CHANGE_BIGGER = "html-font-bigger";
  885. /** The font size decrease to next value action identifier
  886. */
  887. public static final String FONT_CHANGE_SMALLER = "html-font-smaller";
  888. /** The Color choice action identifier
  889. The color is passed as an argument
  890. */
  891. public static final String COLOR_ACTION = "html-color-action";
  892. /** The logical style choice action identifier
  893. The logical style is passed in as an argument
  894. */
  895. public static final String LOGICAL_STYLE_ACTION = "html-logical-style-action";
  896. /**
  897. * Align images at the top.
  898. */
  899. public static final String IMG_ALIGN_TOP = "html-image-align-top";
  900. /**
  901. * Align images in the middle.
  902. */
  903. public static final String IMG_ALIGN_MIDDLE = "html-image-align-middle";
  904. /**
  905. * Align images at the bottom.
  906. */
  907. public static final String IMG_ALIGN_BOTTOM = "html-image-align-bottom";
  908. /**
  909. * Align images at the border.
  910. */
  911. public static final String IMG_BORDER = "html-image-border";
  912. /** HTML used when inserting tables. */
  913. private static final String INSERT_TABLE_HTML = "<table border=1><tr><td></td></tr></table>";
  914. /** HTML used when inserting unordered lists. */
  915. private static final String INSERT_UL_HTML = "<ul><li></li></ul>";
  916. /** HTML used when inserting ordered lists. */
  917. private static final String INSERT_OL_HTML = "<ol><li></li></ol>";
  918. /** HTML used when inserting hr. */
  919. private static final String INSERT_HR_HTML = "<hr>";
  920. /** HTML used when inserting pre. */
  921. private static final String INSERT_PRE_HTML = "<pre></pre>";
  922. private static final Action[] defaultActions = {
  923. new InsertHTMLTextAction("InsertTable", INSERT_TABLE_HTML,
  924. HTML.Tag.BODY, HTML.Tag.TABLE),
  925. new InsertHTMLTextAction("InsertTableRow", INSERT_TABLE_HTML,
  926. HTML.Tag.TABLE, HTML.Tag.TR,
  927. HTML.Tag.BODY, HTML.Tag.TABLE),
  928. new InsertHTMLTextAction("InsertTableDataCell", INSERT_TABLE_HTML,
  929. HTML.Tag.TR, HTML.Tag.TD,
  930. HTML.Tag.BODY, HTML.Tag.TABLE),
  931. new InsertHTMLTextAction("InsertUnorderedList", INSERT_UL_HTML,
  932. HTML.Tag.BODY, HTML.Tag.UL),
  933. new InsertHTMLTextAction("InsertUnorderedListItem", INSERT_UL_HTML,
  934. HTML.Tag.UL, HTML.Tag.LI,
  935. HTML.Tag.BODY, HTML.Tag.UL),
  936. new InsertHTMLTextAction("InsertOrderedList", INSERT_OL_HTML,
  937. HTML.Tag.BODY, HTML.Tag.OL),
  938. new InsertHTMLTextAction("InsertOrderedListItem", INSERT_OL_HTML,
  939. HTML.Tag.OL, HTML.Tag.LI,
  940. HTML.Tag.BODY, HTML.Tag.OL),
  941. new InsertHRAction(),
  942. new InsertHTMLTextAction("InsertPre", INSERT_PRE_HTML,
  943. HTML.Tag.BODY, HTML.Tag.PRE),
  944. };
  945. /**
  946. * An abstract Action providing some convenience methods that may
  947. * be useful in inserting HTML into an existing document.
  948. * <p>NOTE: None of the convenience methods obtain a lock on the
  949. * document. If you have another thread modifying the text these
  950. * methods may have inconsistant behavior, or return the wrong thing.
  951. */
  952. public static abstract class HTMLTextAction extends StyledTextAction {
  953. public HTMLTextAction(String name) {
  954. super(name);
  955. }
  956. /**
  957. * @return HTMLDocument of <code>e</code>.
  958. */
  959. protected HTMLDocument getHTMLDocument(JEditorPane e) {
  960. Document d = e.getDocument();
  961. if (d instanceof HTMLDocument) {
  962. return (HTMLDocument) d;
  963. }
  964. throw new IllegalArgumentException("document must be HTMLDocument");
  965. }
  966. /**
  967. * @return HTMLEditorKit for <code>e</code>.
  968. */
  969. protected HTMLEditorKit getHTMLEditorKit(JEditorPane e) {
  970. EditorKit k = e.getEditorKit();
  971. if (k instanceof HTMLEditorKit) {
  972. return (HTMLEditorKit) k;
  973. }
  974. throw new IllegalArgumentException("EditorKit must be HTMLEditorKit");
  975. }
  976. /**
  977. * Returns an array of the Elements that contain <code>offset</code>.
  978. * The first elements corresponds to the root.
  979. */
  980. protected Element[] getElementsAt(HTMLDocument doc, int offset) {
  981. return getElementsAt(doc.getDefaultRootElement(), offset, 0);
  982. }
  983. /**
  984. * Recursive method used by getElementsAt.
  985. */
  986. private Element[] getElementsAt(Element parent, int offset,
  987. int depth) {
  988. if (parent.isLeaf()) {
  989. Element[] retValue = new Element[depth + 1];
  990. retValue[depth] = parent;
  991. return retValue;
  992. }
  993. Element[] retValue = getElementsAt(parent.getElement
  994. (parent.getElementIndex(offset)), offset, depth + 1);
  995. retValue[depth] = parent;
  996. return retValue;
  997. }
  998. /**
  999. * Returns number of elements, starting at the deepest leaf, needed
  1000. * to get to an element representing <code>tag</code>. This will
  1001. * return -1 if no elements is found representing <code>tag</code>,
  1002. * or 0 if the parent of the leaf at <code>offset</code> represents
  1003. * <code>tag</code>.
  1004. */
  1005. protected int elementCountToTag(HTMLDocument doc, int offset,
  1006. HTML.Tag tag) {
  1007. int depth = -1;
  1008. Element e = doc.getCharacterElement(offset);
  1009. while (e != null && e.getAttributes().getAttribute
  1010. (StyleConstants.NameAttribute) != tag) {
  1011. e = e.getParentElement();
  1012. depth++;
  1013. }
  1014. if (e == null) {
  1015. return -1;
  1016. }
  1017. return depth;
  1018. }
  1019. /**
  1020. * Returns the deepest element at <code>offset</code> matching
  1021. * <code>tag</code>.
  1022. */
  1023. protected Element findElementMatchingTag(HTMLDocument doc, int offset,
  1024. HTML.Tag tag) {
  1025. Element e = doc.getDefaultRootElement();
  1026. Element lastMatch = null;
  1027. while (e != null) {
  1028. if (e.getAttributes().getAttribute
  1029. (StyleConstants.NameAttribute) == tag) {
  1030. lastMatch = e;
  1031. }
  1032. e = e.getElement(e.getElementIndex(offset));
  1033. }
  1034. return lastMatch;
  1035. }
  1036. }
  1037. /**
  1038. * InsertHTMLTextAction can be used to insert an arbitrary string of HTML
  1039. * into an existing HTML document. At least two HTML.Tags need to be
  1040. * supplied. The first Tag, parentTag, identifies the parent in
  1041. * the document to add the elements to. The second tag, addTag,
  1042. * identifies the first tag that should be added to the document as
  1043. * seen in the HTML string. One important thing to remember, is that
  1044. * the parser is going to generate all the appropriate tags, even if
  1045. * they aren't in the HTML string passed in.<p>
  1046. * For example, lets say you wanted to create an action to insert
  1047. * a table into the body. The parentTag would be HTML.Tag.BODY,
  1048. * addTag would be HTML.Tag.TABLE, and the string could be something
  1049. * like <table><tr><td></td></tr></table>.
  1050. * <p>There is also an option to supply an alternate parentTag and
  1051. * addTag. These will be checked for if there is no parentTag at
  1052. * offset.
  1053. */
  1054. public static class InsertHTMLTextAction extends HTMLTextAction {
  1055. public InsertHTMLTextAction(String name, String html,
  1056. HTML.Tag parentTag, HTML.Tag addTag) {
  1057. this(name, html, parentTag, addTag, null, null);
  1058. }
  1059. public InsertHTMLTextAction(String name, String html,
  1060. HTML.Tag parentTag,
  1061. HTML.Tag addTag,
  1062. HTML.Tag alternateParentTag,
  1063. HTML.Tag alternateAddTag) {
  1064. this(name, html, parentTag, addTag, alternateParentTag,
  1065. alternateAddTag, true);
  1066. }
  1067. /* public */
  1068. InsertHTMLTextAction(String name, String html,
  1069. HTML.Tag parentTag,
  1070. HTML.Tag addTag,
  1071. HTML.Tag alternateParentTag,
  1072. HTML.Tag alternateAddTag,
  1073. boolean adjustSelection) {
  1074. super(name);
  1075. this.html = html;
  1076. this.parentTag = parentTag;
  1077. this.addTag = addTag;
  1078. this.alternateParentTag = alternateParentTag;
  1079. this.alternateAddTag = alternateAddTag;
  1080. this.adjustSelection = adjustSelection;
  1081. }
  1082. /**
  1083. * A cover for HTMLEditorKit.insertHTML. If an exception it
  1084. * thrown it is wrapped in a RuntimeException and thrown.
  1085. */
  1086. protected void insertHTML(JEditorPane editor, HTMLDocument doc,
  1087. int offset, String html, int popDepth,
  1088. int pushDepth, HTML.Tag addTag) {
  1089. try {
  1090. getHTMLEditorKit(editor).insertHTML(doc, offset, html,
  1091. popDepth, pushDepth,
  1092. addTag);
  1093. } catch (IOException ioe) {
  1094. throw new RuntimeException("Unable to insert: " + ioe);
  1095. } catch (BadLocationException ble) {
  1096. throw new RuntimeException("Unable to insert: " + ble);
  1097. }
  1098. }
  1099. /**
  1100. * This is invoked when inserting at a boundry. It determines
  1101. * the number of pops, and then the number of pushes that need
  1102. * to be performed, and then invokes insertHTML.
  1103. */
  1104. protected void insertAtBoundry(JEditorPane editor, HTMLDocument doc,
  1105. int offset, Element insertElement,
  1106. String html, HTML.Tag parentTag,
  1107. HTML.Tag addTag) {
  1108. // Find the common parent.
  1109. Element e;
  1110. Element commonParent;
  1111. boolean isFirst = (offset == 0);
  1112. if (offset > 0 || insertElement == null) {
  1113. e = doc.getDefaultRootElement();
  1114. while (e != null && e.getStartOffset() != offset &&
  1115. !e.isLeaf()) {
  1116. e = e.getElement(e.getElementIndex(offset));
  1117. }
  1118. commonParent = (e != null) ? e.getParentElement() : null;
  1119. }
  1120. else {
  1121. // If inserting at the origin, the common parent is the
  1122. // insertElement.
  1123. commonParent = insertElement;
  1124. }
  1125. if (commonParent != null) {
  1126. // Determine how many pops to do.
  1127. int pops = 0;
  1128. int pushes = 0;
  1129. if (isFirst && insertElement != null) {
  1130. e = commonParent;
  1131. while (e != null && !e.isLeaf()) {
  1132. e = e.getElement(e.getElementIndex(offset));
  1133. pops++;
  1134. }
  1135. }
  1136. else {
  1137. e = commonParent;
  1138. offset--;
  1139. while (e != null && !e.isLeaf()) {
  1140. e = e.getElement(e.getElementIndex(offset));
  1141. pops++;
  1142. }
  1143. // And how many pushes
  1144. e = commonParent;
  1145. offset++;
  1146. while (e != null && e != insertElement) {
  1147. e = e.getElement(e.getElementIndex(offset));
  1148. pushes++;
  1149. }
  1150. }
  1151. pops = Math.max(0, pops - 1);
  1152. // And insert!
  1153. insertHTML(editor, doc, offset, html, pops, pushes, addTag);
  1154. }
  1155. }
  1156. /**
  1157. * If there is an Element with name <code>tag</code> at
  1158. * <code>offset</code>, this will invoke either insertAtBoundry
  1159. * or <code>insertHTML</code>. This returns true if there is
  1160. * a match, and one of the inserts is invoked.
  1161. */
  1162. /*protected*/
  1163. boolean insertIntoTag(JEditorPane editor, HTMLDocument doc,
  1164. int offset, HTML.Tag tag, HTML.Tag addTag) {
  1165. Element e = findElementMatchingTag(doc, offset, tag);
  1166. if (e != null && e.getStartOffset() == offset) {
  1167. insertAtBoundry(editor, doc, offset, e, html,
  1168. tag, addTag);
  1169. return true;
  1170. }
  1171. else if (offset > 0) {
  1172. int depth = elementCountToTag(doc, offset - 1, tag);
  1173. if (depth != -1) {
  1174. insertHTML(editor, doc, offset, html, depth, 0, addTag);
  1175. return true;
  1176. }
  1177. }
  1178. return false;
  1179. }
  1180. /**
  1181. * Called after an insertion to adjust the selection.
  1182. */
  1183. /* protected */
  1184. void adjustSelection(JEditorPane pane, HTMLDocument doc,
  1185. int startOffset, int oldLength) {
  1186. int newLength = doc.getLength();
  1187. if (newLength != oldLength && startOffset < newLength) {
  1188. if (startOffset > 0) {
  1189. String text;
  1190. try {
  1191. text = doc.getText(startOffset - 1, 1);
  1192. } catch (BadLocationException ble) {
  1193. text = null;
  1194. }
  1195. if (text != null && text.length() > 0 &&
  1196. text.charAt(0) == '\n') {
  1197. pane.select(startOffset, startOffset);
  1198. }
  1199. else {
  1200. pane.select(startOffset + 1, startOffset + 1);
  1201. }
  1202. }
  1203. else {
  1204. pane.select(1, 1);
  1205. }
  1206. }
  1207. }
  1208. /**
  1209. * Inserts the html into the document.
  1210. *
  1211. * @param e the event
  1212. */
  1213. public void actionPerformed(ActionEvent ae) {
  1214. JEditorPane editor = getEditor(ae);
  1215. if (editor != null) {
  1216. HTMLDocument doc = getHTMLDocument(editor);
  1217. int offset = editor.getSelectionStart();
  1218. int length = doc.getLength();
  1219. boolean inserted;
  1220. // Try first choice
  1221. if (!insertIntoTag(editor, doc, offset, parentTag, addTag) &&
  1222. alternateParentTag != null) {
  1223. // Then alternate.
  1224. inserted = insertIntoTag(editor, doc, offset,
  1225. alternateParentTag,
  1226. alternateAddTag);
  1227. }
  1228. else {
  1229. inserted = true;
  1230. }
  1231. if (adjustSelection && inserted) {
  1232. adjustSelection(editor, doc, offset, length);
  1233. }
  1234. }
  1235. }
  1236. /** HTML to insert. */
  1237. protected String html;
  1238. /** Tag to check for in the document. */
  1239. protected HTML.Tag parentTag;
  1240. /** Tag in HTML to start adding tags from. */
  1241. protected HTML.Tag addTag;
  1242. /** Alternate Tag to check for in the document if parentTag is
  1243. * not found. */
  1244. protected HTML.Tag alternateParentTag;
  1245. /** Alternate tag in HTML to start adding tags from if parentTag
  1246. * is not found and alternateParentTag is found. */
  1247. protected HTML.Tag alternateAddTag;
  1248. /** True indicates the selection should be adjusted after an insert. */
  1249. boolean adjustSelection;
  1250. }
  1251. /**
  1252. * InsertHRAction is special, at actionPerformed time it will determine
  1253. * the parent HTML.Tag based on the paragraph element at the selection
  1254. * start.
  1255. */
  1256. static class InsertHRAction extends InsertHTMLTextAction {
  1257. InsertHRAction() {
  1258. super("InsertHR", "<hr>", null, HTML.Tag.IMPLIED, null, null,
  1259. false);
  1260. }
  1261. /**
  1262. * Inserts the html into the document.
  1263. *
  1264. * @param e the event
  1265. */
  1266. public void actionPerformed(ActionEvent ae) {
  1267. JEditorPane editor = getEditor(ae);
  1268. if (editor != null) {
  1269. HTMLDocument doc = getHTMLDocument(editor);
  1270. int offset = editor.getSelectionStart();
  1271. Element paragraph = doc.getParagraphElement(offset);
  1272. if (paragraph.getParentElement() != null) {
  1273. parentTag = (HTML.Tag)paragraph.getParentElement().
  1274. getAttributes().getAttribute
  1275. (StyleConstants.NameAttribute);
  1276. super.actionPerformed(ae);
  1277. }
  1278. }
  1279. }
  1280. }
  1281. }