Source for javax.swing.JEditorPane

   1: /* JEditorPane.java --
   2:    Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing;
  40: 
  41: import java.awt.Container;
  42: import java.awt.Dimension;
  43: import java.io.IOException;
  44: import java.io.InputStream;
  45: import java.io.InputStreamReader;
  46: import java.io.Reader;
  47: import java.io.StringReader;
  48: import java.net.MalformedURLException;
  49: import java.net.URL;
  50: import java.util.HashMap;
  51: 
  52: import javax.accessibility.AccessibleContext;
  53: import javax.accessibility.AccessibleHyperlink;
  54: import javax.accessibility.AccessibleHypertext;
  55: import javax.accessibility.AccessibleStateSet;
  56: import javax.accessibility.AccessibleText;
  57: import javax.swing.event.HyperlinkEvent;
  58: import javax.swing.event.HyperlinkListener;
  59: import javax.swing.text.BadLocationException;
  60: import javax.swing.text.DefaultEditorKit;
  61: import javax.swing.text.Document;
  62: import javax.swing.text.EditorKit;
  63: import javax.swing.text.Element;
  64: import javax.swing.text.JTextComponent;
  65: import javax.swing.text.View;
  66: import javax.swing.text.ViewFactory;
  67: import javax.swing.text.WrappedPlainView;
  68: import javax.swing.text.html.HTML;
  69: import javax.swing.text.html.HTMLDocument;
  70: import javax.swing.text.html.HTMLEditorKit;
  71: 
  72: /**
  73:  * A powerful text editor component that can handle different types of
  74:  * content.
  75:  *
  76:  * The JEditorPane text component is driven by an instance of
  77:  * {@link EditorKit}. The editor kit is responsible for providing
  78:  * a default {@link Document} implementation, a mechanism for loading
  79:  * and saving documents of its supported content type and providing
  80:  * a set of {@link Action}s for manipulating the content.
  81:  *
  82:  * By default the following content types are supported:
  83:  * <ul>
  84:  * <li><code>text/plain</code>: Plain text, handled by
  85:  *   {@link javax.swing.text.DefaultEditorKit}.</li>
  86:  * <li><code>text/html</code>: HTML 4.0 styled text, handled by
  87:  *   {@link javax.swing.text.html.HTMLEditorKit}.</li>
  88:  * <li><code>text/rtf</code>: RTF text, handled by
  89:  *   {@link javax.swing.text.rtf.RTFEditorKit}.</li>
  90:  * </ul>
  91:  *
  92:  * @author original author unknown
  93:  * @author Roman Kennke (roman@kennke.org)
  94:  * @author Anthony Balkissoon abalkiss at redhat dot com
  95:  */
  96: public class JEditorPane extends JTextComponent
  97: {
  98:   /**
  99:    * Provides accessibility support for <code>JEditorPane</code>.
 100:    *
 101:    * @author Roman Kennke (kennke@aicas.com)
 102:    */
 103:   protected class AccessibleJEditorPane extends AccessibleJTextComponent
 104:   {
 105: 
 106:     /**
 107:      * Creates a new <code>AccessibleJEditorPane</code> object.
 108:      */
 109:     protected AccessibleJEditorPane()
 110:     {
 111:       super();
 112:     }
 113: 
 114:     /**
 115:      * Returns a description of this <code>AccessibleJEditorPane</code>. If
 116:      * this property is not set, then this returns the content-type of the
 117:      * editor pane.
 118:      *
 119:      * @return a description of this AccessibleJEditorPane
 120:      */
 121:     public String getAccessibleDescription()
 122:     {
 123:       String descr = super.getAccessibleDescription(); 
 124:       if (descr == null)
 125:         return getContentType();
 126:       else
 127:         return descr;
 128:     }
 129: 
 130:     /**
 131:      * Returns the accessible state of this <code>AccessibleJEditorPane</code>.
 132:      *
 133:      * @return  the accessible state of this <code>AccessibleJEditorPane</code>
 134:      */
 135:     public AccessibleStateSet getAccessibleStateSet()
 136:     {
 137:       AccessibleStateSet state = super.getAccessibleStateSet();
 138:       // TODO: Figure out what state must be added here to the super's state.
 139:       return state;
 140:     }
 141:   }
 142: 
 143:   /**
 144:    * Provides accessibility support for <code>JEditorPane</code>s, when the
 145:    * editor kit is an instance of {@link HTMLEditorKit}.
 146:    *
 147:    * @author Roman Kennke (kennke@aicas.com)
 148:    */
 149:   protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane
 150:   {
 151:     /**
 152:      * Returns the accessible text of the <code>JEditorPane</code>. This will
 153:      * be an instance of
 154:      * {@link JEditorPaneAccessibleHypertextSupport}.
 155:      *
 156:      * @return the accessible text of the <code>JEditorPane</code>
 157:      */
 158:     public AccessibleText getAccessibleText()
 159:     {
 160:       return new JEditorPaneAccessibleHypertextSupport();
 161:     }
 162:   }
 163: 
 164:   /**
 165:    * This is the accessible text that is returned by
 166:    * {@link AccessibleJEditorPaneHTML#getAccessibleText()}.
 167:    *
 168:    * @author Roman Kennke (kennke@aicas.com)
 169:    */
 170:   protected class JEditorPaneAccessibleHypertextSupport
 171:     extends AccessibleJEditorPane implements AccessibleHypertext
 172:   {
 173: 
 174:     /**
 175:      * Creates a new JEditorPaneAccessibleHypertextSupport object.
 176:      */
 177:     public JEditorPaneAccessibleHypertextSupport()
 178:     {
 179:       super();
 180:     }
 181:     
 182:     /**
 183:      * The accessible representation of a HTML link. 
 184:      *
 185:      * @author Roman Kennke (kennke@aicas.com)
 186:      */
 187:     public class HTMLLink extends AccessibleHyperlink
 188:     {
 189: 
 190:       /**
 191:        * The element in the document that represents the link.
 192:        */
 193:       Element element;
 194: 
 195:       /**
 196:        * Creates a new <code>HTMLLink</code>.
 197:        *
 198:        * @param el the link element
 199:        */
 200:       public HTMLLink(Element el)
 201:       {
 202:         this.element = el;
 203:       }
 204: 
 205:       /**
 206:        * Returns <code>true</code> if this <code>HTMLLink</code> is still
 207:        * valid. A <code>HTMLLink</code> can become invalid when the document
 208:        * changes.
 209:        *
 210:        * @return <code>true</code> if this <code>HTMLLink</code> is still
 211:        *         valid
 212:        */
 213:       public boolean isValid()
 214:       {
 215:         // I test here if the element at our element's start offset is the
 216:         // same as the element in the document at this offset. If this is true,
 217:         // I consider the link valid, if not, then this link no longer
 218:         // represented by this HTMLLink and therefor invalid.
 219:         HTMLDocument doc = (HTMLDocument) getDocument();
 220:         return doc.getCharacterElement(element.getStartOffset()) == element;
 221:       }
 222: 
 223:       /**
 224:        * Returns the number of AccessibleActions in this link object. In
 225:        * general, link have 1 AccessibleAction associated with them. There are
 226:        * special cases where links can have multiple actions associated, like
 227:        * in image maps.
 228:        * 
 229:        * @return the number of AccessibleActions in this link object
 230:        */
 231:       public int getAccessibleActionCount()
 232:       {
 233:         // TODO: Implement the special cases.
 234:         return 1;
 235:       }
 236: 
 237:       /**
 238:        * Performs the specified action on the link object. This ususally means
 239:        * activating the link.
 240:        *
 241:        * @return <code>true</code> if the action has been performed
 242:        *         successfully, <code>false</code> otherwise
 243:        */
 244:       public boolean doAccessibleAction(int i)
 245:       {
 246:         String href = (String) element.getAttributes().getAttribute("href");
 247:         HTMLDocument doc = (HTMLDocument) getDocument();
 248:         try
 249:           {
 250:             URL url = new URL(doc.getBase(), href);
 251:             setPage(url);
 252:             String desc = doc.getText(element.getStartOffset(),
 253:                             element.getEndOffset() - element.getStartOffset());
 254:             HyperlinkEvent ev =
 255:               new HyperlinkEvent(JEditorPane.this,
 256:                                  HyperlinkEvent.EventType.ACTIVATED, url, desc,
 257:                                  element);
 258:             fireHyperlinkUpdate(ev);
 259:             return true;
 260:           }
 261:         catch (Exception ex)
 262:           {
 263:             return false;
 264:           }
 265:       }
 266: 
 267:       /**
 268:        * Returns the description of the action at action index <code>i</code>.
 269:        * This method returns the text within the element associated with this
 270:        * link.
 271:        *
 272:        * @param i the action index
 273:        *
 274:        * @return the description of the action at action index <code>i</code>
 275:        */
 276:       public String getAccessibleActionDescription(int i)
 277:       {
 278:         HTMLDocument doc = (HTMLDocument) getDocument();
 279:         try
 280:           {
 281:             return doc.getText(element.getStartOffset(),
 282:                             element.getEndOffset() - element.getStartOffset());
 283:           }
 284:         catch (BadLocationException ex)
 285:           {
 286:             throw (AssertionError)
 287:             new AssertionError("BadLocationException must not be thrown "
 288:                                + "here.")
 289:               .initCause(ex);
 290:           }
 291:       }
 292: 
 293:       /**
 294:        * Returns an {@link URL} object, that represents the action at action
 295:        * index <code>i</code>.
 296:        *
 297:        * @param i the action index
 298:        *
 299:        * @return an {@link URL} object, that represents the action at action
 300:        *         index <code>i</code>
 301:        */
 302:       public Object getAccessibleActionObject(int i)
 303:       {
 304:         String href = (String) element.getAttributes().getAttribute("href");
 305:         HTMLDocument doc = (HTMLDocument) getDocument();
 306:         try
 307:           {
 308:             URL url = new URL(doc.getBase(), href);
 309:             return url;
 310:           }
 311:         catch (MalformedURLException ex)
 312:           {
 313:             return null;
 314:           }
 315:       }
 316: 
 317:       /**
 318:        * Returns an object that represents the link anchor. For examples, if
 319:        * the link encloses a string, then a <code>String</code> object is
 320:        * returned, if the link encloses an &lt;img&gt; tag, then an
 321:        * <code>ImageIcon</code> object is returned.
 322:        *
 323:        * @return an object that represents the link anchor
 324:        */
 325:       public Object getAccessibleActionAnchor(int i)
 326:       {
 327:         // TODO: This is only the String case. Implement all cases.
 328:         return getAccessibleActionDescription(i);
 329:       }
 330: 
 331:       /**
 332:        * Returns the start index of the hyperlink element.
 333:        *
 334:        * @return the start index of the hyperlink element
 335:        */
 336:       public int getStartIndex()
 337:       {
 338:         return element.getStartOffset();
 339:       }
 340: 
 341:       /**
 342:        * Returns the end index of the hyperlink element.
 343:        *
 344:        * @return the end index of the hyperlink element
 345:        */
 346:       public int getEndIndex()
 347:       {
 348:         return element.getEndOffset();
 349:       }
 350:       
 351:     }
 352: 
 353:     /**
 354:      * Returns the number of hyperlinks in the document.
 355:      *
 356:      * @return the number of hyperlinks in the document
 357:      */
 358:     public int getLinkCount()
 359:     {
 360:       HTMLDocument doc = (HTMLDocument) getDocument();
 361:       HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
 362:       int count = 0;
 363:       while (linkIter.isValid())
 364:         {
 365:           count++;
 366:           linkIter.next();
 367:         }
 368:       return count;
 369:     }
 370: 
 371:     /**
 372:      * Returns the <code>i</code>-th hyperlink in the document or
 373:      * <code>null</code> if there is no hyperlink with the specified index.
 374:      *
 375:      * @param i the index of the hyperlink to return
 376:      *
 377:      * @return the <code>i</code>-th hyperlink in the document or
 378:      *         <code>null</code> if there is no hyperlink with the specified
 379:      *         index
 380:      */
 381:     public AccessibleHyperlink getLink(int i)
 382:     {
 383:       HTMLDocument doc = (HTMLDocument) getDocument();
 384:       HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
 385:       int count = 0;
 386:       while (linkIter.isValid())
 387:         {
 388:           count++;
 389:           if (count == i)
 390:             break;
 391:           linkIter.next();
 392:         }
 393:       if (linkIter.isValid())
 394:         {
 395:           int offset = linkIter.getStartOffset();
 396:           // TODO: I fetch the element for the link via getCharacterElement().
 397:           // I am not sure that this is correct, maybe we must use
 398:           // getParagraphElement()?
 399:           Element el = doc.getCharacterElement(offset);
 400:           HTMLLink link = new HTMLLink(el);
 401:           return link;
 402:         }
 403:       else
 404:         return null;
 405:     }
 406: 
 407:     /**
 408:      * Returns the index of the link element at the character position
 409:      * <code>c</code> within the document, or <code>-1</code> if there is no
 410:      * link at the specified position.
 411:      *
 412:      * @param c the character index from which to fetch the link index
 413:      *
 414:      * @return the index of the link element at the character position
 415:      *         <code>c</code> within the document, or <code>-1</code> if there
 416:      *         is no link at the specified position
 417:      */
 418:     public int getLinkIndex(int c)
 419:     {
 420:       HTMLDocument doc = (HTMLDocument) getDocument();
 421:       HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
 422:       int count = 0;
 423:       while (linkIter.isValid())
 424:         {
 425:           if (linkIter.getStartOffset() <= c && linkIter.getEndOffset() > c)
 426:             break;
 427:           count++;
 428:           linkIter.next();
 429:         }
 430:       if (linkIter.isValid())
 431:         return count;
 432:       else
 433:         return -1;
 434:     }
 435: 
 436:     /**
 437:      * Returns the link text of the link at index <code>i</code>, or
 438:      * <code>null</code>, if there is no link at the specified position.
 439:      *
 440:      * @param i the index of the link
 441:      *
 442:      * @return  the link text of the link at index <code>i</code>, or
 443:      *          <code>null</code>, if there is no link at the specified
 444:      *          position
 445:      */
 446:     public String getLinkText(int i)
 447:     {
 448:       HTMLDocument doc = (HTMLDocument) getDocument();
 449:       HTMLDocument.Iterator linkIter = doc.getIterator(HTML.Tag.A);
 450:       int count = 0;
 451:       while (linkIter.isValid())
 452:         {
 453:           count++;
 454:           if (count == i)
 455:             break;
 456:           linkIter.next();
 457:         }
 458:       if (linkIter.isValid())
 459:         {
 460:           int offset = linkIter.getStartOffset();
 461:           // TODO: I fetch the element for the link via getCharacterElement().
 462:           // I am not sure that this is correct, maybe we must use
 463:           // getParagraphElement()?
 464:           Element el = doc.getCharacterElement(offset);
 465:           try
 466:             {
 467:               String text = doc.getText(el.getStartOffset(),
 468:                                       el.getEndOffset() - el.getStartOffset());
 469:               return text;
 470:             }
 471:           catch (BadLocationException ex)
 472:             {
 473:               throw (AssertionError)
 474:                 new AssertionError("BadLocationException must not be thrown "
 475:                                    + "here.")
 476:                   .initCause(ex);
 477:             }
 478:         }
 479:       else
 480:         return null;
 481:     }
 482:   }
 483: 
 484:   /**
 485:    * An EditorKit used for plain text. This is the default editor kit for
 486:    * JEditorPanes.
 487:    *
 488:    * @author Roman Kennke (kennke@aicas.com)
 489:    */
 490:   private static class PlainEditorKit extends DefaultEditorKit
 491:   {
 492: 
 493:     /**
 494:      * Returns a ViewFactory that supplies WrappedPlainViews.
 495:      */
 496:     public ViewFactory getViewFactory()
 497:     {
 498:       return new ViewFactory()
 499:       {
 500:         public View create(Element el)
 501:         {
 502:           return new WrappedPlainView(el);
 503:         }
 504:       };
 505:     }
 506:   }
 507: 
 508:   private static final long serialVersionUID = 3140472492599046285L;
 509:   
 510:   private URL page;
 511:   private EditorKit editorKit;
 512:   
 513:   boolean focus_root;
 514:   
 515:   // A mapping between content types and registered EditorKit types
 516:   static HashMap registerMap;
 517:   
 518:   // A mapping between content types and used EditorKits
 519:   HashMap editorMap;  
 520: 
 521:   public JEditorPane()
 522:   {
 523:     init();
 524:     setEditorKit(createDefaultEditorKit());
 525:   }
 526: 
 527:   public JEditorPane(String url) throws IOException
 528:   {
 529:     this(new URL(url));
 530:   }
 531: 
 532:   public JEditorPane(String type, String text)
 533:   {
 534:     init();
 535:     setEditorKit(createEditorKitForContentType(type));
 536:     setText(text);
 537:   }
 538: 
 539:   public JEditorPane(URL url) throws IOException
 540:   {
 541:     init();
 542:     setEditorKit(createEditorKitForContentType("text/html"));;
 543:     setPage(url);
 544:   }
 545:   
 546:   /**
 547:    * Called by the constructors to set up the default bindings for content 
 548:    * types and EditorKits.
 549:    */
 550:   void init()
 551:   {
 552:     editorMap = new HashMap();
 553:     registerMap = new HashMap();
 554:     registerEditorKitForContentType("application/rtf",
 555:                                     "javax.swing.text.rtf.RTFEditorKit");
 556:     registerEditorKitForContentType("text/plain",
 557:                                     "javax.swing.JEditorPane$PlainEditorKit");
 558:     registerEditorKitForContentType("text/html",
 559:                                     "javax.swing.text.html.HTMLEditorKit");
 560:     registerEditorKitForContentType("text/rtf",
 561:                                     "javax.swing.text.rtf.RTFEditorKit");
 562:   }
 563: 
 564:   protected EditorKit createDefaultEditorKit()
 565:   {
 566:     return new PlainEditorKit();
 567:   }
 568: 
 569:   /**
 570:    * Creates and returns an EditorKit that is appropriate for the given 
 571:    * content type.  This is created using the default recognized types
 572:    * plus any EditorKit types that have been registered.
 573:    * 
 574:    * @see #registerEditorKitForContentType(String, String)
 575:    * @see #registerEditorKitForContentType(String, String, ClassLoader)
 576:    * @param type the content type
 577:    * @return an EditorKit for use with the given content type
 578:    */
 579:   public static EditorKit createEditorKitForContentType(String type)
 580:   {
 581:     // TODO: Have to handle the case where a ClassLoader was specified
 582:     // when the EditorKit was registered
 583:     EditorKit e = null;
 584:     String className = (String) registerMap.get(type);
 585:     if (className != null)
 586:       {
 587:         try
 588:         {
 589:           e = (EditorKit) Class.forName(className).newInstance();
 590:         }
 591:         catch (Exception e2)
 592:         {    
 593:           // TODO: Not sure what to do here.
 594:         }
 595:       }
 596:     return e;
 597:   }
 598: 
 599:   /**
 600:    * Sends a given <code>HyperlinkEvent</code> to all registered listeners.
 601:    *
 602:    * @param event the event to send
 603:    */
 604:   public void fireHyperlinkUpdate(HyperlinkEvent event)
 605:   {
 606:     HyperlinkListener[] listeners = getHyperlinkListeners();
 607: 
 608:     for (int index = 0; index < listeners.length; ++index)
 609:        listeners[index].hyperlinkUpdate(event);
 610:   }
 611: 
 612:   /**
 613:    * Returns the accessible context associated with this editor pane.
 614:    *
 615:    * @return the accessible context associated with this editor pane
 616:    */
 617:   public AccessibleContext getAccessibleContext()
 618:   {
 619:     if (accessibleContext == null)
 620:       {
 621:         if (getEditorKit() instanceof HTMLEditorKit)
 622:           accessibleContext = new AccessibleJEditorPaneHTML();
 623:         else
 624:           accessibleContext = new AccessibleJEditorPane();
 625:       }
 626:     return accessibleContext;
 627:   }
 628: 
 629:   public final String getContentType()
 630:   {
 631:     return getEditorKit().getContentType();
 632:   }
 633: 
 634:   /**
 635:    * Returns the EditorKit. If there is no EditorKit set this method
 636:    * calls createDefaultEditorKit() and setEditorKit() first.
 637:    */
 638:   public EditorKit getEditorKit()
 639:   {
 640:     if (editorKit == null)
 641:       setEditorKit(createDefaultEditorKit());
 642:     return editorKit;
 643:   }
 644: 
 645:   /**
 646:    * Returns the class name of the EditorKit associated with the given
 647:    * content type.
 648:    * 
 649:    * @since 1.3
 650:    * @param type the content type
 651:    * @return the class name of the EditorKit associated with this content type
 652:    */
 653:   public static String getEditorKitClassNameForContentType(String type)
 654:   {
 655:     return (String) registerMap.get(type);
 656:   }
 657: 
 658:   /**
 659:    * Returns the EditorKit to use for the given content type.  If an
 660:    * EditorKit has been explicitly set via 
 661:    * <code>setEditorKitForContentType</code>
 662:    * then it will be returned.  Otherwise an attempt will be made to create
 663:    * an EditorKit from the default recognzied content types or any
 664:    * EditorKits that have been registered.  If none can be created, a
 665:    * PlainEditorKit is created.
 666:    * 
 667:    * @see #registerEditorKitForContentType(String, String)
 668:    * @see #registerEditorKitForContentType(String, String, ClassLoader)
 669:    * @param type the content type
 670:    * @return an appropriate EditorKit for the given content type
 671:    */
 672:   public EditorKit getEditorKitForContentType(String type)
 673:   {
 674:     // First check if an EditorKit has been explicitly set.
 675:     EditorKit e = (EditorKit) editorMap.get(type);
 676:     // Then check to see if we can create one.
 677:     if (e == null)
 678:       e = createEditorKitForContentType(type);
 679:     // Otherwise default to PlainEditorKit.
 680:     if (e == null)
 681:       e = new PlainEditorKit();
 682:     return e;
 683:   }
 684: 
 685:   /**
 686:    * Returns the preferred size for the JEditorPane. This is implemented to
 687:    * return the super's preferred size, unless one of
 688:    * {@link #getScrollableTracksViewportHeight()} or
 689:    * {@link #getScrollableTracksViewportWidth()} returns <code>true</code>,
 690:    * in which case the preferred width and/or height is replaced by the UI's
 691:    * minimum size.
 692:    *
 693:    * @return the preferred size for the JEditorPane
 694:    */
 695:   public Dimension getPreferredSize()
 696:   {
 697:     Dimension pref = super.getPreferredSize();
 698:     if (getScrollableTracksViewportWidth())
 699:       pref.width = getUI().getMinimumSize(this).width;
 700:     if (getScrollableTracksViewportHeight())
 701:       pref.height = getUI().getMinimumSize(this).height;
 702:     return pref;
 703:   }
 704: 
 705:   /**
 706:    * Returns <code>true</code> when a Viewport should force the height of
 707:    * this component to match the viewport height. This is implemented to return
 708:    * <code>true</code> when  the parent is an instance of JViewport and
 709:    * the viewport height > the UI's minimum height.
 710:    *
 711:    * @return <code>true</code> when a Viewport should force the height of
 712:    *         this component to match the viewport height
 713:    */
 714:   public boolean getScrollableTracksViewportHeight()
 715:   {
 716:     // Tests show that this returns true when the parent is a JViewport
 717:     // and has a height > minimum UI height.
 718:     Container parent = getParent();
 719:     return parent instanceof JViewport
 720:            && parent.getHeight() > getUI().getMinimumSize(this).height;
 721:   }
 722: 
 723:   /**
 724:    * Returns <code>true</code> when a Viewport should force the width of
 725:    * this component to match the viewport width. This is implemented to return
 726:    * <code>true</code> when  the parent is an instance of JViewport and
 727:    * the viewport width > the UI's minimum width.
 728:    *
 729:    * @return <code>true</code> when a Viewport should force the width of
 730:    *         this component to match the viewport width
 731:    */
 732:   public boolean getScrollableTracksViewportWidth()
 733:   {
 734:     // Tests show that this returns true when the parent is a JViewport
 735:     // and has a width > minimum UI width.
 736:     Container parent = getParent();
 737:     return parent != null && parent instanceof JViewport
 738:            && parent.getWidth() > getUI().getMinimumSize(this).width;
 739:   }
 740: 
 741:   public URL getPage()
 742:   {
 743:     return page;
 744:   }
 745: 
 746:   protected InputStream getStream(URL page)
 747:     throws IOException
 748:   {
 749:     return page.openStream();
 750:   }
 751: 
 752:   public String getText()
 753:   {
 754:     return super.getText();
 755:   }
 756: 
 757:   public String getUIClassID()
 758:   {
 759:     return "EditorPaneUI";
 760:   }
 761: 
 762:   public boolean isFocusCycleRoot()
 763:   {
 764:     return focus_root;
 765:   }
 766: 
 767:   protected String paramString()
 768:   {
 769:     return "JEditorPane";
 770:   }
 771: 
 772:   /**
 773:    * This method initializes from a stream. 
 774:    */
 775:   public void read(InputStream in, Object desc) throws IOException
 776:   {
 777:     EditorKit kit = getEditorKit();
 778:     if (kit instanceof HTMLEditorKit && desc instanceof HTMLDocument)
 779:       {
 780:         Document doc = (Document) desc;
 781:         try
 782:           {
 783:             kit.read(in, doc, 0);
 784:           }
 785:         catch (BadLocationException ex)
 786:           {
 787:             assert false : "BadLocationException must not be thrown here.";
 788:           }
 789:       }
 790:     else
 791:       {
 792:         Reader inRead = new InputStreamReader(in);
 793:         super.read(inRead, desc);
 794:       }
 795:   }
 796: 
 797:   /**
 798:    * Establishes a binding between type and classname.  This enables
 799:    * us to create an EditorKit later for the given content type.
 800:    * 
 801:    * @param type the content type
 802:    * @param classname the name of the class that is associated with this 
 803:    * content type
 804:    */
 805:   public static void registerEditorKitForContentType(String type,
 806:                                                      String classname)
 807:   {
 808:     registerMap.put(type, classname);
 809:   }
 810: 
 811:   /**
 812:    * Establishes the default bindings of type to classname.
 813:    */
 814:   public static void registerEditorKitForContentType(String type,
 815:                                                      String classname,
 816:                                                      ClassLoader loader)
 817:   {
 818:     // TODO: Implement this properly.
 819:   }
 820: 
 821:   /**
 822:    * Replaces the currently selected content with new content represented
 823:    * by the given string.
 824:    */
 825:   public void replaceSelection(String content)
 826:   {
 827:     // TODO: Implement this properly.
 828:     super.replaceSelection(content);
 829:   }
 830: 
 831:   /**
 832:    * Scrolls the view to the given reference location (that is, the value
 833:    * returned by the UL.getRef method for the URL being displayed).
 834:    */
 835:   public void scrollToReference(String reference)
 836:   {
 837:     // TODO: Implement this properly.
 838:   }
 839: 
 840:   public final void setContentType(String type)
 841:   {
 842:     if (editorKit != null
 843:     && editorKit.getContentType().equals(type))
 844:       return;
 845:               
 846:     EditorKit kit = getEditorKitForContentType(type);
 847:             
 848:     if (kit != null)
 849:       setEditorKit(kit);
 850:   }
 851: 
 852:   public void setEditorKit(EditorKit newValue)
 853:   {
 854:     if (editorKit == newValue)
 855:       return;
 856:         
 857:     if (editorKit != null)
 858:       editorKit.deinstall(this);
 859:                 
 860:     EditorKit oldValue = editorKit;
 861:     editorKit = newValue;
 862:                     
 863:     if (editorKit != null)
 864:       {
 865:     editorKit.install(this);
 866:     setDocument(editorKit.createDefaultDocument());
 867:       }
 868:                             
 869:     firePropertyChange("editorKit", oldValue, newValue);
 870:     invalidate();
 871:     repaint();
 872:     // Reset the accessibleContext since this depends on the editorKit.
 873:     accessibleContext = null;
 874:   }
 875: 
 876:   /**
 877:    * Explicitly sets an EditorKit to be used for the given content type.
 878:    * @param type the content type
 879:    * @param k the EditorKit to use for the given content type
 880:    */
 881:   public void setEditorKitForContentType(String type, EditorKit k)
 882:   {
 883:     editorMap.put(type, k);
 884:   }
 885: 
 886:   /**
 887:    * Sets the current URL being displayed.  
 888:    */
 889:   public void setPage(String url) throws IOException
 890:   {
 891:     setPage(new URL(url));
 892:   }
 893: 
 894:   /**
 895:    * Sets the current URL being displayed.  
 896:    */
 897:   public void setPage(URL page) throws IOException
 898:   {
 899:     if (page == null)
 900:       throw new IOException("invalid url");
 901: 
 902:     try
 903:       {
 904:     this.page = page;
 905:     getEditorKit().read(page.openStream(), getDocument(), 0);
 906:       }
 907:     catch (BadLocationException e)
 908:       {
 909:     // Ignored. '0' is always a valid offset.
 910:       }
 911:   }
 912: 
 913:   /**
 914:    * Sets the text of the JEditorPane.  The argument <code>t</code>
 915:    * is expected to be in the format of the current EditorKit.  This removes
 916:    * the content of the current document and uses the EditorKit to read in the
 917:    * new text.  This allows the EditorKit to handle the String rather than just
 918:    * inserting in plain text.
 919:    * 
 920:    * @param t the text to display in this JEditorPane
 921:    */
 922:   public void setText(String t)
 923:   {
 924:     try
 925:     {
 926:       // Remove the current content.
 927:       Document doc = getDocument();
 928:       doc.remove(0, doc.getLength());
 929:       if (t == null || t.equals(""))
 930:         return;
 931:       
 932:       // Let the EditorKit read the text into the Document.
 933:       getEditorKit().read(new StringReader(t), doc, 0);
 934:     }
 935:     catch (BadLocationException ble)
 936:     {
 937:       // TODO: Don't know what to do here.
 938:     }
 939:     catch (IOException ioe)
 940:     {
 941:       // TODO: Don't know what to do here.
 942:     }
 943:   }
 944: 
 945:   /**
 946:    * Add a <code>HyperlinkListener</code> object to this editor pane.
 947:    *
 948:    * @param listener the listener to add
 949:    */
 950:   public void addHyperlinkListener(HyperlinkListener listener)
 951:   {
 952:     listenerList.add(HyperlinkListener.class, listener);
 953:   }
 954: 
 955:   /**
 956:    * Removes a <code>HyperlinkListener</code> object to this editor pane.
 957:    *
 958:    * @param listener the listener to remove
 959:    */
 960:   public void removeHyperlinkListener(HyperlinkListener listener)
 961:   {
 962:     listenerList.remove(HyperlinkListener.class, listener);
 963:   }
 964: 
 965:   /**
 966:    * Returns all added <code>HyperlinkListener</code> objects.
 967:    *
 968:    * @return array of listeners
 969:    *
 970:    * @since 1.4
 971:    */
 972:   public HyperlinkListener[] getHyperlinkListeners()
 973:   {
 974:     return (HyperlinkListener[]) getListeners(HyperlinkListener.class);
 975:   }
 976: }