0001 /*
0002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025 package javax.swing;
0026
0027 import java.awt.*;
0028 import java.awt.event.*;
0029 import java.lang.reflect.*;
0030 import java.net.*;
0031 import java.util.*;
0032 import java.io.*;
0033 import java.util.*;
0034
0035 import javax.swing.plaf.*;
0036 import javax.swing.text.*;
0037 import javax.swing.event.*;
0038 import javax.swing.text.html.*;
0039 import javax.accessibility.*;
0040
0041 /**
0042 * A text component to edit various kinds of content.
0043 * You can find how-to information and examples of using editor panes in
0044 * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/text.html">Using Text Components</a>,
0045 * a section in <em>The Java Tutorial.</em>
0046 *
0047 * <p>
0048 * This component uses implementations of the
0049 * <code>EditorKit</code> to accomplish its behavior. It effectively
0050 * morphs into the proper kind of text editor for the kind
0051 * of content it is given. The content type that editor is bound
0052 * to at any given time is determined by the <code>EditorKit</code> currently
0053 * installed. If the content is set to a new URL, its type is used
0054 * to determine the <code>EditorKit</code> that should be used to
0055 * load the content.
0056 * <p>
0057 * By default, the following types of content are known:
0058 * <dl>
0059 * <dt><b>text/plain</b>
0060 * <dd>Plain text, which is the default the type given isn't
0061 * recognized. The kit used in this case is an extension of
0062 * <code>DefaultEditorKit</code> that produces a wrapped plain text view.
0063 * <dt><b>text/html</b>
0064 * <dd>HTML text. The kit used in this case is the class
0065 * <code>javax.swing.text.html.HTMLEditorKit</code>
0066 * which provides HTML 3.2 support.
0067 * <dt><b>text/rtf</b>
0068 * <dd>RTF text. The kit used in this case is the class
0069 * <code>javax.swing.text.rtf.RTFEditorKit</code>
0070 * which provides a limited support of the Rich Text Format.
0071 * </dl>
0072 * <p>
0073 * There are several ways to load content into this component.
0074 * <ol>
0075 * <li>
0076 * The {@link #setText setText} method can be used to initialize
0077 * the component from a string. In this case the current
0078 * <code>EditorKit</code> will be used, and the content type will be
0079 * expected to be of this type.
0080 * <li>
0081 * The {@link #read read} method can be used to initialize the
0082 * component from a <code>Reader</code>. Note that if the content type is HTML,
0083 * relative references (e.g. for things like images) can't be resolved
0084 * unless the <base> tag is used or the <em>Base</em> property
0085 * on <code>HTMLDocument</code> is set.
0086 * In this case the current <code>EditorKit</code> will be used,
0087 * and the content type will be expected to be of this type.
0088 * <li>
0089 * The {@link #setPage setPage} method can be used to initialize
0090 * the component from a URL. In this case, the content type will be
0091 * determined from the URL, and the registered <code>EditorKit</code>
0092 * for that content type will be set.
0093 * </ol>
0094 * <p>
0095 * Some kinds of content may provide hyperlink support by generating
0096 * hyperlink events. The HTML <code>EditorKit</code> will generate
0097 * hyperlink events if the <code>JEditorPane</code> is <em>not editable</em>
0098 * (<code>JEditorPane.setEditable(false);</code> has been called).
0099 * If HTML frames are embedded in the document, the typical response would be
0100 * to change a portion of the current document. The following code
0101 * fragment is a possible hyperlink listener implementation, that treats
0102 * HTML frame events specially, and simply displays any other activated
0103 * hyperlinks.
0104 * <code><pre>
0105
0106 class Hyperactive implements HyperlinkListener {
0107
0108 public void hyperlinkUpdate(HyperlinkEvent e) {
0109 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
0110 JEditorPane pane = (JEditorPane) e.getSource();
0111 if (e instanceof HTMLFrameHyperlinkEvent) {
0112 HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
0113 HTMLDocument doc = (HTMLDocument)pane.getDocument();
0114 doc.processHTMLFrameHyperlinkEvent(evt);
0115 } else {
0116 try {
0117 pane.setPage(e.getURL());
0118 } catch (Throwable t) {
0119 t.printStackTrace();
0120 }
0121 }
0122 }
0123 }
0124 }
0125
0126 * </pre></code>
0127 * <p>
0128 * For information on customizing how <b>text/html</b> is rendered please see
0129 * {@link #W3C_LENGTH_UNITS} and {@link #HONOR_DISPLAY_PROPERTIES}
0130 * <p>
0131 * Culturally dependent information in some documents is handled through
0132 * a mechanism called character encoding. Character encoding is an
0133 * unambiguous mapping of the members of a character set (letters, ideographs,
0134 * digits, symbols, or control functions) to specific numeric code values. It
0135 * represents the way the file is stored. Example character encodings are
0136 * ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is
0137 * passed to an user agent (<code>JEditorPane</code>) it is converted to
0138 * the document character set (ISO-10646 aka Unicode).
0139 * <p>
0140 * There are multiple ways to get a character set mapping to happen
0141 * with <code>JEditorPane</code>.
0142 * <ol>
0143 * <li>
0144 * One way is to specify the character set as a parameter of the MIME
0145 * type. This will be established by a call to the
0146 * <a href="#setContentType">setContentType</a> method. If the content
0147 * is loaded by the <a href="#setPage">setPage</a> method the content
0148 * type will have been set according to the specification of the URL.
0149 * It the file is loaded directly, the content type would be expected to
0150 * have been set prior to loading.
0151 * <li>
0152 * Another way the character set can be specified is in the document itself.
0153 * This requires reading the document prior to determining the character set
0154 * that is desired. To handle this, it is expected that the
0155 * <code>EditorKit</code>.read operation throw a
0156 * <code>ChangedCharSetException</code> which will
0157 * be caught. The read is then restarted with a new Reader that uses
0158 * the character set specified in the <code>ChangedCharSetException</code>
0159 * (which is an <code>IOException</code>).
0160 * </ol>
0161 * <p>
0162 * <dl>
0163 * <dt><b><font size=+1>Newlines</font></b>
0164 * <dd>
0165 * For a discussion on how newlines are handled, see
0166 * <a href="text/DefaultEditorKit.html">DefaultEditorKit</a>.
0167 * </dl>
0168 *
0169 * <p>
0170 * <strong>Warning:</strong> Swing is not thread safe. For more
0171 * information see <a
0172 * href="package-summary.html#threading">Swing's Threading
0173 * Policy</a>.
0174 * <p>
0175 * <strong>Warning:</strong>
0176 * Serialized objects of this class will not be compatible with
0177 * future Swing releases. The current serialization support is
0178 * appropriate for short term storage or RMI between applications running
0179 * the same version of Swing. As of 1.4, support for long term storage
0180 * of all JavaBeans<sup><font size="-2">TM</font></sup>
0181 * has been added to the <code>java.beans</code> package.
0182 * Please see {@link java.beans.XMLEncoder}.
0183 *
0184 * @beaninfo
0185 * attribute: isContainer false
0186 * description: A text component to edit various types of content.
0187 *
0188 * @author Timothy Prinzing
0189 * @version 1.145 05/05/07
0190 */
0191 public class JEditorPane extends JTextComponent {
0192
0193 /**
0194 * Creates a new <code>JEditorPane</code>.
0195 * The document model is set to <code>null</code>.
0196 */
0197 public JEditorPane() {
0198 super ();
0199 setFocusCycleRoot(true);
0200 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
0201 public Component getComponentAfter(
0202 Container focusCycleRoot, Component aComponent) {
0203 if (focusCycleRoot != JEditorPane.this
0204 || (!isEditable() && getComponentCount() > 0)) {
0205 return super .getComponentAfter(focusCycleRoot,
0206 aComponent);
0207 } else {
0208 Container rootAncestor = getFocusCycleRootAncestor();
0209 return (rootAncestor != null) ? rootAncestor
0210 .getFocusTraversalPolicy()
0211 .getComponentAfter(rootAncestor,
0212 JEditorPane.this ) : null;
0213 }
0214 }
0215
0216 public Component getComponentBefore(
0217 Container focusCycleRoot, Component aComponent) {
0218 if (focusCycleRoot != JEditorPane.this
0219 || (!isEditable() && getComponentCount() > 0)) {
0220 return super .getComponentBefore(focusCycleRoot,
0221 aComponent);
0222 } else {
0223 Container rootAncestor = getFocusCycleRootAncestor();
0224 return (rootAncestor != null) ? rootAncestor
0225 .getFocusTraversalPolicy()
0226 .getComponentBefore(rootAncestor,
0227 JEditorPane.this ) : null;
0228 }
0229 }
0230
0231 public Component getDefaultComponent(
0232 Container focusCycleRoot) {
0233 return (focusCycleRoot != JEditorPane.this || (!isEditable() && getComponentCount() > 0)) ? super
0234 .getDefaultComponent(focusCycleRoot)
0235 : null;
0236 }
0237
0238 protected boolean accept(Component aComponent) {
0239 return (aComponent != JEditorPane.this ) ? super
0240 .accept(aComponent) : false;
0241 }
0242 });
0243 LookAndFeel.installProperty(this , "focusTraversalKeysForward",
0244 JComponent.getManagingFocusForwardTraversalKeys());
0245 LookAndFeel.installProperty(this , "focusTraversalKeysBackward",
0246 JComponent.getManagingFocusBackwardTraversalKeys());
0247 }
0248
0249 /**
0250 * Creates a <code>JEditorPane</code> based on a specified URL for input.
0251 *
0252 * @param initialPage the URL
0253 * @exception IOException if the URL is <code>null</code>
0254 * or cannot be accessed
0255 */
0256 public JEditorPane(URL initialPage) throws IOException {
0257 this ();
0258 setPage(initialPage);
0259 }
0260
0261 /**
0262 * Creates a <code>JEditorPane</code> based on a string containing
0263 * a URL specification.
0264 *
0265 * @param url the URL
0266 * @exception IOException if the URL is <code>null</code> or
0267 * cannot be accessed
0268 */
0269 public JEditorPane(String url) throws IOException {
0270 this ();
0271 setPage(url);
0272 }
0273
0274 /**
0275 * Creates a <code>JEditorPane</code> that has been initialized
0276 * to the given text. This is a convenience constructor that calls the
0277 * <code>setContentType</code> and <code>setText</code> methods.
0278 *
0279 * @param type mime type of the given text
0280 * @param text the text to initialize with; may be <code>null</code>
0281 * @exception NullPointerException if the <code>type</code> parameter
0282 * is <code>null</code>
0283 */
0284 public JEditorPane(String type, String text) {
0285 this ();
0286 setContentType(type);
0287 setText(text);
0288 }
0289
0290 /**
0291 * Adds a hyperlink listener for notification of any changes, for example
0292 * when a link is selected and entered.
0293 *
0294 * @param listener the listener
0295 */
0296 public synchronized void addHyperlinkListener(
0297 HyperlinkListener listener) {
0298 listenerList.add(HyperlinkListener.class, listener);
0299 }
0300
0301 /**
0302 * Removes a hyperlink listener.
0303 *
0304 * @param listener the listener
0305 */
0306 public synchronized void removeHyperlinkListener(
0307 HyperlinkListener listener) {
0308 listenerList.remove(HyperlinkListener.class, listener);
0309 }
0310
0311 /**
0312 * Returns an array of all the <code>HyperLinkListener</code>s added
0313 * to this JEditorPane with addHyperlinkListener().
0314 *
0315 * @return all of the <code>HyperLinkListener</code>s added or an empty
0316 * array if no listeners have been added
0317 * @since 1.4
0318 */
0319 public synchronized HyperlinkListener[] getHyperlinkListeners() {
0320 return (HyperlinkListener[]) listenerList
0321 .getListeners(HyperlinkListener.class);
0322 }
0323
0324 /**
0325 * Notifies all listeners that have registered interest for
0326 * notification on this event type. This is normally called
0327 * by the currently installed <code>EditorKit</code> if a content type
0328 * that supports hyperlinks is currently active and there
0329 * was activity with a link. The listener list is processed
0330 * last to first.
0331 *
0332 * @param e the event
0333 * @see EventListenerList
0334 */
0335 public void fireHyperlinkUpdate(HyperlinkEvent e) {
0336 // Guaranteed to return a non-null array
0337 Object[] listeners = listenerList.getListenerList();
0338 // Process the listeners last to first, notifying
0339 // those that are interested in this event
0340 for (int i = listeners.length - 2; i >= 0; i -= 2) {
0341 if (listeners[i] == HyperlinkListener.class) {
0342 ((HyperlinkListener) listeners[i + 1])
0343 .hyperlinkUpdate(e);
0344 }
0345 }
0346 }
0347
0348 /**
0349 * Sets the current URL being displayed. The content type of the
0350 * pane is set, and if the editor kit for the pane is
0351 * non-<code>null</code>, then
0352 * a new default document is created and the URL is read into it.
0353 * If the URL contains and reference location, the location will
0354 * be scrolled to by calling the <code>scrollToReference</code>
0355 * method. If the desired URL is the one currently being displayed,
0356 * the document will not be reloaded. To force a document
0357 * reload it is necessary to clear the stream description property
0358 * of the document. The following code shows how this can be done:
0359 *
0360 * <pre>
0361 * Document doc = jEditorPane.getDocument();
0362 * doc.putProperty(Document.StreamDescriptionProperty, null);
0363 * </pre>
0364 *
0365 * If the desired URL is not the one currently being
0366 * displayed, the <code>getStream</code> method is called to
0367 * give subclasses control over the stream provided.
0368 * <p>
0369 * This may load either synchronously or asynchronously
0370 * depending upon the document returned by the <code>EditorKit</code>.
0371 * If the <code>Document</code> is of type
0372 * <code>AbstractDocument</code> and has a value returned by
0373 * <code>AbstractDocument.getAsynchronousLoadPriority</code>
0374 * that is greater than or equal to zero, the page will be
0375 * loaded on a separate thread using that priority.
0376 * <p>
0377 * If the document is loaded synchronously, it will be
0378 * filled in with the stream prior to being installed into
0379 * the editor with a call to <code>setDocument</code>, which
0380 * is bound and will fire a property change event. If an
0381 * <code>IOException</code> is thrown the partially loaded
0382 * document will
0383 * be discarded and neither the document or page property
0384 * change events will be fired. If the document is
0385 * successfully loaded and installed, a view will be
0386 * built for it by the UI which will then be scrolled if
0387 * necessary, and then the page property change event
0388 * will be fired.
0389 * <p>
0390 * If the document is loaded asynchronously, the document
0391 * will be installed into the editor immediately using a
0392 * call to <code>setDocument</code> which will fire a
0393 * document property change event, then a thread will be
0394 * created which will begin doing the actual loading.
0395 * In this case, the page property change event will not be
0396 * fired by the call to this method directly, but rather will be
0397 * fired when the thread doing the loading has finished.
0398 * It will also be fired on the event-dispatch thread.
0399 * Since the calling thread can not throw an <code>IOException</code>
0400 * in the event of failure on the other thread, the page
0401 * property change event will be fired when the other
0402 * thread is done whether the load was successful or not.
0403 *
0404 * @param page the URL of the page
0405 * @exception IOException for a <code>null</code> or invalid
0406 * page specification, or exception from the stream being read
0407 * @see #getPage
0408 * @beaninfo
0409 * description: the URL used to set content
0410 * bound: true
0411 * expert: true
0412 */
0413 public void setPage(URL page) throws IOException {
0414 if (page == null) {
0415 throw new IOException("invalid url");
0416 }
0417 URL loaded = getPage();
0418
0419 // reset scrollbar
0420 if (!page.equals(loaded) && page.getRef() == null) {
0421 scrollRectToVisible(new Rectangle(0, 0, 1, 1));
0422 }
0423 boolean reloaded = false;
0424 Object postData = getPostData();
0425 if ((loaded == null) || !loaded.sameFile(page)
0426 || (postData != null)) {
0427 // different url or POST method, load the new content
0428
0429 int p = getAsynchronousLoadPriority(getDocument());
0430 if ((postData == null) || (p < 0)) {
0431 // Either we do not have POST data, or should submit the data
0432 // synchronously.
0433 InputStream in = getStream(page);
0434 if (kit != null) {
0435 Document doc = initializeModel(kit, page);
0436
0437 // At this point, one could either load up the model with no
0438 // view notifications slowing it down (i.e. best synchronous
0439 // behavior) or set the model and start to feed it on a separate
0440 // thread (best asynchronous behavior).
0441 synchronized (this ) {
0442 if (loading != null) {
0443 // we are loading asynchronously, so we need to cancel
0444 // the old stream.
0445 loading.cancel();
0446 loading = null;
0447 }
0448 }
0449 p = getAsynchronousLoadPriority(doc);
0450 if (p >= 0) {
0451 // load asynchronously
0452 setDocument(doc);
0453 synchronized (this ) {
0454 loading = new PageStream(in);
0455 Thread pl = new PageLoader(doc, loading, p,
0456 loaded, page);
0457 pl.start();
0458 }
0459 return;
0460 }
0461 read(in, doc);
0462 setDocument(doc);
0463 reloaded = true;
0464 }
0465 } else {
0466 // We have POST data and should send it asynchronously.
0467 // Send (and subsequentally read) data in separate thread.
0468 // Model initialization is deferred to that thread, too.
0469 Thread pl = new PageLoader(null, null, p, loaded, page);
0470 pl.start();
0471 return;
0472 }
0473 }
0474 final String reference = page.getRef();
0475 if (reference != null) {
0476 if (!reloaded) {
0477 scrollToReference(reference);
0478 } else {
0479 // Have to scroll after painted.
0480 SwingUtilities.invokeLater(new Runnable() {
0481 public void run() {
0482 scrollToReference(reference);
0483 }
0484 });
0485 }
0486 getDocument().putProperty(
0487 Document.StreamDescriptionProperty, page);
0488 }
0489 firePropertyChange("page", loaded, page);
0490 }
0491
0492 /**
0493 * Create model and initialize document properties from page properties.
0494 */
0495 private Document initializeModel(EditorKit kit, URL page) {
0496 Document doc = kit.createDefaultDocument();
0497 if (pageProperties != null) {
0498 // transfer properties discovered in stream to the
0499 // document property collection.
0500 for (Enumeration e = pageProperties.keys(); e
0501 .hasMoreElements();) {
0502 Object key = e.nextElement();
0503 doc.putProperty(key, pageProperties.get(key));
0504 }
0505 pageProperties.clear();
0506 }
0507 if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
0508 doc.putProperty(Document.StreamDescriptionProperty, page);
0509 }
0510 return doc;
0511 }
0512
0513 /**
0514 * Return load priority for the document or -1 if priority not supported.
0515 */
0516 private int getAsynchronousLoadPriority(Document doc) {
0517 return (doc instanceof AbstractDocument ? ((AbstractDocument) doc)
0518 .getAsynchronousLoadPriority()
0519 : -1);
0520 }
0521
0522 /**
0523 * This method initializes from a stream. If the kit is
0524 * set to be of type <code>HTMLEditorKit</code>, and the
0525 * <code>desc</code> parameter is an <code>HTMLDocument</code>,
0526 * then it invokes the <code>HTMLEditorKit</code> to initiate
0527 * the read. Otherwise it calls the superclass
0528 * method which loads the model as plain text.
0529 *
0530 * @param in the stream from which to read
0531 * @param desc an object describing the stream
0532 * @exception IOException as thrown by the stream being
0533 * used to initialize
0534 * @see JTextComponent#read
0535 * @see #setDocument
0536 */
0537 public void read(InputStream in, Object desc) throws IOException {
0538
0539 if (desc instanceof HTMLDocument
0540 && kit instanceof HTMLEditorKit) {
0541 HTMLDocument hdoc = (HTMLDocument) desc;
0542 setDocument(hdoc);
0543 read(in, hdoc);
0544 } else {
0545 String charset = (String) getClientProperty("charset");
0546 Reader r = (charset != null) ? new InputStreamReader(in,
0547 charset) : new InputStreamReader(in);
0548 super .read(r, desc);
0549 }
0550 }
0551
0552 /**
0553 * This method invokes the <code>EditorKit</code> to initiate a
0554 * read. In the case where a <code>ChangedCharSetException</code>
0555 * is thrown this exception will contain the new CharSet.
0556 * Therefore the <code>read</code> operation
0557 * is then restarted after building a new Reader with the new charset.
0558 *
0559 * @param in the inputstream to use
0560 * @param doc the document to load
0561 *
0562 */
0563 void read(InputStream in, Document doc) throws IOException {
0564 if (!Boolean.TRUE.equals(doc
0565 .getProperty("IgnoreCharsetDirective"))) {
0566 final int READ_LIMIT = 1024 * 10;
0567 in = new BufferedInputStream(in, READ_LIMIT);
0568 in.mark(READ_LIMIT);
0569 }
0570 try {
0571 String charset = (String) getClientProperty("charset");
0572 Reader r = (charset != null) ? new InputStreamReader(in,
0573 charset) : new InputStreamReader(in);
0574 kit.read(r, doc, 0);
0575 } catch (BadLocationException e) {
0576 throw new IOException(e.getMessage());
0577 } catch (ChangedCharSetException changedCharSetException) {
0578 String charSetSpec = changedCharSetException
0579 .getCharSetSpec();
0580 if (changedCharSetException.keyEqualsCharSet()) {
0581 putClientProperty("charset", charSetSpec);
0582 } else {
0583 setCharsetFromContentTypeParameters(charSetSpec);
0584 }
0585 try {
0586 in.reset();
0587 } catch (IOException exception) {
0588 //mark was invalidated
0589 in.close();
0590 URL url = (URL) doc
0591 .getProperty(Document.StreamDescriptionProperty);
0592 if (url != null) {
0593 URLConnection conn = url.openConnection();
0594 in = conn.getInputStream();
0595 } else {
0596 //there is nothing we can do to recover stream
0597 throw changedCharSetException;
0598 }
0599 }
0600 try {
0601 doc.remove(0, doc.getLength());
0602 } catch (BadLocationException e) {
0603 }
0604 doc.putProperty("IgnoreCharsetDirective", Boolean
0605 .valueOf(true));
0606 read(in, doc);
0607 }
0608 }
0609
0610 /**
0611 * Thread to load a stream into the text document model.
0612 */
0613 class PageLoader extends Thread {
0614
0615 /**
0616 * Construct an asynchronous page loader.
0617 */
0618 PageLoader(Document doc, InputStream in, int priority, URL old,
0619 URL page) {
0620 setPriority(priority);
0621 this .in = in;
0622 this .old = old;
0623 this .page = page;
0624 this .doc = doc;
0625 }
0626
0627 boolean pageLoaded = false;
0628
0629 /**
0630 * Try to load the document, then scroll the view
0631 * to the reference (if specified). When done, fire
0632 * a page property change event.
0633 */
0634 public void run() {
0635 try {
0636 if (in == null) {
0637 in = getStream(page);
0638 if (kit == null) {
0639 // We received document of unknown content type.
0640 UIManager.getLookAndFeel()
0641 .provideErrorFeedback(JEditorPane.this );
0642 return;
0643 }
0644 // Access to <code>loading</code> should be synchronized.
0645 synchronized (JEditorPane.this ) {
0646 in = loading = new PageStream(in);
0647 }
0648 }
0649 if (doc == null) {
0650 try {
0651 SwingUtilities.invokeAndWait(new Runnable() {
0652 public void run() {
0653 doc = initializeModel(kit, page);
0654 setDocument(doc);
0655 }
0656 });
0657 } catch (InvocationTargetException ex) {
0658 UIManager.getLookAndFeel()
0659 .provideErrorFeedback(JEditorPane.this );
0660 return;
0661 } catch (InterruptedException ex) {
0662 UIManager.getLookAndFeel()
0663 .provideErrorFeedback(JEditorPane.this );
0664 return;
0665 }
0666 }
0667
0668 read(in, doc);
0669 URL page = (URL) doc
0670 .getProperty(Document.StreamDescriptionProperty);
0671 String reference = page.getRef();
0672 if (reference != null) {
0673 // scroll the page if necessary, but do it on the
0674 // event thread... that is the only guarantee that
0675 // modelToView can be safely called.
0676 Runnable callScrollToReference = new Runnable() {
0677 public void run() {
0678 URL u = (URL) getDocument().getProperty(
0679 Document.StreamDescriptionProperty);
0680 String ref = u.getRef();
0681 scrollToReference(ref);
0682 }
0683 };
0684 SwingUtilities.invokeLater(callScrollToReference);
0685 }
0686 pageLoaded = true;
0687 } catch (IOException ioe) {
0688 UIManager.getLookAndFeel().provideErrorFeedback(
0689 JEditorPane.this );
0690 } finally {
0691 synchronized (JEditorPane.this ) {
0692 loading = null;
0693 }
0694 SwingUtilities.invokeLater(new Runnable() {
0695 public void run() {
0696 if (pageLoaded) {
0697 firePropertyChange("page", old, page);
0698 }
0699 }
0700 });
0701 }
0702 }
0703
0704 /**
0705 * The stream to load the document with
0706 */
0707 InputStream in;
0708
0709 /**
0710 * URL of the old page that was replaced (for the property change event)
0711 */
0712 URL old;
0713
0714 /**
0715 * URL of the page being loaded (for the property change event)
0716 */
0717 URL page;
0718
0719 /**
0720 * The Document instance to load into. This is cached in case a
0721 * new Document is created between the time the thread this is created
0722 * and run.
0723 */
0724 Document doc;
0725 }
0726
0727 static class PageStream extends FilterInputStream {
0728
0729 boolean canceled;
0730
0731 public PageStream(InputStream i) {
0732 super (i);
0733 canceled = false;
0734 }
0735
0736 /**
0737 * Cancel the loading of the stream by throwing
0738 * an IOException on the next request.
0739 */
0740 public synchronized void cancel() {
0741 canceled = true;
0742 }
0743
0744 protected synchronized void checkCanceled() throws IOException {
0745 if (canceled) {
0746 throw new IOException("page canceled");
0747 }
0748 }
0749
0750 public int read() throws IOException {
0751 checkCanceled();
0752 return super .read();
0753 }
0754
0755 public long skip(long n) throws IOException {
0756 checkCanceled();
0757 return super .skip(n);
0758 }
0759
0760 public int available() throws IOException {
0761 checkCanceled();
0762 return super .available();
0763 }
0764
0765 public void reset() throws IOException {
0766 checkCanceled();
0767 super .reset();
0768 }
0769
0770 }
0771
0772 /**
0773 * Fetches a stream for the given URL, which is about to
0774 * be loaded by the <code>setPage</code> method. By
0775 * default, this simply opens the URL and returns the
0776 * stream. This can be reimplemented to do useful things
0777 * like fetch the stream from a cache, monitor the progress
0778 * of the stream, etc.
0779 * <p>
0780 * This method is expected to have the the side effect of
0781 * establishing the content type, and therefore setting the
0782 * appropriate <code>EditorKit</code> to use for loading the stream.
0783 * <p>
0784 * If this the stream was an http connection, redirects
0785 * will be followed and the resulting URL will be set as
0786 * the <code>Document.StreamDescriptionProperty</code> so that relative
0787 * URL's can be properly resolved.
0788 *
0789 * @param page the URL of the page
0790 */
0791 protected InputStream getStream(URL page) throws IOException {
0792 final URLConnection conn = page.openConnection();
0793 if (conn instanceof HttpURLConnection) {
0794 HttpURLConnection hconn = (HttpURLConnection) conn;
0795 hconn.setInstanceFollowRedirects(false);
0796 Object postData = getPostData();
0797 if (postData != null) {
0798 handlePostData(hconn, postData);
0799 }
0800 int response = hconn.getResponseCode();
0801 boolean redirect = (response >= 300 && response <= 399);
0802
0803 /*
0804 * In the case of a redirect, we want to actually change the URL
0805 * that was input to the new, redirected URL
0806 */
0807 if (redirect) {
0808 String loc = conn.getHeaderField("Location");
0809 if (loc.startsWith("http", 0)) {
0810 page = new URL(loc);
0811 } else {
0812 page = new URL(page, loc);
0813 }
0814 return getStream(page);
0815 }
0816 }
0817
0818 // Connection properties handler should be forced to run on EDT,
0819 // as it instantiates the EditorKit.
0820 if (SwingUtilities.isEventDispatchThread()) {
0821 handleConnectionProperties(conn);
0822 } else {
0823 try {
0824 SwingUtilities.invokeAndWait(new Runnable() {
0825 public void run() {
0826 handleConnectionProperties(conn);
0827 }
0828 });
0829 } catch (InterruptedException e) {
0830 throw new RuntimeException(e);
0831 } catch (InvocationTargetException e) {
0832 throw new RuntimeException(e);
0833 }
0834 }
0835 return conn.getInputStream();
0836 }
0837
0838 /**
0839 * Handle URL connection properties (most notably, content type).
0840 */
0841 private void handleConnectionProperties(URLConnection conn) {
0842 if (pageProperties == null) {
0843 pageProperties = new Hashtable();
0844 }
0845 String type = conn.getContentType();
0846 if (type != null) {
0847 setContentType(type);
0848 pageProperties.put("content-type", type);
0849 }
0850 pageProperties.put(Document.StreamDescriptionProperty, conn
0851 .getURL());
0852 String enc = conn.getContentEncoding();
0853 if (enc != null) {
0854 pageProperties.put("content-encoding", enc);
0855 }
0856 }
0857
0858 private Object getPostData() {
0859 return getDocument().getProperty(PostDataProperty);
0860 }
0861
0862 private void handlePostData(HttpURLConnection conn, Object postData)
0863 throws IOException {
0864 conn.setDoOutput(true);
0865 DataOutputStream os = null;
0866 try {
0867 conn.setRequestProperty("Content-Type",
0868 "application/x-www-form-urlencoded");
0869 os = new DataOutputStream(conn.getOutputStream());
0870 os.writeBytes((String) postData);
0871 } finally {
0872 if (os != null) {
0873 os.close();
0874 }
0875 }
0876 }
0877
0878 /**
0879 * Scrolls the view to the given reference location
0880 * (that is, the value returned by the <code>UL.getRef</code>
0881 * method for the URL being displayed). By default, this
0882 * method only knows how to locate a reference in an
0883 * HTMLDocument. The implementation calls the
0884 * <code>scrollRectToVisible</code> method to
0885 * accomplish the actual scrolling. If scrolling to a
0886 * reference location is needed for document types other
0887 * than HTML, this method should be reimplemented.
0888 * This method will have no effect if the component
0889 * is not visible.
0890 *
0891 * @param reference the named location to scroll to
0892 */
0893 public void scrollToReference(String reference) {
0894 Document d = getDocument();
0895 if (d instanceof HTMLDocument) {
0896 HTMLDocument doc = (HTMLDocument) d;
0897 HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A);
0898 for (; iter.isValid(); iter.next()) {
0899 AttributeSet a = iter.getAttributes();
0900 String nm = (String) a
0901 .getAttribute(HTML.Attribute.NAME);
0902 if ((nm != null) && nm.equals(reference)) {
0903 // found a matching reference in the document.
0904 try {
0905 Rectangle r = modelToView(iter.getStartOffset());
0906 if (r != null) {
0907 // the view is visible, scroll it to the
0908 // center of the current visible area.
0909 Rectangle vis = getVisibleRect();
0910 //r.y -= (vis.height / 2);
0911 r.height = vis.height;
0912 scrollRectToVisible(r);
0913 }
0914 } catch (BadLocationException ble) {
0915 UIManager.getLookAndFeel()
0916 .provideErrorFeedback(JEditorPane.this );
0917 }
0918 }
0919 }
0920 }
0921 }
0922
0923 /**
0924 * Gets the current URL being displayed. If a URL was
0925 * not specified in the creation of the document, this
0926 * will return <code>null</code>, and relative URL's will not be
0927 * resolved.
0928 *
0929 * @return the URL, or <code>null</code> if none
0930 */
0931 public URL getPage() {
0932 return (URL) getDocument().getProperty(
0933 Document.StreamDescriptionProperty);
0934 }
0935
0936 /**
0937 * Sets the current URL being displayed.
0938 *
0939 * @param url the URL for display
0940 * @exception IOException for a <code>null</code> or invalid URL
0941 * specification
0942 */
0943 public void setPage(String url) throws IOException {
0944 if (url == null) {
0945 throw new IOException("invalid url");
0946 }
0947 URL page = new URL(url);
0948 setPage(page);
0949 }
0950
0951 /**
0952 * Gets the class ID for the UI.
0953 *
0954 * @return the string "EditorPaneUI"
0955 * @see JComponent#getUIClassID
0956 * @see UIDefaults#getUI
0957 */
0958 public String getUIClassID() {
0959 return uiClassID;
0960 }
0961
0962 /**
0963 * Creates the default editor kit (<code>PlainEditorKit</code>) for when
0964 * the component is first created.
0965 *
0966 * @return the editor kit
0967 */
0968 protected EditorKit createDefaultEditorKit() {
0969 return new PlainEditorKit();
0970 }
0971
0972 /**
0973 * Fetches the currently installed kit for handling content.
0974 * <code>createDefaultEditorKit</code> is called to set up a default
0975 * if necessary.
0976 *
0977 * @return the editor kit
0978 */
0979 public EditorKit getEditorKit() {
0980 if (kit == null) {
0981 kit = createDefaultEditorKit();
0982 isUserSetEditorKit = false;
0983 }
0984 return kit;
0985 }
0986
0987 /**
0988 * Gets the type of content that this editor
0989 * is currently set to deal with. This is
0990 * defined to be the type associated with the
0991 * currently installed <code>EditorKit</code>.
0992 *
0993 * @return the content type, <code>null</code> if no editor kit set
0994 */
0995 public final String getContentType() {
0996 return (kit != null) ? kit.getContentType() : null;
0997 }
0998
0999 /**
1000 * Sets the type of content that this editor
1001 * handles. This calls <code>getEditorKitForContentType</code>,
1002 * and then <code>setEditorKit</code> if an editor kit can
1003 * be successfully located. This is mostly convenience method
1004 * that can be used as an alternative to calling
1005 * <code>setEditorKit</code> directly.
1006 * <p>
1007 * If there is a charset definition specified as a parameter
1008 * of the content type specification, it will be used when
1009 * loading input streams using the associated <code>EditorKit</code>.
1010 * For example if the type is specified as
1011 * <code>text/html; charset=EUC-JP</code> the content
1012 * will be loaded using the <code>EditorKit</code> registered for
1013 * <code>text/html</code> and the Reader provided to
1014 * the <code>EditorKit</code> to load unicode into the document will
1015 * use the <code>EUC-JP</code> charset for translating
1016 * to unicode. If the type is not recognized, the content
1017 * will be loaded using the <code>EditorKit</code> registered
1018 * for plain text, <code>text/plain</code>.
1019 *
1020 * @param type the non-<code>null</code> mime type for the content editing
1021 * support
1022 * @see #getContentType
1023 * @beaninfo
1024 * description: the type of content
1025 * @throws NullPointerException if the <code>type</code> parameter
1026 * is <code>null</code>
1027 */
1028 public final void setContentType(String type) {
1029 // The type could have optional info is part of it,
1030 // for example some charset info. We need to strip that
1031 // of and save it.
1032 int parm = type.indexOf(";");
1033 if (parm > -1) {
1034 // Save the paramList.
1035 String paramList = type.substring(parm);
1036 // update the content type string.
1037 type = type.substring(0, parm).trim();
1038 if (type.toLowerCase().startsWith("text/")) {
1039 setCharsetFromContentTypeParameters(paramList);
1040 }
1041 }
1042 if ((kit == null) || (!type.equals(kit.getContentType()))
1043 || !isUserSetEditorKit) {
1044 EditorKit k = getEditorKitForContentType(type);
1045 if (k != null && k != kit) {
1046 setEditorKit(k);
1047 isUserSetEditorKit = false;
1048 }
1049 }
1050
1051 }
1052
1053 /**
1054 * This method gets the charset information specified as part
1055 * of the content type in the http header information.
1056 */
1057 private void setCharsetFromContentTypeParameters(String paramlist) {
1058 String charset = null;
1059 try {
1060 // paramlist is handed to us with a leading ';', strip it.
1061 int semi = paramlist.indexOf(';');
1062 if (semi > -1 && semi < paramlist.length() - 1) {
1063 paramlist = paramlist.substring(semi + 1);
1064 }
1065
1066 if (paramlist.length() > 0) {
1067 // parse the paramlist into attr-value pairs & get the
1068 // charset pair's value
1069 HeaderParser hdrParser = new HeaderParser(paramlist);
1070 charset = hdrParser.findValue("charset");
1071 if (charset != null) {
1072 putClientProperty("charset", charset);
1073 }
1074 }
1075 } catch (IndexOutOfBoundsException e) {
1076 // malformed parameter list, use charset we have
1077 } catch (NullPointerException e) {
1078 // malformed parameter list, use charset we have
1079 } catch (Exception e) {
1080 // malformed parameter list, use charset we have; but complain
1081 System.err
1082 .println("JEditorPane.getCharsetFromContentTypeParameters failed on: "
1083 + paramlist);
1084 e.printStackTrace();
1085 }
1086 }
1087
1088 /**
1089 * Sets the currently installed kit for handling
1090 * content. This is the bound property that
1091 * establishes the content type of the editor.
1092 * Any old kit is first deinstalled, then if kit is
1093 * non-<code>null</code>,
1094 * the new kit is installed, and a default document created for it.
1095 * A <code>PropertyChange</code> event ("editorKit") is always fired when
1096 * <code>setEditorKit</code> is called.
1097 * <p>
1098 * <em>NOTE: This has the side effect of changing the model,
1099 * because the <code>EditorKit</code> is the source of how a
1100 * particular type
1101 * of content is modeled. This method will cause <code>setDocument</code>
1102 * to be called on behalf of the caller to ensure integrity
1103 * of the internal state.</em>
1104 *
1105 * @param kit the desired editor behavior
1106 * @see #getEditorKit
1107 * @beaninfo
1108 * description: the currently installed kit for handling content
1109 * bound: true
1110 * expert: true
1111 */
1112 public void setEditorKit(EditorKit kit) {
1113 EditorKit old = this .kit;
1114 isUserSetEditorKit = true;
1115 if (old != null) {
1116 old.deinstall(this );
1117 }
1118 this .kit = kit;
1119 if (this .kit != null) {
1120 this .kit.install(this );
1121 setDocument(this .kit.createDefaultDocument());
1122 }
1123 firePropertyChange("editorKit", old, kit);
1124 }
1125
1126 /**
1127 * Fetches the editor kit to use for the given type
1128 * of content. This is called when a type is requested
1129 * that doesn't match the currently installed type.
1130 * If the component doesn't have an <code>EditorKit</code> registered
1131 * for the given type, it will try to create an
1132 * <code>EditorKit</code> from the default <code>EditorKit</code> registry.
1133 * If that fails, a <code>PlainEditorKit</code> is used on the
1134 * assumption that all text documents can be represented
1135 * as plain text.
1136 * <p>
1137 * This method can be reimplemented to use some
1138 * other kind of type registry. This can
1139 * be reimplemented to use the Java Activation
1140 * Framework, for example.
1141 *
1142 * @param type the non-<code>null</code> content type
1143 * @return the editor kit
1144 */
1145 public EditorKit getEditorKitForContentType(String type) {
1146 if (typeHandlers == null) {
1147 typeHandlers = new Hashtable(3);
1148 }
1149 EditorKit k = (EditorKit) typeHandlers.get(type);
1150 if (k == null) {
1151 k = createEditorKitForContentType(type);
1152 if (k != null) {
1153 setEditorKitForContentType(type, k);
1154 }
1155 }
1156 if (k == null) {
1157 k = createDefaultEditorKit();
1158 }
1159 return k;
1160 }
1161
1162 /**
1163 * Directly sets the editor kit to use for the given type. A
1164 * look-and-feel implementation might use this in conjunction
1165 * with <code>createEditorKitForContentType</code> to install handlers for
1166 * content types with a look-and-feel bias.
1167 *
1168 * @param type the non-<code>null</code> content type
1169 * @param k the editor kit to be set
1170 */
1171 public void setEditorKitForContentType(String type, EditorKit k) {
1172 if (typeHandlers == null) {
1173 typeHandlers = new Hashtable(3);
1174 }
1175 typeHandlers.put(type, k);
1176 }
1177
1178 /**
1179 * Replaces the currently selected content with new content
1180 * represented by the given string. If there is no selection
1181 * this amounts to an insert of the given text. If there
1182 * is no replacement text (i.e. the content string is empty
1183 * or <code>null</code>) this amounts to a removal of the
1184 * current selection. The replacement text will have the
1185 * attributes currently defined for input. If the component is not
1186 * editable, beep and return.
1187 * <p>
1188 * This method is thread safe, although most Swing methods
1189 * are not. Please see
1190 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
1191 * to Use Threads</A> for more information.
1192 *
1193 * @param content the content to replace the selection with. This
1194 * value can be <code>null</code>
1195 */
1196 public void replaceSelection(String content) {
1197 if (!isEditable()) {
1198 UIManager.getLookAndFeel().provideErrorFeedback(
1199 JEditorPane.this );
1200 return;
1201 }
1202 EditorKit kit = getEditorKit();
1203 if (kit instanceof StyledEditorKit) {
1204 try {
1205 Document doc = getDocument();
1206 Caret caret = getCaret();
1207 int p0 = Math.min(caret.getDot(), caret.getMark());
1208 int p1 = Math.max(caret.getDot(), caret.getMark());
1209 if (doc instanceof AbstractDocument) {
1210 ((AbstractDocument) doc).replace(p0, p1 - p0,
1211 content, ((StyledEditorKit) kit)
1212 .getInputAttributes());
1213 } else {
1214 if (p0 != p1) {
1215 doc.remove(p0, p1 - p0);
1216 }
1217 if (content != null && content.length() > 0) {
1218 doc.insertString(p0, content,
1219 ((StyledEditorKit) kit)
1220 .getInputAttributes());
1221 }
1222 }
1223 } catch (BadLocationException e) {
1224 UIManager.getLookAndFeel().provideErrorFeedback(
1225 JEditorPane.this );
1226 }
1227 } else {
1228 super .replaceSelection(content);
1229 }
1230 }
1231
1232 /**
1233 * Creates a handler for the given type from the default registry
1234 * of editor kits. The registry is created if necessary. If the
1235 * registered class has not yet been loaded, an attempt
1236 * is made to dynamically load the prototype of the kit for the
1237 * given type. If the type was registered with a <code>ClassLoader</code>,
1238 * that <code>ClassLoader</code> will be used to load the prototype.
1239 * If there was no registered <code>ClassLoader</code>,
1240 * <code>Class.forName</code> will be used to load the prototype.
1241 * <p>
1242 * Once a prototype <code>EditorKit</code> instance is successfully
1243 * located, it is cloned and the clone is returned.
1244 *
1245 * @param type the content type
1246 * @return the editor kit, or <code>null</code> if there is nothing
1247 * registered for the given type
1248 */
1249 public static EditorKit createEditorKitForContentType(String type) {
1250 EditorKit k = null;
1251 Hashtable kitRegistry = getKitRegisty();
1252 k = (EditorKit) kitRegistry.get(type);
1253 if (k == null) {
1254 // try to dynamically load the support
1255 String classname = (String) getKitTypeRegistry().get(type);
1256 ClassLoader loader = (ClassLoader) getKitLoaderRegistry()
1257 .get(type);
1258 try {
1259 Class c;
1260 if (loader != null) {
1261 c = loader.loadClass(classname);
1262 } else {
1263 // Will only happen if developer has invoked
1264 // registerEditorKitForContentType(type, class, null).
1265 c = Class.forName(classname, true, Thread
1266 .currentThread().getContextClassLoader());
1267 }
1268 k = (EditorKit) c.newInstance();
1269 kitRegistry.put(type, k);
1270 } catch (Throwable e) {
1271 k = null;
1272 }
1273 }
1274
1275 // create a copy of the prototype or null if there
1276 // is no prototype.
1277 if (k != null) {
1278 return (EditorKit) k.clone();
1279 }
1280 return null;
1281 }
1282
1283 /**
1284 * Establishes the default bindings of <code>type</code> to
1285 * <code>classname</code>.
1286 * The class will be dynamically loaded later when actually
1287 * needed, and can be safely changed before attempted uses
1288 * to avoid loading unwanted classes. The prototype
1289 * <code>EditorKit</code> will be loaded with <code>Class.forName</code>
1290 * when registered with this method.
1291 *
1292 * @param type the non-<code>null</code> content type
1293 * @param classname the class to load later
1294 */
1295 public static void registerEditorKitForContentType(String type,
1296 String classname) {
1297 registerEditorKitForContentType(type, classname, Thread
1298 .currentThread().getContextClassLoader());
1299 }
1300
1301 /**
1302 * Establishes the default bindings of <code>type</code> to
1303 * <code>classname</code>.
1304 * The class will be dynamically loaded later when actually
1305 * needed using the given <code>ClassLoader</code>,
1306 * and can be safely changed
1307 * before attempted uses to avoid loading unwanted classes.
1308 *
1309 * @param type the non-<code>null</code> content type
1310 * @param classname the class to load later
1311 * @param loader the <code>ClassLoader</code> to use to load the name
1312 */
1313 public static void registerEditorKitForContentType(String type,
1314 String classname, ClassLoader loader) {
1315 getKitTypeRegistry().put(type, classname);
1316 getKitLoaderRegistry().put(type, loader);
1317 getKitRegisty().remove(type);
1318 }
1319
1320 /**
1321 * Returns the currently registered <code>EditorKit</code>
1322 * class name for the type <code>type</code>.
1323 *
1324 * @param type the non-<code>null</code> content type
1325 *
1326 * @since 1.3
1327 */
1328 public static String getEditorKitClassNameForContentType(String type) {
1329 return (String) getKitTypeRegistry().get(type);
1330 }
1331
1332 private static Hashtable getKitTypeRegistry() {
1333 loadDefaultKitsIfNecessary();
1334 return (Hashtable) SwingUtilities
1335 .appContextGet(kitTypeRegistryKey);
1336 }
1337
1338 private static Hashtable getKitLoaderRegistry() {
1339 loadDefaultKitsIfNecessary();
1340 return (Hashtable) SwingUtilities
1341 .appContextGet(kitLoaderRegistryKey);
1342 }
1343
1344 private static Hashtable getKitRegisty() {
1345 Hashtable ht = (Hashtable) SwingUtilities
1346 .appContextGet(kitRegistryKey);
1347 if (ht == null) {
1348 ht = new Hashtable(3);
1349 SwingUtilities.appContextPut(kitRegistryKey, ht);
1350 }
1351 return ht;
1352 }
1353
1354 /**
1355 * This is invoked every time the registries are accessed. Loading
1356 * is done this way instead of via a static as the static is only
1357 * called once when running in plugin resulting in the entries only
1358 * appearing in the first applet.
1359 */
1360 private static void loadDefaultKitsIfNecessary() {
1361 if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
1362 synchronized (defaultEditorKitMap) {
1363 if (defaultEditorKitMap.size() == 0) {
1364 defaultEditorKitMap.put("text/plain",
1365 "javax.swing.JEditorPane$PlainEditorKit");
1366 defaultEditorKitMap.put("text/html",
1367 "javax.swing.text.html.HTMLEditorKit");
1368 defaultEditorKitMap.put("text/rtf",
1369 "javax.swing.text.rtf.RTFEditorKit");
1370 defaultEditorKitMap.put("application/rtf",
1371 "javax.swing.text.rtf.RTFEditorKit");
1372 }
1373 }
1374 Hashtable ht = new Hashtable();
1375 SwingUtilities.appContextPut(kitTypeRegistryKey, ht);
1376 ht = new Hashtable();
1377 SwingUtilities.appContextPut(kitLoaderRegistryKey, ht);
1378 for (String key : defaultEditorKitMap.keySet()) {
1379 registerEditorKitForContentType(key,
1380 defaultEditorKitMap.get(key));
1381 }
1382
1383 }
1384 }
1385
1386 // --- java.awt.Component methods --------------------------
1387
1388 /**
1389 * Returns the preferred size for the <code>JEditorPane</code>.
1390 * The preferred size for <code>JEditorPane</code> is slightly altered
1391 * from the preferred size of the superclass. If the size
1392 * of the viewport has become smaller than the minimum size
1393 * of the component, the scrollable definition for tracking
1394 * width or height will turn to false. The default viewport
1395 * layout will give the preferred size, and that is not desired
1396 * in the case where the scrollable is tracking. In that case
1397 * the <em>normal</em> preferred size is adjusted to the
1398 * minimum size. This allows things like HTML tables to
1399 * shrink down to their minimum size and then be laid out at
1400 * their minimum size, refusing to shrink any further.
1401 *
1402 * @return a <code>Dimension</code> containing the preferred size
1403 */
1404 public Dimension getPreferredSize() {
1405 Dimension d = super .getPreferredSize();
1406 if (getParent() instanceof JViewport) {
1407 JViewport port = (JViewport) getParent();
1408 TextUI ui = getUI();
1409 int prefWidth = d.width;
1410 int prefHeight = d.height;
1411 if (!getScrollableTracksViewportWidth()) {
1412 int w = port.getWidth();
1413 Dimension min = ui.getMinimumSize(this );
1414 if (w != 0 && w < min.width) {
1415 // Only adjust to min if we have a valid size
1416 prefWidth = min.width;
1417 }
1418 }
1419 if (!getScrollableTracksViewportHeight()) {
1420 int h = port.getHeight();
1421 Dimension min = ui.getMinimumSize(this );
1422 if (h != 0 && h < min.height) {
1423 // Only adjust to min if we have a valid size
1424 prefHeight = min.height;
1425 }
1426 }
1427 if (prefWidth != d.width || prefHeight != d.height) {
1428 d = new Dimension(prefWidth, prefHeight);
1429 }
1430 }
1431 return d;
1432 }
1433
1434 // --- JTextComponent methods -----------------------------
1435
1436 /**
1437 * Sets the text of this <code>TextComponent</code> to the specified
1438 * content,
1439 * which is expected to be in the format of the content type of
1440 * this editor. For example, if the type is set to <code>text/html</code>
1441 * the string should be specified in terms of HTML.
1442 * <p>
1443 * This is implemented to remove the contents of the current document,
1444 * and replace them by parsing the given string using the current
1445 * <code>EditorKit</code>. This gives the semantics of the
1446 * superclass by not changing
1447 * out the model, while supporting the content type currently set on
1448 * this component. The assumption is that the previous content is
1449 * relatively
1450 * small, and that the previous content doesn't have side effects.
1451 * Both of those assumptions can be violated and cause undesirable results.
1452 * To avoid this, create a new document,
1453 * <code>getEditorKit().createDefaultDocument()</code>, and replace the
1454 * existing <code>Document</code> with the new one. You are then assured the
1455 * previous <code>Document</code> won't have any lingering state.
1456 * <ol>
1457 * <li>
1458 * Leaving the existing model in place means that the old view will be
1459 * torn down, and a new view created, where replacing the document would
1460 * avoid the tear down of the old view.
1461 * <li>
1462 * Some formats (such as HTML) can install things into the document that
1463 * can influence future contents. HTML can have style information embedded
1464 * that would influence the next content installed unexpectedly.
1465 * </ol>
1466 * <p>
1467 * An alternative way to load this component with a string would be to
1468 * create a StringReader and call the read method. In this case the model
1469 * would be replaced after it was initialized with the contents of the
1470 * string.
1471 * <p>
1472 * This method is thread safe, although most Swing methods
1473 * are not. Please see
1474 * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How
1475 * to Use Threads</A> for more information.
1476 *
1477 * @param t the new text to be set; if <code>null</code> the old
1478 * text will be deleted
1479 * @see #getText
1480 * @beaninfo
1481 * description: the text of this component
1482 */
1483 public void setText(String t) {
1484 try {
1485 Document doc = getDocument();
1486 doc.remove(0, doc.getLength());
1487 if (t == null || t.equals("")) {
1488 return;
1489 }
1490 Reader r = new StringReader(t);
1491 EditorKit kit = getEditorKit();
1492 kit.read(r, doc, 0);
1493 } catch (IOException ioe) {
1494 UIManager.getLookAndFeel().provideErrorFeedback(
1495 JEditorPane.this );
1496 } catch (BadLocationException ble) {
1497 UIManager.getLookAndFeel().provideErrorFeedback(
1498 JEditorPane.this );
1499 }
1500 }
1501
1502 /**
1503 * Returns the text contained in this <code>TextComponent</code>
1504 * in terms of the
1505 * content type of this editor. If an exception is thrown while
1506 * attempting to retrieve the text, <code>null</code> will be returned.
1507 * This is implemented to call <code>JTextComponent.write</code> with
1508 * a <code>StringWriter</code>.
1509 *
1510 * @return the text
1511 * @see #setText
1512 */
1513 public String getText() {
1514 String txt;
1515 try {
1516 StringWriter buf = new StringWriter();
1517 write(buf);
1518 txt = buf.toString();
1519 } catch (IOException ioe) {
1520 txt = null;
1521 }
1522 return txt;
1523 }
1524
1525 // --- Scrollable ----------------------------------------
1526
1527 /**
1528 * Returns true if a viewport should always force the width of this
1529 * <code>Scrollable</code> to match the width of the viewport.
1530 *
1531 * @return true if a viewport should force the Scrollables width to
1532 * match its own, false otherwise
1533 */
1534 public boolean getScrollableTracksViewportWidth() {
1535 if (getParent() instanceof JViewport) {
1536 JViewport port = (JViewport) getParent();
1537 TextUI ui = getUI();
1538 int w = port.getWidth();
1539 Dimension min = ui.getMinimumSize(this );
1540 Dimension max = ui.getMaximumSize(this );
1541 if ((w >= min.width) && (w <= max.width)) {
1542 return true;
1543 }
1544 }
1545 return false;
1546 }
1547
1548 /**
1549 * Returns true if a viewport should always force the height of this
1550 * <code>Scrollable</code> to match the height of the viewport.
1551 *
1552 * @return true if a viewport should force the
1553 * <code>Scrollable</code>'s height to match its own,
1554 * false otherwise
1555 */
1556 public boolean getScrollableTracksViewportHeight() {
1557 if (getParent() instanceof JViewport) {
1558 JViewport port = (JViewport) getParent();
1559 TextUI ui = getUI();
1560 int h = port.getHeight();
1561 Dimension min = ui.getMinimumSize(this );
1562 if (h >= min.height) {
1563 Dimension max = ui.getMaximumSize(this );
1564 if (h <= max.height) {
1565 return true;
1566 }
1567 }
1568 }
1569 return false;
1570 }
1571
1572 // --- Serialization ------------------------------------
1573
1574 /**
1575 * See <code>readObject</code> and <code>writeObject</code> in
1576 * <code>JComponent</code> for more
1577 * information about serialization in Swing.
1578 */
1579 private void writeObject(ObjectOutputStream s) throws IOException {
1580 s.defaultWriteObject();
1581 if (getUIClassID().equals(uiClassID)) {
1582 byte count = JComponent.getWriteObjCounter(this );
1583 JComponent.setWriteObjCounter(this , --count);
1584 if (count == 0 && ui != null) {
1585 ui.installUI(this );
1586 }
1587 }
1588 }
1589
1590 // --- variables ---------------------------------------
1591
1592 /**
1593 * Stream currently loading asynchronously (potentially cancelable).
1594 * Access to this variable should be synchronized.
1595 */
1596 PageStream loading;
1597
1598 /**
1599 * Current content binding of the editor.
1600 */
1601 private EditorKit kit;
1602 private boolean isUserSetEditorKit;
1603
1604 private Hashtable pageProperties;
1605
1606 /** Should be kept in sync with javax.swing.text.html.FormView counterpart. */
1607 final static String PostDataProperty = "javax.swing.JEditorPane.postdata";
1608
1609 /**
1610 * Table of registered type handlers for this editor.
1611 */
1612 private Hashtable typeHandlers;
1613
1614 /*
1615 * Private AppContext keys for this class's static variables.
1616 */
1617 private static final Object kitRegistryKey = new StringBuffer(
1618 "JEditorPane.kitRegistry");
1619 private static final Object kitTypeRegistryKey = new StringBuffer(
1620 "JEditorPane.kitTypeRegistry");
1621 private static final Object kitLoaderRegistryKey = new StringBuffer(
1622 "JEditorPane.kitLoaderRegistry");
1623
1624 /**
1625 * @see #getUIClassID
1626 * @see #readObject
1627 */
1628 private static final String uiClassID = "EditorPaneUI";
1629
1630 /**
1631 * Key for a client property used to indicate whether
1632 * <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units">
1633 * w3c compliant</a> length units are used for html rendering.
1634 * <p>
1635 * By default this is not enabled; to enable
1636 * it set the client {@link #putClientProperty property} with this name
1637 * to <code>Boolean.TRUE</code>.
1638 *
1639 * @since 1.5
1640 */
1641 public static final String W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits";
1642
1643 /**
1644 * Key for a client property used to indicate whether
1645 * the default font and foreground color from the component are
1646 * used if a font or foreground color is not specified in the styled
1647 * text.
1648 * <p>
1649 * The default varies based on the look and feel;
1650 * to enable it set the client {@link #putClientProperty property} with
1651 * this name to <code>Boolean.TRUE</code>.
1652 *
1653 * @since 1.5
1654 */
1655 public static final String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
1656
1657 static final Map<String, String> defaultEditorKitMap = new HashMap<String, String>(
1658 0);
1659
1660 /**
1661 * Returns a string representation of this <code>JEditorPane</code>.
1662 * This method
1663 * is intended to be used only for debugging purposes, and the
1664 * content and format of the returned string may vary between
1665 * implementations. The returned string may be empty but may not
1666 * be <code>null</code>.
1667 *
1668 * @return a string representation of this <code>JEditorPane</code>
1669 */
1670 protected String paramString() {
1671 String kitString = (kit != null ? kit.toString() : "");
1672 String typeHandlersString = (typeHandlers != null ? typeHandlers
1673 .toString()
1674 : "");
1675
1676 return super .paramString() + ",kit=" + kitString
1677 + ",typeHandlers=" + typeHandlersString;
1678 }
1679
1680 /////////////////
1681 // Accessibility support
1682 ////////////////
1683
1684 /**
1685 * Gets the AccessibleContext associated with this JEditorPane.
1686 * For editor panes, the AccessibleContext takes the form of an
1687 * AccessibleJEditorPane.
1688 * A new AccessibleJEditorPane instance is created if necessary.
1689 *
1690 * @return an AccessibleJEditorPane that serves as the
1691 * AccessibleContext of this JEditorPane
1692 */
1693 public AccessibleContext getAccessibleContext() {
1694 if (getEditorKit() instanceof HTMLEditorKit) {
1695 if (accessibleContext == null
1696 || accessibleContext.getClass() != AccessibleJEditorPaneHTML.class) {
1697 accessibleContext = new AccessibleJEditorPaneHTML();
1698 }
1699 } else if (accessibleContext == null
1700 || accessibleContext.getClass() != AccessibleJEditorPane.class) {
1701 accessibleContext = new AccessibleJEditorPane();
1702 }
1703 return accessibleContext;
1704 }
1705
1706 /**
1707 * This class implements accessibility support for the
1708 * <code>JEditorPane</code> class. It provides an implementation of the
1709 * Java Accessibility API appropriate to editor pane user-interface
1710 * elements.
1711 * <p>
1712 * <strong>Warning:</strong>
1713 * Serialized objects of this class will not be compatible with
1714 * future Swing releases. The current serialization support is
1715 * appropriate for short term storage or RMI between applications running
1716 * the same version of Swing. As of 1.4, support for long term storage
1717 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1718 * has been added to the <code>java.beans</code> package.
1719 * Please see {@link java.beans.XMLEncoder}.
1720 */
1721 protected class AccessibleJEditorPane extends
1722 AccessibleJTextComponent {
1723
1724 /**
1725 * Gets the accessibleDescription property of this object. If this
1726 * property isn't set, returns the content type of this
1727 * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1728 *
1729 * @return the localized description of the object; <code>null</code>
1730 * if this object does not have a description
1731 *
1732 * @see #setAccessibleName
1733 */
1734 public String getAccessibleDescription() {
1735 String description = accessibleDescription;
1736
1737 // fallback to client property
1738 if (description == null) {
1739 description = (String) getClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY);
1740 }
1741 if (description == null) {
1742 description = JEditorPane.this .getContentType();
1743 }
1744 return description;
1745 }
1746
1747 /**
1748 * Gets the state set of this object.
1749 *
1750 * @return an instance of AccessibleStateSet describing the states
1751 * of the object
1752 * @see AccessibleStateSet
1753 */
1754 public AccessibleStateSet getAccessibleStateSet() {
1755 AccessibleStateSet states = super .getAccessibleStateSet();
1756 states.add(AccessibleState.MULTI_LINE);
1757 return states;
1758 }
1759 }
1760
1761 /**
1762 * This class provides support for <code>AccessibleHypertext</code>,
1763 * and is used in instances where the <code>EditorKit</code>
1764 * installed in this <code>JEditorPane</code> is an instance of
1765 * <code>HTMLEditorKit</code>.
1766 * <p>
1767 * <strong>Warning:</strong>
1768 * Serialized objects of this class will not be compatible with
1769 * future Swing releases. The current serialization support is
1770 * appropriate for short term storage or RMI between applications running
1771 * the same version of Swing. As of 1.4, support for long term storage
1772 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1773 * has been added to the <code>java.beans</code> package.
1774 * Please see {@link java.beans.XMLEncoder}.
1775 */
1776 protected class AccessibleJEditorPaneHTML extends
1777 AccessibleJEditorPane {
1778
1779 private AccessibleContext accessibleContext;
1780
1781 public AccessibleText getAccessibleText() {
1782 return new JEditorPaneAccessibleHypertextSupport();
1783 }
1784
1785 protected AccessibleJEditorPaneHTML() {
1786 HTMLEditorKit kit = (HTMLEditorKit) JEditorPane.this
1787 .getEditorKit();
1788 accessibleContext = kit.getAccessibleContext();
1789 }
1790
1791 /**
1792 * Returns the number of accessible children of the object.
1793 *
1794 * @return the number of accessible children of the object.
1795 */
1796 public int getAccessibleChildrenCount() {
1797 if (accessibleContext != null) {
1798 return accessibleContext.getAccessibleChildrenCount();
1799 } else {
1800 return 0;
1801 }
1802 }
1803
1804 /**
1805 * Returns the specified Accessible child of the object. The Accessible
1806 * children of an Accessible object are zero-based, so the first child
1807 * of an Accessible child is at index 0, the second child is at index 1,
1808 * and so on.
1809 *
1810 * @param i zero-based index of child
1811 * @return the Accessible child of the object
1812 * @see #getAccessibleChildrenCount
1813 */
1814 public Accessible getAccessibleChild(int i) {
1815 if (accessibleContext != null) {
1816 return accessibleContext.getAccessibleChild(i);
1817 } else {
1818 return null;
1819 }
1820 }
1821
1822 /**
1823 * Returns the Accessible child, if one exists, contained at the local
1824 * coordinate Point.
1825 *
1826 * @param p The point relative to the coordinate system of this object.
1827 * @return the Accessible, if it exists, at the specified location;
1828 * otherwise null
1829 */
1830 public Accessible getAccessibleAt(Point p) {
1831 if (accessibleContext != null && p != null) {
1832 try {
1833 AccessibleComponent acomp = accessibleContext
1834 .getAccessibleComponent();
1835 if (acomp != null) {
1836 return acomp.getAccessibleAt(p);
1837 } else {
1838 return null;
1839 }
1840 } catch (IllegalComponentStateException e) {
1841 return null;
1842 }
1843 } else {
1844 return null;
1845 }
1846 }
1847 }
1848
1849 /**
1850 * What's returned by
1851 * <code>AccessibleJEditorPaneHTML.getAccessibleText</code>.
1852 *
1853 * Provides support for <code>AccessibleHypertext</code> in case
1854 * there is an HTML document being displayed in this
1855 * <code>JEditorPane</code>.
1856 *
1857 */
1858 protected class JEditorPaneAccessibleHypertextSupport extends
1859 AccessibleJEditorPane implements AccessibleHypertext {
1860
1861 public class HTMLLink extends AccessibleHyperlink {
1862 Element element;
1863
1864 public HTMLLink(Element e) {
1865 element = e;
1866 }
1867
1868 /**
1869 * Since the document a link is associated with may have
1870 * changed, this method returns whether this Link is valid
1871 * anymore (with respect to the document it references).
1872 *
1873 * @return a flag indicating whether this link is still valid with
1874 * respect to the AccessibleHypertext it belongs to
1875 */
1876 public boolean isValid() {
1877 return JEditorPaneAccessibleHypertextSupport.this .linksValid;
1878 }
1879
1880 /**
1881 * Returns the number of accessible actions available in this Link
1882 * If there are more than one, the first one is NOT considered the
1883 * "default" action of this LINK object (e.g. in an HTML imagemap).
1884 * In general, links will have only one AccessibleAction in them.
1885 *
1886 * @return the zero-based number of Actions in this object
1887 */
1888 public int getAccessibleActionCount() {
1889 return 1;
1890 }
1891
1892 /**
1893 * Perform the specified Action on the object
1894 *
1895 * @param i zero-based index of actions
1896 * @return true if the the action was performed; else false.
1897 * @see #getAccessibleActionCount
1898 */
1899 public boolean doAccessibleAction(int i) {
1900 if (i == 0 && isValid() == true) {
1901 URL u = (URL) getAccessibleActionObject(i);
1902 if (u != null) {
1903 HyperlinkEvent linkEvent = new HyperlinkEvent(
1904 JEditorPane.this ,
1905 HyperlinkEvent.EventType.ACTIVATED, u);
1906 JEditorPane.this .fireHyperlinkUpdate(linkEvent);
1907 return true;
1908 }
1909 }
1910 return false; // link invalid or i != 0
1911 }
1912
1913 /**
1914 * Return a String description of this particular
1915 * link action. The string returned is the text
1916 * within the document associated with the element
1917 * which contains this link.
1918 *
1919 * @param i zero-based index of the actions
1920 * @return a String description of the action
1921 * @see #getAccessibleActionCount
1922 */
1923 public String getAccessibleActionDescription(int i) {
1924 if (i == 0 && isValid() == true) {
1925 Document d = JEditorPane.this .getDocument();
1926 if (d != null) {
1927 try {
1928 return d.getText(getStartIndex(),
1929 getEndIndex() - getStartIndex());
1930 } catch (BadLocationException exception) {
1931 return null;
1932 }
1933 }
1934 }
1935 return null;
1936 }
1937
1938 /**
1939 * Returns a URL object that represents the link.
1940 *
1941 * @param i zero-based index of the actions
1942 * @return an URL representing the HTML link itself
1943 * @see #getAccessibleActionCount
1944 */
1945 public Object getAccessibleActionObject(int i) {
1946 if (i == 0 && isValid() == true) {
1947 AttributeSet as = element.getAttributes();
1948 AttributeSet anchor = (AttributeSet) as
1949 .getAttribute(HTML.Tag.A);
1950 String href = (anchor != null) ? (String) anchor
1951 .getAttribute(HTML.Attribute.HREF) : null;
1952 if (href != null) {
1953 URL u;
1954 try {
1955 u = new URL(JEditorPane.this .getPage(),
1956 href);
1957 } catch (MalformedURLException m) {
1958 u = null;
1959 }
1960 return u;
1961 }
1962 }
1963 return null; // link invalid or i != 0
1964 }
1965
1966 /**
1967 * Return an object that represents the link anchor,
1968 * as appropriate for that link. E.g. from HTML:
1969 * <a href="http://www.sun.com/access">Accessibility</a>
1970 * this method would return a String containing the text:
1971 * 'Accessibility'.
1972 *
1973 * Similarly, from this HTML:
1974 * <a HREF="#top"><img src="top-hat.gif" alt="top hat"></a>
1975 * this might return the object ImageIcon("top-hat.gif", "top hat");
1976 *
1977 * @param i zero-based index of the actions
1978 * @return an Object representing the hypertext anchor
1979 * @see #getAccessibleActionCount
1980 */
1981 public Object getAccessibleActionAnchor(int i) {
1982 return getAccessibleActionDescription(i);
1983 }
1984
1985 /**
1986 * Get the index with the hypertext document at which this
1987 * link begins
1988 *
1989 * @return index of start of link
1990 */
1991 public int getStartIndex() {
1992 return element.getStartOffset();
1993 }
1994
1995 /**
1996 * Get the index with the hypertext document at which this
1997 * link ends
1998 *
1999 * @return index of end of link
2000 */
2001 public int getEndIndex() {
2002 return element.getEndOffset();
2003 }
2004 }
2005
2006 private class LinkVector extends Vector {
2007 public int baseElementIndex(Element e) {
2008 HTMLLink l;
2009 for (int i = 0; i < elementCount; i++) {
2010 l = (HTMLLink) elementAt(i);
2011 if (l.element == e) {
2012 return i;
2013 }
2014 }
2015 return -1;
2016 }
2017 }
2018
2019 LinkVector hyperlinks;
2020 boolean linksValid = false;
2021
2022 /**
2023 * Build the private table mapping links to locations in the text
2024 */
2025 private void buildLinkTable() {
2026 hyperlinks.removeAllElements();
2027 Document d = JEditorPane.this .getDocument();
2028 if (d != null) {
2029 ElementIterator ei = new ElementIterator(d);
2030 Element e;
2031 AttributeSet as;
2032 AttributeSet anchor;
2033 String href;
2034 while ((e = ei.next()) != null) {
2035 if (e.isLeaf()) {
2036 as = e.getAttributes();
2037 anchor = (AttributeSet) as
2038 .getAttribute(HTML.Tag.A);
2039 href = (anchor != null) ? (String) anchor
2040 .getAttribute(HTML.Attribute.HREF)
2041 : null;
2042 if (href != null) {
2043 hyperlinks.addElement(new HTMLLink(e));
2044 }
2045 }
2046 }
2047 }
2048 linksValid = true;
2049 }
2050
2051 /**
2052 * Make one of these puppies
2053 */
2054 public JEditorPaneAccessibleHypertextSupport() {
2055 hyperlinks = new LinkVector();
2056 Document d = JEditorPane.this .getDocument();
2057 if (d != null) {
2058 d.addDocumentListener(new DocumentListener() {
2059 public void changedUpdate(DocumentEvent theEvent) {
2060 linksValid = false;
2061 }
2062
2063 public void insertUpdate(DocumentEvent theEvent) {
2064 linksValid = false;
2065 }
2066
2067 public void removeUpdate(DocumentEvent theEvent) {
2068 linksValid = false;
2069 }
2070 });
2071 }
2072 }
2073
2074 /**
2075 * Returns the number of links within this hypertext doc.
2076 *
2077 * @return number of links in this hypertext doc.
2078 */
2079 public int getLinkCount() {
2080 if (linksValid == false) {
2081 buildLinkTable();
2082 }
2083 return hyperlinks.size();
2084 }
2085
2086 /**
2087 * Returns the index into an array of hyperlinks that
2088 * is associated with this character index, or -1 if there
2089 * is no hyperlink associated with this index.
2090 *
2091 * @param charIndex index within the text
2092 * @return index into the set of hyperlinks for this hypertext doc.
2093 */
2094 public int getLinkIndex(int charIndex) {
2095 if (linksValid == false) {
2096 buildLinkTable();
2097 }
2098 Element e = null;
2099 Document doc = JEditorPane.this .getDocument();
2100 if (doc != null) {
2101 for (e = doc.getDefaultRootElement(); !e.isLeaf();) {
2102 int index = e.getElementIndex(charIndex);
2103 e = e.getElement(index);
2104 }
2105 }
2106
2107 // don't need to verify that it's an HREF element; if
2108 // not, then it won't be in the hyperlinks Vector, and
2109 // so indexOf will return -1 in any case
2110 return hyperlinks.baseElementIndex(e);
2111 }
2112
2113 /**
2114 * Returns the index into an array of hyperlinks that
2115 * index. If there is no hyperlink at this index, it returns
2116 * null.
2117 *
2118 * @param linkIndex into the set of hyperlinks for this hypertext doc.
2119 * @return string representation of the hyperlink
2120 */
2121 public AccessibleHyperlink getLink(int linkIndex) {
2122 if (linksValid == false) {
2123 buildLinkTable();
2124 }
2125 if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
2126 return (AccessibleHyperlink) hyperlinks
2127 .elementAt(linkIndex);
2128 } else {
2129 return null;
2130 }
2131 }
2132
2133 /**
2134 * Returns the contiguous text within the document that
2135 * is associated with this hyperlink.
2136 *
2137 * @param linkIndex into the set of hyperlinks for this hypertext doc.
2138 * @return the contiguous text sharing the link at this index
2139 */
2140 public String getLinkText(int linkIndex) {
2141 if (linksValid == false) {
2142 buildLinkTable();
2143 }
2144 Element e = (Element) hyperlinks.elementAt(linkIndex);
2145 if (e != null) {
2146 Document d = JEditorPane.this .getDocument();
2147 if (d != null) {
2148 try {
2149 return d.getText(e.getStartOffset(), e
2150 .getEndOffset()
2151 - e.getStartOffset());
2152 } catch (BadLocationException exception) {
2153 return null;
2154 }
2155 }
2156 }
2157 return null;
2158 }
2159 }
2160
2161 static class PlainEditorKit extends DefaultEditorKit implements
2162 ViewFactory {
2163
2164 /**
2165 * Fetches a factory that is suitable for producing
2166 * views of any models that are produced by this
2167 * kit. The default is to have the UI produce the
2168 * factory, so this method has no implementation.
2169 *
2170 * @return the view factory
2171 */
2172 public ViewFactory getViewFactory() {
2173 return this ;
2174 }
2175
2176 /**
2177 * Creates a view from the given structural element of a
2178 * document.
2179 *
2180 * @param elem the piece of the document to build a view of
2181 * @return the view
2182 * @see View
2183 */
2184 public View create(Element elem) {
2185 Document doc = elem.getDocument();
2186 Object i18nFlag = doc
2187 .getProperty("i18n"/*AbstractDocument.I18NProperty*/);
2188 if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
2189 // build a view that support bidi
2190 return createI18N(elem);
2191 } else {
2192 return new WrappedPlainView(elem);
2193 }
2194 }
2195
2196 View createI18N(Element elem) {
2197 String kind = elem.getName();
2198 if (kind != null) {
2199 if (kind.equals(AbstractDocument.ContentElementName)) {
2200 return new PlainParagraph(elem);
2201 } else if (kind
2202 .equals(AbstractDocument.ParagraphElementName)) {
2203 return new BoxView(elem, View.Y_AXIS);
2204 }
2205 }
2206 return null;
2207 }
2208
2209 /**
2210 * Paragraph for representing plain-text lines that support
2211 * bidirectional text.
2212 */
2213 static class PlainParagraph extends
2214 javax.swing.text.ParagraphView {
2215
2216 PlainParagraph(Element elem) {
2217 super (elem);
2218 layoutPool = new LogicalView(elem);
2219 layoutPool.setParent(this );
2220 }
2221
2222 protected void setPropertiesFromAttributes() {
2223 Component c = getContainer();
2224 if ((c != null)
2225 && (!c.getComponentOrientation()
2226 .isLeftToRight())) {
2227 setJustification(StyleConstants.ALIGN_RIGHT);
2228 } else {
2229 setJustification(StyleConstants.ALIGN_LEFT);
2230 }
2231 }
2232
2233 /**
2234 * Fetch the constraining span to flow against for
2235 * the given child index.
2236 */
2237 public int getFlowSpan(int index) {
2238 Component c = getContainer();
2239 if (c instanceof JTextArea) {
2240 JTextArea area = (JTextArea) c;
2241 if (!area.getLineWrap()) {
2242 // no limit if unwrapped
2243 return Integer.MAX_VALUE;
2244 }
2245 }
2246 return super .getFlowSpan(index);
2247 }
2248
2249 protected SizeRequirements calculateMinorAxisRequirements(
2250 int axis, SizeRequirements r) {
2251 SizeRequirements req = super
2252 .calculateMinorAxisRequirements(axis, r);
2253 Component c = getContainer();
2254 if (c instanceof JTextArea) {
2255 JTextArea area = (JTextArea) c;
2256 if (!area.getLineWrap()) {
2257 // min is pref if unwrapped
2258 req.minimum = req.preferred;
2259 }
2260 }
2261 return req;
2262 }
2263
2264 /**
2265 * This class can be used to represent a logical view for
2266 * a flow. It keeps the children updated to reflect the state
2267 * of the model, gives the logical child views access to the
2268 * view hierarchy, and calculates a preferred span. It doesn't
2269 * do any rendering, layout, or model/view translation.
2270 */
2271 static class LogicalView extends CompositeView {
2272
2273 LogicalView(Element elem) {
2274 super (elem);
2275 }
2276
2277 protected int getViewIndexAtPosition(int pos) {
2278 Element elem = getElement();
2279 if (elem.getElementCount() > 0) {
2280 return elem.getElementIndex(pos);
2281 }
2282 return 0;
2283 }
2284
2285 protected boolean updateChildren(
2286 DocumentEvent.ElementChange ec,
2287 DocumentEvent e, ViewFactory f) {
2288 return false;
2289 }
2290
2291 protected void loadChildren(ViewFactory f) {
2292 Element elem = getElement();
2293 if (elem.getElementCount() > 0) {
2294 super .loadChildren(f);
2295 } else {
2296 View v = new GlyphView(elem);
2297 append(v);
2298 }
2299 }
2300
2301 public float getPreferredSpan(int axis) {
2302 if (getViewCount() != 1)
2303 throw new Error("One child view is assumed.");
2304
2305 View v = getView(0);
2306 //((GlyphView)v).setGlyphPainter(null);
2307 return v.getPreferredSpan(axis);
2308 }
2309
2310 /**
2311 * Forward the DocumentEvent to the given child view. This
2312 * is implemented to reparent the child to the logical view
2313 * (the children may have been parented by a row in the flow
2314 * if they fit without breaking) and then execute the
2315 * superclass behavior.
2316 *
2317 * @param v the child view to forward the event to.
2318 * @param e the change information from the associated document
2319 * @param a the current allocation of the view
2320 * @param f the factory to use to rebuild if the view has
2321 * children
2322 * @see #forwardUpdate
2323 * @since 1.3
2324 */
2325 protected void forwardUpdateToView(View v,
2326 DocumentEvent e, Shape a, ViewFactory f) {
2327 v.setParent(this );
2328 super .forwardUpdateToView(v, e, a, f);
2329 }
2330
2331 // The following methods don't do anything useful, they
2332 // simply keep the class from being abstract.
2333
2334 public void paint(Graphics g, Shape allocation) {
2335 }
2336
2337 protected boolean isBefore(int x, int y, Rectangle alloc) {
2338 return false;
2339 }
2340
2341 protected boolean isAfter(int x, int y, Rectangle alloc) {
2342 return false;
2343 }
2344
2345 protected View getViewAtPoint(int x, int y,
2346 Rectangle alloc) {
2347 return null;
2348 }
2349
2350 protected void childAllocation(int index, Rectangle a) {
2351 }
2352 }
2353 }
2354 }
2355
2356 /* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
2357 * sensibly:
2358 * From a String like: 'timeout=15, max=5'
2359 * create an array of Strings:
2360 * { {"timeout", "15"},
2361 * {"max", "5"}
2362 * }
2363 * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
2364 * create one like (no quotes in literal):
2365 * { {"basic", null},
2366 * {"realm", "FuzzFace"}
2367 * {"foo", "Biz Bar Baz"}
2368 * }
2369 * keys are converted to lower case, vals are left as is....
2370 *
2371 * author Dave Brown
2372 */
2373
2374 static class HeaderParser {
2375
2376 /* table of key/val pairs - maxes out at 10!!!!*/
2377 String raw;
2378 String[][] tab;
2379
2380 public HeaderParser(String raw) {
2381 this .raw = raw;
2382 tab = new String[10][2];
2383 parse();
2384 }
2385
2386 private void parse() {
2387
2388 if (raw != null) {
2389 raw = raw.trim();
2390 char[] ca = raw.toCharArray();
2391 int beg = 0, end = 0, i = 0;
2392 boolean inKey = true;
2393 boolean inQuote = false;
2394 int len = ca.length;
2395 while (end < len) {
2396 char c = ca[end];
2397 if (c == '=') { // end of a key
2398 tab[i][0] = new String(ca, beg, end - beg)
2399 .toLowerCase();
2400 inKey = false;
2401 end++;
2402 beg = end;
2403 } else if (c == '\"') {
2404 if (inQuote) {
2405 tab[i++][1] = new String(ca, beg, end - beg);
2406 inQuote = false;
2407 do {
2408 end++;
2409 } while (end < len
2410 && (ca[end] == ' ' || ca[end] == ','));
2411 inKey = true;
2412 beg = end;
2413 } else {
2414 inQuote = true;
2415 end++;
2416 beg = end;
2417 }
2418 } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
2419 if (inQuote) {
2420 end++;
2421 continue;
2422 } else if (inKey) {
2423 tab[i++][0] = (new String(ca, beg, end
2424 - beg)).toLowerCase();
2425 } else {
2426 tab[i++][1] = (new String(ca, beg, end
2427 - beg));
2428 }
2429 while (end < len
2430 && (ca[end] == ' ' || ca[end] == ',')) {
2431 end++;
2432 }
2433 inKey = true;
2434 beg = end;
2435 } else {
2436 end++;
2437 }
2438 }
2439 // get last key/val, if any
2440 if (--end > beg) {
2441 if (!inKey) {
2442 if (ca[end] == '\"') {
2443 tab[i++][1] = (new String(ca, beg, end
2444 - beg));
2445 } else {
2446 tab[i++][1] = (new String(ca, beg, end
2447 - beg + 1));
2448 }
2449 } else {
2450 tab[i][0] = (new String(ca, beg, end - beg + 1))
2451 .toLowerCase();
2452 }
2453 } else if (end == beg) {
2454 if (!inKey) {
2455 if (ca[end] == '\"') {
2456 tab[i++][1] = String.valueOf(ca[end - 1]);
2457 } else {
2458 tab[i++][1] = String.valueOf(ca[end]);
2459 }
2460 } else {
2461 tab[i][0] = String.valueOf(ca[end])
2462 .toLowerCase();
2463 }
2464 }
2465 }
2466
2467 }
2468
2469 public String findKey(int i) {
2470 if (i < 0 || i > 10)
2471 return null;
2472 return tab[i][0];
2473 }
2474
2475 public String findValue(int i) {
2476 if (i < 0 || i > 10)
2477 return null;
2478 return tab[i][1];
2479 }
2480
2481 public String findValue(String key) {
2482 return findValue(key, null);
2483 }
2484
2485 public String findValue(String k, String Default) {
2486 if (k == null)
2487 return Default;
2488 k = k.toLowerCase();
2489 for (int i = 0; i < 10; ++i) {
2490 if (tab[i][0] == null) {
2491 return Default;
2492 } else if (k.equals(tab[i][0])) {
2493 return tab[i][1];
2494 }
2495 }
2496 return Default;
2497 }
2498
2499 public int findInt(String k, int Default) {
2500 try {
2501 return Integer.parseInt(findValue(k, String
2502 .valueOf(Default)));
2503 } catch (Throwable t) {
2504 return Default;
2505 }
2506 }
2507 }
2508
2509 }
|