blob: 8395075295f5e64c12ccecd08d157f57aaea762f [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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 */
25package javax.swing;
26
27import java.awt.*;
28import java.awt.event.*;
29import java.lang.reflect.*;
30import java.net.*;
31import java.util.*;
32import java.io.*;
33import java.util.*;
34
35import javax.swing.plaf.*;
36import javax.swing.text.*;
37import javax.swing.event.*;
38import javax.swing.text.html.*;
39import 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 &lt;base&gt; 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&nbsp; class Hyperactive implements HyperlinkListener {
107&nbsp;
108&nbsp; public void hyperlinkUpdate(HyperlinkEvent e) {
109&nbsp; if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
110&nbsp; JEditorPane pane = (JEditorPane) e.getSource();
111&nbsp; if (e instanceof HTMLFrameHyperlinkEvent) {
112&nbsp; HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
113&nbsp; HTMLDocument doc = (HTMLDocument)pane.getDocument();
114&nbsp; doc.processHTMLFrameHyperlinkEvent(evt);
115&nbsp; } else {
116&nbsp; try {
117&nbsp; pane.setPage(e.getURL());
118&nbsp; } catch (Throwable t) {
119&nbsp; t.printStackTrace();
120&nbsp; }
121&nbsp; }
122&nbsp; }
123&nbsp; }
124&nbsp; }
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 */
190public 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 * &lt;a HREF="#top"&gt;&lt;img src="top-hat.gif" alt="top hat"&gt;&lt;/a&gt;
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
2350static 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}