1. /*
  2. * @(#)JEditorPane.java 1.97 00/04/06
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing;
  11. import java.awt.*;
  12. import java.awt.event.*;
  13. import java.net.*;
  14. import java.util.*;
  15. import java.io.*;
  16. import java.util.*;
  17. import javax.swing.plaf.*;
  18. import javax.swing.text.*;
  19. import javax.swing.event.*;
  20. import javax.swing.text.html.*;
  21. import javax.accessibility.*;
  22. /**
  23. * A text component to edit various kinds of content.
  24. * You can find how-to information and examples of using editor panes in
  25. * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/text.html">Using Text Components</a>,
  26. * a section in <em>The Java Tutorial.</em>
  27. *
  28. * <p>
  29. * This component uses implementations of the
  30. * <code>EditorKit</code> to accomplish its behavior. It effectively
  31. * morphs into the proper kind of text editor for the kind
  32. * of content it is given. The content type that editor is bound
  33. * to at any given time is determined by the <code>EditorKit</code> currently
  34. * installed. If the content is set to a new URL, its type is used
  35. * to determine the <code>EditorKit</code> that should be used to
  36. * load the content.
  37. * <p>
  38. * By default, the following types of content are known:
  39. * <dl>
  40. * <dt><b>text/plain</b>
  41. * <dd>Plain text, which is the default the type given isn't
  42. * recognized. The kit used in this case is an extension of
  43. * <code>DefaultEditorKit</code> that produces a wrapped plain text view.
  44. * <dt><b>text/html</b>
  45. * <dd>HTML text. The kit used in this case is the class
  46. * <code>javax.swing.text.html.HTMLEditorKit</code>
  47. * which provides HTML 3.2 support.
  48. * <dt><b>text/rtf</b>
  49. * <dd>RTF text. The kit used in this case is the class
  50. * <code>javax.swing.text.rtf.RTFEditorKit</code>
  51. * which provides a limited support of the Rich Text Format.
  52. * </dl>
  53. * <p>
  54. * There are several ways to load content into this component.
  55. * <ol>
  56. * <li>
  57. * The <a href="#setText">setText</a> method can be used to initialize
  58. * the component from a string. In this case the current
  59. * <code>EditorKit</code> will be used, and the content type will be
  60. * expected to be of this type.
  61. * <li>
  62. * The <a href="#read">read</a> method can be used to initialize the
  63. * component from a Reader. Note that if the content type is HTML,
  64. * relative references (e.g. for things like images) can't be resolved
  65. * unless the <base> tag is used or the <em>Base</em> property
  66. * on HTMLDocument is set. In this case the current <code>EditorKit</code>
  67. * will be used, and the content type will be expected to be of this
  68. * type.
  69. * <li>
  70. * The <a href="#setPage">setPage</a> method can be used to initialize
  71. * the component from a URL. In this case, the content type will be
  72. * determined from the URL, and the registered <code>EditorKit</code>
  73. * for that content type will be set.
  74. * </ol>
  75. * <p>
  76. * For the keyboard keys used by this component in the standard Look and
  77. * Feel (L&F) renditions, see the
  78. * <a href="doc-files/Key-Index.html#JEditorPane">JEditorPane</a> key assignments.
  79. * <p>
  80. * Some kinds of content may provide hyperlink support by generating
  81. * hyperlink events. The HTML <code>EditorKit</code> will generate
  82. * hyperlink events if the <code>JEditorPane</code> is <em>not editable</em>
  83. * (<code>JEditorPane.setEditable(false);</code> has been called).
  84. * If HTML frames are embedded in the document, the typical response would be
  85. * to change a portion of the current document. The following code
  86. * fragment is a possible hyperlink listener implementation, that treats
  87. * HTML frame events specially, and simply displays any other activated
  88. * hyperlinks.
  89. * <code><pre>
  90.   class Hyperactive implements HyperlinkListener {
  91.  
  92.   public void hyperlinkUpdate(HyperlinkEvent e) {
  93.   if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
  94.   JEditorPane pane = (JEditorPane) e.getSource();
  95.   if (e instanceof HTMLFrameHyperlinkEvent) {
  96.   HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
  97.   HTMLDocument doc = (HTMLDocument)pane.getDocument();
  98.   doc.processHTMLFrameHyperlinkEvent(evt);
  99.   } else {
  100.   try {
  101.   pane.setPage(e.getURL());
  102.   } catch (Throwable t) {
  103.   t.printStackTrace();
  104.   }
  105.   }
  106.   }
  107.   }
  108.   }
  109. * </pre></code>
  110. * <p>
  111. * Culturally dependent information in some documents is handled through
  112. * a mechanism called character encoding. Character encoding is an
  113. * unambiguous mapping of the members of a character set (letters, ideographs,
  114. * digits, symbols, or control functions) to specific numeric code values. It
  115. * represents the way the file is stored. Example character encodings are
  116. * ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is
  117. * passed to an user agent (<code>JEditorPane</code>) it is converted to
  118. * the document character set (ISO-10646 aka Unicode).
  119. * <p>
  120. * There are multiple ways to get a character set mapping to happen
  121. * with <code>JEditorPane</code>.
  122. * <ol>
  123. * <li>
  124. * One way is to specify the character set as a parameter of the MIME
  125. * type. This will be established by a call to the
  126. * <a href="#setContentType">setContentType</a> method. If the content
  127. * is loaded by the <a href="#setPage">setPage</a> method the content
  128. * type will have been set according to the specification of the URL.
  129. * It the file is loaded directly, the content type would be expected to
  130. * have been set prior to loading.
  131. * <li>
  132. * Another way the character set can be specified is in the document itself.
  133. * This requires reading the document prior to determining the character set
  134. * that is desired. To handle this, it is expected that the
  135. * <code>EditorKit</code>.read operation throw a
  136. * <code>ChangedCharSetException</code> which will
  137. * be caught. The read is then restarted with a new Reader that uses
  138. * the character set specified in the <code>ChangedCharSetException</code>
  139. * (which is an <code>IOException</code>).
  140. * </ol>
  141. * <p>
  142. * <strong>Warning:</strong>
  143. * Serialized objects of this class will not be compatible with
  144. * future Swing releases. The current serialization support is appropriate
  145. * for short term storage or RMI between applications running the same
  146. * version of Swing. A future release of Swing will provide support for
  147. * long term persistence.
  148. *
  149. * @beaninfo
  150. * attribute: isContainer false
  151. * description: A text component to edit various types of content.
  152. *
  153. * @author Timothy Prinzing
  154. * @version 1.97 04/06/00
  155. */
  156. public class JEditorPane extends JTextComponent {
  157. /**
  158. * Creates a new <code>JEditorPane</code>.
  159. * The document model is set to <code>null</code>.
  160. */
  161. public JEditorPane() {
  162. super();
  163. }
  164. /**
  165. * Creates a <code>JEditorPane</code> based on a specified URL for input.
  166. *
  167. * @param initialPage the URL
  168. * @exception IOException if the URL is <code>null</code>
  169. * or cannot be accessed
  170. */
  171. public JEditorPane(URL initialPage) throws IOException {
  172. this();
  173. setPage(initialPage);
  174. }
  175. /**
  176. * Creates a <code>JEditorPane</code> based on a string containing
  177. * a URL specification.
  178. *
  179. * @param url the URL
  180. * @exception IOException if the URL is <code>null</code> or
  181. * cannot be accessed
  182. */
  183. public JEditorPane(String url) throws IOException {
  184. this();
  185. setPage(url);
  186. }
  187. /**
  188. * Creates a <code>JEditorPane</code> that has been initialized
  189. * to the given text. This is a convenience constructor that calls the
  190. * <code>setContentType</code> and <code>setText</code> methods.
  191. *
  192. * @param type mime type of the given text
  193. * @param text the text to initialize with
  194. */
  195. public JEditorPane(String type, String text) {
  196. this();
  197. setContentType(type);
  198. setText(text);
  199. }
  200. /**
  201. * Adds a hyperlink listener for notification of any changes, for example
  202. * when a link is selected and entered.
  203. *
  204. * @param listener the listener
  205. */
  206. public synchronized void addHyperlinkListener(HyperlinkListener listener) {
  207. listenerList.add(HyperlinkListener.class, listener);
  208. }
  209. /**
  210. * Removes a hyperlink listener.
  211. *
  212. * @param listener the listener
  213. */
  214. public synchronized void removeHyperlinkListener(HyperlinkListener listener) {
  215. listenerList.remove(HyperlinkListener.class, listener);
  216. }
  217. /**
  218. * Notifies all listeners that have registered interest for
  219. * notification on this event type. This is normally called
  220. * by the currently installed <code>EditorKit</code> if a content type
  221. * that supports hyperlinks is currently active and there
  222. * was activity with a link. The listener list is processed
  223. * last to first.
  224. *
  225. * @param e the event
  226. * @see EventListenerList
  227. */
  228. public void fireHyperlinkUpdate(HyperlinkEvent e) {
  229. // Guaranteed to return a non-null array
  230. Object[] listeners = listenerList.getListenerList();
  231. // Process the listeners last to first, notifying
  232. // those that are interested in this event
  233. for (int i = listeners.length-2; i>=0; i-=2) {
  234. if (listeners[i]==HyperlinkListener.class) {
  235. ((HyperlinkListener)listeners[i+1]).hyperlinkUpdate(e);
  236. }
  237. }
  238. }
  239. /**
  240. * Sets the current URL being displayed. The content type of the
  241. * pane is set, and if the editor kit for the pane is
  242. * non-<code>null</code>, then
  243. * a new default document is created and the URL is read into it.
  244. * If the URL contains and reference location, the location will
  245. * be scrolled to by calling the <code>scrollToReference</code>
  246. * method. If the desired URL is not the one currently being
  247. * displayed, the <code>getStream</code> method is called to
  248. * give subclasses control over the stream provided.
  249. * <p>
  250. * This may load either synchronously or asynchronously
  251. * depending upon the document returned by the <code>EditorKit</code>.
  252. * If the <code>Document</code> is of type
  253. * <code>AbstractDocument</code> and has a value returned by
  254. * <code>AbstractDocument.getAsynchronousLoadPriority</code>
  255. * that is greater than or equal to zero, the page will be
  256. * loaded on a separate thread using that priority.
  257. * <p>
  258. * If the document is loaded synchronously, it will be
  259. * filled in with the stream prior to being installed into
  260. * the editor with a call to <code>setDocument</code>, which
  261. * is bound and will fire a property change event. If an
  262. * <code>IOException</code> is thrown the partially loaded
  263. * document will
  264. * be discarded and neither the document or page property
  265. * change events will be fired. If the document is
  266. * successfully loaded and installed, a view will be
  267. * built for it by the UI which will then be scrolled if
  268. * necessary, and then the page property change event
  269. * will be fired.
  270. * <p>
  271. * If the document is loaded asynchronously, the document
  272. * will be installed into the editor immediately using a
  273. * call to <code>setDocument</code> which will fire a
  274. * document property change event, then a thread will be
  275. * created which will begin doing the actual loading.
  276. * In this case, the page property change event will not be
  277. * fired by the call to this method directly, but rather will be
  278. * fired when the thread doing the loading has finished.
  279. * Since the calling thread can not throw an <code>IOException</code>
  280. * in the event of failure on the other thread, the page
  281. * property change event will be fired when the other
  282. * thread is done whether the load was successful or not.
  283. *
  284. * @param page the URL of the page
  285. * @exception IOException for a <code>null</code> or invalid
  286. * page specification, or exception from the stream being read
  287. * @see #getPage
  288. * @beaninfo
  289. * description: the URL used to set content
  290. * bound: true
  291. * expert: true
  292. */
  293. public void setPage(URL page) throws IOException {
  294. if (page == null) {
  295. throw new IOException("invalid url");
  296. }
  297. URL loaded = getPage();
  298. // reset scrollbar
  299. scrollRectToVisible(new Rectangle(0,0,1,1));
  300. boolean reloaded = false;
  301. if ((loaded == null) || (! loaded.sameFile(page))) {
  302. // different url, load the new content
  303. InputStream in = getStream(page);
  304. if (kit != null) {
  305. Document doc = kit.createDefaultDocument();
  306. if (pageProperties != null) {
  307. // transfer properties discovered in stream to the
  308. // document property collection.
  309. for (Enumeration e = pageProperties.keys(); e.hasMoreElements() ;) {
  310. Object key = e.nextElement();
  311. doc.putProperty(key, pageProperties.get(key));
  312. }
  313. pageProperties.clear();
  314. }
  315. if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
  316. doc.putProperty(Document.StreamDescriptionProperty, page);
  317. }
  318. // At this point, one could either load up the model with no
  319. // view notifications slowing it down (i.e. best synchronous
  320. // behavior) or set the model and start to feed it on a seperate
  321. // thread (best asynchronous behavior).
  322. if (doc instanceof AbstractDocument) {
  323. AbstractDocument adoc = (AbstractDocument) doc;
  324. int p = adoc.getAsynchronousLoadPriority();
  325. if (p >= 0) {
  326. // load asynchronously
  327. setDocument(doc);
  328. Thread pl = new PageLoader(doc, in, p, loaded, page);
  329. pl.start();
  330. return;
  331. }
  332. }
  333. read(in, doc);
  334. setDocument(doc);
  335. reloaded = true;
  336. }
  337. }
  338. final String reference = page.getRef();
  339. if (reference != null) {
  340. if (!reloaded) {
  341. scrollToReference(reference);
  342. }
  343. else {
  344. // Have to scroll after painted.
  345. SwingUtilities.invokeLater(new Runnable() {
  346. public void run() {
  347. scrollToReference(reference);
  348. }
  349. });
  350. }
  351. }
  352. firePropertyChange("page", loaded, page);
  353. }
  354. /**
  355. * This method initializes from a stream. If the kit is
  356. * set to be of type <code>HTMLEditorKit</code>, and the
  357. * <code>desc</code> parameter is an <code>HTMLDocument</code>,
  358. * then it invokes the <code>HTMLEditorKit</code> to initiate
  359. * the read. Otherwise it calls the superclass
  360. * method which loads the model as plain text.
  361. *
  362. * @param in the stream from which to read
  363. * @param desc an object describing the stream
  364. * @exception IOException as thrown by the stream being
  365. * used to initialize
  366. * @see JTextComponent#read
  367. * @see #setDocument
  368. */
  369. public void read(InputStream in, Object desc) throws IOException {
  370. if (desc instanceof HTMLDocument &&
  371. kit instanceof HTMLEditorKit) {
  372. HTMLDocument hdoc = (HTMLDocument) desc;
  373. setDocument(hdoc);
  374. read(in, hdoc);
  375. } else {
  376. String charset = (String) getClientProperty("charset");
  377. Reader r = (charset != null) ? new InputStreamReader(in, charset) :
  378. new InputStreamReader(in);
  379. super.read(r, desc);
  380. }
  381. }
  382. /**
  383. * This method invokes the <code>EditorKit</code> to initiate a
  384. * read. In the case where a <code>ChangedCharSetException</code>
  385. * is thrown this exception will contain the new CharSet.
  386. * Therefore the <code>read</code> operation
  387. * is then restarted after building a new Reader with the new charset.
  388. *
  389. * @param in the inputstream to use
  390. * @param doc the document to load
  391. *
  392. */
  393. void read(InputStream in, Document doc) throws IOException {
  394. try {
  395. String charset = (String) getClientProperty("charset");
  396. Reader r = (charset != null) ? new InputStreamReader(in, charset) :
  397. new InputStreamReader(in);
  398. kit.read(r, doc, 0);
  399. } catch (BadLocationException e) {
  400. throw new IOException(e.getMessage());
  401. } catch (ChangedCharSetException e1) {
  402. String charSetSpec = e1.getCharSetSpec();
  403. if (e1.keyEqualsCharSet()) {
  404. putClientProperty("charset", charSetSpec);
  405. } else {
  406. setCharsetFromContentTypeParameters(charSetSpec);
  407. }
  408. in.close();
  409. URL url = (URL)doc.getProperty(Document.StreamDescriptionProperty);
  410. URLConnection conn = url.openConnection();
  411. in = conn.getInputStream();
  412. try {
  413. doc.remove(0, doc.getLength());
  414. } catch (BadLocationException e) {}
  415. doc.putProperty("IgnoreCharsetDirective", new Boolean(true));
  416. read(in, doc);
  417. }
  418. }
  419. /**
  420. * Thread to load a stream into the text document model.
  421. */
  422. class PageLoader extends Thread {
  423. /**
  424. * Construct an asynchronous page loader.
  425. */
  426. PageLoader(Document doc, InputStream in, int priority, URL old,
  427. URL page) {
  428. setPriority(priority);
  429. this.in = in;
  430. this.old = old;
  431. this.page = page;
  432. this.doc = doc;
  433. }
  434. /**
  435. * Try to load the document, then scroll the view
  436. * to the reference (if specified). When done, fire
  437. * a page property change event.
  438. */
  439. public void run() {
  440. try {
  441. read(in, doc);
  442. URL page = (URL) doc.getProperty(Document.StreamDescriptionProperty);
  443. String reference = page.getRef();
  444. if (reference != null) {
  445. // scroll the page if necessary, but do it on the
  446. // event thread... that is the only guarantee that
  447. // modelToView can be safely called.
  448. Runnable callScrollToReference = new Runnable() {
  449. public void run() {
  450. URL u = (URL) getDocument().getProperty
  451. (Document.StreamDescriptionProperty);
  452. String ref = u.getRef();
  453. scrollToReference(ref);
  454. }
  455. };
  456. SwingUtilities.invokeLater(callScrollToReference);
  457. }
  458. } catch (IOException ioe) {
  459. getToolkit().beep();
  460. } finally {
  461. firePropertyChange("page", old, page);
  462. }
  463. }
  464. /**
  465. * The stream to load the document with
  466. */
  467. InputStream in;
  468. /**
  469. * URL of the old page that was replaced (for the property change event)
  470. */
  471. URL old;
  472. /**
  473. * URL of the page being loaded (for the property change event)
  474. */
  475. URL page;
  476. /**
  477. * The Document instance to load into. This is cached in case a
  478. * new Document is created between the time the thread this is created
  479. * and run.
  480. */
  481. Document doc;
  482. }
  483. /**
  484. * Fetches a stream for the given URL, which is about to
  485. * be loaded by the <code>setPage</code> method. By
  486. * default, this simply opens the URL and returns the
  487. * stream. This can be reimplemented to do useful things
  488. * like fetch the stream from a cache, monitor the progress
  489. * of the stream, etc.
  490. * <p>
  491. * This method is expected to have the the side effect of
  492. * establishing the content type, and therefore setting the
  493. * appropriate <code>EditorKit</code> to use for loading the stream.
  494. * <p>
  495. * If this the stream was an http connection, redirects
  496. * will be followed and the resulting URL will be set as
  497. * the <code>Document.StreamDescriptionProperty</code> so that relative
  498. * URL's can be properly resolved.
  499. *
  500. * @param page the URL of the page
  501. */
  502. protected InputStream getStream(URL page) throws IOException {
  503. URLConnection conn = page.openConnection();
  504. if (conn instanceof HttpURLConnection) {
  505. HttpURLConnection hconn = (HttpURLConnection) conn;
  506. hconn.setInstanceFollowRedirects(false);
  507. int response = hconn.getResponseCode();
  508. boolean redirect = (response >= 300 && response <= 399);
  509. /*
  510. * In the case of a redirect, we want to actually change the URL
  511. * that was input to the new, redirected URL
  512. */
  513. if (redirect) {
  514. String loc = conn.getHeaderField("Location");
  515. if (loc.startsWith("http", 0)) {
  516. page = new URL(loc);
  517. } else {
  518. page = new URL(page, loc);
  519. }
  520. return getStream(page);
  521. }
  522. }
  523. if (pageProperties == null) {
  524. pageProperties = new Hashtable();
  525. }
  526. String type = conn.getContentType();
  527. if (type != null) {
  528. setContentType(type);
  529. pageProperties.put("content-type", type);
  530. }
  531. pageProperties.put(Document.StreamDescriptionProperty, page);
  532. String enc = conn.getContentEncoding();
  533. if (enc != null) {
  534. pageProperties.put("content-encoding", enc);
  535. }
  536. InputStream in = conn.getInputStream();
  537. return in;
  538. }
  539. /**
  540. * Scrolls the view to the given reference location
  541. * (that is, the value returned by the <code>UL.getRef</code>
  542. * method for the URL being displayed). By default, this
  543. * method only knows how to locate a reference in an
  544. * HTMLDocument. The implementation calls the
  545. * <code>scrollRectToVisible</code> method to
  546. * accomplish the actual scrolling. If scrolling to a
  547. * reference location is needed for document types other
  548. * than HTML, this method should be reimplemented.
  549. * This method will have no effect if the component
  550. * is not visible.
  551. *
  552. * @param reference the named location to scroll to
  553. */
  554. protected void scrollToReference(String reference) {
  555. Document d = getDocument();
  556. if (d instanceof HTMLDocument) {
  557. HTMLDocument doc = (HTMLDocument) d;
  558. HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A);
  559. for (; iter.isValid(); iter.next()) {
  560. AttributeSet a = iter.getAttributes();
  561. String nm = (String) a.getAttribute(HTML.Attribute.NAME);
  562. if ((nm != null) && nm.equals(reference)) {
  563. // found a matching reference in the document.
  564. try {
  565. Rectangle r = modelToView(iter.getStartOffset());
  566. if (r != null) {
  567. // the view is visible, scroll it to the
  568. // center of the current visible area.
  569. Rectangle vis = getVisibleRect();
  570. //r.y -= (vis.height / 2);
  571. r.height = vis.height;
  572. scrollRectToVisible(r);
  573. }
  574. } catch (BadLocationException ble) {
  575. getToolkit().beep();
  576. }
  577. }
  578. }
  579. }
  580. }
  581. /**
  582. * Gets the current URL being displayed. If a URL was
  583. * not specified in the creation of the document, this
  584. * will return <code>null</code>, and relative URL's will not be
  585. * resolved.
  586. *
  587. * @return the URL, or <code>null</code> if none
  588. */
  589. public URL getPage() {
  590. return (URL) getDocument().getProperty(Document.StreamDescriptionProperty);
  591. }
  592. /**
  593. * Sets the current URL being displayed.
  594. *
  595. * @param url the URL for display
  596. * @exception IOException for a <code>null</code> or invalid URL
  597. * specification
  598. */
  599. public void setPage(String url) throws IOException {
  600. if (url == null) {
  601. throw new IOException("invalid url");
  602. }
  603. URL page = new URL(url);
  604. setPage(page);
  605. }
  606. /**
  607. * Gets the class ID for the UI.
  608. *
  609. * @return the string "EditorPaneUI"
  610. * @see JComponent#getUIClassID
  611. * @see UIDefaults#getUI
  612. */
  613. public String getUIClassID() {
  614. return uiClassID;
  615. }
  616. /**
  617. * Creates the default editor kit (<code>PlainEditorKit</code>) for when
  618. * the component is first created.
  619. *
  620. * @return the editor kit
  621. */
  622. protected EditorKit createDefaultEditorKit() {
  623. return new PlainEditorKit();
  624. }
  625. /**
  626. * Fetches the currently installed kit for handling content.
  627. * <code>createDefaultEditorKit</code> is called to set up a default
  628. * if necessary.
  629. *
  630. * @return the editor kit
  631. */
  632. public EditorKit getEditorKit() {
  633. if (kit == null) {
  634. kit = createDefaultEditorKit();
  635. }
  636. return kit;
  637. }
  638. /**
  639. * Gets the type of content that this editor
  640. * is currently set to deal with. This is
  641. * defined to be the type associated with the
  642. * currently installed <code>EditorKit</code>.
  643. *
  644. * @return the content type, <code>null</code> if no editor kit set
  645. */
  646. public final String getContentType() {
  647. return (kit != null) ? kit.getContentType() : null;
  648. }
  649. /**
  650. * Sets the type of content that this editor
  651. * handles. This calls <code>getEditorKitForContentType</code>,
  652. * and then <code>setEditorKit</code> if an editor kit can
  653. * be successfully located. This is mostly convenience method
  654. * that can be used as an alternative to calling
  655. * <code>setEditorKit</code> directly.
  656. * <p>
  657. * If there is a charset definition specified as a parameter
  658. * of the content type specification, it will be used when
  659. * loading input streams using the associated <code>EditorKit</code>.
  660. * For example if the type is specified as
  661. * <code>text/html; charset=EUC-JP</code> the content
  662. * will be loaded using the <code>EditorKit</code> registered for
  663. * <code>text/html</code> and the Reader provided to
  664. * the <code>EditorKit</code> to load unicode into the document will
  665. * use the <code>EUC-JP</code> charset for translating
  666. * to unicode.
  667. *
  668. * @param type the non-<code>null</code> mime type for the content editing
  669. * support
  670. * @see #getContentType
  671. * @beaninfo
  672. * description: the type of content
  673. */
  674. public final void setContentType(String type) {
  675. // The type could have optional info is part of it,
  676. // for example some charset info. We need to strip that
  677. // of and save it.
  678. int parm = type.indexOf(";");
  679. if (parm > -1) {
  680. // Save the paramList.
  681. String paramList = type.substring(parm);
  682. // update the content type string.
  683. type = type.substring(0, parm).trim();
  684. if (type.toLowerCase().startsWith("text/")) {
  685. setCharsetFromContentTypeParameters(paramList);
  686. }
  687. }
  688. if ((kit == null) || (! type.equals(kit.getContentType()))) {
  689. EditorKit k = getEditorKitForContentType(type);
  690. if (k != null) {
  691. setEditorKit(k);
  692. }
  693. }
  694. }
  695. /**
  696. * This method gets the charset information specified as part
  697. * of the content type in the http header information.
  698. */
  699. private void setCharsetFromContentTypeParameters(String paramlist) {
  700. String charset = null;
  701. try {
  702. // paramlist is handed to us with a leading ';', strip it.
  703. int semi = paramlist.indexOf(';');
  704. if (semi > -1 && semi < paramlist.length()-1) {
  705. paramlist = paramlist.substring(semi + 1);
  706. }
  707. if (paramlist.length() > 0) {
  708. // parse the paramlist into attr-value pairs & get the
  709. // charset pair's value
  710. HeaderParser hdrParser = new HeaderParser(paramlist);
  711. charset = hdrParser.findValue("charset");
  712. if (charset != null) {
  713. putClientProperty("charset", charset);
  714. }
  715. }
  716. }
  717. catch (IndexOutOfBoundsException e) {
  718. // malformed parameter list, use charset we have
  719. }
  720. catch (NullPointerException e) {
  721. // malformed parameter list, use charset we have
  722. }
  723. catch (Exception e) {
  724. // malformed parameter list, use charset we have; but complain
  725. System.err.println("JEditorPane.getCharsetFromContentTypeParameters failed on: " + paramlist);
  726. e.printStackTrace();
  727. }
  728. }
  729. /**
  730. * Sets the currently installed kit for handling
  731. * content. This is the bound property that
  732. * establishes the content type of the editor.
  733. * Any old kit is first deinstalled, then if kit is
  734. * non-<code>null</code>,
  735. * the new kit is installed, and a default document created for it.
  736. * A <code>PropertyChange</code> event ("editorKit") is always fired when
  737. * <code>setEditorKit</code> is called.
  738. * <p>
  739. * <em>NOTE: This has the side effect of changing the model,
  740. * because the <code>EditorKit</code> is the source of how a
  741. * particular type
  742. * of content is modeled. This method will cause <code>setDocument</code>
  743. * to be called on behalf of the caller to ensure integrity
  744. * of the internal state.</em>
  745. *
  746. * @param kit the desired editor behavior
  747. * @see #getEditorKit
  748. * @beaninfo
  749. * description: the currently installed kit for handling content
  750. * bound: true
  751. * expert: true
  752. */
  753. public void setEditorKit(EditorKit kit) {
  754. EditorKit old = this.kit;
  755. if (old != null) {
  756. old.deinstall(this);
  757. }
  758. this.kit = kit;
  759. if (this.kit != null) {
  760. this.kit.install(this);
  761. setDocument(this.kit.createDefaultDocument());
  762. }
  763. firePropertyChange("editorKit", old, kit);
  764. }
  765. /**
  766. * Fetches the editor kit to use for the given type
  767. * of content. This is called when a type is requested
  768. * that doesn't match the currently installed type.
  769. * If the component doesn't have an <code>EditorKit</code> registered
  770. * for the given type, it will try to create an
  771. * <code>EditorKit</code> from the default <code>EditorKit</code> registry.
  772. * If that fails, a <code>PlainEditorKit</code> is used on the
  773. * assumption that all text documents can be represented
  774. * as plain text.
  775. * <p>
  776. * This method can be reimplemented to use some
  777. * other kind of type registry. This can
  778. * be reimplemented to use the Java Activation
  779. * Framework, for example.
  780. *
  781. * @param type the non-</code>null</code> content type
  782. * @return the editor kit
  783. */
  784. public EditorKit getEditorKitForContentType(String type) {
  785. if (typeHandlers == null) {
  786. typeHandlers = new Hashtable(3);
  787. }
  788. EditorKit k = (EditorKit) typeHandlers.get(type);
  789. if (k == null) {
  790. k = createEditorKitForContentType(type);
  791. if (k != null) {
  792. setEditorKitForContentType(type, k);
  793. }
  794. }
  795. if (k == null) {
  796. k = createDefaultEditorKit();
  797. }
  798. return k;
  799. }
  800. /**
  801. * Directly sets the editor kit to use for the given type. A
  802. * look-and-feel implementation might use this in conjunction
  803. * with <code>createEditorKitForContentType</code> to install handlers for
  804. * content types with a look-and-feel bias.
  805. *
  806. * @param type the non-<code>null</code> content type
  807. * @param k the editor kit to be set
  808. */
  809. public void setEditorKitForContentType(String type, EditorKit k) {
  810. if (typeHandlers == null) {
  811. typeHandlers = new Hashtable(3);
  812. }
  813. typeHandlers.put(type, k);
  814. }
  815. /**
  816. * Replaces the currently selected content with new content
  817. * represented by the given string. If there is no selection
  818. * this amounts to an insert of the given text. If there
  819. * is no replacement text (i.e. the content string is empty
  820. * or <code>null</code>) this amounts to a removal of the
  821. * current selection. The replacement text will have the
  822. * attributes currently defined for input. If the component is not
  823. * editable, beep and return.
  824. * <p>
  825. * This method is thread safe, although most Swing methods
  826. * are not. Please see
  827. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  828. * and Swing</A> for more information.
  829. *
  830. * @param content the content to replace the selection with. This
  831. * value can be <code>null</code>
  832. */
  833. public void replaceSelection(String content) {
  834. if (! isEditable()) {
  835. getToolkit().beep();
  836. return;
  837. }
  838. EditorKit kit = getEditorKit();
  839. if(kit instanceof StyledEditorKit) {
  840. try {
  841. Document doc = getDocument();
  842. Caret caret = getCaret();
  843. int p0 = Math.min(caret.getDot(), caret.getMark());
  844. int p1 = Math.max(caret.getDot(), caret.getMark());
  845. if (p0 != p1) {
  846. doc.remove(p0, p1 - p0);
  847. }
  848. if (content != null && content.length() > 0) {
  849. doc.insertString(p0, content, ((StyledEditorKit)kit).
  850. getInputAttributes());
  851. }
  852. } catch (BadLocationException e) {
  853. getToolkit().beep();
  854. }
  855. }
  856. else {
  857. super.replaceSelection(content);
  858. }
  859. }
  860. /**
  861. * Creates a handler for the given type from the default registry
  862. * of editor kits. The registry is created if necessary. If the
  863. * registered class has not yet been loaded, an attempt
  864. * is made to dynamically load the prototype of the kit for the
  865. * given type. If the type was registered with a <code>ClassLoader</code>,
  866. * that <code>ClassLoader</code> will be used to load the prototype.
  867. * If there was no registered <code>ClassLoader</code>,
  868. * <code>Class.forName</code> will be used to load the prototype.
  869. * <p>
  870. * Once a prototype <code>EditorKit</code> instance is successfully
  871. * located, it is cloned and the clone is returned.
  872. *
  873. * @param type the content type
  874. * @return the editor kit, or <code>null</code> if there is nothing
  875. * registered for the given type
  876. */
  877. public static EditorKit createEditorKitForContentType(String type) {
  878. EditorKit k = null;
  879. Hashtable kitRegistry = getKitRegisty();
  880. k = (EditorKit) kitRegistry.get(type);
  881. if (k == null) {
  882. // try to dynamically load the support
  883. String classname = (String) getKitTypeRegistry().get(type);
  884. ClassLoader loader = (ClassLoader) getKitLoaderRegistry().get(type);
  885. try {
  886. Class c;
  887. if (loader != null) {
  888. c = loader.loadClass(classname);
  889. } else {
  890. c = Class.forName(classname);
  891. }
  892. k = (EditorKit) c.newInstance();
  893. kitRegistry.put(type, k);
  894. } catch (Throwable e) {
  895. k = null;
  896. }
  897. }
  898. // create a copy of the prototype or null if there
  899. // is no prototype.
  900. if (k != null) {
  901. return (EditorKit) k.clone();
  902. }
  903. return null;
  904. }
  905. /**
  906. * Establishes the default bindings of <code>type</code> to
  907. * <code>classname</code>.
  908. * The class will be dynamically loaded later when actually
  909. * needed, and can be safely changed before attempted uses
  910. * to avoid loading unwanted classes. The prototype
  911. * <code>EditorKit</code> will be loaded with <code>Class.forName</code>
  912. * when registered with this method.
  913. *
  914. * @param type the non-<code>null</code> content type
  915. * @param classname the class to load later
  916. */
  917. public static void registerEditorKitForContentType(String type, String classname) {
  918. getKitLoaderRegistry().remove(type);
  919. getKitTypeRegistry().put(type, classname);
  920. getKitRegisty().remove(type);
  921. }
  922. /**
  923. * Establishes the default bindings of <code>type</code> to
  924. * <code>classname</code>.
  925. * The class will be dynamically loaded later when actually
  926. * needed using the given <code>ClassLoader</code>,
  927. * and can be safely changed
  928. * before attempted uses to avoid loading unwanted classes.
  929. *
  930. * @param type the non-</code>null</code> content type
  931. * @param classname the class to load later
  932. * @param loader the <code>ClassLoader</code> to use to load the name
  933. */
  934. public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) {
  935. getKitTypeRegistry().put(type, classname);
  936. getKitLoaderRegistry().put(type, loader);
  937. getKitRegisty().remove(type);
  938. }
  939. /**
  940. * Returns the currently registered <code>EditorKit</code>
  941. * class name for the type <code>type</code>.
  942. *
  943. * @param type the non-<code>null</code> content type
  944. *
  945. * @since 1.3
  946. */
  947. public static String getEditorKitClassNameForContentType(String type) {
  948. return (String)getKitTypeRegistry().get(type);
  949. }
  950. private static Hashtable getKitTypeRegistry() {
  951. loadDefaultKitsIfNecessary();
  952. return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
  953. }
  954. private static Hashtable getKitLoaderRegistry() {
  955. loadDefaultKitsIfNecessary();
  956. return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
  957. }
  958. private static Hashtable getKitRegisty() {
  959. Hashtable ht = (Hashtable)SwingUtilities.appContextGet(kitRegistryKey);
  960. if (ht == null) {
  961. ht = new Hashtable(3);
  962. SwingUtilities.appContextPut(kitRegistryKey, ht);
  963. }
  964. return ht;
  965. }
  966. /**
  967. * This is invoked every time the registries are accessed. Loading
  968. * is done this way instead of via a static as the static is only
  969. * called once when running in plugin resulting in the entries only
  970. * appearing in the first applet.
  971. */
  972. private static void loadDefaultKitsIfNecessary() {
  973. if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
  974. Hashtable ht = new Hashtable();
  975. SwingUtilities.appContextPut(kitTypeRegistryKey, ht);
  976. ht = new Hashtable();
  977. SwingUtilities.appContextPut(kitLoaderRegistryKey, ht);
  978. registerEditorKitForContentType("text/plain",
  979. "javax.swing.JEditorPane$PlainEditorKit");
  980. registerEditorKitForContentType("text/html",
  981. "javax.swing.text.html.HTMLEditorKit");
  982. registerEditorKitForContentType("text/rtf",
  983. "javax.swing.text.rtf.RTFEditorKit");
  984. registerEditorKitForContentType("application/rtf",
  985. "javax.swing.text.rtf.RTFEditorKit");
  986. }
  987. }
  988. // --- java.awt.Component methods --------------------------
  989. /**
  990. * Returns the preferred size for the <code>JEditorPane</code>.
  991. * The preferred size for <code>JEditorPane</code> is slightly altered
  992. * from the preferred size of the superclass. If the size
  993. * of the viewport has become smaller than the minimum size
  994. * of the component, the scrollable definition for tracking
  995. * width or height will turn to false. The default viewport
  996. * layout will give the preferred size, and that is not desired
  997. * in the case where the scrollable is tracking. In that case
  998. * the <em>normal</em> preferred size is adjusted to the
  999. * minimum size. This allows things like HTML tables to
  1000. * shrink down to their minimum size and then be laid out at
  1001. * their minimum size, refusing to shrink any further.
  1002. *
  1003. * @return a <code>Dimension</code> containing the preferred size
  1004. */
  1005. public Dimension getPreferredSize() {
  1006. Dimension d = super.getPreferredSize();
  1007. if (getParent() instanceof JViewport) {
  1008. JViewport port = (JViewport)getParent();
  1009. TextUI ui = getUI();
  1010. if (! getScrollableTracksViewportWidth()) {
  1011. int w = port.getWidth();
  1012. Dimension min = ui.getMinimumSize(this);
  1013. if (w < min.width) {
  1014. d.width = min.width;
  1015. }
  1016. }
  1017. if (! getScrollableTracksViewportHeight()) {
  1018. int h = port.getHeight();
  1019. Dimension min = ui.getMinimumSize(this);
  1020. if (h < min.height) {
  1021. d.height = min.height;
  1022. }
  1023. }
  1024. }
  1025. return d;
  1026. }
  1027. // --- JComponent methods ---------------------------------
  1028. /**
  1029. * Turns off tab traversal once focus is gained.
  1030. *
  1031. * @return true, to indicate that the focus is being managed;
  1032. * or false otherwise
  1033. */
  1034. public boolean isManagingFocus() {
  1035. return managingFocus;
  1036. }
  1037. /**
  1038. * Makes <code>JEditorPane</code> be the root of a focus cycle.
  1039. * This means that, by default, tabbing within the editor
  1040. * pane will move between components of the pane, such as
  1041. * form controls, but not out of the pane.
  1042. * <p>Note that this method always returns true.
  1043. *
  1044. * @see JComponent#isFocusCycleRoot
  1045. * @return true
  1046. */
  1047. public boolean isFocusCycleRoot() {
  1048. return true;
  1049. }
  1050. /**
  1051. * Overridden to handle processing of tab/shift tab. If <code>e</code>
  1052. * identifies a tab, the editor pane is not editable and has components,
  1053. * then the <code>FocusManager</code> is asked to pass focus to the
  1054. * next/previous component.
  1055. *
  1056. * @param e the current key event
  1057. */
  1058. protected void processComponentKeyEvent(KeyEvent e) {
  1059. super.processComponentKeyEvent(e);
  1060. if (isManagingFocus()) {
  1061. if ((e.getKeyCode() == KeyEvent.VK_TAB || e.getKeyChar() == '\t')) {
  1062. if (e.getID() == KeyEvent.KEY_PRESSED && !isEditable() &&
  1063. getComponentCount() > 0) {
  1064. // We aren't editable, and have child components, which
  1065. // are likely form elements. By setting managingFocus to
  1066. // false the FocusManager will look at our children and
  1067. // give one of them focus.
  1068. managingFocus = false;
  1069. try {
  1070. if ((e.getModifiers() & ActionEvent.SHIFT_MASK) ==
  1071. ActionEvent.SHIFT_MASK) {
  1072. FocusManager.getCurrentManager().
  1073. focusPreviousComponent(this);
  1074. }
  1075. else {
  1076. FocusManager.getCurrentManager().
  1077. focusNextComponent(this);
  1078. }
  1079. e.consume();
  1080. }
  1081. finally {
  1082. managingFocus = true;
  1083. }
  1084. }
  1085. }
  1086. }
  1087. }
  1088. /**
  1089. * Make sure that TAB and Shift-TAB events get consumed, so that
  1090. * awt doesn't attempt focus traversal.
  1091. *
  1092. * @param e the current key event
  1093. */
  1094. protected void processKeyEvent(KeyEvent e) {
  1095. super.processKeyEvent(e);
  1096. // tab consumption
  1097. // We are actually consuming any TABs modified in any way, because
  1098. // we don't want awt to get anything it can use for focus traversal.
  1099. if (isManagingFocus()) {
  1100. if ((e.getKeyCode() == KeyEvent.VK_TAB || e.getKeyChar() == '\t')) {
  1101. e.consume();
  1102. }
  1103. }
  1104. }
  1105. // --- JTextComponent methods -----------------------------
  1106. /**
  1107. * Sets the text of this <code>TextComponent</code> to the specified
  1108. * content,
  1109. * which is expected to be in the format of the content type of
  1110. * this editor. For example, if the type is set to <code>text/html</code>
  1111. * the string should be specified in terms of HTML.
  1112. * <p>
  1113. * This is implemented to remove the contents of the current document,
  1114. * and replace them by parsing the given string using the current
  1115. * <code>EditorKit</code>. This gives the semantics of the
  1116. * superclass by not changing
  1117. * out the model, while supporting the content type currently set on
  1118. * this component. The assumption is that the previous content is
  1119. * relatively
  1120. * small, and that the previous content doesn't have side effects.
  1121. * Both of those assumptions can be violated and cause undesirable results.
  1122. * To avoid this, create a new document,
  1123. * <code>getEditorKit().createDefaultDocument()</code>, and replace the
  1124. * existing <code>Document</code> with the new one. You are then assured the
  1125. * previous <code>Document</code> won't have any lingering state.
  1126. * <ol>
  1127. * <li>
  1128. * Leaving the existing model in place means that the old view will be
  1129. * torn down, and a new view created, where replacing the document would
  1130. * avoid the tear down of the old view.
  1131. * <li>
  1132. * Some formats (such as HTML) can install things into the document that
  1133. * can influence future contents. HTML can have style information embedded
  1134. * that would influence the next content installed unexpectedly.
  1135. * </ol>
  1136. * <p>
  1137. * An alternative way to load this component with a string would be to
  1138. * create a StringReader and call the read method. In this case the model
  1139. * would be replaced after it was initialized with the contents of the
  1140. * string.
  1141. * <p>
  1142. * This method is thread safe, although most Swing methods
  1143. * are not. Please see
  1144. * <A HREF="http://java.sun.com/products/jfc/swingdoc-archive/threads.html">Threads
  1145. * and Swing</A> for more information.
  1146. *
  1147. * @param t the new text to be set
  1148. * @see #getText
  1149. * @beaninfo
  1150. * description: the text of this component
  1151. */
  1152. public void setText(String t) {
  1153. try {
  1154. Document doc = getDocument();
  1155. doc.remove(0, doc.getLength());
  1156. Reader r = new StringReader(t);
  1157. EditorKit kit = getEditorKit();
  1158. kit.read(r, doc, 0);
  1159. } catch (IOException ioe) {
  1160. getToolkit().beep();
  1161. } catch (BadLocationException ble) {
  1162. getToolkit().beep();
  1163. }
  1164. }
  1165. /**
  1166. * Returns the text contained in this <code>TextComponent</code>
  1167. * in terms of the
  1168. * content type of this editor. If an exception is thrown while
  1169. * attempting to retrieve the text, <code>null</code> will be returned.
  1170. * This is implemented to call <code>JTextComponent.write</code> with
  1171. * a <code>StringWriter</code>.
  1172. *
  1173. * @return the text
  1174. * @see #setText
  1175. */
  1176. public String getText() {
  1177. String txt;
  1178. try {
  1179. StringWriter buf = new StringWriter();
  1180. write(buf);
  1181. txt = buf.toString();
  1182. } catch (IOException ioe) {
  1183. txt = null;
  1184. }
  1185. return txt;
  1186. }
  1187. // --- Scrollable ----------------------------------------
  1188. /**
  1189. * Returns true if a viewport should always force the width of this
  1190. * <code>Scrollable</code> to match the width of the viewport.
  1191. *
  1192. * @return true if a viewport should force the Scrollables width to
  1193. * match its own, false otherwise
  1194. */
  1195. public boolean getScrollableTracksViewportWidth() {
  1196. if (getParent() instanceof JViewport) {
  1197. JViewport port = (JViewport)getParent();
  1198. TextUI ui = getUI();
  1199. int w = port.getWidth();
  1200. Dimension min = ui.getMinimumSize(this);
  1201. Dimension max = ui.getMaximumSize(this);
  1202. if ((w >= min.width) && (w <= max.width)) {
  1203. return true;
  1204. }
  1205. }
  1206. return false;
  1207. }
  1208. /**
  1209. * Returns true if a viewport should always force the height of this
  1210. * <code>Scrollable</code> to match the height of the viewport.
  1211. *
  1212. * @return true if a viewport should force the
  1213. * <code>Scrollable</code>'s height to match its own,
  1214. * false otherwise
  1215. */
  1216. public boolean getScrollableTracksViewportHeight() {
  1217. if (getParent() instanceof JViewport) {
  1218. JViewport port = (JViewport)getParent();
  1219. TextUI ui = getUI();
  1220. int h = port.getHeight();
  1221. Dimension min = ui.getMinimumSize(this);
  1222. if (h >= min.height) {
  1223. Dimension max = ui.getMaximumSize(this);
  1224. if (h <= max.height) {
  1225. return true;
  1226. }
  1227. }
  1228. }
  1229. return false;
  1230. }
  1231. // --- Serialization ------------------------------------
  1232. /**
  1233. * See <code>readObject</code> and <code>writeObject</code> in
  1234. * <code>JComponent</code> for more
  1235. * information about serialization in Swing.
  1236. */
  1237. private void writeObject(ObjectOutputStream s) throws IOException {
  1238. s.defaultWriteObject();
  1239. if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  1240. ui.installUI(this);
  1241. }
  1242. }
  1243. // --- variables ---------------------------------------
  1244. /**
  1245. * Current content binding of the editor.
  1246. */
  1247. private EditorKit kit;
  1248. private Hashtable pageProperties;
  1249. /**
  1250. * Table of registered type handlers for this editor.
  1251. */
  1252. private Hashtable typeHandlers;
  1253. /**
  1254. * Indicates whether we are managing focus.
  1255. * @see #processComponentKeyEvent
  1256. * @see #isManagingFocus
  1257. */
  1258. private boolean managingFocus = true;
  1259. /*
  1260. * Private AppContext keys for this class's static variables.
  1261. */
  1262. private static final Object kitRegistryKey =
  1263. new StringBuffer("JEditorPane.kitRegistry");
  1264. private static final Object kitTypeRegistryKey =
  1265. new StringBuffer("JEditorPane.kitTypeRegistry");
  1266. private static final Object kitLoaderRegistryKey =
  1267. new StringBuffer("JEditorPane.kitLoaderRegistry");
  1268. /**
  1269. * @see #getUIClassID
  1270. * @see #readObject
  1271. */
  1272. private static final String uiClassID = "EditorPaneUI";
  1273. /**
  1274. * Returns a string representation of this <code>JEditorPane</code>.
  1275. * This method
  1276. * is intended to be used only for debugging purposes, and the
  1277. * content and format of the returned string may vary between
  1278. * implementations. The returned string may be empty but may not
  1279. * be <code>null</code>.
  1280. *
  1281. * @return a string representation of this <code>JEditorPane</code>
  1282. */
  1283. protected String paramString() {
  1284. String kitString = (kit != null ?
  1285. kit.toString() : "");
  1286. String typeHandlersString = (typeHandlers != null ?
  1287. typeHandlers.toString() : "");
  1288. return super.paramString() +
  1289. ",kit=" + kitString +
  1290. ",typeHandlers=" + typeHandlersString;
  1291. }
  1292. /////////////////
  1293. // Accessibility support
  1294. ////////////////
  1295. /**
  1296. * Gets the AccessibleContext associated with this JEditorPane.
  1297. * For editor panes, the AccessibleContext takes the form of an
  1298. * AccessibleJEditorPane.
  1299. * A new AccessibleJEditorPane instance is created if necessary.
  1300. *
  1301. * @return an AccessibleJEditorPane that serves as the
  1302. * AccessibleContext of this JEditorPane
  1303. */
  1304. public AccessibleContext getAccessibleContext() {
  1305. if (accessibleContext == null) {
  1306. if (JEditorPane.this.getEditorKit() instanceof HTMLEditorKit) {
  1307. accessibleContext = new AccessibleJEditorPaneHTML();
  1308. } else {
  1309. accessibleContext = new AccessibleJEditorPane();
  1310. }
  1311. }
  1312. return accessibleContext;
  1313. }
  1314. /**
  1315. * This class implements accessibility support for the
  1316. * <code>JEditorPane</code> class. It provides an implementation of the
  1317. * Java Accessibility API appropriate to editor pane user-interface
  1318. * elements.
  1319. * <p>
  1320. * <strong>Warning:</strong>
  1321. * Serialized objects of this class will not be compatible with
  1322. * future Swing releases. The current serialization support is appropriate
  1323. * for short term storage or RMI between applications running the same
  1324. * version of Swing. A future release of Swing will provide support for
  1325. * long term persistence.
  1326. */
  1327. protected class AccessibleJEditorPane extends AccessibleJTextComponent {
  1328. /**
  1329. * Gets the accessibleDescription property of this object. If this
  1330. * property isn't set, return the content type of this JEditorPane
  1331. * instead (e.g. "plain/text", "html/text", etc.
  1332. *
  1333. * @return the localized description of the object; null if
  1334. * this object does not have a description
  1335. *
  1336. * @see #setAccessibleName
  1337. */
  1338. public String getAccessibleDescription() {
  1339. if (accessibleDescription != null) {
  1340. return accessibleDescription;
  1341. } else {
  1342. return JEditorPane.this.getContentType();
  1343. }
  1344. }
  1345. /**
  1346. * Gets the state set of this object.
  1347. *
  1348. * @return an instance of AccessibleStateSet describing the states
  1349. * of the object
  1350. * @see AccessibleStateSet
  1351. */
  1352. public AccessibleStateSet getAccessibleStateSet() {
  1353. AccessibleStateSet states = super.getAccessibleStateSet();
  1354. states.add(AccessibleState.MULTI_LINE);
  1355. return states;
  1356. }
  1357. }
  1358. /**
  1359. * This class provides support for <code>AccessibleHypertext</code>,
  1360. * and is used in instances where the <code>EditorKit</code>
  1361. * installed in this <code>JEditorPane</code> is an instance of
  1362. * <code>HTMLEditorKit</code>.
  1363. * <p>
  1364. * <strong>Warning:</strong>
  1365. * Serialized objects of this class will not be compatible with
  1366. * future Swing releases. The current serialization support is appropriate
  1367. * for short term storage or RMI between applications running the same
  1368. * version of Swing. A future release of Swing will provide support for
  1369. * baseline for serialized form of Swing objects.
  1370. */
  1371. protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane {
  1372. public AccessibleText getAccessibleText() {
  1373. return new JEditorPaneAccessibleHypertextSupport();
  1374. }
  1375. }
  1376. /**
  1377. * What's returned by
  1378. * <code>AccessibleJEditorPaneHTML.getAccessibleText</code>.
  1379. *
  1380. * Provides support for <code>AccessibleHypertext</code> in case
  1381. * there is an HTML document being displayed in this
  1382. * <code>JEditorPane</code>.
  1383. *
  1384. */
  1385. protected class JEditorPaneAccessibleHypertextSupport
  1386. extends AccessibleJEditorPane implements AccessibleHypertext {
  1387. public class HTMLLink extends AccessibleHyperlink {
  1388. Element element;
  1389. public HTMLLink(Element e) {
  1390. element = e;
  1391. }
  1392. /**
  1393. * Since the document a link is associated with may have
  1394. * changed, this method returns whether this Link is valid
  1395. * anymore (with respect to the document it references).
  1396. *
  1397. * @return a flag indicating whether this link is still valid with
  1398. * respect to the AccessibleHypertext it belongs to
  1399. */
  1400. public boolean isValid() {
  1401. return JEditorPaneAccessibleHypertextSupport.this.linksValid;
  1402. }
  1403. /**
  1404. * Returns the number of accessible actions available in this Link
  1405. * If there are more than one, the first one is NOT considered the
  1406. * "default" action of this LINK object (e.g. in an HTML imagemap).
  1407. * In general, links will have only one AccessibleAction in them.
  1408. *
  1409. * @return the zero-based number of Actions in this object
  1410. */
  1411. public int getAccessibleActionCount() {
  1412. return 1;
  1413. }
  1414. /**
  1415. * Perform the specified Action on the object
  1416. *
  1417. * @param i zero-based index of actions
  1418. * @return true if the the action was performed; else false.
  1419. * @see #getAccessibleActionCount
  1420. */
  1421. public boolean doAccessibleAction(int i) {
  1422. if (i == 0 && isValid() == true) {
  1423. URL u = (URL) getAccessibleActionObject(i);
  1424. if (u != null) {
  1425. HyperlinkEvent linkEvent =
  1426. new HyperlinkEvent(JEditorPane.this, HyperlinkEvent.EventType.ACTIVATED, u);
  1427. JEditorPane.this.fireHyperlinkUpdate(linkEvent);
  1428. return true;
  1429. }
  1430. }
  1431. return false; // link invalid or i != 0
  1432. }
  1433. /**
  1434. * Return a String description of this particular
  1435. * link action. The string returned is the text
  1436. * within the document associated with the element
  1437. * which contains this link.
  1438. *
  1439. * @param i zero-based index of the actions
  1440. * @return a String description of the action
  1441. * @see #getAccessibleActionCount
  1442. */
  1443. public String getAccessibleActionDescription(int i) {
  1444. if (i == 0 && isValid() == true) {
  1445. Document d = JEditorPane.this.getDocument();
  1446. if (d != null) {
  1447. try {
  1448. return d.getText(getStartIndex(),
  1449. getEndIndex() - getStartIndex());
  1450. } catch (BadLocationException exception) {
  1451. return null;
  1452. }
  1453. }
  1454. }
  1455. return null;
  1456. }
  1457. /**
  1458. * Returns a URL object that represents the link.
  1459. *
  1460. * @param i zero-based index of the actions
  1461. * @return an URL representing the HTML link itself
  1462. * @see #getAccessibleActionCount
  1463. */
  1464. public Object getAccessibleActionObject(int i) {
  1465. if (i == 0 && isValid() == true) {
  1466. AttributeSet as = element.getAttributes();
  1467. AttributeSet anchor =
  1468. (AttributeSet) as.getAttribute(HTML.Tag.A);
  1469. String href = (anchor != null) ?
  1470. (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
  1471. if (href != null) {
  1472. URL u;
  1473. try {
  1474. u = new URL(JEditorPane.this.getPage(), href);
  1475. } catch (MalformedURLException m) {
  1476. u = null;
  1477. }
  1478. return u;
  1479. }
  1480. }
  1481. return null; // link invalid or i != 0
  1482. }
  1483. /**
  1484. * Return an object that represents the link anchor,
  1485. * as appropriate for that link. E.g. from HTML:
  1486. * <a href="http://www.sun.com/access">Accessibility</a>
  1487. * this method would return a String containing the text:
  1488. * 'Accessibility'.
  1489. *
  1490. * Similarly, from this HTML:
  1491. * <a HREF="#top"><img src="top-hat.gif" alt="top hat"></a>
  1492. * this might return the object ImageIcon("top-hat.gif", "top hat");
  1493. *
  1494. * @param i zero-based index of the actions
  1495. * @return an Object representing the hypertext anchor
  1496. * @see #getAccessibleActionCount
  1497. */
  1498. public Object getAccessibleActionAnchor(int i) {
  1499. return getAccessibleActionDescription(i);
  1500. }
  1501. /**
  1502. * Get the index with the hypertext document at which this
  1503. * link begins
  1504. *
  1505. * @return index of start of link
  1506. */
  1507. public int getStartIndex() {
  1508. return element.getStartOffset();
  1509. }
  1510. /**
  1511. * Get the index with the hypertext document at which this
  1512. * link ends
  1513. *
  1514. * @return index of end of link
  1515. */
  1516. public int getEndIndex() {
  1517. return element.getEndOffset();
  1518. }
  1519. }
  1520. private class LinkVector extends Vector {
  1521. public int baseElementIndex(Element e) {
  1522. HTMLLink l;
  1523. for (int i = 0; i < elementCount; i++) {
  1524. l = (HTMLLink) elementAt(i);
  1525. if (l.element == e) {
  1526. return i;
  1527. }
  1528. }
  1529. return -1;
  1530. }
  1531. }
  1532. LinkVector hyperlinks;
  1533. boolean linksValid = false;
  1534. /**
  1535. * Build the private table mapping links to locations in the text
  1536. */
  1537. private void buildLinkTable() {
  1538. hyperlinks.removeAllElements();
  1539. Document d = JEditorPane.this.getDocument();
  1540. if (d != null) {
  1541. ElementIterator ei = new ElementIterator(d);
  1542. Element e;
  1543. AttributeSet as;
  1544. AttributeSet anchor;
  1545. String href;
  1546. while ((e = ei.next()) != null) {
  1547. if (e.isLeaf()) {
  1548. as = e.getAttributes();
  1549. anchor = (AttributeSet) as.getAttribute(HTML.Tag.A);
  1550. href = (anchor != null) ?
  1551. (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
  1552. if (href != null) {
  1553. hyperlinks.addElement(new HTMLLink(e));
  1554. }
  1555. }
  1556. }
  1557. }
  1558. linksValid = true;
  1559. }
  1560. /**
  1561. * Make one of these puppies
  1562. */
  1563. public JEditorPaneAccessibleHypertextSupport() {
  1564. hyperlinks = new LinkVector();
  1565. Document d = JEditorPane.this.getDocument();
  1566. if (d != null) {
  1567. d.addDocumentListener(new DocumentListener() {
  1568. public void changedUpdate(DocumentEvent theEvent) {
  1569. linksValid = false;
  1570. }
  1571. public void insertUpdate(DocumentEvent theEvent) {
  1572. linksValid = false;
  1573. }
  1574. public void removeUpdate(DocumentEvent theEvent) {
  1575. linksValid = false;
  1576. }
  1577. });
  1578. }
  1579. }
  1580. /**
  1581. * Returns the number of links within this hypertext doc.
  1582. *
  1583. * @return number of links in this hypertext doc.
  1584. */
  1585. public int getLinkCount() {
  1586. if (linksValid == false) {
  1587. buildLinkTable();
  1588. }
  1589. return hyperlinks.size();
  1590. }
  1591. /**
  1592. * Returns the index into an array of hyperlinks that
  1593. * is associated with this character index, or -1 if there
  1594. * is no hyperlink associated with this index.
  1595. *
  1596. * @param character index within the text
  1597. * @return index into the set of hyperlinks for this hypertext doc.
  1598. */
  1599. public int getLinkIndex(int charIndex) {
  1600. if (linksValid == false) {
  1601. buildLinkTable();
  1602. }
  1603. Element e = null;
  1604. Document doc = JEditorPane.this.getDocument();
  1605. if (doc != null) {
  1606. for (e = doc.getDefaultRootElement(); ! e.isLeaf(); ) {
  1607. int index = e.getElementIndex(charIndex);
  1608. e = e.getElement(index);
  1609. }
  1610. }
  1611. // don't need to verify that it's an HREF element; if
  1612. // not, then it won't be in the hyperlinks Vector, and
  1613. // so indexOf will return -1 in any case
  1614. return hyperlinks.baseElementIndex(e);
  1615. }
  1616. /**
  1617. * Returns the index into an array of hyperlinks that
  1618. * index. If there is no hyperlink at this index, it returns
  1619. * null.
  1620. *
  1621. * @param index into the set of hyperlinks for this hypertext doc.
  1622. * @return string representation of the hyperlink
  1623. */
  1624. public AccessibleHyperlink getLink(int linkIndex) {
  1625. if (linksValid == false) {
  1626. buildLinkTable();
  1627. }
  1628. if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
  1629. return (AccessibleHyperlink) hyperlinks.elementAt(linkIndex);
  1630. } else {
  1631. return null;
  1632. }
  1633. }
  1634. /**
  1635. * Returns the contiguous text within the document that
  1636. * is associated with this hyperlink.
  1637. *
  1638. * @param index into the set of hyperlinks for this hypertext doc.
  1639. * @return the contiguous text sharing the link at this index
  1640. */
  1641. public String getLinkText(int linkIndex) {
  1642. if (linksValid == false) {
  1643. buildLinkTable();
  1644. }
  1645. Element e = (Element) hyperlinks.elementAt(linkIndex);
  1646. if (e != null) {
  1647. Document d = JEditorPane.this.getDocument();
  1648. if (d != null) {
  1649. try {
  1650. return d.getText(e.getStartOffset(),
  1651. e.getEndOffset() - e.getStartOffset());
  1652. } catch (BadLocationException exception) {
  1653. return null;
  1654. }
  1655. }
  1656. }
  1657. return null;
  1658. }
  1659. }
  1660. static class PlainEditorKit extends DefaultEditorKit implements ViewFactory {
  1661. /**
  1662. * Creates a copy of the editor kit. This
  1663. * allows an implementation to serve as a prototype
  1664. * for others, so that they can be quickly created.
  1665. *
  1666. * @return the copy
  1667. */
  1668. public Object clone() {
  1669. return new PlainEditorKit();
  1670. }
  1671. /**
  1672. * Fetches a factory that is suitable for producing
  1673. * views of any models that are produced by this
  1674. * kit. The default is to have the UI produce the
  1675. * factory, so this method has no implementation.
  1676. *
  1677. * @return the view factory
  1678. */
  1679. public ViewFactory getViewFactory() {
  1680. return this;
  1681. }
  1682. /**
  1683. * Creates a view from the given structural element of a
  1684. * document.
  1685. *
  1686. * @param elem the piece of the document to build a view of
  1687. * @return the view
  1688. * @see View
  1689. */
  1690. public View create(Element elem) {
  1691. Document doc = elem.getDocument();
  1692. Object i18nFlag
  1693. = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
  1694. if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
  1695. // build a view that support bidi
  1696. return createI18N(elem);
  1697. } else {
  1698. return new WrappedPlainView(elem);
  1699. }
  1700. }
  1701. View createI18N(Element elem) {
  1702. String kind = elem.getName();
  1703. if (kind != null) {
  1704. if (kind.equals(AbstractDocument.ContentElementName)) {
  1705. return new PlainParagraph(elem);
  1706. } else if (kind.equals(AbstractDocument.ParagraphElementName)){
  1707. return new BoxView(elem, View.Y_AXIS);
  1708. }
  1709. }
  1710. return null;
  1711. }
  1712. /**
  1713. * Paragraph for representing plain-text lines that support
  1714. * bidirectional text.
  1715. */
  1716. static class PlainParagraph extends javax.swing.text.ParagraphView {
  1717. PlainParagraph(Element elem) {
  1718. super(elem);
  1719. layoutPool = new LogicalView(elem);
  1720. layoutPool.setParent(this);
  1721. }
  1722. protected void setPropertiesFromAttributes() {
  1723. Component c = getContainer();
  1724. if ((c != null)
  1725. && (! c.getComponentOrientation().isLeftToRight()))
  1726. {
  1727. setJustification(StyleConstants.ALIGN_RIGHT);
  1728. } else {
  1729. setJustification(StyleConstants.ALIGN_LEFT);
  1730. }
  1731. }
  1732. /**
  1733. * Fetch the constraining span to flow against for
  1734. * the given child index.
  1735. */
  1736. public int getFlowSpan(int index) {
  1737. Component c = getContainer();
  1738. if (c instanceof JTextArea) {
  1739. JTextArea area = (JTextArea) c;
  1740. if (! area.getLineWrap()) {
  1741. // no limit if unwrapped
  1742. return Integer.MAX_VALUE;
  1743. }
  1744. }
  1745. return super.getFlowSpan(index);
  1746. }
  1747. protected SizeRequirements calculateMinorAxisRequirements(int axis,
  1748. SizeRequirements r)
  1749. {
  1750. SizeRequirements req
  1751. = super.calculateMinorAxisRequirements(axis, r);
  1752. Component c = getContainer();
  1753. if (c instanceof JTextArea) {
  1754. JTextArea area = (JTextArea) c;
  1755. if (! area.getLineWrap()) {
  1756. // min is pref if unwrapped
  1757. req.minimum = req.preferred;
  1758. }
  1759. }
  1760. return req;
  1761. }
  1762. /**
  1763. * This class can be used to represent a logical view for
  1764. * a flow. It keeps the children updated to reflect the state
  1765. * of the model, gives the logical child views access to the
  1766. * view hierarchy, and calculates a preferred span. It doesn't
  1767. * do any rendering, layout, or model/view translation.
  1768. */
  1769. static class LogicalView extends CompositeView {
  1770. LogicalView(Element elem) {
  1771. super(elem);
  1772. }
  1773. protected int getViewIndexAtPosition(int pos) {
  1774. Element elem = getElement();
  1775. if (elem.getElementCount() > 0) {
  1776. return elem.getElementIndex(pos);
  1777. }
  1778. return 0;
  1779. }
  1780. protected boolean
  1781. updateChildren(DocumentEvent.ElementChange ec,
  1782. DocumentEvent e, ViewFactory f)
  1783. {
  1784. return false;
  1785. }
  1786. protected void loadChildren(ViewFactory f) {
  1787. Element elem = getElement();
  1788. if (elem.getElementCount() > 0) {
  1789. super.loadChildren(f);
  1790. } else {
  1791. View v = new GlyphView(elem);
  1792. append(v);
  1793. }
  1794. }
  1795. public float getPreferredSpan(int axis) {
  1796. if( getViewCount() != 1 )
  1797. throw new Error("One child view is assumed.");
  1798. View v = getView(0);
  1799. //((GlyphView)v).setGlyphPainter(null);
  1800. return v.getPreferredSpan(axis);
  1801. }
  1802. /**
  1803. * Forward the DocumentEvent to the given child view. This
  1804. * is implemented to reparent the child to the logical view
  1805. * (the children may have been parented by a row in the flow
  1806. * if they fit without breaking) and then execute the
  1807. * superclass behavior.
  1808. *
  1809. * @param v the child view to forward the event to.
  1810. * @param e the change information from the associated document
  1811. * @param a the current allocation of the view
  1812. * @param f the factory to use to rebuild if the view has
  1813. * children
  1814. * @see #forwardUpdate
  1815. * @since 1.3
  1816. */
  1817. protected void forwardUpdateToView(View v, DocumentEvent e,
  1818. Shape a, ViewFactory f) {
  1819. v.setParent(this);
  1820. super.forwardUpdateToView(v, e, a, f);
  1821. }
  1822. // The following methods don't do anything useful, they
  1823. // simply keep the class from being abstract.
  1824. public void paint(Graphics g, Shape allocation) {
  1825. }
  1826. protected boolean isBefore(int x, int y, Rectangle alloc) {
  1827. return false;
  1828. }
  1829. protected boolean isAfter(int x, int y, Rectangle alloc) {
  1830. return false;
  1831. }
  1832. protected View getViewAtPoint(int x, int y, Rectangle alloc) {
  1833. return null;
  1834. }
  1835. protected void childAllocation(int index, Rectangle a) {
  1836. }
  1837. }
  1838. }
  1839. }
  1840. /* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
  1841. * sensibly:
  1842. * From a String like: 'timeout=15, max=5'
  1843. * create an array of Strings:
  1844. * { {"timeout", "15"},
  1845. * {"max", "5"}
  1846. * }
  1847. * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
  1848. * create one like (no quotes in literal):
  1849. * { {"basic", null},
  1850. * {"realm", "FuzzFace"}
  1851. * {"foo", "Biz Bar Baz"}
  1852. * }
  1853. * keys are converted to lower case, vals are left as is....
  1854. *
  1855. * author Dave Brown
  1856. */
  1857. static class HeaderParser {
  1858. /* table of key/val pairs - maxes out at 10!!!!*/
  1859. String raw;
  1860. String[][] tab;
  1861. public HeaderParser(String raw) {
  1862. this.raw = raw;
  1863. tab = new String[10][2];
  1864. parse();
  1865. }
  1866. private void parse() {
  1867. if (raw != null) {
  1868. raw = raw.trim();
  1869. char[] ca = raw.toCharArray();
  1870. int beg = 0, end = 0, i = 0;
  1871. boolean inKey = true;
  1872. boolean inQuote = false;
  1873. int len = ca.length;
  1874. while (end < len) {
  1875. char c = ca[end];
  1876. if (c == '=') { // end of a key
  1877. tab[i][0] = new String(ca, beg, end-beg).toLowerCase();
  1878. inKey = false;
  1879. end++;
  1880. beg = end;
  1881. } else if (c == '\"') {
  1882. if (inQuote) {
  1883. tab[i++][1]= new String(ca, beg, end-beg);
  1884. inQuote=false;
  1885. do {
  1886. end++;
  1887. } while (end < len && (ca[end] == ' ' || ca[end] == ','));
  1888. inKey=true;
  1889. beg=end;
  1890. } else {
  1891. inQuote=true;
  1892. end++;
  1893. beg=end;
  1894. }
  1895. } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
  1896. if (inQuote) {
  1897. end++;
  1898. continue;
  1899. } else if (inKey) {
  1900. tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase();
  1901. } else {
  1902. tab[i++][1] = (new String(ca, beg, end-beg));
  1903. }
  1904. while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
  1905. end++;
  1906. }
  1907. inKey = true;
  1908. beg = end;
  1909. } else {
  1910. end++;
  1911. }
  1912. }
  1913. // get last key/val, if any
  1914. if (--end > beg) {
  1915. if (!inKey) {
  1916. if (ca[end] == '\"') {
  1917. tab[i++][1] = (new String(ca, beg, end-beg));
  1918. } else {
  1919. tab[i++][1] = (new String(ca, beg, end-beg+1));
  1920. }
  1921. } else {
  1922. tab[i][0] = (new String(ca, beg, end-beg+1)).toLowerCase();
  1923. }
  1924. } else if (end == beg) {
  1925. if (!inKey) {
  1926. if (ca[end] == '\"') {
  1927. tab[i++][1] = String.valueOf(ca[end-1]);
  1928. } else {
  1929. tab[i++][1] = String.valueOf(ca[end]);
  1930. }
  1931. } else {
  1932. tab[i][0] = String.valueOf(ca[end]).toLowerCase();
  1933. }
  1934. }
  1935. }
  1936. }
  1937. public String findKey(int i) {
  1938. if (i < 0 || i > 10)
  1939. return null;
  1940. return tab[i][0];
  1941. }
  1942. public String findValue(int i) {
  1943. if (i < 0 || i > 10)
  1944. return null;
  1945. return tab[i][1];
  1946. }
  1947. public String findValue(String key) {
  1948. return findValue(key, null);
  1949. }
  1950. public String findValue(String k, String Default) {
  1951. if (k == null)
  1952. return Default;
  1953. k.toLowerCase();
  1954. for (int i = 0; i < 10; ++i) {
  1955. if (tab[i][0] == null) {
  1956. return Default;
  1957. } else if (k.equals(tab[i][0])) {
  1958. return tab[i][1];
  1959. }
  1960. }
  1961. return Default;
  1962. }
  1963. public int findInt(String k, int Default) {
  1964. try {
  1965. return Integer.parseInt(findValue(k, String.valueOf(Default)));
  1966. } catch (Throwable t) {
  1967. return Default;
  1968. }
  1969. }
  1970. }
  1971. }