J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | package javax.swing; |
| 26 | |
| 27 | import java.awt.*; |
| 28 | import java.awt.event.*; |
| 29 | import java.lang.reflect.*; |
| 30 | import java.net.*; |
| 31 | import java.util.*; |
| 32 | import java.io.*; |
| 33 | import java.util.*; |
| 34 | |
| 35 | import javax.swing.plaf.*; |
| 36 | import javax.swing.text.*; |
| 37 | import javax.swing.event.*; |
| 38 | import javax.swing.text.html.*; |
| 39 | import javax.accessibility.*; |
| 40 | |
| 41 | /** |
| 42 | * A text component to edit various kinds of content. |
| 43 | * You can find how-to information and examples of using editor panes in |
| 44 | * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/text.html">Using Text Components</a>, |
| 45 | * a section in <em>The Java Tutorial.</em> |
| 46 | * |
| 47 | * <p> |
| 48 | * This component uses implementations of the |
| 49 | * <code>EditorKit</code> to accomplish its behavior. It effectively |
| 50 | * morphs into the proper kind of text editor for the kind |
| 51 | * of content it is given. The content type that editor is bound |
| 52 | * to at any given time is determined by the <code>EditorKit</code> currently |
| 53 | * installed. If the content is set to a new URL, its type is used |
| 54 | * to determine the <code>EditorKit</code> that should be used to |
| 55 | * load the content. |
| 56 | * <p> |
| 57 | * By default, the following types of content are known: |
| 58 | * <dl> |
| 59 | * <dt><b>text/plain</b> |
| 60 | * <dd>Plain text, which is the default the type given isn't |
| 61 | * recognized. The kit used in this case is an extension of |
| 62 | * <code>DefaultEditorKit</code> that produces a wrapped plain text view. |
| 63 | * <dt><b>text/html</b> |
| 64 | * <dd>HTML text. The kit used in this case is the class |
| 65 | * <code>javax.swing.text.html.HTMLEditorKit</code> |
| 66 | * which provides HTML 3.2 support. |
| 67 | * <dt><b>text/rtf</b> |
| 68 | * <dd>RTF text. The kit used in this case is the class |
| 69 | * <code>javax.swing.text.rtf.RTFEditorKit</code> |
| 70 | * which provides a limited support of the Rich Text Format. |
| 71 | * </dl> |
| 72 | * <p> |
| 73 | * There are several ways to load content into this component. |
| 74 | * <ol> |
| 75 | * <li> |
| 76 | * The {@link #setText setText} method can be used to initialize |
| 77 | * the component from a string. In this case the current |
| 78 | * <code>EditorKit</code> will be used, and the content type will be |
| 79 | * expected to be of this type. |
| 80 | * <li> |
| 81 | * The {@link #read read} method can be used to initialize the |
| 82 | * component from a <code>Reader</code>. Note that if the content type is HTML, |
| 83 | * relative references (e.g. for things like images) can't be resolved |
| 84 | * unless the <base> tag is used or the <em>Base</em> property |
| 85 | * on <code>HTMLDocument</code> is set. |
| 86 | * In this case the current <code>EditorKit</code> will be used, |
| 87 | * and the content type will be expected to be of this type. |
| 88 | * <li> |
| 89 | * The {@link #setPage setPage} method can be used to initialize |
| 90 | * the component from a URL. In this case, the content type will be |
| 91 | * determined from the URL, and the registered <code>EditorKit</code> |
| 92 | * for that content type will be set. |
| 93 | * </ol> |
| 94 | * <p> |
| 95 | * Some kinds of content may provide hyperlink support by generating |
| 96 | * hyperlink events. The HTML <code>EditorKit</code> will generate |
| 97 | * hyperlink events if the <code>JEditorPane</code> is <em>not editable</em> |
| 98 | * (<code>JEditorPane.setEditable(false);</code> has been called). |
| 99 | * If HTML frames are embedded in the document, the typical response would be |
| 100 | * to change a portion of the current document. The following code |
| 101 | * fragment is a possible hyperlink listener implementation, that treats |
| 102 | * HTML frame events specially, and simply displays any other activated |
| 103 | * hyperlinks. |
| 104 | * <code><pre> |
| 105 | |
| 106 | class Hyperactive implements HyperlinkListener { |
| 107 | |
| 108 | public void hyperlinkUpdate(HyperlinkEvent e) { |
| 109 | if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { |
| 110 | JEditorPane pane = (JEditorPane) e.getSource(); |
| 111 | if (e instanceof HTMLFrameHyperlinkEvent) { |
| 112 | HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e; |
| 113 | HTMLDocument doc = (HTMLDocument)pane.getDocument(); |
| 114 | doc.processHTMLFrameHyperlinkEvent(evt); |
| 115 | } else { |
| 116 | try { |
| 117 | pane.setPage(e.getURL()); |
| 118 | } catch (Throwable t) { |
| 119 | t.printStackTrace(); |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | * </pre></code> |
| 127 | * <p> |
| 128 | * For information on customizing how <b>text/html</b> is rendered please see |
| 129 | * {@link #W3C_LENGTH_UNITS} and {@link #HONOR_DISPLAY_PROPERTIES} |
| 130 | * <p> |
| 131 | * Culturally dependent information in some documents is handled through |
| 132 | * a mechanism called character encoding. Character encoding is an |
| 133 | * unambiguous mapping of the members of a character set (letters, ideographs, |
| 134 | * digits, symbols, or control functions) to specific numeric code values. It |
| 135 | * represents the way the file is stored. Example character encodings are |
| 136 | * ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is |
| 137 | * passed to an user agent (<code>JEditorPane</code>) it is converted to |
| 138 | * the document character set (ISO-10646 aka Unicode). |
| 139 | * <p> |
| 140 | * There are multiple ways to get a character set mapping to happen |
| 141 | * with <code>JEditorPane</code>. |
| 142 | * <ol> |
| 143 | * <li> |
| 144 | * One way is to specify the character set as a parameter of the MIME |
| 145 | * type. This will be established by a call to the |
| 146 | * <a href="#setContentType">setContentType</a> method. If the content |
| 147 | * is loaded by the <a href="#setPage">setPage</a> method the content |
| 148 | * type will have been set according to the specification of the URL. |
| 149 | * It the file is loaded directly, the content type would be expected to |
| 150 | * have been set prior to loading. |
| 151 | * <li> |
| 152 | * Another way the character set can be specified is in the document itself. |
| 153 | * This requires reading the document prior to determining the character set |
| 154 | * that is desired. To handle this, it is expected that the |
| 155 | * <code>EditorKit</code>.read operation throw a |
| 156 | * <code>ChangedCharSetException</code> which will |
| 157 | * be caught. The read is then restarted with a new Reader that uses |
| 158 | * the character set specified in the <code>ChangedCharSetException</code> |
| 159 | * (which is an <code>IOException</code>). |
| 160 | * </ol> |
| 161 | * <p> |
| 162 | * <dl> |
| 163 | * <dt><b><font size=+1>Newlines</font></b> |
| 164 | * <dd> |
| 165 | * For a discussion on how newlines are handled, see |
| 166 | * <a href="text/DefaultEditorKit.html">DefaultEditorKit</a>. |
| 167 | * </dl> |
| 168 | * |
| 169 | * <p> |
| 170 | * <strong>Warning:</strong> Swing is not thread safe. For more |
| 171 | * information see <a |
| 172 | * href="package-summary.html#threading">Swing's Threading |
| 173 | * Policy</a>. |
| 174 | * <p> |
| 175 | * <strong>Warning:</strong> |
| 176 | * Serialized objects of this class will not be compatible with |
| 177 | * future Swing releases. The current serialization support is |
| 178 | * appropriate for short term storage or RMI between applications running |
| 179 | * the same version of Swing. As of 1.4, support for long term storage |
| 180 | * of all JavaBeans<sup><font size="-2">TM</font></sup> |
| 181 | * has been added to the <code>java.beans</code> package. |
| 182 | * Please see {@link java.beans.XMLEncoder}. |
| 183 | * |
| 184 | * @beaninfo |
| 185 | * attribute: isContainer false |
| 186 | * description: A text component to edit various types of content. |
| 187 | * |
| 188 | * @author Timothy Prinzing |
| 189 | */ |
| 190 | public class JEditorPane extends JTextComponent { |
| 191 | |
| 192 | /** |
| 193 | * Creates a new <code>JEditorPane</code>. |
| 194 | * The document model is set to <code>null</code>. |
| 195 | */ |
| 196 | public JEditorPane() { |
| 197 | super(); |
| 198 | setFocusCycleRoot(true); |
| 199 | setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { |
| 200 | public Component getComponentAfter(Container focusCycleRoot, |
| 201 | Component aComponent) { |
| 202 | if (focusCycleRoot != JEditorPane.this || |
| 203 | (!isEditable() && getComponentCount() > 0)) { |
| 204 | return super.getComponentAfter(focusCycleRoot, |
| 205 | aComponent); |
| 206 | } else { |
| 207 | Container rootAncestor = getFocusCycleRootAncestor(); |
| 208 | return (rootAncestor != null) |
| 209 | ? rootAncestor.getFocusTraversalPolicy(). |
| 210 | getComponentAfter(rootAncestor, |
| 211 | JEditorPane.this) |
| 212 | : null; |
| 213 | } |
| 214 | } |
| 215 | public Component getComponentBefore(Container focusCycleRoot, |
| 216 | Component aComponent) { |
| 217 | if (focusCycleRoot != JEditorPane.this || |
| 218 | (!isEditable() && getComponentCount() > 0)) { |
| 219 | return super.getComponentBefore(focusCycleRoot, |
| 220 | aComponent); |
| 221 | } else { |
| 222 | Container rootAncestor = getFocusCycleRootAncestor(); |
| 223 | return (rootAncestor != null) |
| 224 | ? rootAncestor.getFocusTraversalPolicy(). |
| 225 | getComponentBefore(rootAncestor, |
| 226 | JEditorPane.this) |
| 227 | : null; |
| 228 | } |
| 229 | } |
| 230 | public Component getDefaultComponent(Container focusCycleRoot) |
| 231 | { |
| 232 | return (focusCycleRoot != JEditorPane.this || |
| 233 | (!isEditable() && getComponentCount() > 0)) |
| 234 | ? super.getDefaultComponent(focusCycleRoot) |
| 235 | : null; |
| 236 | } |
| 237 | protected boolean accept(Component aComponent) { |
| 238 | return (aComponent != JEditorPane.this) |
| 239 | ? super.accept(aComponent) |
| 240 | : false; |
| 241 | } |
| 242 | }); |
| 243 | LookAndFeel.installProperty(this, |
| 244 | "focusTraversalKeysForward", |
| 245 | JComponent. |
| 246 | getManagingFocusForwardTraversalKeys()); |
| 247 | LookAndFeel.installProperty(this, |
| 248 | "focusTraversalKeysBackward", |
| 249 | JComponent. |
| 250 | getManagingFocusBackwardTraversalKeys()); |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * Creates a <code>JEditorPane</code> based on a specified URL for input. |
| 255 | * |
| 256 | * @param initialPage the URL |
| 257 | * @exception IOException if the URL is <code>null</code> |
| 258 | * or cannot be accessed |
| 259 | */ |
| 260 | public JEditorPane(URL initialPage) throws IOException { |
| 261 | this(); |
| 262 | setPage(initialPage); |
| 263 | } |
| 264 | |
| 265 | /** |
| 266 | * Creates a <code>JEditorPane</code> based on a string containing |
| 267 | * a URL specification. |
| 268 | * |
| 269 | * @param url the URL |
| 270 | * @exception IOException if the URL is <code>null</code> or |
| 271 | * cannot be accessed |
| 272 | */ |
| 273 | public JEditorPane(String url) throws IOException { |
| 274 | this(); |
| 275 | setPage(url); |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * Creates a <code>JEditorPane</code> that has been initialized |
| 280 | * to the given text. This is a convenience constructor that calls the |
| 281 | * <code>setContentType</code> and <code>setText</code> methods. |
| 282 | * |
| 283 | * @param type mime type of the given text |
| 284 | * @param text the text to initialize with; may be <code>null</code> |
| 285 | * @exception NullPointerException if the <code>type</code> parameter |
| 286 | * is <code>null</code> |
| 287 | */ |
| 288 | public JEditorPane(String type, String text) { |
| 289 | this(); |
| 290 | setContentType(type); |
| 291 | setText(text); |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Adds a hyperlink listener for notification of any changes, for example |
| 296 | * when a link is selected and entered. |
| 297 | * |
| 298 | * @param listener the listener |
| 299 | */ |
| 300 | public synchronized void addHyperlinkListener(HyperlinkListener listener) { |
| 301 | listenerList.add(HyperlinkListener.class, listener); |
| 302 | } |
| 303 | |
| 304 | /** |
| 305 | * Removes a hyperlink listener. |
| 306 | * |
| 307 | * @param listener the listener |
| 308 | */ |
| 309 | public synchronized void removeHyperlinkListener(HyperlinkListener listener) { |
| 310 | listenerList.remove(HyperlinkListener.class, listener); |
| 311 | } |
| 312 | |
| 313 | /** |
| 314 | * Returns an array of all the <code>HyperLinkListener</code>s added |
| 315 | * to this JEditorPane with addHyperlinkListener(). |
| 316 | * |
| 317 | * @return all of the <code>HyperLinkListener</code>s added or an empty |
| 318 | * array if no listeners have been added |
| 319 | * @since 1.4 |
| 320 | */ |
| 321 | public synchronized HyperlinkListener[] getHyperlinkListeners() { |
| 322 | return (HyperlinkListener[])listenerList.getListeners( |
| 323 | HyperlinkListener.class); |
| 324 | } |
| 325 | |
| 326 | /** |
| 327 | * Notifies all listeners that have registered interest for |
| 328 | * notification on this event type. This is normally called |
| 329 | * by the currently installed <code>EditorKit</code> if a content type |
| 330 | * that supports hyperlinks is currently active and there |
| 331 | * was activity with a link. The listener list is processed |
| 332 | * last to first. |
| 333 | * |
| 334 | * @param e the event |
| 335 | * @see EventListenerList |
| 336 | */ |
| 337 | public void fireHyperlinkUpdate(HyperlinkEvent e) { |
| 338 | // Guaranteed to return a non-null array |
| 339 | Object[] listeners = listenerList.getListenerList(); |
| 340 | // Process the listeners last to first, notifying |
| 341 | // those that are interested in this event |
| 342 | for (int i = listeners.length-2; i>=0; i-=2) { |
| 343 | if (listeners[i]==HyperlinkListener.class) { |
| 344 | ((HyperlinkListener)listeners[i+1]).hyperlinkUpdate(e); |
| 345 | } |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | |
| 350 | /** |
| 351 | * Sets the current URL being displayed. The content type of the |
| 352 | * pane is set, and if the editor kit for the pane is |
| 353 | * non-<code>null</code>, then |
| 354 | * a new default document is created and the URL is read into it. |
| 355 | * If the URL contains and reference location, the location will |
| 356 | * be scrolled to by calling the <code>scrollToReference</code> |
| 357 | * method. If the desired URL is the one currently being displayed, |
| 358 | * the document will not be reloaded. To force a document |
| 359 | * reload it is necessary to clear the stream description property |
| 360 | * of the document. The following code shows how this can be done: |
| 361 | * |
| 362 | * <pre> |
| 363 | * Document doc = jEditorPane.getDocument(); |
| 364 | * doc.putProperty(Document.StreamDescriptionProperty, null); |
| 365 | * </pre> |
| 366 | * |
| 367 | * If the desired URL is not the one currently being |
| 368 | * displayed, the <code>getStream</code> method is called to |
| 369 | * give subclasses control over the stream provided. |
| 370 | * <p> |
| 371 | * This may load either synchronously or asynchronously |
| 372 | * depending upon the document returned by the <code>EditorKit</code>. |
| 373 | * If the <code>Document</code> is of type |
| 374 | * <code>AbstractDocument</code> and has a value returned by |
| 375 | * <code>AbstractDocument.getAsynchronousLoadPriority</code> |
| 376 | * that is greater than or equal to zero, the page will be |
| 377 | * loaded on a separate thread using that priority. |
| 378 | * <p> |
| 379 | * If the document is loaded synchronously, it will be |
| 380 | * filled in with the stream prior to being installed into |
| 381 | * the editor with a call to <code>setDocument</code>, which |
| 382 | * is bound and will fire a property change event. If an |
| 383 | * <code>IOException</code> is thrown the partially loaded |
| 384 | * document will |
| 385 | * be discarded and neither the document or page property |
| 386 | * change events will be fired. If the document is |
| 387 | * successfully loaded and installed, a view will be |
| 388 | * built for it by the UI which will then be scrolled if |
| 389 | * necessary, and then the page property change event |
| 390 | * will be fired. |
| 391 | * <p> |
| 392 | * If the document is loaded asynchronously, the document |
| 393 | * will be installed into the editor immediately using a |
| 394 | * call to <code>setDocument</code> which will fire a |
| 395 | * document property change event, then a thread will be |
| 396 | * created which will begin doing the actual loading. |
| 397 | * In this case, the page property change event will not be |
| 398 | * fired by the call to this method directly, but rather will be |
| 399 | * fired when the thread doing the loading has finished. |
| 400 | * It will also be fired on the event-dispatch thread. |
| 401 | * Since the calling thread can not throw an <code>IOException</code> |
| 402 | * in the event of failure on the other thread, the page |
| 403 | * property change event will be fired when the other |
| 404 | * thread is done whether the load was successful or not. |
| 405 | * |
| 406 | * @param page the URL of the page |
| 407 | * @exception IOException for a <code>null</code> or invalid |
| 408 | * page specification, or exception from the stream being read |
| 409 | * @see #getPage |
| 410 | * @beaninfo |
| 411 | * description: the URL used to set content |
| 412 | * bound: true |
| 413 | * expert: true |
| 414 | */ |
| 415 | public void setPage(URL page) throws IOException { |
| 416 | if (page == null) { |
| 417 | throw new IOException("invalid url"); |
| 418 | } |
| 419 | URL loaded = getPage(); |
| 420 | |
| 421 | |
| 422 | // reset scrollbar |
| 423 | if (!page.equals(loaded) && page.getRef() == null) { |
| 424 | scrollRectToVisible(new Rectangle(0,0,1,1)); |
| 425 | } |
| 426 | boolean reloaded = false; |
| 427 | Object postData = getPostData(); |
| 428 | if ((loaded == null) || !loaded.sameFile(page) || (postData != null)) { |
| 429 | // different url or POST method, load the new content |
| 430 | |
| 431 | int p = getAsynchronousLoadPriority(getDocument()); |
| 432 | if ((postData == null) || (p < 0)) { |
| 433 | // Either we do not have POST data, or should submit the data |
| 434 | // synchronously. |
| 435 | InputStream in = getStream(page); |
| 436 | if (kit != null) { |
| 437 | Document doc = initializeModel(kit, page); |
| 438 | |
| 439 | // At this point, one could either load up the model with no |
| 440 | // view notifications slowing it down (i.e. best synchronous |
| 441 | // behavior) or set the model and start to feed it on a separate |
| 442 | // thread (best asynchronous behavior). |
| 443 | synchronized(this) { |
| 444 | if (loading != null) { |
| 445 | // we are loading asynchronously, so we need to cancel |
| 446 | // the old stream. |
| 447 | loading.cancel(); |
| 448 | loading = null; |
| 449 | } |
| 450 | } |
| 451 | p = getAsynchronousLoadPriority(doc); |
| 452 | if (p >= 0) { |
| 453 | // load asynchronously |
| 454 | setDocument(doc); |
| 455 | synchronized(this) { |
| 456 | loading = new PageStream(in); |
| 457 | Thread pl = new PageLoader(doc, loading, p, loaded, page); |
| 458 | pl.start(); |
| 459 | } |
| 460 | return; |
| 461 | } |
| 462 | read(in, doc); |
| 463 | setDocument(doc); |
| 464 | reloaded = true; |
| 465 | } |
| 466 | } else { |
| 467 | // We have POST data and should send it asynchronously. |
| 468 | // Send (and subsequentally read) data in separate thread. |
| 469 | // Model initialization is deferred to that thread, too. |
| 470 | Thread pl = new PageLoader(null, null, p, loaded, page); |
| 471 | pl.start(); |
| 472 | return; |
| 473 | } |
| 474 | } |
| 475 | final String reference = page.getRef(); |
| 476 | if (reference != null) { |
| 477 | if (!reloaded) { |
| 478 | scrollToReference(reference); |
| 479 | } |
| 480 | else { |
| 481 | // Have to scroll after painted. |
| 482 | SwingUtilities.invokeLater(new Runnable() { |
| 483 | public void run() { |
| 484 | scrollToReference(reference); |
| 485 | } |
| 486 | }); |
| 487 | } |
| 488 | getDocument().putProperty(Document.StreamDescriptionProperty, page); |
| 489 | } |
| 490 | firePropertyChange("page", loaded, page); |
| 491 | } |
| 492 | |
| 493 | /** |
| 494 | * Create model and initialize document properties from page properties. |
| 495 | */ |
| 496 | private Document initializeModel(EditorKit kit, URL page) { |
| 497 | Document doc = kit.createDefaultDocument(); |
| 498 | if (pageProperties != null) { |
| 499 | // transfer properties discovered in stream to the |
| 500 | // document property collection. |
| 501 | for (Enumeration e = pageProperties.keys(); e.hasMoreElements() ;) { |
| 502 | Object key = e.nextElement(); |
| 503 | doc.putProperty(key, pageProperties.get(key)); |
| 504 | } |
| 505 | pageProperties.clear(); |
| 506 | } |
| 507 | if (doc.getProperty(Document.StreamDescriptionProperty) == null) { |
| 508 | doc.putProperty(Document.StreamDescriptionProperty, page); |
| 509 | } |
| 510 | return doc; |
| 511 | } |
| 512 | |
| 513 | /** |
| 514 | * Return load priority for the document or -1 if priority not supported. |
| 515 | */ |
| 516 | private int getAsynchronousLoadPriority(Document doc) { |
| 517 | return (doc instanceof AbstractDocument ? |
| 518 | ((AbstractDocument) doc).getAsynchronousLoadPriority() : -1); |
| 519 | } |
| 520 | |
| 521 | /** |
| 522 | * This method initializes from a stream. If the kit is |
| 523 | * set to be of type <code>HTMLEditorKit</code>, and the |
| 524 | * <code>desc</code> parameter is an <code>HTMLDocument</code>, |
| 525 | * then it invokes the <code>HTMLEditorKit</code> to initiate |
| 526 | * the read. Otherwise it calls the superclass |
| 527 | * method which loads the model as plain text. |
| 528 | * |
| 529 | * @param in the stream from which to read |
| 530 | * @param desc an object describing the stream |
| 531 | * @exception IOException as thrown by the stream being |
| 532 | * used to initialize |
| 533 | * @see JTextComponent#read |
| 534 | * @see #setDocument |
| 535 | */ |
| 536 | public void read(InputStream in, Object desc) throws IOException { |
| 537 | |
| 538 | if (desc instanceof HTMLDocument && |
| 539 | kit instanceof HTMLEditorKit) { |
| 540 | HTMLDocument hdoc = (HTMLDocument) desc; |
| 541 | setDocument(hdoc); |
| 542 | read(in, hdoc); |
| 543 | } else { |
| 544 | String charset = (String) getClientProperty("charset"); |
| 545 | Reader r = (charset != null) ? new InputStreamReader(in, charset) : |
| 546 | new InputStreamReader(in); |
| 547 | super.read(r, desc); |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | |
| 552 | /** |
| 553 | * This method invokes the <code>EditorKit</code> to initiate a |
| 554 | * read. In the case where a <code>ChangedCharSetException</code> |
| 555 | * is thrown this exception will contain the new CharSet. |
| 556 | * Therefore the <code>read</code> operation |
| 557 | * is then restarted after building a new Reader with the new charset. |
| 558 | * |
| 559 | * @param in the inputstream to use |
| 560 | * @param doc the document to load |
| 561 | * |
| 562 | */ |
| 563 | void read(InputStream in, Document doc) throws IOException { |
| 564 | if (! Boolean.TRUE.equals(doc.getProperty("IgnoreCharsetDirective"))) { |
| 565 | final int READ_LIMIT = 1024 * 10; |
| 566 | in = new BufferedInputStream(in, READ_LIMIT); |
| 567 | in.mark(READ_LIMIT); |
| 568 | } |
| 569 | try { |
| 570 | String charset = (String) getClientProperty("charset"); |
| 571 | Reader r = (charset != null) ? new InputStreamReader(in, charset) : |
| 572 | new InputStreamReader(in); |
| 573 | kit.read(r, doc, 0); |
| 574 | } catch (BadLocationException e) { |
| 575 | throw new IOException(e.getMessage()); |
| 576 | } catch (ChangedCharSetException changedCharSetException) { |
| 577 | String charSetSpec = changedCharSetException.getCharSetSpec(); |
| 578 | if (changedCharSetException.keyEqualsCharSet()) { |
| 579 | putClientProperty("charset", charSetSpec); |
| 580 | } else { |
| 581 | setCharsetFromContentTypeParameters(charSetSpec); |
| 582 | } |
| 583 | try { |
| 584 | in.reset(); |
| 585 | } catch (IOException exception) { |
| 586 | //mark was invalidated |
| 587 | in.close(); |
| 588 | URL url = (URL)doc.getProperty(Document.StreamDescriptionProperty); |
| 589 | if (url != null) { |
| 590 | URLConnection conn = url.openConnection(); |
| 591 | in = conn.getInputStream(); |
| 592 | } else { |
| 593 | //there is nothing we can do to recover stream |
| 594 | throw changedCharSetException; |
| 595 | } |
| 596 | } |
| 597 | try { |
| 598 | doc.remove(0, doc.getLength()); |
| 599 | } catch (BadLocationException e) {} |
| 600 | doc.putProperty("IgnoreCharsetDirective", Boolean.valueOf(true)); |
| 601 | read(in, doc); |
| 602 | } |
| 603 | } |
| 604 | |
| 605 | |
| 606 | /** |
| 607 | * Thread to load a stream into the text document model. |
| 608 | */ |
| 609 | class PageLoader extends Thread { |
| 610 | |
| 611 | /** |
| 612 | * Construct an asynchronous page loader. |
| 613 | */ |
| 614 | PageLoader(Document doc, InputStream in, int priority, URL old, |
| 615 | URL page) { |
| 616 | setPriority(priority); |
| 617 | this.in = in; |
| 618 | this.old = old; |
| 619 | this.page = page; |
| 620 | this.doc = doc; |
| 621 | } |
| 622 | |
| 623 | boolean pageLoaded = false; |
| 624 | |
| 625 | /** |
| 626 | * Try to load the document, then scroll the view |
| 627 | * to the reference (if specified). When done, fire |
| 628 | * a page property change event. |
| 629 | */ |
| 630 | public void run() { |
| 631 | try { |
| 632 | if (in == null) { |
| 633 | in = getStream(page); |
| 634 | if (kit == null) { |
| 635 | // We received document of unknown content type. |
| 636 | UIManager.getLookAndFeel().provideErrorFeedback( |
| 637 | JEditorPane.this); |
| 638 | return; |
| 639 | } |
| 640 | // Access to <code>loading</code> should be synchronized. |
| 641 | synchronized(JEditorPane.this) { |
| 642 | in = loading = new PageStream(in); |
| 643 | } |
| 644 | } |
| 645 | if (doc == null) { |
| 646 | try { |
| 647 | SwingUtilities.invokeAndWait(new Runnable() { |
| 648 | public void run() { |
| 649 | doc = initializeModel(kit, page); |
| 650 | setDocument(doc); |
| 651 | } |
| 652 | }); |
| 653 | } catch (InvocationTargetException ex) { |
| 654 | UIManager.getLookAndFeel().provideErrorFeedback( |
| 655 | JEditorPane.this); |
| 656 | return; |
| 657 | } catch (InterruptedException ex) { |
| 658 | UIManager.getLookAndFeel().provideErrorFeedback( |
| 659 | JEditorPane.this); |
| 660 | return; |
| 661 | } |
| 662 | } |
| 663 | |
| 664 | read(in, doc); |
| 665 | URL page = (URL) doc.getProperty(Document.StreamDescriptionProperty); |
| 666 | String reference = page.getRef(); |
| 667 | if (reference != null) { |
| 668 | // scroll the page if necessary, but do it on the |
| 669 | // event thread... that is the only guarantee that |
| 670 | // modelToView can be safely called. |
| 671 | Runnable callScrollToReference = new Runnable() { |
| 672 | public void run() { |
| 673 | URL u = (URL) getDocument().getProperty |
| 674 | (Document.StreamDescriptionProperty); |
| 675 | String ref = u.getRef(); |
| 676 | scrollToReference(ref); |
| 677 | } |
| 678 | }; |
| 679 | SwingUtilities.invokeLater(callScrollToReference); |
| 680 | } |
| 681 | pageLoaded = true; |
| 682 | } catch (IOException ioe) { |
| 683 | UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this); |
| 684 | } finally { |
| 685 | synchronized(JEditorPane.this) { |
| 686 | loading = null; |
| 687 | } |
| 688 | SwingUtilities.invokeLater(new Runnable() { |
| 689 | public void run() { |
| 690 | if (pageLoaded) { |
| 691 | firePropertyChange("page", old, page); |
| 692 | } |
| 693 | } |
| 694 | }); |
| 695 | } |
| 696 | } |
| 697 | |
| 698 | /** |
| 699 | * The stream to load the document with |
| 700 | */ |
| 701 | InputStream in; |
| 702 | |
| 703 | /** |
| 704 | * URL of the old page that was replaced (for the property change event) |
| 705 | */ |
| 706 | URL old; |
| 707 | |
| 708 | /** |
| 709 | * URL of the page being loaded (for the property change event) |
| 710 | */ |
| 711 | URL page; |
| 712 | |
| 713 | /** |
| 714 | * The Document instance to load into. This is cached in case a |
| 715 | * new Document is created between the time the thread this is created |
| 716 | * and run. |
| 717 | */ |
| 718 | Document doc; |
| 719 | } |
| 720 | |
| 721 | static class PageStream extends FilterInputStream { |
| 722 | |
| 723 | boolean canceled; |
| 724 | |
| 725 | public PageStream(InputStream i) { |
| 726 | super(i); |
| 727 | canceled = false; |
| 728 | } |
| 729 | |
| 730 | /** |
| 731 | * Cancel the loading of the stream by throwing |
| 732 | * an IOException on the next request. |
| 733 | */ |
| 734 | public synchronized void cancel() { |
| 735 | canceled = true; |
| 736 | } |
| 737 | |
| 738 | protected synchronized void checkCanceled() throws IOException { |
| 739 | if (canceled) { |
| 740 | throw new IOException("page canceled"); |
| 741 | } |
| 742 | } |
| 743 | |
| 744 | public int read() throws IOException { |
| 745 | checkCanceled(); |
| 746 | return super.read(); |
| 747 | } |
| 748 | |
| 749 | public long skip(long n) throws IOException { |
| 750 | checkCanceled(); |
| 751 | return super.skip(n); |
| 752 | } |
| 753 | |
| 754 | public int available() throws IOException { |
| 755 | checkCanceled(); |
| 756 | return super.available(); |
| 757 | } |
| 758 | |
| 759 | public void reset() throws IOException { |
| 760 | checkCanceled(); |
| 761 | super.reset(); |
| 762 | } |
| 763 | |
| 764 | } |
| 765 | |
| 766 | /** |
| 767 | * Fetches a stream for the given URL, which is about to |
| 768 | * be loaded by the <code>setPage</code> method. By |
| 769 | * default, this simply opens the URL and returns the |
| 770 | * stream. This can be reimplemented to do useful things |
| 771 | * like fetch the stream from a cache, monitor the progress |
| 772 | * of the stream, etc. |
| 773 | * <p> |
| 774 | * This method is expected to have the the side effect of |
| 775 | * establishing the content type, and therefore setting the |
| 776 | * appropriate <code>EditorKit</code> to use for loading the stream. |
| 777 | * <p> |
| 778 | * If this the stream was an http connection, redirects |
| 779 | * will be followed and the resulting URL will be set as |
| 780 | * the <code>Document.StreamDescriptionProperty</code> so that relative |
| 781 | * URL's can be properly resolved. |
| 782 | * |
| 783 | * @param page the URL of the page |
| 784 | */ |
| 785 | protected InputStream getStream(URL page) throws IOException { |
| 786 | final URLConnection conn = page.openConnection(); |
| 787 | if (conn instanceof HttpURLConnection) { |
| 788 | HttpURLConnection hconn = (HttpURLConnection) conn; |
| 789 | hconn.setInstanceFollowRedirects(false); |
| 790 | Object postData = getPostData(); |
| 791 | if (postData != null) { |
| 792 | handlePostData(hconn, postData); |
| 793 | } |
| 794 | int response = hconn.getResponseCode(); |
| 795 | boolean redirect = (response >= 300 && response <= 399); |
| 796 | |
| 797 | /* |
| 798 | * In the case of a redirect, we want to actually change the URL |
| 799 | * that was input to the new, redirected URL |
| 800 | */ |
| 801 | if (redirect) { |
| 802 | String loc = conn.getHeaderField("Location"); |
| 803 | if (loc.startsWith("http", 0)) { |
| 804 | page = new URL(loc); |
| 805 | } else { |
| 806 | page = new URL(page, loc); |
| 807 | } |
| 808 | return getStream(page); |
| 809 | } |
| 810 | } |
| 811 | |
| 812 | // Connection properties handler should be forced to run on EDT, |
| 813 | // as it instantiates the EditorKit. |
| 814 | if (SwingUtilities.isEventDispatchThread()) { |
| 815 | handleConnectionProperties(conn); |
| 816 | } else { |
| 817 | try { |
| 818 | SwingUtilities.invokeAndWait(new Runnable() { |
| 819 | public void run() { |
| 820 | handleConnectionProperties(conn); |
| 821 | } |
| 822 | }); |
| 823 | } catch (InterruptedException e) { |
| 824 | throw new RuntimeException(e); |
| 825 | } catch (InvocationTargetException e) { |
| 826 | throw new RuntimeException(e); |
| 827 | } |
| 828 | } |
| 829 | return conn.getInputStream(); |
| 830 | } |
| 831 | |
| 832 | /** |
| 833 | * Handle URL connection properties (most notably, content type). |
| 834 | */ |
| 835 | private void handleConnectionProperties(URLConnection conn) { |
| 836 | if (pageProperties == null) { |
| 837 | pageProperties = new Hashtable(); |
| 838 | } |
| 839 | String type = conn.getContentType(); |
| 840 | if (type != null) { |
| 841 | setContentType(type); |
| 842 | pageProperties.put("content-type", type); |
| 843 | } |
| 844 | pageProperties.put(Document.StreamDescriptionProperty, conn.getURL()); |
| 845 | String enc = conn.getContentEncoding(); |
| 846 | if (enc != null) { |
| 847 | pageProperties.put("content-encoding", enc); |
| 848 | } |
| 849 | } |
| 850 | |
| 851 | private Object getPostData() { |
| 852 | return getDocument().getProperty(PostDataProperty); |
| 853 | } |
| 854 | |
| 855 | private void handlePostData(HttpURLConnection conn, Object postData) |
| 856 | throws IOException { |
| 857 | conn.setDoOutput(true); |
| 858 | DataOutputStream os = null; |
| 859 | try { |
| 860 | conn.setRequestProperty("Content-Type", |
| 861 | "application/x-www-form-urlencoded"); |
| 862 | os = new DataOutputStream(conn.getOutputStream()); |
| 863 | os.writeBytes((String) postData); |
| 864 | } finally { |
| 865 | if (os != null) { |
| 866 | os.close(); |
| 867 | } |
| 868 | } |
| 869 | } |
| 870 | |
| 871 | |
| 872 | /** |
| 873 | * Scrolls the view to the given reference location |
| 874 | * (that is, the value returned by the <code>UL.getRef</code> |
| 875 | * method for the URL being displayed). By default, this |
| 876 | * method only knows how to locate a reference in an |
| 877 | * HTMLDocument. The implementation calls the |
| 878 | * <code>scrollRectToVisible</code> method to |
| 879 | * accomplish the actual scrolling. If scrolling to a |
| 880 | * reference location is needed for document types other |
| 881 | * than HTML, this method should be reimplemented. |
| 882 | * This method will have no effect if the component |
| 883 | * is not visible. |
| 884 | * |
| 885 | * @param reference the named location to scroll to |
| 886 | */ |
| 887 | public void scrollToReference(String reference) { |
| 888 | Document d = getDocument(); |
| 889 | if (d instanceof HTMLDocument) { |
| 890 | HTMLDocument doc = (HTMLDocument) d; |
| 891 | HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A); |
| 892 | for (; iter.isValid(); iter.next()) { |
| 893 | AttributeSet a = iter.getAttributes(); |
| 894 | String nm = (String) a.getAttribute(HTML.Attribute.NAME); |
| 895 | if ((nm != null) && nm.equals(reference)) { |
| 896 | // found a matching reference in the document. |
| 897 | try { |
| 898 | int pos = iter.getStartOffset(); |
| 899 | Rectangle r = modelToView(pos); |
| 900 | if (r != null) { |
| 901 | // the view is visible, scroll it to the |
| 902 | // center of the current visible area. |
| 903 | Rectangle vis = getVisibleRect(); |
| 904 | //r.y -= (vis.height / 2); |
| 905 | r.height = vis.height; |
| 906 | scrollRectToVisible(r); |
| 907 | setCaretPosition(pos); |
| 908 | } |
| 909 | } catch (BadLocationException ble) { |
| 910 | UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this); |
| 911 | } |
| 912 | } |
| 913 | } |
| 914 | } |
| 915 | } |
| 916 | |
| 917 | /** |
| 918 | * Gets the current URL being displayed. If a URL was |
| 919 | * not specified in the creation of the document, this |
| 920 | * will return <code>null</code>, and relative URL's will not be |
| 921 | * resolved. |
| 922 | * |
| 923 | * @return the URL, or <code>null</code> if none |
| 924 | */ |
| 925 | public URL getPage() { |
| 926 | return (URL) getDocument().getProperty(Document.StreamDescriptionProperty); |
| 927 | } |
| 928 | |
| 929 | /** |
| 930 | * Sets the current URL being displayed. |
| 931 | * |
| 932 | * @param url the URL for display |
| 933 | * @exception IOException for a <code>null</code> or invalid URL |
| 934 | * specification |
| 935 | */ |
| 936 | public void setPage(String url) throws IOException { |
| 937 | if (url == null) { |
| 938 | throw new IOException("invalid url"); |
| 939 | } |
| 940 | URL page = new URL(url); |
| 941 | setPage(page); |
| 942 | } |
| 943 | |
| 944 | /** |
| 945 | * Gets the class ID for the UI. |
| 946 | * |
| 947 | * @return the string "EditorPaneUI" |
| 948 | * @see JComponent#getUIClassID |
| 949 | * @see UIDefaults#getUI |
| 950 | */ |
| 951 | public String getUIClassID() { |
| 952 | return uiClassID; |
| 953 | } |
| 954 | |
| 955 | /** |
| 956 | * Creates the default editor kit (<code>PlainEditorKit</code>) for when |
| 957 | * the component is first created. |
| 958 | * |
| 959 | * @return the editor kit |
| 960 | */ |
| 961 | protected EditorKit createDefaultEditorKit() { |
| 962 | return new PlainEditorKit(); |
| 963 | } |
| 964 | |
| 965 | /** |
| 966 | * Fetches the currently installed kit for handling content. |
| 967 | * <code>createDefaultEditorKit</code> is called to set up a default |
| 968 | * if necessary. |
| 969 | * |
| 970 | * @return the editor kit |
| 971 | */ |
| 972 | public EditorKit getEditorKit() { |
| 973 | if (kit == null) { |
| 974 | kit = createDefaultEditorKit(); |
| 975 | isUserSetEditorKit = false; |
| 976 | } |
| 977 | return kit; |
| 978 | } |
| 979 | |
| 980 | /** |
| 981 | * Gets the type of content that this editor |
| 982 | * is currently set to deal with. This is |
| 983 | * defined to be the type associated with the |
| 984 | * currently installed <code>EditorKit</code>. |
| 985 | * |
| 986 | * @return the content type, <code>null</code> if no editor kit set |
| 987 | */ |
| 988 | public final String getContentType() { |
| 989 | return (kit != null) ? kit.getContentType() : null; |
| 990 | } |
| 991 | |
| 992 | /** |
| 993 | * Sets the type of content that this editor |
| 994 | * handles. This calls <code>getEditorKitForContentType</code>, |
| 995 | * and then <code>setEditorKit</code> if an editor kit can |
| 996 | * be successfully located. This is mostly convenience method |
| 997 | * that can be used as an alternative to calling |
| 998 | * <code>setEditorKit</code> directly. |
| 999 | * <p> |
| 1000 | * If there is a charset definition specified as a parameter |
| 1001 | * of the content type specification, it will be used when |
| 1002 | * loading input streams using the associated <code>EditorKit</code>. |
| 1003 | * For example if the type is specified as |
| 1004 | * <code>text/html; charset=EUC-JP</code> the content |
| 1005 | * will be loaded using the <code>EditorKit</code> registered for |
| 1006 | * <code>text/html</code> and the Reader provided to |
| 1007 | * the <code>EditorKit</code> to load unicode into the document will |
| 1008 | * use the <code>EUC-JP</code> charset for translating |
| 1009 | * to unicode. If the type is not recognized, the content |
| 1010 | * will be loaded using the <code>EditorKit</code> registered |
| 1011 | * for plain text, <code>text/plain</code>. |
| 1012 | * |
| 1013 | * @param type the non-<code>null</code> mime type for the content editing |
| 1014 | * support |
| 1015 | * @see #getContentType |
| 1016 | * @beaninfo |
| 1017 | * description: the type of content |
| 1018 | * @throws NullPointerException if the <code>type</code> parameter |
| 1019 | * is <code>null</code> |
| 1020 | */ |
| 1021 | public final void setContentType(String type) { |
| 1022 | // The type could have optional info is part of it, |
| 1023 | // for example some charset info. We need to strip that |
| 1024 | // of and save it. |
| 1025 | int parm = type.indexOf(";"); |
| 1026 | if (parm > -1) { |
| 1027 | // Save the paramList. |
| 1028 | String paramList = type.substring(parm); |
| 1029 | // update the content type string. |
| 1030 | type = type.substring(0, parm).trim(); |
| 1031 | if (type.toLowerCase().startsWith("text/")) { |
| 1032 | setCharsetFromContentTypeParameters(paramList); |
| 1033 | } |
| 1034 | } |
| 1035 | if ((kit == null) || (! type.equals(kit.getContentType())) |
| 1036 | || !isUserSetEditorKit) { |
| 1037 | EditorKit k = getEditorKitForContentType(type); |
| 1038 | if (k != null && k != kit) { |
| 1039 | setEditorKit(k); |
| 1040 | isUserSetEditorKit = false; |
| 1041 | } |
| 1042 | } |
| 1043 | |
| 1044 | } |
| 1045 | |
| 1046 | /** |
| 1047 | * This method gets the charset information specified as part |
| 1048 | * of the content type in the http header information. |
| 1049 | */ |
| 1050 | private void setCharsetFromContentTypeParameters(String paramlist) { |
| 1051 | String charset = null; |
| 1052 | try { |
| 1053 | // paramlist is handed to us with a leading ';', strip it. |
| 1054 | int semi = paramlist.indexOf(';'); |
| 1055 | if (semi > -1 && semi < paramlist.length()-1) { |
| 1056 | paramlist = paramlist.substring(semi + 1); |
| 1057 | } |
| 1058 | |
| 1059 | if (paramlist.length() > 0) { |
| 1060 | // parse the paramlist into attr-value pairs & get the |
| 1061 | // charset pair's value |
| 1062 | HeaderParser hdrParser = new HeaderParser(paramlist); |
| 1063 | charset = hdrParser.findValue("charset"); |
| 1064 | if (charset != null) { |
| 1065 | putClientProperty("charset", charset); |
| 1066 | } |
| 1067 | } |
| 1068 | } |
| 1069 | catch (IndexOutOfBoundsException e) { |
| 1070 | // malformed parameter list, use charset we have |
| 1071 | } |
| 1072 | catch (NullPointerException e) { |
| 1073 | // malformed parameter list, use charset we have |
| 1074 | } |
| 1075 | catch (Exception e) { |
| 1076 | // malformed parameter list, use charset we have; but complain |
| 1077 | System.err.println("JEditorPane.getCharsetFromContentTypeParameters failed on: " + paramlist); |
| 1078 | e.printStackTrace(); |
| 1079 | } |
| 1080 | } |
| 1081 | |
| 1082 | |
| 1083 | /** |
| 1084 | * Sets the currently installed kit for handling |
| 1085 | * content. This is the bound property that |
| 1086 | * establishes the content type of the editor. |
| 1087 | * Any old kit is first deinstalled, then if kit is |
| 1088 | * non-<code>null</code>, |
| 1089 | * the new kit is installed, and a default document created for it. |
| 1090 | * A <code>PropertyChange</code> event ("editorKit") is always fired when |
| 1091 | * <code>setEditorKit</code> is called. |
| 1092 | * <p> |
| 1093 | * <em>NOTE: This has the side effect of changing the model, |
| 1094 | * because the <code>EditorKit</code> is the source of how a |
| 1095 | * particular type |
| 1096 | * of content is modeled. This method will cause <code>setDocument</code> |
| 1097 | * to be called on behalf of the caller to ensure integrity |
| 1098 | * of the internal state.</em> |
| 1099 | * |
| 1100 | * @param kit the desired editor behavior |
| 1101 | * @see #getEditorKit |
| 1102 | * @beaninfo |
| 1103 | * description: the currently installed kit for handling content |
| 1104 | * bound: true |
| 1105 | * expert: true |
| 1106 | */ |
| 1107 | public void setEditorKit(EditorKit kit) { |
| 1108 | EditorKit old = this.kit; |
| 1109 | isUserSetEditorKit = true; |
| 1110 | if (old != null) { |
| 1111 | old.deinstall(this); |
| 1112 | } |
| 1113 | this.kit = kit; |
| 1114 | if (this.kit != null) { |
| 1115 | this.kit.install(this); |
| 1116 | setDocument(this.kit.createDefaultDocument()); |
| 1117 | } |
| 1118 | firePropertyChange("editorKit", old, kit); |
| 1119 | } |
| 1120 | |
| 1121 | /** |
| 1122 | * Fetches the editor kit to use for the given type |
| 1123 | * of content. This is called when a type is requested |
| 1124 | * that doesn't match the currently installed type. |
| 1125 | * If the component doesn't have an <code>EditorKit</code> registered |
| 1126 | * for the given type, it will try to create an |
| 1127 | * <code>EditorKit</code> from the default <code>EditorKit</code> registry. |
| 1128 | * If that fails, a <code>PlainEditorKit</code> is used on the |
| 1129 | * assumption that all text documents can be represented |
| 1130 | * as plain text. |
| 1131 | * <p> |
| 1132 | * This method can be reimplemented to use some |
| 1133 | * other kind of type registry. This can |
| 1134 | * be reimplemented to use the Java Activation |
| 1135 | * Framework, for example. |
| 1136 | * |
| 1137 | * @param type the non-<code>null</code> content type |
| 1138 | * @return the editor kit |
| 1139 | */ |
| 1140 | public EditorKit getEditorKitForContentType(String type) { |
| 1141 | if (typeHandlers == null) { |
| 1142 | typeHandlers = new Hashtable(3); |
| 1143 | } |
| 1144 | EditorKit k = (EditorKit) typeHandlers.get(type); |
| 1145 | if (k == null) { |
| 1146 | k = createEditorKitForContentType(type); |
| 1147 | if (k != null) { |
| 1148 | setEditorKitForContentType(type, k); |
| 1149 | } |
| 1150 | } |
| 1151 | if (k == null) { |
| 1152 | k = createDefaultEditorKit(); |
| 1153 | } |
| 1154 | return k; |
| 1155 | } |
| 1156 | |
| 1157 | /** |
| 1158 | * Directly sets the editor kit to use for the given type. A |
| 1159 | * look-and-feel implementation might use this in conjunction |
| 1160 | * with <code>createEditorKitForContentType</code> to install handlers for |
| 1161 | * content types with a look-and-feel bias. |
| 1162 | * |
| 1163 | * @param type the non-<code>null</code> content type |
| 1164 | * @param k the editor kit to be set |
| 1165 | */ |
| 1166 | public void setEditorKitForContentType(String type, EditorKit k) { |
| 1167 | if (typeHandlers == null) { |
| 1168 | typeHandlers = new Hashtable(3); |
| 1169 | } |
| 1170 | typeHandlers.put(type, k); |
| 1171 | } |
| 1172 | |
| 1173 | /** |
| 1174 | * Replaces the currently selected content with new content |
| 1175 | * represented by the given string. If there is no selection |
| 1176 | * this amounts to an insert of the given text. If there |
| 1177 | * is no replacement text (i.e. the content string is empty |
| 1178 | * or <code>null</code>) this amounts to a removal of the |
| 1179 | * current selection. The replacement text will have the |
| 1180 | * attributes currently defined for input. If the component is not |
| 1181 | * editable, beep and return. |
| 1182 | * <p> |
| 1183 | * This method is thread safe, although most Swing methods |
| 1184 | * are not. Please see |
| 1185 | * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How |
| 1186 | * to Use Threads</A> for more information. |
| 1187 | * |
| 1188 | * @param content the content to replace the selection with. This |
| 1189 | * value can be <code>null</code> |
| 1190 | */ |
| 1191 | public void replaceSelection(String content) { |
| 1192 | if (! isEditable()) { |
| 1193 | UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this); |
| 1194 | return; |
| 1195 | } |
| 1196 | EditorKit kit = getEditorKit(); |
| 1197 | if(kit instanceof StyledEditorKit) { |
| 1198 | try { |
| 1199 | Document doc = getDocument(); |
| 1200 | Caret caret = getCaret(); |
| 1201 | int p0 = Math.min(caret.getDot(), caret.getMark()); |
| 1202 | int p1 = Math.max(caret.getDot(), caret.getMark()); |
| 1203 | if (doc instanceof AbstractDocument) { |
| 1204 | ((AbstractDocument)doc).replace(p0, p1 - p0, content, |
| 1205 | ((StyledEditorKit)kit).getInputAttributes()); |
| 1206 | } |
| 1207 | else { |
| 1208 | if (p0 != p1) { |
| 1209 | doc.remove(p0, p1 - p0); |
| 1210 | } |
| 1211 | if (content != null && content.length() > 0) { |
| 1212 | doc.insertString(p0, content, ((StyledEditorKit)kit). |
| 1213 | getInputAttributes()); |
| 1214 | } |
| 1215 | } |
| 1216 | } catch (BadLocationException e) { |
| 1217 | UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this); |
| 1218 | } |
| 1219 | } |
| 1220 | else { |
| 1221 | super.replaceSelection(content); |
| 1222 | } |
| 1223 | } |
| 1224 | |
| 1225 | /** |
| 1226 | * Creates a handler for the given type from the default registry |
| 1227 | * of editor kits. The registry is created if necessary. If the |
| 1228 | * registered class has not yet been loaded, an attempt |
| 1229 | * is made to dynamically load the prototype of the kit for the |
| 1230 | * given type. If the type was registered with a <code>ClassLoader</code>, |
| 1231 | * that <code>ClassLoader</code> will be used to load the prototype. |
| 1232 | * If there was no registered <code>ClassLoader</code>, |
| 1233 | * <code>Class.forName</code> will be used to load the prototype. |
| 1234 | * <p> |
| 1235 | * Once a prototype <code>EditorKit</code> instance is successfully |
| 1236 | * located, it is cloned and the clone is returned. |
| 1237 | * |
| 1238 | * @param type the content type |
| 1239 | * @return the editor kit, or <code>null</code> if there is nothing |
| 1240 | * registered for the given type |
| 1241 | */ |
| 1242 | public static EditorKit createEditorKitForContentType(String type) { |
| 1243 | EditorKit k = null; |
| 1244 | Hashtable kitRegistry = getKitRegisty(); |
| 1245 | k = (EditorKit) kitRegistry.get(type); |
| 1246 | if (k == null) { |
| 1247 | // try to dynamically load the support |
| 1248 | String classname = (String) getKitTypeRegistry().get(type); |
| 1249 | ClassLoader loader = (ClassLoader) getKitLoaderRegistry().get(type); |
| 1250 | try { |
| 1251 | Class c; |
| 1252 | if (loader != null) { |
| 1253 | c = loader.loadClass(classname); |
| 1254 | } else { |
| 1255 | // Will only happen if developer has invoked |
| 1256 | // registerEditorKitForContentType(type, class, null). |
| 1257 | c = Class.forName(classname, true, Thread.currentThread(). |
| 1258 | getContextClassLoader()); |
| 1259 | } |
| 1260 | k = (EditorKit) c.newInstance(); |
| 1261 | kitRegistry.put(type, k); |
| 1262 | } catch (Throwable e) { |
| 1263 | k = null; |
| 1264 | } |
| 1265 | } |
| 1266 | |
| 1267 | // create a copy of the prototype or null if there |
| 1268 | // is no prototype. |
| 1269 | if (k != null) { |
| 1270 | return (EditorKit) k.clone(); |
| 1271 | } |
| 1272 | return null; |
| 1273 | } |
| 1274 | |
| 1275 | /** |
| 1276 | * Establishes the default bindings of <code>type</code> to |
| 1277 | * <code>classname</code>. |
| 1278 | * The class will be dynamically loaded later when actually |
| 1279 | * needed, and can be safely changed before attempted uses |
| 1280 | * to avoid loading unwanted classes. The prototype |
| 1281 | * <code>EditorKit</code> will be loaded with <code>Class.forName</code> |
| 1282 | * when registered with this method. |
| 1283 | * |
| 1284 | * @param type the non-<code>null</code> content type |
| 1285 | * @param classname the class to load later |
| 1286 | */ |
| 1287 | public static void registerEditorKitForContentType(String type, String classname) { |
| 1288 | registerEditorKitForContentType(type, classname,Thread.currentThread(). |
| 1289 | getContextClassLoader()); |
| 1290 | } |
| 1291 | |
| 1292 | /** |
| 1293 | * Establishes the default bindings of <code>type</code> to |
| 1294 | * <code>classname</code>. |
| 1295 | * The class will be dynamically loaded later when actually |
| 1296 | * needed using the given <code>ClassLoader</code>, |
| 1297 | * and can be safely changed |
| 1298 | * before attempted uses to avoid loading unwanted classes. |
| 1299 | * |
| 1300 | * @param type the non-<code>null</code> content type |
| 1301 | * @param classname the class to load later |
| 1302 | * @param loader the <code>ClassLoader</code> to use to load the name |
| 1303 | */ |
| 1304 | public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) { |
| 1305 | getKitTypeRegistry().put(type, classname); |
| 1306 | getKitLoaderRegistry().put(type, loader); |
| 1307 | getKitRegisty().remove(type); |
| 1308 | } |
| 1309 | |
| 1310 | /** |
| 1311 | * Returns the currently registered <code>EditorKit</code> |
| 1312 | * class name for the type <code>type</code>. |
| 1313 | * |
| 1314 | * @param type the non-<code>null</code> content type |
| 1315 | * |
| 1316 | * @since 1.3 |
| 1317 | */ |
| 1318 | public static String getEditorKitClassNameForContentType(String type) { |
| 1319 | return (String)getKitTypeRegistry().get(type); |
| 1320 | } |
| 1321 | |
| 1322 | private static Hashtable getKitTypeRegistry() { |
| 1323 | loadDefaultKitsIfNecessary(); |
| 1324 | return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey); |
| 1325 | } |
| 1326 | |
| 1327 | private static Hashtable getKitLoaderRegistry() { |
| 1328 | loadDefaultKitsIfNecessary(); |
| 1329 | return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey); |
| 1330 | } |
| 1331 | |
| 1332 | private static Hashtable getKitRegisty() { |
| 1333 | Hashtable ht = (Hashtable)SwingUtilities.appContextGet(kitRegistryKey); |
| 1334 | if (ht == null) { |
| 1335 | ht = new Hashtable(3); |
| 1336 | SwingUtilities.appContextPut(kitRegistryKey, ht); |
| 1337 | } |
| 1338 | return ht; |
| 1339 | } |
| 1340 | |
| 1341 | /** |
| 1342 | * This is invoked every time the registries are accessed. Loading |
| 1343 | * is done this way instead of via a static as the static is only |
| 1344 | * called once when running in plugin resulting in the entries only |
| 1345 | * appearing in the first applet. |
| 1346 | */ |
| 1347 | private static void loadDefaultKitsIfNecessary() { |
| 1348 | if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) { |
| 1349 | synchronized(defaultEditorKitMap) { |
| 1350 | if (defaultEditorKitMap.size() == 0) { |
| 1351 | defaultEditorKitMap.put("text/plain", |
| 1352 | "javax.swing.JEditorPane$PlainEditorKit"); |
| 1353 | defaultEditorKitMap.put("text/html", |
| 1354 | "javax.swing.text.html.HTMLEditorKit"); |
| 1355 | defaultEditorKitMap.put("text/rtf", |
| 1356 | "javax.swing.text.rtf.RTFEditorKit"); |
| 1357 | defaultEditorKitMap.put("application/rtf", |
| 1358 | "javax.swing.text.rtf.RTFEditorKit"); |
| 1359 | } |
| 1360 | } |
| 1361 | Hashtable ht = new Hashtable(); |
| 1362 | SwingUtilities.appContextPut(kitTypeRegistryKey, ht); |
| 1363 | ht = new Hashtable(); |
| 1364 | SwingUtilities.appContextPut(kitLoaderRegistryKey, ht); |
| 1365 | for (String key : defaultEditorKitMap.keySet()) { |
| 1366 | registerEditorKitForContentType(key,defaultEditorKitMap.get(key)); |
| 1367 | } |
| 1368 | |
| 1369 | } |
| 1370 | } |
| 1371 | |
| 1372 | // --- java.awt.Component methods -------------------------- |
| 1373 | |
| 1374 | /** |
| 1375 | * Returns the preferred size for the <code>JEditorPane</code>. |
| 1376 | * The preferred size for <code>JEditorPane</code> is slightly altered |
| 1377 | * from the preferred size of the superclass. If the size |
| 1378 | * of the viewport has become smaller than the minimum size |
| 1379 | * of the component, the scrollable definition for tracking |
| 1380 | * width or height will turn to false. The default viewport |
| 1381 | * layout will give the preferred size, and that is not desired |
| 1382 | * in the case where the scrollable is tracking. In that case |
| 1383 | * the <em>normal</em> preferred size is adjusted to the |
| 1384 | * minimum size. This allows things like HTML tables to |
| 1385 | * shrink down to their minimum size and then be laid out at |
| 1386 | * their minimum size, refusing to shrink any further. |
| 1387 | * |
| 1388 | * @return a <code>Dimension</code> containing the preferred size |
| 1389 | */ |
| 1390 | public Dimension getPreferredSize() { |
| 1391 | Dimension d = super.getPreferredSize(); |
| 1392 | if (getParent() instanceof JViewport) { |
| 1393 | JViewport port = (JViewport)getParent(); |
| 1394 | TextUI ui = getUI(); |
| 1395 | int prefWidth = d.width; |
| 1396 | int prefHeight = d.height; |
| 1397 | if (! getScrollableTracksViewportWidth()) { |
| 1398 | int w = port.getWidth(); |
| 1399 | Dimension min = ui.getMinimumSize(this); |
| 1400 | if (w != 0 && w < min.width) { |
| 1401 | // Only adjust to min if we have a valid size |
| 1402 | prefWidth = min.width; |
| 1403 | } |
| 1404 | } |
| 1405 | if (! getScrollableTracksViewportHeight()) { |
| 1406 | int h = port.getHeight(); |
| 1407 | Dimension min = ui.getMinimumSize(this); |
| 1408 | if (h != 0 && h < min.height) { |
| 1409 | // Only adjust to min if we have a valid size |
| 1410 | prefHeight = min.height; |
| 1411 | } |
| 1412 | } |
| 1413 | if (prefWidth != d.width || prefHeight != d.height) { |
| 1414 | d = new Dimension(prefWidth, prefHeight); |
| 1415 | } |
| 1416 | } |
| 1417 | return d; |
| 1418 | } |
| 1419 | |
| 1420 | // --- JTextComponent methods ----------------------------- |
| 1421 | |
| 1422 | /** |
| 1423 | * Sets the text of this <code>TextComponent</code> to the specified |
| 1424 | * content, |
| 1425 | * which is expected to be in the format of the content type of |
| 1426 | * this editor. For example, if the type is set to <code>text/html</code> |
| 1427 | * the string should be specified in terms of HTML. |
| 1428 | * <p> |
| 1429 | * This is implemented to remove the contents of the current document, |
| 1430 | * and replace them by parsing the given string using the current |
| 1431 | * <code>EditorKit</code>. This gives the semantics of the |
| 1432 | * superclass by not changing |
| 1433 | * out the model, while supporting the content type currently set on |
| 1434 | * this component. The assumption is that the previous content is |
| 1435 | * relatively |
| 1436 | * small, and that the previous content doesn't have side effects. |
| 1437 | * Both of those assumptions can be violated and cause undesirable results. |
| 1438 | * To avoid this, create a new document, |
| 1439 | * <code>getEditorKit().createDefaultDocument()</code>, and replace the |
| 1440 | * existing <code>Document</code> with the new one. You are then assured the |
| 1441 | * previous <code>Document</code> won't have any lingering state. |
| 1442 | * <ol> |
| 1443 | * <li> |
| 1444 | * Leaving the existing model in place means that the old view will be |
| 1445 | * torn down, and a new view created, where replacing the document would |
| 1446 | * avoid the tear down of the old view. |
| 1447 | * <li> |
| 1448 | * Some formats (such as HTML) can install things into the document that |
| 1449 | * can influence future contents. HTML can have style information embedded |
| 1450 | * that would influence the next content installed unexpectedly. |
| 1451 | * </ol> |
| 1452 | * <p> |
| 1453 | * An alternative way to load this component with a string would be to |
| 1454 | * create a StringReader and call the read method. In this case the model |
| 1455 | * would be replaced after it was initialized with the contents of the |
| 1456 | * string. |
| 1457 | * <p> |
| 1458 | * This method is thread safe, although most Swing methods |
| 1459 | * are not. Please see |
| 1460 | * <A HREF="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html">How |
| 1461 | * to Use Threads</A> for more information. |
| 1462 | * |
| 1463 | * @param t the new text to be set; if <code>null</code> the old |
| 1464 | * text will be deleted |
| 1465 | * @see #getText |
| 1466 | * @beaninfo |
| 1467 | * description: the text of this component |
| 1468 | */ |
| 1469 | public void setText(String t) { |
| 1470 | try { |
| 1471 | Document doc = getDocument(); |
| 1472 | doc.remove(0, doc.getLength()); |
| 1473 | if (t == null || t.equals("")) { |
| 1474 | return; |
| 1475 | } |
| 1476 | Reader r = new StringReader(t); |
| 1477 | EditorKit kit = getEditorKit(); |
| 1478 | kit.read(r, doc, 0); |
| 1479 | } catch (IOException ioe) { |
| 1480 | UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this); |
| 1481 | } catch (BadLocationException ble) { |
| 1482 | UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this); |
| 1483 | } |
| 1484 | } |
| 1485 | |
| 1486 | /** |
| 1487 | * Returns the text contained in this <code>TextComponent</code> |
| 1488 | * in terms of the |
| 1489 | * content type of this editor. If an exception is thrown while |
| 1490 | * attempting to retrieve the text, <code>null</code> will be returned. |
| 1491 | * This is implemented to call <code>JTextComponent.write</code> with |
| 1492 | * a <code>StringWriter</code>. |
| 1493 | * |
| 1494 | * @return the text |
| 1495 | * @see #setText |
| 1496 | */ |
| 1497 | public String getText() { |
| 1498 | String txt; |
| 1499 | try { |
| 1500 | StringWriter buf = new StringWriter(); |
| 1501 | write(buf); |
| 1502 | txt = buf.toString(); |
| 1503 | } catch (IOException ioe) { |
| 1504 | txt = null; |
| 1505 | } |
| 1506 | return txt; |
| 1507 | } |
| 1508 | |
| 1509 | // --- Scrollable ---------------------------------------- |
| 1510 | |
| 1511 | /** |
| 1512 | * Returns true if a viewport should always force the width of this |
| 1513 | * <code>Scrollable</code> to match the width of the viewport. |
| 1514 | * |
| 1515 | * @return true if a viewport should force the Scrollables width to |
| 1516 | * match its own, false otherwise |
| 1517 | */ |
| 1518 | public boolean getScrollableTracksViewportWidth() { |
| 1519 | if (getParent() instanceof JViewport) { |
| 1520 | JViewport port = (JViewport)getParent(); |
| 1521 | TextUI ui = getUI(); |
| 1522 | int w = port.getWidth(); |
| 1523 | Dimension min = ui.getMinimumSize(this); |
| 1524 | Dimension max = ui.getMaximumSize(this); |
| 1525 | if ((w >= min.width) && (w <= max.width)) { |
| 1526 | return true; |
| 1527 | } |
| 1528 | } |
| 1529 | return false; |
| 1530 | } |
| 1531 | |
| 1532 | /** |
| 1533 | * Returns true if a viewport should always force the height of this |
| 1534 | * <code>Scrollable</code> to match the height of the viewport. |
| 1535 | * |
| 1536 | * @return true if a viewport should force the |
| 1537 | * <code>Scrollable</code>'s height to match its own, |
| 1538 | * false otherwise |
| 1539 | */ |
| 1540 | public boolean getScrollableTracksViewportHeight() { |
| 1541 | if (getParent() instanceof JViewport) { |
| 1542 | JViewport port = (JViewport)getParent(); |
| 1543 | TextUI ui = getUI(); |
| 1544 | int h = port.getHeight(); |
| 1545 | Dimension min = ui.getMinimumSize(this); |
| 1546 | if (h >= min.height) { |
| 1547 | Dimension max = ui.getMaximumSize(this); |
| 1548 | if (h <= max.height) { |
| 1549 | return true; |
| 1550 | } |
| 1551 | } |
| 1552 | } |
| 1553 | return false; |
| 1554 | } |
| 1555 | |
| 1556 | // --- Serialization ------------------------------------ |
| 1557 | |
| 1558 | /** |
| 1559 | * See <code>readObject</code> and <code>writeObject</code> in |
| 1560 | * <code>JComponent</code> for more |
| 1561 | * information about serialization in Swing. |
| 1562 | */ |
| 1563 | private void writeObject(ObjectOutputStream s) throws IOException { |
| 1564 | s.defaultWriteObject(); |
| 1565 | if (getUIClassID().equals(uiClassID)) { |
| 1566 | byte count = JComponent.getWriteObjCounter(this); |
| 1567 | JComponent.setWriteObjCounter(this, --count); |
| 1568 | if (count == 0 && ui != null) { |
| 1569 | ui.installUI(this); |
| 1570 | } |
| 1571 | } |
| 1572 | } |
| 1573 | |
| 1574 | // --- variables --------------------------------------- |
| 1575 | |
| 1576 | /** |
| 1577 | * Stream currently loading asynchronously (potentially cancelable). |
| 1578 | * Access to this variable should be synchronized. |
| 1579 | */ |
| 1580 | PageStream loading; |
| 1581 | |
| 1582 | /** |
| 1583 | * Current content binding of the editor. |
| 1584 | */ |
| 1585 | private EditorKit kit; |
| 1586 | private boolean isUserSetEditorKit; |
| 1587 | |
| 1588 | private Hashtable pageProperties; |
| 1589 | |
| 1590 | /** Should be kept in sync with javax.swing.text.html.FormView counterpart. */ |
| 1591 | final static String PostDataProperty = "javax.swing.JEditorPane.postdata"; |
| 1592 | |
| 1593 | /** |
| 1594 | * Table of registered type handlers for this editor. |
| 1595 | */ |
| 1596 | private Hashtable typeHandlers; |
| 1597 | |
| 1598 | /* |
| 1599 | * Private AppContext keys for this class's static variables. |
| 1600 | */ |
| 1601 | private static final Object kitRegistryKey = |
| 1602 | new StringBuffer("JEditorPane.kitRegistry"); |
| 1603 | private static final Object kitTypeRegistryKey = |
| 1604 | new StringBuffer("JEditorPane.kitTypeRegistry"); |
| 1605 | private static final Object kitLoaderRegistryKey = |
| 1606 | new StringBuffer("JEditorPane.kitLoaderRegistry"); |
| 1607 | |
| 1608 | /** |
| 1609 | * @see #getUIClassID |
| 1610 | * @see #readObject |
| 1611 | */ |
| 1612 | private static final String uiClassID = "EditorPaneUI"; |
| 1613 | |
| 1614 | |
| 1615 | /** |
| 1616 | * Key for a client property used to indicate whether |
| 1617 | * <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units"> |
| 1618 | * w3c compliant</a> length units are used for html rendering. |
| 1619 | * <p> |
| 1620 | * By default this is not enabled; to enable |
| 1621 | * it set the client {@link #putClientProperty property} with this name |
| 1622 | * to <code>Boolean.TRUE</code>. |
| 1623 | * |
| 1624 | * @since 1.5 |
| 1625 | */ |
| 1626 | public static final String W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits"; |
| 1627 | |
| 1628 | /** |
| 1629 | * Key for a client property used to indicate whether |
| 1630 | * the default font and foreground color from the component are |
| 1631 | * used if a font or foreground color is not specified in the styled |
| 1632 | * text. |
| 1633 | * <p> |
| 1634 | * The default varies based on the look and feel; |
| 1635 | * to enable it set the client {@link #putClientProperty property} with |
| 1636 | * this name to <code>Boolean.TRUE</code>. |
| 1637 | * |
| 1638 | * @since 1.5 |
| 1639 | */ |
| 1640 | public static final String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties"; |
| 1641 | |
| 1642 | static final Map<String, String> defaultEditorKitMap = new HashMap<String, String>(0); |
| 1643 | |
| 1644 | /** |
| 1645 | * Returns a string representation of this <code>JEditorPane</code>. |
| 1646 | * This method |
| 1647 | * is intended to be used only for debugging purposes, and the |
| 1648 | * content and format of the returned string may vary between |
| 1649 | * implementations. The returned string may be empty but may not |
| 1650 | * be <code>null</code>. |
| 1651 | * |
| 1652 | * @return a string representation of this <code>JEditorPane</code> |
| 1653 | */ |
| 1654 | protected String paramString() { |
| 1655 | String kitString = (kit != null ? |
| 1656 | kit.toString() : ""); |
| 1657 | String typeHandlersString = (typeHandlers != null ? |
| 1658 | typeHandlers.toString() : ""); |
| 1659 | |
| 1660 | return super.paramString() + |
| 1661 | ",kit=" + kitString + |
| 1662 | ",typeHandlers=" + typeHandlersString; |
| 1663 | } |
| 1664 | |
| 1665 | |
| 1666 | ///////////////// |
| 1667 | // Accessibility support |
| 1668 | //////////////// |
| 1669 | |
| 1670 | |
| 1671 | /** |
| 1672 | * Gets the AccessibleContext associated with this JEditorPane. |
| 1673 | * For editor panes, the AccessibleContext takes the form of an |
| 1674 | * AccessibleJEditorPane. |
| 1675 | * A new AccessibleJEditorPane instance is created if necessary. |
| 1676 | * |
| 1677 | * @return an AccessibleJEditorPane that serves as the |
| 1678 | * AccessibleContext of this JEditorPane |
| 1679 | */ |
| 1680 | public AccessibleContext getAccessibleContext() { |
| 1681 | if (getEditorKit() instanceof HTMLEditorKit) { |
| 1682 | if (accessibleContext == null || accessibleContext.getClass() != |
| 1683 | AccessibleJEditorPaneHTML.class) { |
| 1684 | accessibleContext = new AccessibleJEditorPaneHTML(); |
| 1685 | } |
| 1686 | } else if (accessibleContext == null || accessibleContext.getClass() != |
| 1687 | AccessibleJEditorPane.class) { |
| 1688 | accessibleContext = new AccessibleJEditorPane(); |
| 1689 | } |
| 1690 | return accessibleContext; |
| 1691 | } |
| 1692 | |
| 1693 | /** |
| 1694 | * This class implements accessibility support for the |
| 1695 | * <code>JEditorPane</code> class. It provides an implementation of the |
| 1696 | * Java Accessibility API appropriate to editor pane user-interface |
| 1697 | * elements. |
| 1698 | * <p> |
| 1699 | * <strong>Warning:</strong> |
| 1700 | * Serialized objects of this class will not be compatible with |
| 1701 | * future Swing releases. The current serialization support is |
| 1702 | * appropriate for short term storage or RMI between applications running |
| 1703 | * the same version of Swing. As of 1.4, support for long term storage |
| 1704 | * of all JavaBeans<sup><font size="-2">TM</font></sup> |
| 1705 | * has been added to the <code>java.beans</code> package. |
| 1706 | * Please see {@link java.beans.XMLEncoder}. |
| 1707 | */ |
| 1708 | protected class AccessibleJEditorPane extends AccessibleJTextComponent { |
| 1709 | |
| 1710 | /** |
| 1711 | * Gets the accessibleDescription property of this object. If this |
| 1712 | * property isn't set, returns the content type of this |
| 1713 | * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text"). |
| 1714 | * |
| 1715 | * @return the localized description of the object; <code>null</code> |
| 1716 | * if this object does not have a description |
| 1717 | * |
| 1718 | * @see #setAccessibleName |
| 1719 | */ |
| 1720 | public String getAccessibleDescription() { |
| 1721 | String description = accessibleDescription; |
| 1722 | |
| 1723 | // fallback to client property |
| 1724 | if (description == null) { |
| 1725 | description = (String)getClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY); |
| 1726 | } |
| 1727 | if (description == null) { |
| 1728 | description = JEditorPane.this.getContentType(); |
| 1729 | } |
| 1730 | return description; |
| 1731 | } |
| 1732 | |
| 1733 | /** |
| 1734 | * Gets the state set of this object. |
| 1735 | * |
| 1736 | * @return an instance of AccessibleStateSet describing the states |
| 1737 | * of the object |
| 1738 | * @see AccessibleStateSet |
| 1739 | */ |
| 1740 | public AccessibleStateSet getAccessibleStateSet() { |
| 1741 | AccessibleStateSet states = super.getAccessibleStateSet(); |
| 1742 | states.add(AccessibleState.MULTI_LINE); |
| 1743 | return states; |
| 1744 | } |
| 1745 | } |
| 1746 | |
| 1747 | /** |
| 1748 | * This class provides support for <code>AccessibleHypertext</code>, |
| 1749 | * and is used in instances where the <code>EditorKit</code> |
| 1750 | * installed in this <code>JEditorPane</code> is an instance of |
| 1751 | * <code>HTMLEditorKit</code>. |
| 1752 | * <p> |
| 1753 | * <strong>Warning:</strong> |
| 1754 | * Serialized objects of this class will not be compatible with |
| 1755 | * future Swing releases. The current serialization support is |
| 1756 | * appropriate for short term storage or RMI between applications running |
| 1757 | * the same version of Swing. As of 1.4, support for long term storage |
| 1758 | * of all JavaBeans<sup><font size="-2">TM</font></sup> |
| 1759 | * has been added to the <code>java.beans</code> package. |
| 1760 | * Please see {@link java.beans.XMLEncoder}. |
| 1761 | */ |
| 1762 | protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane { |
| 1763 | |
| 1764 | private AccessibleContext accessibleContext; |
| 1765 | |
| 1766 | public AccessibleText getAccessibleText() { |
| 1767 | return new JEditorPaneAccessibleHypertextSupport(); |
| 1768 | } |
| 1769 | |
| 1770 | protected AccessibleJEditorPaneHTML () { |
| 1771 | HTMLEditorKit kit = (HTMLEditorKit)JEditorPane.this.getEditorKit(); |
| 1772 | accessibleContext = kit.getAccessibleContext(); |
| 1773 | } |
| 1774 | |
| 1775 | /** |
| 1776 | * Returns the number of accessible children of the object. |
| 1777 | * |
| 1778 | * @return the number of accessible children of the object. |
| 1779 | */ |
| 1780 | public int getAccessibleChildrenCount() { |
| 1781 | if (accessibleContext != null) { |
| 1782 | return accessibleContext.getAccessibleChildrenCount(); |
| 1783 | } else { |
| 1784 | return 0; |
| 1785 | } |
| 1786 | } |
| 1787 | |
| 1788 | /** |
| 1789 | * Returns the specified Accessible child of the object. The Accessible |
| 1790 | * children of an Accessible object are zero-based, so the first child |
| 1791 | * of an Accessible child is at index 0, the second child is at index 1, |
| 1792 | * and so on. |
| 1793 | * |
| 1794 | * @param i zero-based index of child |
| 1795 | * @return the Accessible child of the object |
| 1796 | * @see #getAccessibleChildrenCount |
| 1797 | */ |
| 1798 | public Accessible getAccessibleChild(int i) { |
| 1799 | if (accessibleContext != null) { |
| 1800 | return accessibleContext.getAccessibleChild(i); |
| 1801 | } else { |
| 1802 | return null; |
| 1803 | } |
| 1804 | } |
| 1805 | |
| 1806 | /** |
| 1807 | * Returns the Accessible child, if one exists, contained at the local |
| 1808 | * coordinate Point. |
| 1809 | * |
| 1810 | * @param p The point relative to the coordinate system of this object. |
| 1811 | * @return the Accessible, if it exists, at the specified location; |
| 1812 | * otherwise null |
| 1813 | */ |
| 1814 | public Accessible getAccessibleAt(Point p) { |
| 1815 | if (accessibleContext != null && p != null) { |
| 1816 | try { |
| 1817 | AccessibleComponent acomp = |
| 1818 | accessibleContext.getAccessibleComponent(); |
| 1819 | if (acomp != null) { |
| 1820 | return acomp.getAccessibleAt(p); |
| 1821 | } else { |
| 1822 | return null; |
| 1823 | } |
| 1824 | } catch (IllegalComponentStateException e) { |
| 1825 | return null; |
| 1826 | } |
| 1827 | } else { |
| 1828 | return null; |
| 1829 | } |
| 1830 | } |
| 1831 | } |
| 1832 | |
| 1833 | /** |
| 1834 | * What's returned by |
| 1835 | * <code>AccessibleJEditorPaneHTML.getAccessibleText</code>. |
| 1836 | * |
| 1837 | * Provides support for <code>AccessibleHypertext</code> in case |
| 1838 | * there is an HTML document being displayed in this |
| 1839 | * <code>JEditorPane</code>. |
| 1840 | * |
| 1841 | */ |
| 1842 | protected class JEditorPaneAccessibleHypertextSupport |
| 1843 | extends AccessibleJEditorPane implements AccessibleHypertext { |
| 1844 | |
| 1845 | public class HTMLLink extends AccessibleHyperlink { |
| 1846 | Element element; |
| 1847 | |
| 1848 | public HTMLLink(Element e) { |
| 1849 | element = e; |
| 1850 | } |
| 1851 | |
| 1852 | /** |
| 1853 | * Since the document a link is associated with may have |
| 1854 | * changed, this method returns whether this Link is valid |
| 1855 | * anymore (with respect to the document it references). |
| 1856 | * |
| 1857 | * @return a flag indicating whether this link is still valid with |
| 1858 | * respect to the AccessibleHypertext it belongs to |
| 1859 | */ |
| 1860 | public boolean isValid() { |
| 1861 | return JEditorPaneAccessibleHypertextSupport.this.linksValid; |
| 1862 | } |
| 1863 | |
| 1864 | /** |
| 1865 | * Returns the number of accessible actions available in this Link |
| 1866 | * If there are more than one, the first one is NOT considered the |
| 1867 | * "default" action of this LINK object (e.g. in an HTML imagemap). |
| 1868 | * In general, links will have only one AccessibleAction in them. |
| 1869 | * |
| 1870 | * @return the zero-based number of Actions in this object |
| 1871 | */ |
| 1872 | public int getAccessibleActionCount() { |
| 1873 | return 1; |
| 1874 | } |
| 1875 | |
| 1876 | /** |
| 1877 | * Perform the specified Action on the object |
| 1878 | * |
| 1879 | * @param i zero-based index of actions |
| 1880 | * @return true if the the action was performed; else false. |
| 1881 | * @see #getAccessibleActionCount |
| 1882 | */ |
| 1883 | public boolean doAccessibleAction(int i) { |
| 1884 | if (i == 0 && isValid() == true) { |
| 1885 | URL u = (URL) getAccessibleActionObject(i); |
| 1886 | if (u != null) { |
| 1887 | HyperlinkEvent linkEvent = |
| 1888 | new HyperlinkEvent(JEditorPane.this, HyperlinkEvent.EventType.ACTIVATED, u); |
| 1889 | JEditorPane.this.fireHyperlinkUpdate(linkEvent); |
| 1890 | return true; |
| 1891 | } |
| 1892 | } |
| 1893 | return false; // link invalid or i != 0 |
| 1894 | } |
| 1895 | |
| 1896 | /** |
| 1897 | * Return a String description of this particular |
| 1898 | * link action. The string returned is the text |
| 1899 | * within the document associated with the element |
| 1900 | * which contains this link. |
| 1901 | * |
| 1902 | * @param i zero-based index of the actions |
| 1903 | * @return a String description of the action |
| 1904 | * @see #getAccessibleActionCount |
| 1905 | */ |
| 1906 | public String getAccessibleActionDescription(int i) { |
| 1907 | if (i == 0 && isValid() == true) { |
| 1908 | Document d = JEditorPane.this.getDocument(); |
| 1909 | if (d != null) { |
| 1910 | try { |
| 1911 | return d.getText(getStartIndex(), |
| 1912 | getEndIndex() - getStartIndex()); |
| 1913 | } catch (BadLocationException exception) { |
| 1914 | return null; |
| 1915 | } |
| 1916 | } |
| 1917 | } |
| 1918 | return null; |
| 1919 | } |
| 1920 | |
| 1921 | /** |
| 1922 | * Returns a URL object that represents the link. |
| 1923 | * |
| 1924 | * @param i zero-based index of the actions |
| 1925 | * @return an URL representing the HTML link itself |
| 1926 | * @see #getAccessibleActionCount |
| 1927 | */ |
| 1928 | public Object getAccessibleActionObject(int i) { |
| 1929 | if (i == 0 && isValid() == true) { |
| 1930 | AttributeSet as = element.getAttributes(); |
| 1931 | AttributeSet anchor = |
| 1932 | (AttributeSet) as.getAttribute(HTML.Tag.A); |
| 1933 | String href = (anchor != null) ? |
| 1934 | (String) anchor.getAttribute(HTML.Attribute.HREF) : null; |
| 1935 | if (href != null) { |
| 1936 | URL u; |
| 1937 | try { |
| 1938 | u = new URL(JEditorPane.this.getPage(), href); |
| 1939 | } catch (MalformedURLException m) { |
| 1940 | u = null; |
| 1941 | } |
| 1942 | return u; |
| 1943 | } |
| 1944 | } |
| 1945 | return null; // link invalid or i != 0 |
| 1946 | } |
| 1947 | |
| 1948 | /** |
| 1949 | * Return an object that represents the link anchor, |
| 1950 | * as appropriate for that link. E.g. from HTML: |
| 1951 | * <a href="http://www.sun.com/access">Accessibility</a> |
| 1952 | * this method would return a String containing the text: |
| 1953 | * 'Accessibility'. |
| 1954 | * |
| 1955 | * Similarly, from this HTML: |
| 1956 | * <a HREF="#top"><img src="top-hat.gif" alt="top hat"></a> |
| 1957 | * this might return the object ImageIcon("top-hat.gif", "top hat"); |
| 1958 | * |
| 1959 | * @param i zero-based index of the actions |
| 1960 | * @return an Object representing the hypertext anchor |
| 1961 | * @see #getAccessibleActionCount |
| 1962 | */ |
| 1963 | public Object getAccessibleActionAnchor(int i) { |
| 1964 | return getAccessibleActionDescription(i); |
| 1965 | } |
| 1966 | |
| 1967 | |
| 1968 | /** |
| 1969 | * Get the index with the hypertext document at which this |
| 1970 | * link begins |
| 1971 | * |
| 1972 | * @return index of start of link |
| 1973 | */ |
| 1974 | public int getStartIndex() { |
| 1975 | return element.getStartOffset(); |
| 1976 | } |
| 1977 | |
| 1978 | /** |
| 1979 | * Get the index with the hypertext document at which this |
| 1980 | * link ends |
| 1981 | * |
| 1982 | * @return index of end of link |
| 1983 | */ |
| 1984 | public int getEndIndex() { |
| 1985 | return element.getEndOffset(); |
| 1986 | } |
| 1987 | } |
| 1988 | |
| 1989 | private class LinkVector extends Vector { |
| 1990 | public int baseElementIndex(Element e) { |
| 1991 | HTMLLink l; |
| 1992 | for (int i = 0; i < elementCount; i++) { |
| 1993 | l = (HTMLLink) elementAt(i); |
| 1994 | if (l.element == e) { |
| 1995 | return i; |
| 1996 | } |
| 1997 | } |
| 1998 | return -1; |
| 1999 | } |
| 2000 | } |
| 2001 | |
| 2002 | LinkVector hyperlinks; |
| 2003 | boolean linksValid = false; |
| 2004 | |
| 2005 | /** |
| 2006 | * Build the private table mapping links to locations in the text |
| 2007 | */ |
| 2008 | private void buildLinkTable() { |
| 2009 | hyperlinks.removeAllElements(); |
| 2010 | Document d = JEditorPane.this.getDocument(); |
| 2011 | if (d != null) { |
| 2012 | ElementIterator ei = new ElementIterator(d); |
| 2013 | Element e; |
| 2014 | AttributeSet as; |
| 2015 | AttributeSet anchor; |
| 2016 | String href; |
| 2017 | while ((e = ei.next()) != null) { |
| 2018 | if (e.isLeaf()) { |
| 2019 | as = e.getAttributes(); |
| 2020 | anchor = (AttributeSet) as.getAttribute(HTML.Tag.A); |
| 2021 | href = (anchor != null) ? |
| 2022 | (String) anchor.getAttribute(HTML.Attribute.HREF) : null; |
| 2023 | if (href != null) { |
| 2024 | hyperlinks.addElement(new HTMLLink(e)); |
| 2025 | } |
| 2026 | } |
| 2027 | } |
| 2028 | } |
| 2029 | linksValid = true; |
| 2030 | } |
| 2031 | |
| 2032 | /** |
| 2033 | * Make one of these puppies |
| 2034 | */ |
| 2035 | public JEditorPaneAccessibleHypertextSupport() { |
| 2036 | hyperlinks = new LinkVector(); |
| 2037 | Document d = JEditorPane.this.getDocument(); |
| 2038 | if (d != null) { |
| 2039 | d.addDocumentListener(new DocumentListener() { |
| 2040 | public void changedUpdate(DocumentEvent theEvent) { |
| 2041 | linksValid = false; |
| 2042 | } |
| 2043 | public void insertUpdate(DocumentEvent theEvent) { |
| 2044 | linksValid = false; |
| 2045 | } |
| 2046 | public void removeUpdate(DocumentEvent theEvent) { |
| 2047 | linksValid = false; |
| 2048 | } |
| 2049 | }); |
| 2050 | } |
| 2051 | } |
| 2052 | |
| 2053 | /** |
| 2054 | * Returns the number of links within this hypertext doc. |
| 2055 | * |
| 2056 | * @return number of links in this hypertext doc. |
| 2057 | */ |
| 2058 | public int getLinkCount() { |
| 2059 | if (linksValid == false) { |
| 2060 | buildLinkTable(); |
| 2061 | } |
| 2062 | return hyperlinks.size(); |
| 2063 | } |
| 2064 | |
| 2065 | /** |
| 2066 | * Returns the index into an array of hyperlinks that |
| 2067 | * is associated with this character index, or -1 if there |
| 2068 | * is no hyperlink associated with this index. |
| 2069 | * |
| 2070 | * @param charIndex index within the text |
| 2071 | * @return index into the set of hyperlinks for this hypertext doc. |
| 2072 | */ |
| 2073 | public int getLinkIndex(int charIndex) { |
| 2074 | if (linksValid == false) { |
| 2075 | buildLinkTable(); |
| 2076 | } |
| 2077 | Element e = null; |
| 2078 | Document doc = JEditorPane.this.getDocument(); |
| 2079 | if (doc != null) { |
| 2080 | for (e = doc.getDefaultRootElement(); ! e.isLeaf(); ) { |
| 2081 | int index = e.getElementIndex(charIndex); |
| 2082 | e = e.getElement(index); |
| 2083 | } |
| 2084 | } |
| 2085 | |
| 2086 | // don't need to verify that it's an HREF element; if |
| 2087 | // not, then it won't be in the hyperlinks Vector, and |
| 2088 | // so indexOf will return -1 in any case |
| 2089 | return hyperlinks.baseElementIndex(e); |
| 2090 | } |
| 2091 | |
| 2092 | /** |
| 2093 | * Returns the index into an array of hyperlinks that |
| 2094 | * index. If there is no hyperlink at this index, it returns |
| 2095 | * null. |
| 2096 | * |
| 2097 | * @param linkIndex into the set of hyperlinks for this hypertext doc. |
| 2098 | * @return string representation of the hyperlink |
| 2099 | */ |
| 2100 | public AccessibleHyperlink getLink(int linkIndex) { |
| 2101 | if (linksValid == false) { |
| 2102 | buildLinkTable(); |
| 2103 | } |
| 2104 | if (linkIndex >= 0 && linkIndex < hyperlinks.size()) { |
| 2105 | return (AccessibleHyperlink) hyperlinks.elementAt(linkIndex); |
| 2106 | } else { |
| 2107 | return null; |
| 2108 | } |
| 2109 | } |
| 2110 | |
| 2111 | /** |
| 2112 | * Returns the contiguous text within the document that |
| 2113 | * is associated with this hyperlink. |
| 2114 | * |
| 2115 | * @param linkIndex into the set of hyperlinks for this hypertext doc. |
| 2116 | * @return the contiguous text sharing the link at this index |
| 2117 | */ |
| 2118 | public String getLinkText(int linkIndex) { |
| 2119 | if (linksValid == false) { |
| 2120 | buildLinkTable(); |
| 2121 | } |
| 2122 | Element e = (Element) hyperlinks.elementAt(linkIndex); |
| 2123 | if (e != null) { |
| 2124 | Document d = JEditorPane.this.getDocument(); |
| 2125 | if (d != null) { |
| 2126 | try { |
| 2127 | return d.getText(e.getStartOffset(), |
| 2128 | e.getEndOffset() - e.getStartOffset()); |
| 2129 | } catch (BadLocationException exception) { |
| 2130 | return null; |
| 2131 | } |
| 2132 | } |
| 2133 | } |
| 2134 | return null; |
| 2135 | } |
| 2136 | } |
| 2137 | |
| 2138 | static class PlainEditorKit extends DefaultEditorKit implements ViewFactory { |
| 2139 | |
| 2140 | /** |
| 2141 | * Fetches a factory that is suitable for producing |
| 2142 | * views of any models that are produced by this |
| 2143 | * kit. The default is to have the UI produce the |
| 2144 | * factory, so this method has no implementation. |
| 2145 | * |
| 2146 | * @return the view factory |
| 2147 | */ |
| 2148 | public ViewFactory getViewFactory() { |
| 2149 | return this; |
| 2150 | } |
| 2151 | |
| 2152 | /** |
| 2153 | * Creates a view from the given structural element of a |
| 2154 | * document. |
| 2155 | * |
| 2156 | * @param elem the piece of the document to build a view of |
| 2157 | * @return the view |
| 2158 | * @see View |
| 2159 | */ |
| 2160 | public View create(Element elem) { |
| 2161 | Document doc = elem.getDocument(); |
| 2162 | Object i18nFlag |
| 2163 | = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/); |
| 2164 | if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) { |
| 2165 | // build a view that support bidi |
| 2166 | return createI18N(elem); |
| 2167 | } else { |
| 2168 | return new WrappedPlainView(elem); |
| 2169 | } |
| 2170 | } |
| 2171 | |
| 2172 | View createI18N(Element elem) { |
| 2173 | String kind = elem.getName(); |
| 2174 | if (kind != null) { |
| 2175 | if (kind.equals(AbstractDocument.ContentElementName)) { |
| 2176 | return new PlainParagraph(elem); |
| 2177 | } else if (kind.equals(AbstractDocument.ParagraphElementName)){ |
| 2178 | return new BoxView(elem, View.Y_AXIS); |
| 2179 | } |
| 2180 | } |
| 2181 | return null; |
| 2182 | } |
| 2183 | |
| 2184 | /** |
| 2185 | * Paragraph for representing plain-text lines that support |
| 2186 | * bidirectional text. |
| 2187 | */ |
| 2188 | static class PlainParagraph extends javax.swing.text.ParagraphView { |
| 2189 | |
| 2190 | PlainParagraph(Element elem) { |
| 2191 | super(elem); |
| 2192 | layoutPool = new LogicalView(elem); |
| 2193 | layoutPool.setParent(this); |
| 2194 | } |
| 2195 | |
| 2196 | protected void setPropertiesFromAttributes() { |
| 2197 | Component c = getContainer(); |
| 2198 | if ((c != null) |
| 2199 | && (! c.getComponentOrientation().isLeftToRight())) |
| 2200 | { |
| 2201 | setJustification(StyleConstants.ALIGN_RIGHT); |
| 2202 | } else { |
| 2203 | setJustification(StyleConstants.ALIGN_LEFT); |
| 2204 | } |
| 2205 | } |
| 2206 | |
| 2207 | /** |
| 2208 | * Fetch the constraining span to flow against for |
| 2209 | * the given child index. |
| 2210 | */ |
| 2211 | public int getFlowSpan(int index) { |
| 2212 | Component c = getContainer(); |
| 2213 | if (c instanceof JTextArea) { |
| 2214 | JTextArea area = (JTextArea) c; |
| 2215 | if (! area.getLineWrap()) { |
| 2216 | // no limit if unwrapped |
| 2217 | return Integer.MAX_VALUE; |
| 2218 | } |
| 2219 | } |
| 2220 | return super.getFlowSpan(index); |
| 2221 | } |
| 2222 | |
| 2223 | protected SizeRequirements calculateMinorAxisRequirements(int axis, |
| 2224 | SizeRequirements r) |
| 2225 | { |
| 2226 | SizeRequirements req |
| 2227 | = super.calculateMinorAxisRequirements(axis, r); |
| 2228 | Component c = getContainer(); |
| 2229 | if (c instanceof JTextArea) { |
| 2230 | JTextArea area = (JTextArea) c; |
| 2231 | if (! area.getLineWrap()) { |
| 2232 | // min is pref if unwrapped |
| 2233 | req.minimum = req.preferred; |
| 2234 | } |
| 2235 | } |
| 2236 | return req; |
| 2237 | } |
| 2238 | |
| 2239 | /** |
| 2240 | * This class can be used to represent a logical view for |
| 2241 | * a flow. It keeps the children updated to reflect the state |
| 2242 | * of the model, gives the logical child views access to the |
| 2243 | * view hierarchy, and calculates a preferred span. It doesn't |
| 2244 | * do any rendering, layout, or model/view translation. |
| 2245 | */ |
| 2246 | static class LogicalView extends CompositeView { |
| 2247 | |
| 2248 | LogicalView(Element elem) { |
| 2249 | super(elem); |
| 2250 | } |
| 2251 | |
| 2252 | protected int getViewIndexAtPosition(int pos) { |
| 2253 | Element elem = getElement(); |
| 2254 | if (elem.getElementCount() > 0) { |
| 2255 | return elem.getElementIndex(pos); |
| 2256 | } |
| 2257 | return 0; |
| 2258 | } |
| 2259 | |
| 2260 | protected boolean |
| 2261 | updateChildren(DocumentEvent.ElementChange ec, |
| 2262 | DocumentEvent e, ViewFactory f) |
| 2263 | { |
| 2264 | return false; |
| 2265 | } |
| 2266 | |
| 2267 | protected void loadChildren(ViewFactory f) { |
| 2268 | Element elem = getElement(); |
| 2269 | if (elem.getElementCount() > 0) { |
| 2270 | super.loadChildren(f); |
| 2271 | } else { |
| 2272 | View v = new GlyphView(elem); |
| 2273 | append(v); |
| 2274 | } |
| 2275 | } |
| 2276 | |
| 2277 | public float getPreferredSpan(int axis) { |
| 2278 | if( getViewCount() != 1 ) |
| 2279 | throw new Error("One child view is assumed."); |
| 2280 | |
| 2281 | View v = getView(0); |
| 2282 | //((GlyphView)v).setGlyphPainter(null); |
| 2283 | return v.getPreferredSpan(axis); |
| 2284 | } |
| 2285 | |
| 2286 | /** |
| 2287 | * Forward the DocumentEvent to the given child view. This |
| 2288 | * is implemented to reparent the child to the logical view |
| 2289 | * (the children may have been parented by a row in the flow |
| 2290 | * if they fit without breaking) and then execute the |
| 2291 | * superclass behavior. |
| 2292 | * |
| 2293 | * @param v the child view to forward the event to. |
| 2294 | * @param e the change information from the associated document |
| 2295 | * @param a the current allocation of the view |
| 2296 | * @param f the factory to use to rebuild if the view has |
| 2297 | * children |
| 2298 | * @see #forwardUpdate |
| 2299 | * @since 1.3 |
| 2300 | */ |
| 2301 | protected void forwardUpdateToView(View v, DocumentEvent e, |
| 2302 | Shape a, ViewFactory f) { |
| 2303 | v.setParent(this); |
| 2304 | super.forwardUpdateToView(v, e, a, f); |
| 2305 | } |
| 2306 | |
| 2307 | // The following methods don't do anything useful, they |
| 2308 | // simply keep the class from being abstract. |
| 2309 | |
| 2310 | public void paint(Graphics g, Shape allocation) { |
| 2311 | } |
| 2312 | |
| 2313 | protected boolean isBefore(int x, int y, Rectangle alloc) { |
| 2314 | return false; |
| 2315 | } |
| 2316 | |
| 2317 | protected boolean isAfter(int x, int y, Rectangle alloc) { |
| 2318 | return false; |
| 2319 | } |
| 2320 | |
| 2321 | protected View getViewAtPoint(int x, int y, Rectangle alloc) { |
| 2322 | return null; |
| 2323 | } |
| 2324 | |
| 2325 | protected void childAllocation(int index, Rectangle a) { |
| 2326 | } |
| 2327 | } |
| 2328 | } |
| 2329 | } |
| 2330 | |
| 2331 | /* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers |
| 2332 | * sensibly: |
| 2333 | * From a String like: 'timeout=15, max=5' |
| 2334 | * create an array of Strings: |
| 2335 | * { {"timeout", "15"}, |
| 2336 | * {"max", "5"} |
| 2337 | * } |
| 2338 | * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"' |
| 2339 | * create one like (no quotes in literal): |
| 2340 | * { {"basic", null}, |
| 2341 | * {"realm", "FuzzFace"} |
| 2342 | * {"foo", "Biz Bar Baz"} |
| 2343 | * } |
| 2344 | * keys are converted to lower case, vals are left as is.... |
| 2345 | * |
| 2346 | * author Dave Brown |
| 2347 | */ |
| 2348 | |
| 2349 | |
| 2350 | static class HeaderParser { |
| 2351 | |
| 2352 | /* table of key/val pairs - maxes out at 10!!!!*/ |
| 2353 | String raw; |
| 2354 | String[][] tab; |
| 2355 | |
| 2356 | public HeaderParser(String raw) { |
| 2357 | this.raw = raw; |
| 2358 | tab = new String[10][2]; |
| 2359 | parse(); |
| 2360 | } |
| 2361 | |
| 2362 | private void parse() { |
| 2363 | |
| 2364 | if (raw != null) { |
| 2365 | raw = raw.trim(); |
| 2366 | char[] ca = raw.toCharArray(); |
| 2367 | int beg = 0, end = 0, i = 0; |
| 2368 | boolean inKey = true; |
| 2369 | boolean inQuote = false; |
| 2370 | int len = ca.length; |
| 2371 | while (end < len) { |
| 2372 | char c = ca[end]; |
| 2373 | if (c == '=') { // end of a key |
| 2374 | tab[i][0] = new String(ca, beg, end-beg).toLowerCase(); |
| 2375 | inKey = false; |
| 2376 | end++; |
| 2377 | beg = end; |
| 2378 | } else if (c == '\"') { |
| 2379 | if (inQuote) { |
| 2380 | tab[i++][1]= new String(ca, beg, end-beg); |
| 2381 | inQuote=false; |
| 2382 | do { |
| 2383 | end++; |
| 2384 | } while (end < len && (ca[end] == ' ' || ca[end] == ',')); |
| 2385 | inKey=true; |
| 2386 | beg=end; |
| 2387 | } else { |
| 2388 | inQuote=true; |
| 2389 | end++; |
| 2390 | beg=end; |
| 2391 | } |
| 2392 | } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in |
| 2393 | if (inQuote) { |
| 2394 | end++; |
| 2395 | continue; |
| 2396 | } else if (inKey) { |
| 2397 | tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase(); |
| 2398 | } else { |
| 2399 | tab[i++][1] = (new String(ca, beg, end-beg)); |
| 2400 | } |
| 2401 | while (end < len && (ca[end] == ' ' || ca[end] == ',')) { |
| 2402 | end++; |
| 2403 | } |
| 2404 | inKey = true; |
| 2405 | beg = end; |
| 2406 | } else { |
| 2407 | end++; |
| 2408 | } |
| 2409 | } |
| 2410 | // get last key/val, if any |
| 2411 | if (--end > beg) { |
| 2412 | if (!inKey) { |
| 2413 | if (ca[end] == '\"') { |
| 2414 | tab[i++][1] = (new String(ca, beg, end-beg)); |
| 2415 | } else { |
| 2416 | tab[i++][1] = (new String(ca, beg, end-beg+1)); |
| 2417 | } |
| 2418 | } else { |
| 2419 | tab[i][0] = (new String(ca, beg, end-beg+1)).toLowerCase(); |
| 2420 | } |
| 2421 | } else if (end == beg) { |
| 2422 | if (!inKey) { |
| 2423 | if (ca[end] == '\"') { |
| 2424 | tab[i++][1] = String.valueOf(ca[end-1]); |
| 2425 | } else { |
| 2426 | tab[i++][1] = String.valueOf(ca[end]); |
| 2427 | } |
| 2428 | } else { |
| 2429 | tab[i][0] = String.valueOf(ca[end]).toLowerCase(); |
| 2430 | } |
| 2431 | } |
| 2432 | } |
| 2433 | |
| 2434 | } |
| 2435 | |
| 2436 | public String findKey(int i) { |
| 2437 | if (i < 0 || i > 10) |
| 2438 | return null; |
| 2439 | return tab[i][0]; |
| 2440 | } |
| 2441 | |
| 2442 | public String findValue(int i) { |
| 2443 | if (i < 0 || i > 10) |
| 2444 | return null; |
| 2445 | return tab[i][1]; |
| 2446 | } |
| 2447 | |
| 2448 | public String findValue(String key) { |
| 2449 | return findValue(key, null); |
| 2450 | } |
| 2451 | |
| 2452 | public String findValue(String k, String Default) { |
| 2453 | if (k == null) |
| 2454 | return Default; |
| 2455 | k = k.toLowerCase(); |
| 2456 | for (int i = 0; i < 10; ++i) { |
| 2457 | if (tab[i][0] == null) { |
| 2458 | return Default; |
| 2459 | } else if (k.equals(tab[i][0])) { |
| 2460 | return tab[i][1]; |
| 2461 | } |
| 2462 | } |
| 2463 | return Default; |
| 2464 | } |
| 2465 | |
| 2466 | public int findInt(String k, int Default) { |
| 2467 | try { |
| 2468 | return Integer.parseInt(findValue(k, String.valueOf(Default))); |
| 2469 | } catch (Throwable t) { |
| 2470 | return Default; |
| 2471 | } |
| 2472 | } |
| 2473 | } |
| 2474 | |
| 2475 | } |