001 /* JEditorPane.java --
002 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing;
040
041 import java.awt.Container;
042 import java.awt.Dimension;
043 import java.io.BufferedInputStream;
044 import java.io.FilterInputStream;
045 import java.io.IOException;
046 import java.io.InputStream;
047 import java.io.InputStreamReader;
048 import java.io.Reader;
049 import java.io.StringReader;
050 import java.net.MalformedURLException;
051 import java.net.URL;
052 import java.net.URLConnection;
053 import java.util.HashMap;
054
055 import javax.accessibility.AccessibleContext;
056 import javax.accessibility.AccessibleHyperlink;
057 import javax.accessibility.AccessibleHypertext;
058 import javax.accessibility.AccessibleStateSet;
059 import javax.accessibility.AccessibleText;
060 import javax.swing.event.HyperlinkEvent;
061 import javax.swing.event.HyperlinkListener;
062 import javax.swing.plaf.TextUI;
063 import javax.swing.text.AbstractDocument;
064 import javax.swing.text.BadLocationException;
065 import javax.swing.text.DefaultEditorKit;
066 import javax.swing.text.Document;
067 import javax.swing.text.EditorKit;
068 import javax.swing.text.Element;
069 import javax.swing.text.JTextComponent;
070 import javax.swing.text.View;
071 import javax.swing.text.ViewFactory;
072 import javax.swing.text.WrappedPlainView;
073 import javax.swing.text.html.HTML;
074 import javax.swing.text.html.HTMLDocument;
075 import javax.swing.text.html.HTMLEditorKit;
076
077 /**
078 * A powerful text editor component that can handle different types of
079 * content.
080 *
081 * The JEditorPane text component is driven by an instance of
082 * {@link EditorKit}. The editor kit is responsible for providing
083 * a default {@link Document} implementation, a mechanism for loading
084 * and saving documents of its supported content type and providing
085 * a set of {@link Action}s for manipulating the content.
086 *
087 * By default the following content types are supported:
088 * <ul>
089 * <li><code>text/plain</code>: Plain text, handled by
090 * {@link javax.swing.text.DefaultEditorKit}.</li>
091 * <li><code>text/html</code>: HTML 4.0 styled text, handled by
092 * {@link javax.swing.text.html.HTMLEditorKit}.</li>
093 * <li><code>text/rtf</code>: RTF text, handled by
094 * {@link javax.swing.text.rtf.RTFEditorKit}.</li>
095 * </ul>
096 *
097 * @author original author unknown
098 * @author Roman Kennke (roman@kennke.org)
099 * @author Anthony Balkissoon abalkiss at redhat dot com
100 */
101 public class JEditorPane extends JTextComponent
102 {
103 /**
104 * Provides accessibility support for <code>JEditorPane</code>.
105 *
106 * @author Roman Kennke (kennke@aicas.com)
107 */
108 protected class AccessibleJEditorPane extends AccessibleJTextComponent
109 {
110
111 /**
112 * Creates a new <code>AccessibleJEditorPane</code> object.
113 */
114 protected AccessibleJEditorPane()
115 {
116 super();
117 }
118
119 /**
120 * Returns a description of this <code>AccessibleJEditorPane</code>. If
121 * this property is not set, then this returns the content-type of the
122 * editor pane.
123 *
124 * @return a description of this AccessibleJEditorPane
125 */
126 public String getAccessibleDescription()
127 {
128 String descr = super.getAccessibleDescription();
129 if (descr == null)
130 return getContentType();
131 else
132 return descr;
133 }
134
135 /**
136 * Returns the accessible state of this <code>AccessibleJEditorPane</code>.
137 *
138 * @return the accessible state of this <code>AccessibleJEditorPane</code>
139 */
140 public AccessibleStateSet getAccessibleStateSet()
141 {
142 AccessibleStateSet state = super.getAccessibleStateSet();
143 // TODO: Figure out what state must be added here to the super's state.
144 return state;
145 }
146 }
147
148 /**
149 * Provides accessibility support for <code>JEditorPane</code>s, when the
150 * editor kit is an instance of {@link HTMLEditorKit}.
151 *
152 * @author Roman Kennke (kennke@aicas.com)
153 */
154 protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane
155 {
156 /**
157 * Returns the accessible text of the <code>JEditorPane</code>. This will
158 * be an instance of
159 * {@link JEditorPaneAccessibleHypertextSupport}.
160 *
161 * @return the accessible text of the <code>JEditorPane</code>
162 */
163 public AccessibleText getAccessibleText()
164 {
165 return new JEditorPaneAccessibleHypertextSupport();
166 }
167 }
168
169 /**
170 * This is the accessible text that is returned by
171 * {@link AccessibleJEditorPaneHTML#getAccessibleText()}.
172 *
173 * @author Roman Kennke (kennke@aicas.com)
174 */
175 protected class JEditorPaneAccessibleHypertextSupport
176 extends AccessibleJEditorPane implements AccessibleHypertext
177 {
178
179 /**
180 * Creates a new JEditorPaneAccessibleHypertextSupport object.
181 */
182 public JEditorPaneAccessibleHypertextSupport()
183 {
184 super();
185 }
186
187 /**
188 * The accessible representation of a HTML link.
189 *
190 * @author Roman Kennke (kennke@aicas.com)
191 */
192 public class HTMLLink extends AccessibleHyperlink
193 {
194
195 /**
196 * The element in the document that represents the link.
197 */
198 Element element;
199
200 /**
201 * Creates a new <code>HTMLLink</code>.
202 *
203 * @param el the link element
204 */
205 public HTMLLink(Element el)
206 {
207 this.element = el;
208 }
209
210 /**
211 * Returns <code>true</code> if this <code>HTMLLink</code> is still
212 * valid. A <code>HTMLLink</code> can become invalid when the document
213 * changes.
214 *
215 * @return <code>true</code> if this <code>HTMLLink</code> is still
216 * valid
217 */
218 public boolean isValid()
219 {
220 // I test here if the element at our element's start offset is the
221 // same as the element in the document at this offset. If this is true,
222 // I consider the link valid, if not, then this link no longer
223 // represented by this HTMLLink and therefor invalid.
224 HTMLDocument doc = (HTMLDocument) getDocument();
225 return doc.getCharacterElement(element.getStartOffset()) == element;
226 }
227
228 /**
229 * Returns the number of AccessibleActions in this link object. In
230 * general, link have 1 AccessibleAction associated with them. There are
231 * special cases where links can have multiple actions associated, like
232 * in image maps.
233 *
234 * @return the number of AccessibleActions in this link object
235 */
236 public int getAccessibleActionCount()
237 {
238 // TODO: Implement the special cases.
239 return 1;
240 }
241
242 /**
243 * Performs the specified action on the link object. This ususally means
244 * activating the link.
245 *
246 * @return <code>true</code> if the action has been performed
247 * successfully, <code>false</code> otherwise
248 */
249 public boolean doAccessibleAction(int i)
250 {
251 String href = (String) element.getAttributes().getAttribute("href");
252 HTMLDocument doc = (HTMLDocument) getDocument();
253 try
254 {
255 URL url = new URL(doc.getBase(), href);
256 setPage(url);
257 String desc = doc.getText(element.getStartOffset(),
258 element.getEndOffset() - element.getStartOffset());
259 HyperlinkEvent ev =
260 new HyperlinkEvent(JEditorPane.this,
261 HyperlinkEvent.EventType.ACTIVATED, url, desc,
262 element);
263 fireHyperlinkUpdate(ev);
264 return true;
265 }
266 catch (Exception ex)
267 {
268 return false;
269 }
270 }
271
272 /**
273 * Returns the description of the action at action index <code>i</code>.
274 * This method returns the text within the element associated with this
275 * link.
276 *
277 * @param i the action index
278 *
279 * @return the description of the action at action index <code>i</code>
280 */
281 public String getAccessibleActionDescription(int i)
282 {
283 HTMLDocument doc = (HTMLDocument) getDocument();
284 try
285 {
286 return doc.getText(element.getStartOffset(),
287 element.getEndOffset() - element.getStartOffset());
288 }
289 catch (BadLocationException ex)
290 {
291 throw (AssertionError)
292 new AssertionError("BadLocationException must not be thrown "
293 + "here.")
294 .initCause(ex);
295 }
296 }
297
298 /**
299 * Returns an {@link URL} object, that represents the action at action
300 * index <code>i</code>.
301 *
302 * @param i the action index
303 *
304 * @return an {@link URL} object, that represents the action at action
305 * index <code>i</code>
306 */
307 public Object getAccessibleActionObject(int i)
308 {
309 String href = (String) element.getAttributes().getAttribute("href");
310 HTMLDocument doc = (HTMLDocument) getDocument();
311 try
312 {
313 URL url = new URL(doc.getBase(), href);
314 return url;
315 }
316 catch (MalformedURLException ex)
317 {
318 return null;
319 }
320 }
321
322 /**
323 * Returns an object that represents the link anchor. For examples, if
324 * the link encloses a string, then a <code>String</code> object is
325 * returned, if the link encloses an <img> tag, then an
326 * <code>ImageIcon</code> object is returned.
327 *
328 * @return an object that represents the link anchor
329 */
330 public Object getAccessibleActionAnchor(int i)
331 {
332 // TODO: This is only the String case. Implement all cases.
333 return getAccessibleActionDescription(i);
334 }
335
336 /**
337 * Returns the start index of the hyperlink element.
338 *
339 * @return the start index of the hyperlink element
340 */
341 public int getStartIndex()
342 {
343 return element.getStartOffset();
344 }
345
346 /**
347 * Returns the end index of the hyperlink element.
348 *
349 * @return the end index of the hyperlink element
350 */
351 public int getEndIndex()
352 {
353 return element.getEndOffset();
354 }
355
356 }
357
358 /**
359 * Returns the number of hyperlinks in the document.
360 *
361 * @return the number of hyperlinks in the document
362 */
363 public int getLinkCount()
364 {
365 HTMLDocument doc = (HTMLDocument) getDocument();
366 HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
367 int count = 0;
368 while (linkIter.isValid())
369 {
370 count++;
371 linkIter.next();
372 }
373 return count;
374 }
375
376 /**
377 * Returns the <code>i</code>-th hyperlink in the document or
378 * <code>null</code> if there is no hyperlink with the specified index.
379 *
380 * @param i the index of the hyperlink to return
381 *
382 * @return the <code>i</code>-th hyperlink in the document or
383 * <code>null</code> if there is no hyperlink with the specified
384 * index
385 */
386 public AccessibleHyperlink getLink(int i)
387 {
388 HTMLDocument doc = (HTMLDocument) getDocument();
389 HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
390 int count = 0;
391 while (linkIter.isValid())
392 {
393 count++;
394 if (count == i)
395 break;
396 linkIter.next();
397 }
398 if (linkIter.isValid())
399 {
400 int offset = linkIter.getStartOffset();
401 // TODO: I fetch the element for the link via getCharacterElement().
402 // I am not sure that this is correct, maybe we must use
403 // getParagraphElement()?
404 Element el = doc.getCharacterElement(offset);
405 HTMLLink link = new HTMLLink(el);
406 return link;
407 }
408 else
409 return null;
410 }
411
412 /**
413 * Returns the index of the link element at the character position
414 * <code>c</code> within the document, or <code>-1</code> if there is no
415 * link at the specified position.
416 *
417 * @param c the character index from which to fetch the link index
418 *
419 * @return the index of the link element at the character position
420 * <code>c</code> within the document, or <code>-1</code> if there
421 * is no link at the specified position
422 */
423 public int getLinkIndex(int c)
424 {
425 HTMLDocument doc = (HTMLDocument) getDocument();
426 HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
427 int count = 0;
428 while (linkIter.isValid())
429 {
430 if (linkIter.getStartOffset() <= c && linkIter.getEndOffset() > c)
431 break;
432 count++;
433 linkIter.next();
434 }
435 if (linkIter.isValid())
436 return count;
437 else
438 return -1;
439 }
440
441 /**
442 * Returns the link text of the link at index <code>i</code>, or
443 * <code>null</code>, if there is no link at the specified position.
444 *
445 * @param i the index of the link
446 *
447 * @return the link text of the link at index <code>i</code>, or
448 * <code>null</code>, if there is no link at the specified
449 * position
450 */
451 public String getLinkText(int i)
452 {
453 HTMLDocument doc = (HTMLDocument) getDocument();
454 HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
455 int count = 0;
456 while (linkIter.isValid())
457 {
458 count++;
459 if (count == i)
460 break;
461 linkIter.next();
462 }
463 if (linkIter.isValid())
464 {
465 int offset = linkIter.getStartOffset();
466 // TODO: I fetch the element for the link via getCharacterElement().
467 // I am not sure that this is correct, maybe we must use
468 // getParagraphElement()?
469 Element el = doc.getCharacterElement(offset);
470 try
471 {
472 String text = doc.getText(el.getStartOffset(),
473 el.getEndOffset() - el.getStartOffset());
474 return text;
475 }
476 catch (BadLocationException ex)
477 {
478 throw (AssertionError)
479 new AssertionError("BadLocationException must not be thrown "
480 + "here.")
481 .initCause(ex);
482 }
483 }
484 else
485 return null;
486 }
487 }
488
489 /**
490 * Used to store a mapping for content-type to editor kit class.
491 */
492 private static class EditorKitMapping
493 {
494 /**
495 * The classname of the editor kit.
496 */
497 String className;
498
499 /**
500 * The classloader with which the kit is to be loaded.
501 */
502 ClassLoader classLoader;
503
504 /**
505 * Creates a new EditorKitMapping object.
506 *
507 * @param cn the classname
508 * @param cl the classloader
509 */
510 EditorKitMapping(String cn, ClassLoader cl)
511 {
512 className = cn;
513 classLoader = cl;
514 }
515 }
516
517 /**
518 * An EditorKit used for plain text. This is the default editor kit for
519 * JEditorPanes.
520 *
521 * @author Roman Kennke (kennke@aicas.com)
522 */
523 private static class PlainEditorKit extends DefaultEditorKit
524 {
525
526 /**
527 * Returns a ViewFactory that supplies WrappedPlainViews.
528 */
529 public ViewFactory getViewFactory()
530 {
531 return new ViewFactory()
532 {
533 public View create(Element el)
534 {
535 return new WrappedPlainView(el);
536 }
537 };
538 }
539 }
540
541 /**
542 * A special stream that can be cancelled.
543 */
544 private class PageStream
545 extends FilterInputStream
546 {
547 /**
548 * True when the stream has been cancelled, false otherwise.
549 */
550 private boolean cancelled;
551
552 protected PageStream(InputStream in)
553 {
554 super(in);
555 cancelled = false;
556 }
557
558 private void checkCancelled()
559 throws IOException
560 {
561 if (cancelled)
562 throw new IOException("Stream has been cancelled");
563 }
564
565 void cancel()
566 {
567 cancelled = true;
568 }
569
570 public int read()
571 throws IOException
572 {
573 checkCancelled();
574 return super.read();
575 }
576
577 public int read(byte[] b, int off, int len)
578 throws IOException
579 {
580 checkCancelled();
581 return super.read(b, off, len);
582 }
583
584 public long skip(long n)
585 throws IOException
586 {
587 checkCancelled();
588 return super.skip(n);
589 }
590
591 public int available()
592 throws IOException
593 {
594 checkCancelled();
595 return super.available();
596 }
597
598 public void reset()
599 throws IOException
600 {
601 checkCancelled();
602 super.reset();
603 }
604 }
605
606 /**
607 * The thread that loads documents asynchronously.
608 */
609 private class PageLoader
610 implements Runnable
611 {
612 private Document doc;
613 private PageStream in;
614 private URL old;
615 URL page;
616 PageLoader(Document doc, InputStream in, URL old, URL page)
617 {
618 this.doc = doc;
619 this.in = new PageStream(in);
620 this.old = old;
621 this.page = page;
622 }
623
624 public void run()
625 {
626 try
627 {
628 read(in, doc);
629 }
630 catch (IOException ex)
631 {
632 UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
633 }
634 finally
635 {
636 if (SwingUtilities.isEventDispatchThread())
637 firePropertyChange("page", old, page);
638 else
639 {
640 SwingUtilities.invokeLater(new Runnable()
641 {
642 public void run()
643 {
644 firePropertyChange("page", old, page);
645 }
646 });
647 }
648 }
649 }
650
651 void cancel()
652 {
653 in.cancel();
654 }
655 }
656
657 private static final long serialVersionUID = 3140472492599046285L;
658
659 private EditorKit editorKit;
660
661 boolean focus_root;
662
663 /**
664 * Maps content-types to editor kit instances.
665 */
666 static HashMap editorKits;
667
668 // A mapping between content types and registered EditorKit types
669 static HashMap registerMap;
670
671 static
672 {
673 registerMap = new HashMap();
674 editorKits = new HashMap();
675 registerEditorKitForContentType("application/rtf",
676 "javax.swing.text.rtf.RTFEditorKit");
677 registerEditorKitForContentType("text/plain",
678 "javax.swing.JEditorPane$PlainEditorKit");
679 registerEditorKitForContentType("text/html",
680 "javax.swing.text.html.HTMLEditorKit");
681 registerEditorKitForContentType("text/rtf",
682 "javax.swing.text.rtf.RTFEditorKit");
683
684 }
685
686 // A mapping between content types and used EditorKits
687 HashMap editorMap;
688
689 /**
690 * The currently loading stream, if any.
691 */
692 private PageLoader loader;
693
694 public JEditorPane()
695 {
696 init();
697 setEditorKit(createDefaultEditorKit());
698 }
699
700 public JEditorPane(String url) throws IOException
701 {
702 this(new URL(url));
703 }
704
705 public JEditorPane(String type, String text)
706 {
707 init();
708 setEditorKit(createEditorKitForContentType(type));
709 setText(text);
710 }
711
712 public JEditorPane(URL url) throws IOException
713 {
714 init();
715 setEditorKit(createEditorKitForContentType("text/html"));
716 setPage(url);
717 }
718
719 /**
720 * Called by the constructors to set up the default bindings for content
721 * types and EditorKits.
722 */
723 void init()
724 {
725 editorMap = new HashMap();
726 }
727
728 protected EditorKit createDefaultEditorKit()
729 {
730 return new PlainEditorKit();
731 }
732
733 /**
734 * Creates and returns an EditorKit that is appropriate for the given
735 * content type. This is created using the default recognized types
736 * plus any EditorKit types that have been registered.
737 *
738 * @see #registerEditorKitForContentType(String, String)
739 * @see #registerEditorKitForContentType(String, String, ClassLoader)
740 * @param type the content type
741 * @return an EditorKit for use with the given content type
742 */
743 public static EditorKit createEditorKitForContentType(String type)
744 {
745 // Try cached instance.
746 EditorKit e = (EditorKit) editorKits.get(type);
747 if (e == null)
748 {
749 EditorKitMapping m = (EditorKitMapping) registerMap.get(type);
750 if (m != null)
751 {
752 String className = m.className;
753 ClassLoader loader = m.classLoader;
754 try
755 {
756 e = (EditorKit) loader.loadClass(className).newInstance();
757 }
758 catch (Exception e2)
759 {
760 // The reference implementation returns null when class is not
761 // loadable or instantiatable.
762 }
763 }
764 // Cache this for later retrieval.
765 if (e != null)
766 editorKits.put(type, e);
767 }
768 return e;
769 }
770
771 /**
772 * Sends a given <code>HyperlinkEvent</code> to all registered listeners.
773 *
774 * @param event the event to send
775 */
776 public void fireHyperlinkUpdate(HyperlinkEvent event)
777 {
778 HyperlinkListener[] listeners = getHyperlinkListeners();
779
780 for (int index = 0; index < listeners.length; ++index)
781 listeners[index].hyperlinkUpdate(event);
782 }
783
784 /**
785 * Returns the accessible context associated with this editor pane.
786 *
787 * @return the accessible context associated with this editor pane
788 */
789 public AccessibleContext getAccessibleContext()
790 {
791 if (accessibleContext == null)
792 {
793 if (getEditorKit() instanceof HTMLEditorKit)
794 accessibleContext = new AccessibleJEditorPaneHTML();
795 else
796 accessibleContext = new AccessibleJEditorPane();
797 }
798 return accessibleContext;
799 }
800
801 public final String getContentType()
802 {
803 return getEditorKit().getContentType();
804 }
805
806 /**
807 * Returns the EditorKit. If there is no EditorKit set this method
808 * calls createDefaultEditorKit() and setEditorKit() first.
809 */
810 public EditorKit getEditorKit()
811 {
812 if (editorKit == null)
813 setEditorKit(createDefaultEditorKit());
814 return editorKit;
815 }
816
817 /**
818 * Returns the class name of the EditorKit associated with the given
819 * content type.
820 *
821 * @since 1.3
822 * @param type the content type
823 * @return the class name of the EditorKit associated with this content type
824 */
825 public static String getEditorKitClassNameForContentType(String type)
826 {
827 EditorKitMapping m = (EditorKitMapping) registerMap.get(type);
828 String kitName = m != null ? m.className : null;
829 return kitName;
830 }
831
832 /**
833 * Returns the EditorKit to use for the given content type. If an
834 * EditorKit has been explicitly set via
835 * <code>setEditorKitForContentType</code>
836 * then it will be returned. Otherwise an attempt will be made to create
837 * an EditorKit from the default recognzied content types or any
838 * EditorKits that have been registered. If none can be created, a
839 * PlainEditorKit is created.
840 *
841 * @see #registerEditorKitForContentType(String, String)
842 * @see #registerEditorKitForContentType(String, String, ClassLoader)
843 * @param type the content type
844 * @return an appropriate EditorKit for the given content type
845 */
846 public EditorKit getEditorKitForContentType(String type)
847 {
848 // First check if an EditorKit has been explicitly set.
849 EditorKit e = (EditorKit) editorMap.get(type);
850 // Then check to see if we can create one.
851 if (e == null)
852 {
853 e = createEditorKitForContentType(type);
854 if (e != null)
855 setEditorKitForContentType(type, e);
856 }
857 // Otherwise default to PlainEditorKit.
858 if (e == null)
859 e = createDefaultEditorKit();
860 return e;
861 }
862
863 /**
864 * Returns the preferred size for the JEditorPane. This is implemented to
865 * return the super's preferred size, unless one of
866 * {@link #getScrollableTracksViewportHeight()} or
867 * {@link #getScrollableTracksViewportWidth()} returns <code>true</code>,
868 * in which case the preferred width and/or height is replaced by the UI's
869 * minimum size.
870 *
871 * @return the preferred size for the JEditorPane
872 */
873 public Dimension getPreferredSize()
874 {
875 Dimension pref = super.getPreferredSize();
876 Container parent = getParent();
877 if (parent instanceof JViewport)
878 {
879 JViewport vp = (JViewport) getParent();
880 TextUI ui = getUI();
881 Dimension min = null;
882 if (! getScrollableTracksViewportWidth())
883 {
884 min = ui.getMinimumSize(this);
885 int vpWidth = vp.getWidth();
886 if (vpWidth != 0 && vpWidth < min.width)
887 pref.width = min.width;
888 }
889 if (! getScrollableTracksViewportHeight())
890 {
891 if (min == null)
892 min = ui.getMinimumSize(this);
893 int vpHeight = vp.getHeight();
894 if (vpHeight != 0 && vpHeight < min.height)
895 pref.height = min.height;
896 }
897 }
898 return pref;
899 }
900
901 /**
902 * Returns <code>true</code> when a Viewport should force the height of
903 * this component to match the viewport height. This is implemented to return
904 * <code>true</code> when the parent is an instance of JViewport and
905 * the viewport height > the UI's minimum height.
906 *
907 * @return <code>true</code> when a Viewport should force the height of
908 * this component to match the viewport height
909 */
910 public boolean getScrollableTracksViewportHeight()
911 {
912 // Tests show that this returns true when the parent is a JViewport
913 // and has a height > minimum UI height.
914 Container parent = getParent();
915 int height = parent.getHeight();
916 TextUI ui = getUI();
917 return parent instanceof JViewport
918 && height >= ui.getMinimumSize(this).height
919 && height <= ui.getMaximumSize(this).height;
920 }
921
922 /**
923 * Returns <code>true</code> when a Viewport should force the width of
924 * this component to match the viewport width. This is implemented to return
925 * <code>true</code> when the parent is an instance of JViewport and
926 * the viewport width > the UI's minimum width.
927 *
928 * @return <code>true</code> when a Viewport should force the width of
929 * this component to match the viewport width
930 */
931 public boolean getScrollableTracksViewportWidth()
932 {
933 // Tests show that this returns true when the parent is a JViewport
934 // and has a width > minimum UI width.
935 Container parent = getParent();
936 return parent != null && parent instanceof JViewport
937 && parent.getWidth() > getUI().getMinimumSize(this).width;
938 }
939
940 public URL getPage()
941 {
942 return loader != null ? loader.page : null;
943 }
944
945 protected InputStream getStream(URL page)
946 throws IOException
947 {
948 URLConnection conn = page.openConnection();
949 // Try to detect the content type of the stream data.
950 String type = conn.getContentType();
951 if (type != null)
952 setContentType(type);
953 InputStream stream = conn.getInputStream();
954 return new BufferedInputStream(stream);
955 }
956
957 public String getText()
958 {
959 return super.getText();
960 }
961
962 public String getUIClassID()
963 {
964 return "EditorPaneUI";
965 }
966
967 public boolean isFocusCycleRoot()
968 {
969 return focus_root;
970 }
971
972 protected String paramString()
973 {
974 return "JEditorPane";
975 }
976
977 /**
978 * This method initializes from a stream.
979 */
980 public void read(InputStream in, Object desc) throws IOException
981 {
982 EditorKit kit = getEditorKit();
983 if (kit instanceof HTMLEditorKit && desc instanceof HTMLDocument)
984 {
985 HTMLDocument doc = (HTMLDocument) desc;
986 setDocument(doc);
987 try
988 {
989 InputStreamReader reader = new InputStreamReader(in);
990 kit.read(reader, doc, 0);
991 }
992 catch (BadLocationException ex)
993 {
994 assert false : "BadLocationException must not be thrown here.";
995 }
996 }
997 else
998 {
999 Reader inRead = new InputStreamReader(in);
1000 super.read(inRead, desc);
1001 }
1002 }
1003
1004 /**
1005 * Establishes a binding between type and classname. This enables
1006 * us to create an EditorKit later for the given content type.
1007 *
1008 * @param type the content type
1009 * @param classname the name of the class that is associated with this
1010 * content type
1011 */
1012 public static void registerEditorKitForContentType(String type,
1013 String classname)
1014 {
1015 registerEditorKitForContentType(type, classname,
1016 Thread.currentThread().getContextClassLoader());
1017 }
1018
1019 /**
1020 * Establishes the default bindings of type to classname.
1021 */
1022 public static void registerEditorKitForContentType(String type,
1023 String classname,
1024 ClassLoader loader)
1025 {
1026 registerMap.put(type, new EditorKitMapping(classname, loader));
1027 }
1028
1029 /**
1030 * Replaces the currently selected content with new content represented
1031 * by the given string.
1032 */
1033 public void replaceSelection(String content)
1034 {
1035 // TODO: Implement this properly.
1036 super.replaceSelection(content);
1037 }
1038
1039 /**
1040 * Scrolls the view to the given reference location (that is, the value
1041 * returned by the UL.getRef method for the URL being displayed).
1042 */
1043 public void scrollToReference(String reference)
1044 {
1045 // TODO: Implement this properly.
1046 }
1047
1048 public final void setContentType(String type)
1049 {
1050 // Strip off content type parameters.
1051 int paramIndex = type.indexOf(';');
1052 if (paramIndex > -1)
1053 {
1054 // TODO: Handle character encoding.
1055 type = type.substring(0, paramIndex).trim();
1056 }
1057 if (editorKit != null
1058 && editorKit.getContentType().equals(type))
1059 return;
1060
1061 EditorKit kit = getEditorKitForContentType(type);
1062
1063 if (kit != null)
1064 setEditorKit(kit);
1065 }
1066
1067 public void setEditorKit(EditorKit newValue)
1068 {
1069 if (editorKit == newValue)
1070 return;
1071
1072 if (editorKit != null)
1073 editorKit.deinstall(this);
1074
1075 EditorKit oldValue = editorKit;
1076 editorKit = newValue;
1077
1078 if (editorKit != null)
1079 {
1080 editorKit.install(this);
1081 setDocument(editorKit.createDefaultDocument());
1082 }
1083
1084 firePropertyChange("editorKit", oldValue, newValue);
1085 invalidate();
1086 repaint();
1087 // Reset the accessibleContext since this depends on the editorKit.
1088 accessibleContext = null;
1089 }
1090
1091 /**
1092 * Explicitly sets an EditorKit to be used for the given content type.
1093 * @param type the content type
1094 * @param k the EditorKit to use for the given content type
1095 */
1096 public void setEditorKitForContentType(String type, EditorKit k)
1097 {
1098 editorMap.put(type, k);
1099 }
1100
1101 /**
1102 * Sets the current URL being displayed.
1103 */
1104 public void setPage(String url) throws IOException
1105 {
1106 setPage(new URL(url));
1107 }
1108
1109 /**
1110 * Sets the current URL being displayed.
1111 */
1112 public void setPage(URL page) throws IOException
1113 {
1114 if (page == null)
1115 throw new IOException("invalid url");
1116
1117 URL old = getPage();
1118 // Only reload if the URL doesn't point to the same file.
1119 // This is not the same as equals because there might be different
1120 // URLs on the same file with different anchors.
1121 if (old == null || ! old.sameFile(page))
1122 {
1123 InputStream in = getStream(page);
1124 if (editorKit != null)
1125 {
1126 Document doc = editorKit.createDefaultDocument();
1127 doc.putProperty(Document.StreamDescriptionProperty, page);
1128
1129 if (loader != null)
1130 loader.cancel();
1131 loader = new PageLoader(doc, in, old, page);
1132
1133 int prio = -1;
1134 if (doc instanceof AbstractDocument)
1135 {
1136 AbstractDocument aDoc = (AbstractDocument) doc;
1137 prio = aDoc.getAsynchronousLoadPriority();
1138 }
1139 if (prio >= 0)
1140 {
1141 // Load asynchronously.
1142 setDocument(doc);
1143 Thread loadThread = new Thread(loader,
1144 "JEditorPane.PageLoader");
1145 loadThread.setDaemon(true);
1146 loadThread.setPriority(prio);
1147 loadThread.start();
1148 }
1149 else
1150 {
1151 // Load synchronously.
1152 loader.run();
1153 setDocument(doc);
1154 }
1155 }
1156 }
1157 }
1158
1159 /**
1160 * Sets the text of the JEditorPane. The argument <code>t</code>
1161 * is expected to be in the format of the current EditorKit. This removes
1162 * the content of the current document and uses the EditorKit to read in the
1163 * new text. This allows the EditorKit to handle the String rather than just
1164 * inserting in plain text.
1165 *
1166 * @param t the text to display in this JEditorPane
1167 */
1168 public void setText(String t)
1169 {
1170 try
1171 {
1172 // Remove the current content.
1173 Document doc = getDocument();
1174 doc.remove(0, doc.getLength());
1175 if (t == null || t.equals(""))
1176 return;
1177
1178 // Let the EditorKit read the text into the Document.
1179 getEditorKit().read(new StringReader(t), doc, 0);
1180 }
1181 catch (BadLocationException ble)
1182 {
1183 // TODO: Don't know what to do here.
1184 }
1185 catch (IOException ioe)
1186 {
1187 // TODO: Don't know what to do here.
1188 }
1189 }
1190
1191 /**
1192 * Add a <code>HyperlinkListener</code> object to this editor pane.
1193 *
1194 * @param listener the listener to add
1195 */
1196 public void addHyperlinkListener(HyperlinkListener listener)
1197 {
1198 listenerList.add(HyperlinkListener.class, listener);
1199 }
1200
1201 /**
1202 * Removes a <code>HyperlinkListener</code> object to this editor pane.
1203 *
1204 * @param listener the listener to remove
1205 */
1206 public void removeHyperlinkListener(HyperlinkListener listener)
1207 {
1208 listenerList.remove(HyperlinkListener.class, listener);
1209 }
1210
1211 /**
1212 * Returns all added <code>HyperlinkListener</code> objects.
1213 *
1214 * @return array of listeners
1215 *
1216 * @since 1.4
1217 */
1218 public HyperlinkListener[] getHyperlinkListeners()
1219 {
1220 return (HyperlinkListener[]) getListeners(HyperlinkListener.class);
1221 }
1222 }