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 }