Source for javax.swing.plaf.basic.BasicTextUI

   1: /* BasicTextUI.java --
   2:    Copyright (C) 2002, 2003, 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.plaf.basic;
  40: 
  41: import gnu.classpath.NotImplementedException;
  42: 
  43: import java.awt.Color;
  44: import java.awt.Container;
  45: import java.awt.Dimension;
  46: import java.awt.Graphics;
  47: import java.awt.HeadlessException;
  48: import java.awt.Insets;
  49: import java.awt.Point;
  50: import java.awt.Rectangle;
  51: import java.awt.Shape;
  52: import java.awt.Toolkit;
  53: import java.awt.datatransfer.Clipboard;
  54: import java.awt.datatransfer.StringSelection;
  55: import java.awt.event.FocusEvent;
  56: import java.awt.event.FocusListener;
  57: import java.beans.PropertyChangeEvent;
  58: import java.beans.PropertyChangeListener;
  59: 
  60: import javax.swing.Action;
  61: import javax.swing.ActionMap;
  62: import javax.swing.InputMap;
  63: import javax.swing.JComponent;
  64: import javax.swing.LookAndFeel;
  65: import javax.swing.SwingConstants;
  66: import javax.swing.SwingUtilities;
  67: import javax.swing.UIManager;
  68: import javax.swing.event.DocumentEvent;
  69: import javax.swing.event.DocumentListener;
  70: import javax.swing.plaf.ActionMapUIResource;
  71: import javax.swing.plaf.TextUI;
  72: import javax.swing.plaf.UIResource;
  73: import javax.swing.text.AbstractDocument;
  74: import javax.swing.text.BadLocationException;
  75: import javax.swing.text.Caret;
  76: import javax.swing.text.DefaultCaret;
  77: import javax.swing.text.DefaultEditorKit;
  78: import javax.swing.text.DefaultHighlighter;
  79: import javax.swing.text.Document;
  80: import javax.swing.text.EditorKit;
  81: import javax.swing.text.Element;
  82: import javax.swing.text.Highlighter;
  83: import javax.swing.text.JTextComponent;
  84: import javax.swing.text.Keymap;
  85: import javax.swing.text.Position;
  86: import javax.swing.text.Utilities;
  87: import javax.swing.text.View;
  88: import javax.swing.text.ViewFactory;
  89: 
  90: /**
  91:  * The abstract base class from which the UI classes for Swings text
  92:  * components are derived. This provides most of the functionality for
  93:  * the UI classes.
  94:  *
  95:  * @author original author unknown
  96:  * @author Roman Kennke (roman@kennke.org)
  97:  */
  98: public abstract class BasicTextUI extends TextUI
  99:   implements ViewFactory
 100: {
 101:   /**
 102:    * A {@link DefaultCaret} that implements {@link UIResource}.
 103:    */
 104:   public static class BasicCaret extends DefaultCaret implements UIResource
 105:   {
 106:     public BasicCaret()
 107:     {
 108:       // Nothing to do here.
 109:     }
 110:   }
 111: 
 112:   /**
 113:    * A {@link DefaultHighlighter} that implements {@link UIResource}.
 114:    */
 115:   public static class BasicHighlighter extends DefaultHighlighter
 116:     implements UIResource
 117:   {
 118:     public BasicHighlighter()
 119:     {
 120:       // Nothing to do here.
 121:     }
 122:   }
 123: 
 124:   /**
 125:    * This view forms the root of the View hierarchy. However, it delegates
 126:    * most calls to another View which is the real root of the hierarchy.
 127:    * The purpose is to make sure that all Views in the hierarchy, including
 128:    * the (real) root have a well-defined parent to which they can delegate
 129:    * calls like {@link #preferenceChanged}, {@link #getViewFactory} and
 130:    * {@link #getContainer}.
 131:    */
 132:   private class RootView extends View
 133:   {
 134:     /** The real root view. */
 135:     private View view;
 136: 
 137:     /**
 138:      * Creates a new RootView.
 139:      */
 140:     public RootView()
 141:     {
 142:       super(null);
 143:     }
 144: 
 145:     /**
 146:      * Returns the ViewFactory for this RootView. If the current EditorKit
 147:      * provides a ViewFactory, this is used. Otherwise the TextUI itself
 148:      * is returned as a ViewFactory.
 149:      *
 150:      * @return the ViewFactory for this RootView
 151:      */
 152:     public ViewFactory getViewFactory()
 153:     {
 154:       ViewFactory factory = null;
 155:       EditorKit editorKit = BasicTextUI.this.getEditorKit(getComponent());
 156:       factory = editorKit.getViewFactory();
 157:       if (factory == null)
 158:     factory = BasicTextUI.this;
 159:       return factory;
 160:     }
 161: 
 162:     /**
 163:      * Indicates that the preferences of one of the child view has changed.
 164:      * This calls revalidate on the text component.
 165:      *
 166:      * @param v the child view which's preference has changed
 167:      * @param width <code>true</code> if the width preference has changed
 168:      * @param height <code>true</code> if the height preference has changed
 169:      */
 170:     public void preferenceChanged(View v, boolean width, boolean height)
 171:     {
 172:       textComponent.revalidate();
 173:     }
 174: 
 175:     /**
 176:      * Sets the real root view.
 177:      *
 178:      * @param v the root view to set
 179:      */
 180:     public void setView(View v)
 181:     {
 182:       if (view != null)
 183:         view.setParent(null);
 184:       
 185:       if (v != null)
 186:         v.setParent(this);
 187: 
 188:       view = v;
 189:     }
 190: 
 191:     /**
 192:      * Returns the real root view, regardless of the index.
 193:      *
 194:      * @param index not used here
 195:      *
 196:      * @return the real root view, regardless of the index.
 197:      */
 198:     public View getView(int index)
 199:     {
 200:       return view;
 201:     }
 202: 
 203:     /**
 204:      * Returns <code>1</code> since the RootView always contains one
 205:      * child, that is the real root of the View hierarchy.
 206:      *
 207:      * @return <code>1</code> since the RootView always contains one
 208:      *         child, that is the real root of the View hierarchy
 209:      */
 210:     public int getViewCount()
 211:     {
 212:       int count = 0;
 213:       if (view != null)
 214:         count = 1;
 215:       return count;
 216:     }
 217: 
 218:     /**
 219:      * Returns the <code>Container</code> that contains this view. This
 220:      * normally will be the text component that is managed by this TextUI.
 221:      *
 222:      * @return the <code>Container</code> that contains this view
 223:      */
 224:     public Container getContainer()
 225:     {
 226:       return textComponent;
 227:     }
 228: 
 229:     /**
 230:      * Returns the preferred span along the specified <code>axis</code>.
 231:      * This is delegated to the real root view.
 232:      *
 233:      * @param axis the axis for which the preferred span is queried
 234:      *
 235:      * @return the preferred span along the axis
 236:      */
 237:     public float getPreferredSpan(int axis)
 238:     {
 239:       if (view != null)
 240:     return view.getPreferredSpan(axis);
 241: 
 242:       return Integer.MAX_VALUE;
 243:     }
 244: 
 245:     /**
 246:      * Paints the view. This is delegated to the real root view.
 247:      *
 248:      * @param g the <code>Graphics</code> context to paint to
 249:      * @param s the allocation for the View
 250:      */
 251:     public void paint(Graphics g, Shape s)
 252:     {
 253:       if (view != null)
 254:         {
 255:           Rectangle b = s.getBounds();
 256:           view.setSize(b.width, b.height);
 257:           view.paint(g, s);
 258:         }
 259:     }
 260: 
 261: 
 262:     /**
 263:      * Maps a position in the document into the coordinate space of the View.
 264:      * The output rectangle usually reflects the font height but has a width
 265:      * of zero.
 266:      *
 267:      * This is delegated to the real root view.
 268:      *
 269:      * @param position the position of the character in the model
 270:      * @param a the area that is occupied by the view
 271:      * @param bias either {@link Position.Bias#Forward} or
 272:      *        {@link Position.Bias#Backward} depending on the preferred
 273:      *        direction bias. If <code>null</code> this defaults to
 274:      *        <code>Position.Bias.Forward</code>
 275:      *
 276:      * @return a rectangle that gives the location of the document position
 277:      *         inside the view coordinate space
 278:      *
 279:      * @throws BadLocationException if <code>pos</code> is invalid
 280:      * @throws IllegalArgumentException if b is not one of the above listed
 281:      *         valid values
 282:      */
 283:     public Shape modelToView(int position, Shape a, Position.Bias bias)
 284:       throws BadLocationException
 285:     {
 286:       return view.modelToView(position, a, bias);
 287:     }
 288: 
 289:     /**
 290:      * Maps coordinates from the <code>View</code>'s space into a position
 291:      * in the document model.
 292:      *
 293:      * @param x the x coordinate in the view space
 294:      * @param y the y coordinate in the view space
 295:      * @param a the allocation of this <code>View</code>
 296:      * @param b the bias to use
 297:      *
 298:      * @return the position in the document that corresponds to the screen
 299:      *         coordinates <code>x, y</code>
 300:      */
 301:     public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
 302:     {
 303:       return view.viewToModel(x, y, a, b);
 304:     }
 305: 
 306:     /**
 307:      * Notification about text insertions. These are forwarded to the
 308:      * real root view.
 309:      *
 310:      * @param ev the DocumentEvent describing the change
 311:      * @param shape the current allocation of the view's display
 312:      * @param vf the ViewFactory to use for creating new Views
 313:      */
 314:     public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
 315:     {
 316:       view.insertUpdate(ev, shape, vf);
 317:     }
 318: 
 319:     /**
 320:      * Notification about text removals. These are forwarded to the
 321:      * real root view.
 322:      *
 323:      * @param ev the DocumentEvent describing the change
 324:      * @param shape the current allocation of the view's display
 325:      * @param vf the ViewFactory to use for creating new Views
 326:      */
 327:     public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
 328:     {
 329:       view.removeUpdate(ev, shape, vf);
 330:     }
 331: 
 332:     /**
 333:      * Notification about text changes. These are forwarded to the
 334:      * real root view.
 335:      *
 336:      * @param ev the DocumentEvent describing the change
 337:      * @param shape the current allocation of the view's display
 338:      * @param vf the ViewFactory to use for creating new Views
 339:      */
 340:     public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
 341:     {
 342:       view.changedUpdate(ev, shape, vf);
 343:     }
 344: 
 345:     /**
 346:      * Returns the document position that is (visually) nearest to the given
 347:      * document position <code>pos</code> in the given direction <code>d</code>.
 348:      *
 349:      * @param pos the document position
 350:      * @param b the bias for <code>pos</code>
 351:      * @param a the allocation for the view
 352:      * @param d the direction, must be either {@link SwingConstants#NORTH},
 353:      *        {@link SwingConstants#SOUTH}, {@link SwingConstants#WEST} or
 354:      *        {@link SwingConstants#EAST}
 355:      * @param biasRet an array of {@link Position.Bias} that can hold at least
 356:      *        one element, which is filled with the bias of the return position
 357:      *        on method exit
 358:      *
 359:      * @return the document position that is (visually) nearest to the given
 360:      *         document position <code>pos</code> in the given direction
 361:      *         <code>d</code>
 362:      *
 363:      * @throws BadLocationException if <code>pos</code> is not a valid offset in
 364:      *         the document model
 365:      */
 366:     public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
 367:                                          int d, Position.Bias[] biasRet)
 368:       throws BadLocationException
 369:     {
 370:       return view.getNextVisualPositionFrom(pos, b, a, d, biasRet);
 371:     }
 372: 
 373:     /**
 374:      * Returns the startOffset of this view, which is always the beginning
 375:      * of the document.
 376:      *
 377:      * @return the startOffset of this view
 378:      */
 379:     public int getStartOffset()
 380:     {
 381:       return 0;
 382:     }
 383: 
 384:     /**
 385:      * Returns the endOffset of this view, which is always the end
 386:      * of the document.
 387:      *
 388:      * @return the endOffset of this view
 389:      */
 390:     public int getEndOffset()
 391:     {
 392:       return getDocument().getLength();
 393:     }
 394: 
 395:     /**
 396:      * Returns the document associated with this view.
 397:      *
 398:      * @return the document associated with this view
 399:      */
 400:     public Document getDocument()
 401:     {
 402:       return textComponent.getDocument();
 403:     }
 404:   }
 405: 
 406:   /**
 407:    * Receives notifications when properties of the text component change.
 408:    */
 409:   private class PropertyChangeHandler implements PropertyChangeListener
 410:   {
 411:     /**
 412:      * Notifies when a property of the text component changes.
 413:      *
 414:      * @param event the PropertyChangeEvent describing the change
 415:      */
 416:     public void propertyChange(PropertyChangeEvent event)
 417:     {
 418:       if (event.getPropertyName().equals("document"))
 419:         {
 420:           // Document changed.
 421:           modelChanged();
 422:         }
 423: 
 424:       BasicTextUI.this.propertyChange(event);
 425:     }
 426:   }
 427: 
 428:   /**
 429:    * Listens for changes on the underlying model and forwards notifications
 430:    * to the View. This also updates the caret position of the text component.
 431:    *
 432:    * TODO: Maybe this should somehow be handled through EditorKits
 433:    */
 434:   class DocumentHandler implements DocumentListener
 435:   {
 436:     /**
 437:      * Notification about a document change event.
 438:      *
 439:      * @param ev the DocumentEvent describing the change
 440:      */
 441:     public void changedUpdate(DocumentEvent ev)
 442:     {
 443:       // Updates are forwarded to the View even if 'getVisibleEditorRect'
 444:       // method returns null. This means the View classes have to be
 445:       // aware of that possibility.
 446:       rootView.changedUpdate(ev, getVisibleEditorRect(),
 447:                              rootView.getViewFactory());
 448:     }
 449: 
 450:     /**
 451:      * Notification about a document insert event.
 452:      *
 453:      * @param ev the DocumentEvent describing the insertion
 454:      */
 455:     public void insertUpdate(DocumentEvent ev)
 456:     {
 457:       // Updates are forwarded to the View even if 'getVisibleEditorRect'
 458:       // method returns null. This means the View classes have to be
 459:       // aware of that possibility.
 460:       rootView.insertUpdate(ev, getVisibleEditorRect(),
 461:                             rootView.getViewFactory());
 462:     }
 463: 
 464:     /**
 465:      * Notification about a document removal event.
 466:      *
 467:      * @param ev the DocumentEvent describing the removal
 468:      */
 469:     public void removeUpdate(DocumentEvent ev)
 470:     {
 471:       // Updates are forwarded to the View even if 'getVisibleEditorRect'
 472:       // method returns null. This means the View classes have to be
 473:       // aware of that possibility.
 474:       rootView.removeUpdate(ev, getVisibleEditorRect(),
 475:                             rootView.getViewFactory());
 476:     }
 477:   }
 478: 
 479:   /**
 480:    * The EditorKit used by this TextUI.
 481:    */
 482:   // FIXME: should probably be non-static.
 483:   static EditorKit kit = new DefaultEditorKit();
 484: 
 485:   /**
 486:    * The root view.
 487:    */
 488:   RootView rootView = new RootView();
 489: 
 490:   /**
 491:    * The text component that we handle.
 492:    */
 493:   JTextComponent textComponent;
 494: 
 495:   /**
 496:    * Receives notification when the model changes.
 497:    */
 498:   private PropertyChangeHandler updateHandler = new PropertyChangeHandler();
 499: 
 500:   /** The DocumentEvent handler. */
 501:   DocumentHandler documentHandler = new DocumentHandler();
 502: 
 503:   /**
 504:    * The standard background color. This is the color which is used to paint
 505:    * text in enabled text components.
 506:    */
 507:   Color background;
 508: 
 509:   /**
 510:    * The inactive background color. This is the color which is used to paint
 511:    * text in disabled text components.
 512:    */
 513:   Color inactiveBackground;
 514: 
 515:   /**
 516:    * Creates a new <code>BasicTextUI</code> instance.
 517:    */
 518:   public BasicTextUI()
 519:   {
 520:     // Nothing to do here.
 521:   }
 522: 
 523:   /**
 524:    * Creates a {@link Caret} that should be installed into the text component.
 525:    *
 526:    * @return a caret that should be installed into the text component
 527:    */
 528:   protected Caret createCaret()
 529:   {
 530:     return new BasicCaret();
 531:   }
 532: 
 533:   /**
 534:    * Creates a {@link Highlighter} that should be installed into the text
 535:    * component.
 536:    *
 537:    * @return a <code>Highlighter</code> for the text component
 538:    */
 539:   protected Highlighter createHighlighter()
 540:   {
 541:     return new BasicHighlighter();
 542:   }
 543: 
 544:   /**
 545:    * The text component that is managed by this UI.
 546:    *
 547:    * @return the text component that is managed by this UI
 548:    */
 549:   protected final JTextComponent getComponent()
 550:   {
 551:     return textComponent;
 552:   }
 553: 
 554:   /**
 555:    * Installs this UI on the text component.
 556:    *
 557:    * @param c the text component on which to install the UI
 558:    */
 559:   public void installUI(final JComponent c)
 560:   {
 561:     super.installUI(c);
 562: 
 563:     textComponent = (JTextComponent) c;
 564:     Document doc = textComponent.getDocument();
 565:     if (doc == null)
 566:       {
 567:         doc = getEditorKit(textComponent).createDefaultDocument();
 568:         textComponent.setDocument(doc);
 569:       }
 570:     installDefaults();
 571:     installListeners();
 572:     installKeyboardActions();
 573: 
 574:     // We need to trigger this so that the view hierarchy gets initialized.
 575:     modelChanged();
 576: 
 577:   }
 578: 
 579:   /**
 580:    * Installs UI defaults on the text components.
 581:    */
 582:   protected void installDefaults()
 583:   {
 584:     Caret caret = textComponent.getCaret();
 585:     if (caret == null)
 586:       {
 587:         caret = createCaret();
 588:         textComponent.setCaret(caret);
 589:       }
 590: 
 591:     Highlighter highlighter = textComponent.getHighlighter();
 592:     if (highlighter == null)
 593:       textComponent.setHighlighter(createHighlighter());
 594: 
 595:     String prefix = getPropertyPrefix();
 596:     LookAndFeel.installColorsAndFont(textComponent, prefix + ".background",
 597:                                      prefix + ".foreground", prefix + ".font");
 598:     LookAndFeel.installBorder(textComponent, prefix + ".border");
 599:     textComponent.setMargin(UIManager.getInsets(prefix + ".margin"));
 600: 
 601:     caret.setBlinkRate(UIManager.getInt(prefix + ".caretBlinkRate"));
 602: 
 603:     // Fetch the colors for enabled/disabled text components.
 604:     background = UIManager.getColor(prefix + ".background");
 605:     inactiveBackground = UIManager.getColor(prefix + ".inactiveBackground");
 606:     textComponent.setDisabledTextColor
 607:                          (UIManager.getColor(prefix + ".inactiveForeground"));
 608:     textComponent.setSelectedTextColor(UIManager.getColor(prefix + ".selectionForeground"));
 609:     textComponent.setSelectionColor(UIManager.getColor(prefix + ".selectionBackground"));    
 610:   }
 611: 
 612:   /**
 613:    * This FocusListener triggers repaints on focus shift.
 614:    */
 615:   private FocusListener focuslistener = new FocusListener() {
 616:       public void focusGained(FocusEvent e) 
 617:       {
 618:         textComponent.repaint();
 619:       }
 620:       public void focusLost(FocusEvent e)
 621:       {
 622:         textComponent.repaint();
 623:         
 624:         // Integrates Swing text components with the system clipboard:
 625:         // The idea is that if one wants to copy text around X11-style
 626:         // (select text and middle-click in the target component) the focus
 627:         // will move to the new component which gives the old focus owner the
 628:         // possibility to paste its selection into the clipboard.
 629:         if (!e.isTemporary()
 630:             && textComponent.getSelectionStart()
 631:                != textComponent.getSelectionEnd())
 632:           {
 633:             SecurityManager sm = System.getSecurityManager();
 634:             try
 635:               {
 636:                 if (sm != null)
 637:                   sm.checkSystemClipboardAccess();
 638:                 
 639:                 Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection();
 640:                 if (cb != null)
 641:                   {
 642:                     StringSelection selection = new StringSelection(textComponent.getSelectedText());
 643:                     cb.setContents(selection, selection);
 644:                   }
 645:               }
 646:             catch (SecurityException se)
 647:               {
 648:                 // Not allowed to access the clipboard: Ignore and
 649:                 // do not access it.
 650:               }
 651:             catch (HeadlessException he)
 652:               {
 653:                 // There is no AWT: Ignore and do not access the
 654:                 // clipboard.
 655:               }
 656:             catch (IllegalStateException ise)
 657:             {
 658:                 // Clipboard is currently unavaible.
 659:             }
 660:           }
 661:       }
 662:     };
 663: 
 664:   /**
 665:    * Install all listeners on the text component.
 666:    */
 667:   protected void installListeners()
 668:   {
 669:     textComponent.addFocusListener(focuslistener);
 670:     textComponent.addPropertyChangeListener(updateHandler);
 671:     installDocumentListeners();
 672:   }
 673: 
 674:   /**
 675:    * Installs the document listeners on the textComponent's model.
 676:    */
 677:   private void installDocumentListeners()
 678:   {
 679:     Document doc = textComponent.getDocument();
 680:     if (doc != null)
 681:       doc.addDocumentListener(documentHandler);
 682:   }
 683: 
 684:   /**
 685:    * Returns the name of the keymap for this type of TextUI.
 686:    * 
 687:    * This is implemented so that the classname of this TextUI
 688:    * without the package prefix is returned. This way subclasses
 689:    * don't have to override this method.
 690:    * 
 691:    * @return the name of the keymap for this TextUI
 692:    */
 693:   protected String getKeymapName()
 694:   {
 695:     String fullClassName = getClass().getName();
 696:     int index = fullClassName.lastIndexOf('.');
 697:     String className = fullClassName.substring(index + 1);
 698:     return className;
 699:   }
 700: 
 701:   /**
 702:    * Creates the {@link Keymap} that is installed on the text component.
 703:    *
 704:    * @return the {@link Keymap} that is installed on the text component
 705:    */
 706:   protected Keymap createKeymap()
 707:   {
 708:     String keymapName = getKeymapName();
 709:     Keymap keymap = JTextComponent.getKeymap(keymapName);
 710:     if (keymap == null)
 711:       {
 712:         Keymap parentMap =
 713:           JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
 714:         keymap = JTextComponent.addKeymap(keymapName, parentMap);
 715:         Object val = UIManager.get(getPropertyPrefix() + ".keyBindings");
 716:         if (val != null && val instanceof JTextComponent.KeyBinding[])
 717:           {
 718:             JTextComponent.KeyBinding[] bindings =
 719:               (JTextComponent.KeyBinding[]) val;
 720:             JTextComponent.loadKeymap(keymap, bindings,
 721:                                       getComponent().getActions());
 722:           }
 723:       }
 724:     return keymap;
 725:   }
 726: 
 727:   /**
 728:    * Installs the keyboard actions on the text components.
 729:    */
 730:   protected void installKeyboardActions()
 731:   {
 732:     // This is only there for backwards compatibility.
 733:     textComponent.setKeymap(createKeymap());
 734: 
 735:     // load any bindings for the newer InputMap / ActionMap interface
 736:     SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED,
 737:                                      getInputMap(JComponent.WHEN_FOCUSED));
 738:     SwingUtilities.replaceUIActionMap(textComponent, createActionMap());
 739:     
 740:     ActionMap parentActionMap = new ActionMapUIResource();
 741:     Action[] actions = textComponent.getActions();
 742:     for (int j = 0; j < actions.length; j++)
 743:       {
 744:         Action currAction = actions[j];
 745:         parentActionMap.put(currAction.getValue(Action.NAME), currAction);
 746:       }
 747:     
 748:     SwingUtilities.replaceUIActionMap(textComponent, parentActionMap);
 749:   }
 750:   
 751:   /**
 752:    * Creates an ActionMap to be installed on the text component.
 753:    * 
 754:    * @return an ActionMap to be installed on the text component
 755:    */
 756:   ActionMap createActionMap()
 757:   {
 758:     Action[] actions = textComponent.getActions();
 759:     ActionMap am = new ActionMapUIResource();
 760:     for (int i = 0; i < actions.length; ++i)
 761:       {
 762:         String name = (String) actions[i].getValue(Action.NAME);
 763:         if (name != null)
 764:           am.put(name, actions[i]);
 765:       }
 766:     return am;
 767:   }
 768: 
 769:   /**
 770:    * Gets the input map for the specified <code>condition</code>.
 771:    *
 772:    * @param condition the condition for the InputMap
 773:    *
 774:    * @return the InputMap for the specified condition
 775:    */
 776:   InputMap getInputMap(int condition)
 777:   {
 778:     String prefix = getPropertyPrefix();
 779:     switch (condition)
 780:       {
 781:       case JComponent.WHEN_IN_FOCUSED_WINDOW:
 782:         // FIXME: is this the right string? nobody seems to use it.
 783:         return (InputMap) UIManager.get(prefix + ".windowInputMap"); 
 784:       case JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT:
 785:         return (InputMap) UIManager.get(prefix + ".ancestorInputMap");
 786:       default:
 787:       case JComponent.WHEN_FOCUSED:
 788:         return (InputMap) UIManager.get(prefix + ".focusInputMap");
 789:       }
 790:   }
 791: 
 792:   /**
 793:    * Uninstalls this TextUI from the text component.
 794:    *
 795:    * @param component the text component to uninstall the UI from
 796:    */
 797:   public void uninstallUI(final JComponent component)
 798:   {
 799:     super.uninstallUI(component);
 800:     rootView.setView(null);
 801: 
 802:     uninstallDefaults();
 803:     uninstallListeners();
 804:     uninstallKeyboardActions();
 805: 
 806:     textComponent = null;
 807:   }
 808: 
 809:   /**
 810:    * Uninstalls all default properties that have previously been installed by
 811:    * this UI.
 812:    */
 813:   protected void uninstallDefaults()
 814:   {
 815:     // Do nothing here.
 816:   }
 817: 
 818:   /**
 819:    * Uninstalls all listeners that have previously been installed by
 820:    * this UI.
 821:    */
 822:   protected void uninstallListeners()
 823:   {
 824:     textComponent.removePropertyChangeListener(updateHandler);
 825:     textComponent.removeFocusListener(focuslistener);
 826:     textComponent.getDocument().removeDocumentListener(documentHandler);
 827:   }
 828: 
 829:   /**
 830:    * Uninstalls all keyboard actions that have previously been installed by
 831:    * this UI.
 832:    */
 833:   protected void uninstallKeyboardActions()
 834:     throws NotImplementedException
 835:   {
 836:     // FIXME: Uninstall keyboard actions here.
 837:   }
 838: 
 839:   /**
 840:    * Returns the property prefix by which the text component's UIDefaults
 841:    * are looked up.
 842:    *
 843:    * @return the property prefix by which the text component's UIDefaults
 844:    *     are looked up
 845:    */
 846:   protected abstract String getPropertyPrefix();
 847: 
 848:   /**
 849:    * Returns the preferred size of the text component.
 850:    *
 851:    * @param c not used here
 852:    *
 853:    * @return the preferred size of the text component
 854:    */
 855:   public Dimension getPreferredSize(JComponent c)
 856:   {
 857:     View v = getRootView(textComponent);
 858: 
 859:     float w = v.getPreferredSpan(View.X_AXIS);
 860:     float h = v.getPreferredSpan(View.Y_AXIS);
 861: 
 862:     Insets i = c.getInsets();
 863:     return new Dimension((int) w + i.left + i.right,
 864:                          (int) h + i.top + i.bottom);
 865:   }
 866: 
 867:   /**
 868:    * Returns the maximum size for text components that use this UI.
 869:    *
 870:    * This returns (Integer.MAX_VALUE, Integer.MAX_VALUE).
 871:    *
 872:    * @param c not used here
 873:    *
 874:    * @return the maximum size for text components that use this UI
 875:    */
 876:   public Dimension getMaximumSize(JComponent c)
 877:   {
 878:     // Sun's implementation returns Integer.MAX_VALUE here, so do we.
 879:     return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 880:   }
 881: 
 882:   /**
 883:    * Returns the minimum size for text components. This returns the size
 884:    * of the component's insets.
 885:    *
 886:    * @return the minimum size for text components
 887:    */
 888:   public Dimension getMinimumSize(JComponent c)
 889:   {
 890:     Insets i = c.getInsets();
 891:     return new Dimension(i.left + i.right, i.top + i.bottom);
 892:   }
 893: 
 894:   /**
 895:    * Paints the text component. This acquires a read lock on the model and then
 896:    * calls {@link #paintSafely(Graphics)} in order to actually perform the
 897:    * painting.
 898:    *
 899:    * @param g the <code>Graphics</code> context to paint to
 900:    * @param c not used here
 901:    */
 902:   public final void paint(Graphics g, JComponent c)
 903:   {
 904:     try
 905:       {
 906:         Document doc = textComponent.getDocument();
 907:         if (doc instanceof AbstractDocument)
 908:           {
 909:             AbstractDocument aDoc = (AbstractDocument) doc;
 910:             aDoc.readLock();
 911:           }
 912:         
 913:         paintSafely(g);
 914:       }
 915:     finally
 916:       {
 917:         Document doc = textComponent.getDocument();
 918:         if (doc instanceof AbstractDocument)
 919:           {
 920:             AbstractDocument aDoc = (AbstractDocument) doc;
 921:             aDoc.readUnlock();
 922:           }
 923:       }
 924:   }
 925: 
 926:   /**
 927:    * This paints the text component while beeing sure that the model is not
 928:    * modified while painting.
 929:    *
 930:    * The following is performed in this order:
 931:    * <ol>
 932:    * <li>If the text component is opaque, the background is painted by
 933:    * calling {@link #paintBackground(Graphics)}.</li>
 934:    * <li>If there is a highlighter, the highlighter is painted.</li>
 935:    * <li>The view hierarchy is painted.</li>
 936:    * <li>The Caret is painter.</li>
 937:    * </ol>
 938:    *
 939:    * @param g the <code>Graphics</code> context to paint to
 940:    */
 941:   protected void paintSafely(Graphics g)
 942:   {
 943:     Caret caret = textComponent.getCaret();
 944:     Highlighter highlighter = textComponent.getHighlighter();
 945: 
 946:     if (textComponent.isOpaque())
 947:       paintBackground(g);
 948: 
 949:     // Try painting with the highlighter without checking whether there
 950:     // is a selection because a highlighter can be used to do more than
 951:     // marking selected text.
 952:     if (highlighter != null)
 953:       {
 954:         // Handle restoring of the color here to prevent
 955:         // drawing problems when the Highlighter implementor
 956:         // forgets to restore it.
 957:         Color oldColor = g.getColor();
 958:         highlighter.paint(g);
 959:         g.setColor(oldColor);
 960:       }
 961:       
 962: 
 963:     rootView.paint(g, getVisibleEditorRect());
 964: 
 965:     if (caret != null && textComponent.hasFocus())
 966:       caret.paint(g);
 967:   }
 968: 
 969:   /**
 970:    * Paints the background of the text component.
 971:    *
 972:    * @param g the <code>Graphics</code> context to paint to
 973:    */
 974:   protected void paintBackground(Graphics g)
 975:   {
 976:     Color old = g.getColor();
 977:     g.setColor(textComponent.getBackground());
 978:     g.fillRect(0, 0, textComponent.getWidth(), textComponent.getHeight());
 979:     g.setColor(old);
 980:   }
 981: 
 982:   /**
 983:    * Overridden for better control over background painting. This now simply
 984:    * calls {@link #paint} and this delegates the background painting to
 985:    * {@link #paintBackground}.
 986:    *
 987:    * @param g the graphics to use
 988:    * @param c the component to be painted
 989:    */
 990:   public void update(Graphics g, JComponent c)
 991:   {
 992:     paint(g, c);
 993:   }
 994: 
 995:   /**
 996:    * Marks the specified range inside the text component's model as
 997:    * damaged and queues a repaint request.
 998:    *
 999:    * @param t the text component
1000:    * @param p0 the start location inside the document model of the range that
1001:    *        is damaged
1002:    * @param p1 the end location inside the document model of the range that
1003:    *        is damaged
1004:    */
1005:   public void damageRange(JTextComponent t, int p0, int p1)
1006:   {
1007:     damageRange(t, p0, p1, null, null);
1008:   }
1009: 
1010:   /**
1011:    * Marks the specified range inside the text component's model as
1012:    * damaged and queues a repaint request. This variant of this method
1013:    * allows a {@link Position.Bias} object to be specified for the start
1014:    * and end location of the range.
1015:    *
1016:    * @param t the text component
1017:    * @param p0 the start location inside the document model of the range that
1018:    *        is damaged
1019:    * @param p1 the end location inside the document model of the range that
1020:    *        is damaged
1021:    * @param firstBias the bias for the start location
1022:    * @param secondBias the bias for the end location
1023:    */
1024:   public void damageRange(JTextComponent t, int p0, int p1,
1025:                           Position.Bias firstBias, Position.Bias secondBias)
1026:   {
1027:     // Do nothing if the component cannot be properly displayed.
1028:     if (t.getWidth() == 0 || t.getHeight() == 0)
1029:       return;
1030:     
1031:     try
1032:       {
1033:         // Limit p0 and p1 to sane values to prevent unfriendly
1034:         // BadLocationExceptions. This makes it possible for the highlighter
1035:         // to send us illegal values which can happen when a large number
1036:         // of selected characters are removed (eg. by pressing delete
1037:         // or backspace).
1038:         // The reference implementation does not throw an exception, too.
1039:         p0 = Math.min(p0, t.getDocument().getLength());
1040:         p1 = Math.min(p1, t.getDocument().getLength());
1041: 
1042:         Rectangle l1 = modelToView(t, p0, firstBias);
1043:         Rectangle l2 = modelToView(t, p1, secondBias);
1044:         if (l1.y == l2.y)
1045:           {
1046:             SwingUtilities.computeUnion(l2.x, l2.y, l2.width, l2.height, l1);
1047:             t.repaint(l1);
1048:           }
1049:         else
1050:           {
1051:             // The two rectangles lie on different lines and we need a
1052:             // different algorithm to calculate the damaged area:
1053:             // 1. The line of p0 is damaged from the position of p0
1054:             // to the right border.
1055:             // 2. All lines between the ones where p0 and p1 lie on
1056:             // are completely damaged. Use the allocation area to find
1057:             // out the bounds.
1058:             // 3. The final line is damaged from the left bound to the
1059:             // position of p1.
1060:             Insets insets = t.getInsets();
1061: 
1062:             // Damage first line until the end.
1063:             l1.width = insets.right + t.getWidth() - l1.x;
1064:             t.repaint(l1);
1065:             
1066:             // Note: Utilities.getPositionBelow() may return the offset
1067:             // that was put in. In that case there is no next line and
1068:             // we should stop searching for one.
1069:             
1070:             int posBelow = Utilities.getPositionBelow(t, p0, l1.x);
1071:             int p1RowStart = Utilities.getRowStart(t, p1);
1072:             
1073:             if (posBelow != -1
1074:                 && posBelow != p0
1075:                 && Utilities.getRowStart(t, posBelow) != p1RowStart)
1076:               {
1077:                 // Take the rectangle of the offset we just found and grow it
1078:                 // to the maximum width. Retain y because this is our start
1079:                 // height.
1080:                 Rectangle grow = modelToView(t, posBelow);
1081:                 grow.x = insets.left;
1082:                 grow.width = t.getWidth() + insets.right;
1083:                 
1084:                 // Find further lines which have to be damaged completely.
1085:                 int nextPosBelow = posBelow;
1086:                 while (nextPosBelow != -1
1087:                        && posBelow != nextPosBelow
1088:                        && Utilities.getRowStart(t, nextPosBelow) != p1RowStart)
1089:                   {
1090:                     posBelow = nextPosBelow;
1091:                     nextPosBelow = Utilities.getPositionBelow(t, posBelow, l1.x);
1092:                     
1093:                     if (posBelow == nextPosBelow)
1094:                       break;
1095:                   }
1096:                 // Now posBelow is an offset on the last line which has to be damaged
1097:                 // completely. (newPosBelow is on the same line as p1)
1098:                  
1099:                 // Retrieve the rectangle of posBelow and use its y and height
1100:                 // value to calculate the final height of the multiple line
1101:                 // spanning rectangle.
1102:                 Rectangle end = modelToView(t, posBelow);
1103:                 grow.height = end.y + end.height - grow.y;
1104:                 
1105:                 // Mark that area as damage.
1106:                 t.repaint(grow);
1107:               }
1108:             
1109:             // Damage last line from its beginning to the position of p1.
1110:             l2.width += l2.x;
1111:             l2.x = insets.left;
1112:             t.repaint(l2);
1113:           }
1114:       }
1115:     catch (BadLocationException ex)
1116:       {
1117:         AssertionError err = new AssertionError("Unexpected bad location");
1118:         err.initCause(ex);
1119:         throw err;
1120:       }
1121:   }
1122: 
1123:   /**
1124:    * Returns the {@link EditorKit} used for the text component that is managed
1125:    * by this UI.
1126:    *
1127:    * @param t the text component
1128:    *
1129:    * @return the {@link EditorKit} used for the text component that is managed
1130:    *         by this UI
1131:    */
1132:   public EditorKit getEditorKit(JTextComponent t)
1133:   {
1134:     return kit;
1135:   }
1136: 
1137:   /**
1138:    * Gets the next position inside the document model that is visible on
1139:    * screen, starting from <code>pos</code>.
1140:    *
1141:    * @param t the text component
1142:    * @param pos the start positionn
1143:    * @param b the bias for pos
1144:    * @param direction the search direction
1145:    * @param biasRet filled by the method to indicate the bias of the return
1146:    *        value
1147:    *
1148:    * @return the next position inside the document model that is visible on
1149:    *         screen
1150:    */
1151:   public int getNextVisualPositionFrom(JTextComponent t, int pos,
1152:                                        Position.Bias b, int direction,
1153:                                        Position.Bias[] biasRet)
1154:     throws BadLocationException
1155:   {
1156:     // A comment in the spec of NavigationFilter.getNextVisualPositionFrom()
1157:     // suggests that this method should be implemented by forwarding the call
1158:     // the root view.
1159:     return rootView.getNextVisualPositionFrom(pos, b,
1160:                                               getVisibleEditorRect(),
1161:                                               direction, biasRet);
1162:   }
1163: 
1164:   /**
1165:    * Returns the root {@link View} of a text component.
1166:    *
1167:    * @return the root {@link View} of a text component
1168:    */
1169:   public View getRootView(JTextComponent t)
1170:   {
1171:     return rootView;
1172:   }
1173: 
1174:   /**
1175:    * Maps a position in the document into the coordinate space of the View.
1176:    * The output rectangle usually reflects the font height but has a width
1177:    * of zero. A bias of {@link Position.Bias#Forward} is used in this method.
1178:    *
1179:    * @param t the text component
1180:    * @param pos the position of the character in the model
1181:    *
1182:    * @return a rectangle that gives the location of the document position
1183:    *         inside the view coordinate space
1184:    *
1185:    * @throws BadLocationException if <code>pos</code> is invalid
1186:    * @throws IllegalArgumentException if b is not one of the above listed
1187:    *         valid values
1188:    */
1189:   public Rectangle modelToView(JTextComponent t, int pos)
1190:     throws BadLocationException
1191:   {
1192:     return modelToView(t, pos, Position.Bias.Forward);
1193:   }
1194: 
1195:   /**
1196:    * Maps a position in the document into the coordinate space of the View.
1197:    * The output rectangle usually reflects the font height but has a width
1198:    * of zero.
1199:    *
1200:    * @param t the text component
1201:    * @param pos the position of the character in the model
1202:    * @param bias either {@link Position.Bias#Forward} or
1203:    *        {@link Position.Bias#Backward} depending on the preferred
1204:    *        direction bias. If <code>null</code> this defaults to
1205:    *        <code>Position.Bias.Forward</code>
1206:    *
1207:    * @return a rectangle that gives the location of the document position
1208:    *         inside the view coordinate space
1209:    *
1210:    * @throws BadLocationException if <code>pos</code> is invalid
1211:    * @throws IllegalArgumentException if b is not one of the above listed
1212:    *         valid values
1213:    */
1214:   public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias)
1215:     throws BadLocationException
1216:   {
1217:     Rectangle r = getVisibleEditorRect();
1218:     
1219:     return (r != null) ? rootView.modelToView(pos, r, bias).getBounds()
1220:                        : null;
1221:   }
1222: 
1223:   /**
1224:    * Maps a point in the <code>View</code> coordinate space to a position
1225:    * inside a document model.
1226:    *
1227:    * @param t the text component
1228:    * @param pt the point to be mapped
1229:    *
1230:    * @return the position inside the document model that corresponds to
1231:    *     <code>pt</code>
1232:    */
1233:   public int viewToModel(JTextComponent t, Point pt)
1234:   {
1235:     return viewToModel(t, pt, null);
1236:   }
1237: 
1238:   /**
1239:    * Maps a point in the <code>View</code> coordinate space to a position
1240:    * inside a document model.
1241:    *
1242:    * @param t the text component
1243:    * @param pt the point to be mapped
1244:    * @param biasReturn filled in by the method to indicate the bias of the
1245:    *        return value
1246:    *
1247:    * @return the position inside the document model that corresponds to
1248:    *     <code>pt</code>
1249:    */
1250:   public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn)
1251:   {
1252:     return rootView.viewToModel(pt.x, pt.y, getVisibleEditorRect(), biasReturn);
1253:   }
1254: 
1255:   /**
1256:    * Creates a {@link View} for the specified {@link Element}.
1257:    *
1258:    * @param elem the <code>Element</code> to create a <code>View</code> for
1259:    *
1260:    * @see ViewFactory
1261:    */
1262:   public View create(Element elem)
1263:   {
1264:     // Subclasses have to implement this to get this functionality.
1265:     return null;
1266:   }
1267: 
1268:   /**
1269:    * Creates a {@link View} for the specified {@link Element}.
1270:    *
1271:    * @param elem the <code>Element</code> to create a <code>View</code> for
1272:    * @param p0 the start offset
1273:    * @param p1 the end offset
1274:    *
1275:    * @see ViewFactory
1276:    */
1277:   public View create(Element elem, int p0, int p1)
1278:   {
1279:     // Subclasses have to implement this to get this functionality.
1280:     return null;
1281:   }
1282: 
1283:   /**
1284:    * Returns the allocation to give the root view.
1285:    *
1286:    * @return the allocation to give the root view
1287:    *
1288:    * @specnote The allocation has nothing to do with visibility. According
1289:    *           to the specs the naming of this method is unfortunate and
1290:    *           has historical reasons
1291:    */
1292:   protected Rectangle getVisibleEditorRect()
1293:   {
1294:     int width = textComponent.getWidth();
1295:     int height = textComponent.getHeight();
1296: 
1297:     // Return null if the component has no valid size.
1298:     if (width <= 0 || height <= 0)
1299:       return null;
1300:     
1301:     Insets insets = textComponent.getInsets();
1302:     return new Rectangle(insets.left, insets.top,
1303:              width - insets.left - insets.right,
1304:              height - insets.top - insets.bottom);
1305:   }
1306: 
1307:   /**
1308:    * Sets the root view for the text component.
1309:    *
1310:    * @param view the <code>View</code> to be set as root view
1311:    */
1312:   protected final void setView(View view)
1313:   {
1314:     rootView.setView(view);
1315:     textComponent.revalidate();
1316:     textComponent.repaint();
1317:   }
1318: 
1319:   /**
1320:    * Indicates that the model of a text component has changed. This
1321:    * triggers a rebuild of the view hierarchy.
1322:    */
1323:   protected void modelChanged()
1324:   {
1325:     if (textComponent == null || rootView == null) 
1326:       return;
1327:     ViewFactory factory = rootView.getViewFactory();
1328:     if (factory == null) 
1329:       return;
1330:     Document doc = textComponent.getDocument();
1331:     if (doc == null)
1332:       return;
1333:     installDocumentListeners();
1334:     Element elem = doc.getDefaultRootElement();
1335:     if (elem == null)
1336:       return;
1337:     View view = factory.create(elem);
1338:     setView(view);
1339:   }
1340: 
1341:   /**
1342:    * Receives notification whenever one of the text component's bound
1343:    * properties changes. This default implementation does nothing.
1344:    * It is a hook that enables subclasses to react to property changes
1345:    * on the text component.
1346:    *
1347:    * @param ev the property change event
1348:    */
1349:   protected void propertyChange(PropertyChangeEvent ev)
1350:   {
1351:     // The default implementation does nothing.
1352:   }
1353: }