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.BufferedInputStream;
  44: import java.io.FilterInputStream;
  45: import java.io.IOException;
  46: import java.io.InputStream;
  47: import java.io.InputStreamReader;
  48: import java.io.Reader;
  49: import java.io.StringReader;
  50: import java.net.MalformedURLException;
  51: import java.net.URL;
  52: import java.net.URLConnection;
  53: import java.util.HashMap;
  54: 
  55: import javax.accessibility.AccessibleContext;
  56: import javax.accessibility.AccessibleHyperlink;
  57: import javax.accessibility.AccessibleHypertext;
  58: import javax.accessibility.AccessibleStateSet;
  59: import javax.accessibility.AccessibleText;
  60: import javax.swing.event.HyperlinkEvent;
  61: import javax.swing.event.HyperlinkListener;
  62: import javax.swing.plaf.TextUI;
  63: import javax.swing.text.AbstractDocument;
  64: import javax.swing.text.BadLocationException;
  65: import javax.swing.text.DefaultEditorKit;
  66: import javax.swing.text.Document;
  67: import javax.swing.text.EditorKit;
  68: import javax.swing.text.Element;
  69: import javax.swing.text.JTextComponent;
  70: import javax.swing.text.View;
  71: import javax.swing.text.ViewFactory;
  72: import javax.swing.text.WrappedPlainView;
  73: import javax.swing.text.html.HTML;
  74: import javax.swing.text.html.HTMLDocument;
  75: import javax.swing.text.html.HTMLEditorKit;
  76: 
  77: /**
  78:  * A powerful text editor component that can handle different types of
  79:  * content.
  80:  *
  81:  * The JEditorPane text component is driven by an instance of
  82:  * {@link EditorKit}. The editor kit is responsible for providing
  83:  * a default {@link Document} implementation, a mechanism for loading
  84:  * and saving documents of its supported content type and providing
  85:  * a set of {@link Action}s for manipulating the content.
  86:  *
  87:  * By default the following content types are supported:
  88:  * <ul>
  89:  * <li><code>text/plain</code>: Plain text, handled by
  90:  *   {@link javax.swing.text.DefaultEditorKit}.</li>
  91:  * <li><code>text/html</code>: HTML 4.0 styled text, handled by
  92:  *   {@link javax.swing.text.html.HTMLEditorKit}.</li>
  93:  * <li><code>text/rtf</code>: RTF text, handled by
  94:  *   {@link javax.swing.text.rtf.RTFEditorKit}.</li>
  95:  * </ul>
  96:  *
  97:  * @author original author unknown
  98:  * @author Roman Kennke (roman@kennke.org)
  99:  * @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 &lt;img&gt; 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: }