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 java.awt.Color;
  42: import java.awt.Container;
  43: import java.awt.Dimension;
  44: import java.awt.Graphics;
  45: import java.awt.HeadlessException;
  46: import java.awt.Insets;
  47: import java.awt.Point;
  48: import java.awt.Rectangle;
  49: import java.awt.Shape;
  50: import java.awt.Toolkit;
  51: import java.awt.datatransfer.Clipboard;
  52: import java.awt.datatransfer.StringSelection;
  53: import java.awt.event.FocusEvent;
  54: import java.awt.event.FocusListener;
  55: import java.beans.PropertyChangeEvent;
  56: import java.beans.PropertyChangeListener;
  57: 
  58: import javax.swing.Action;
  59: import javax.swing.ActionMap;
  60: import javax.swing.InputMap;
  61: import javax.swing.JComponent;
  62: import javax.swing.LookAndFeel;
  63: import javax.swing.SwingConstants;
  64: import javax.swing.SwingUtilities;
  65: import javax.swing.TransferHandler;
  66: import javax.swing.UIManager;
  67: import javax.swing.event.DocumentEvent;
  68: import javax.swing.event.DocumentListener;
  69: import javax.swing.plaf.ActionMapUIResource;
  70: import javax.swing.plaf.InputMapUIResource;
  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(UIManager.getColor(prefix 
 607:         + ".inactiveForeground"));
 608:     textComponent.setSelectedTextColor(UIManager.getColor(prefix 
 609:         + ".selectionForeground"));
 610:     textComponent.setSelectionColor(UIManager.getColor(prefix 
 611:         + ".selectionBackground"));    
 612:   }
 613: 
 614:   /**
 615:    * This FocusListener triggers repaints on focus shift.
 616:    */
 617:   private FocusListener focuslistener = new FocusListener() {
 618:       public void focusGained(FocusEvent e) 
 619:       {
 620:         textComponent.repaint();
 621:       }
 622:       public void focusLost(FocusEvent e)
 623:       {
 624:         textComponent.repaint();
 625:         
 626:         // Integrates Swing text components with the system clipboard:
 627:         // The idea is that if one wants to copy text around X11-style
 628:         // (select text and middle-click in the target component) the focus
 629:         // will move to the new component which gives the old focus owner the
 630:         // possibility to paste its selection into the clipboard.
 631:         if (!e.isTemporary()
 632:             && textComponent.getSelectionStart()
 633:                != textComponent.getSelectionEnd())
 634:           {
 635:             SecurityManager sm = System.getSecurityManager();
 636:             try
 637:               {
 638:                 if (sm != null)
 639:                   sm.checkSystemClipboardAccess();
 640:                 
 641:                 Clipboard cb = Toolkit.getDefaultToolkit().getSystemSelection();
 642:                 if (cb != null)
 643:                   {
 644:                     StringSelection selection = new StringSelection(
 645:                         textComponent.getSelectedText());
 646:                     cb.setContents(selection, selection);
 647:                   }
 648:               }
 649:             catch (SecurityException se)
 650:               {
 651:                 // Not allowed to access the clipboard: Ignore and
 652:                 // do not access it.
 653:               }
 654:             catch (HeadlessException he)
 655:               {
 656:                 // There is no AWT: Ignore and do not access the
 657:                 // clipboard.
 658:               }
 659:             catch (IllegalStateException ise)
 660:             {
 661:                 // Clipboard is currently unavaible.
 662:             }
 663:           }
 664:       }
 665:     };
 666: 
 667:   /**
 668:    * Install all listeners on the text component.
 669:    */
 670:   protected void installListeners()
 671:   {
 672:     textComponent.addFocusListener(focuslistener);
 673:     textComponent.addPropertyChangeListener(updateHandler);
 674:     installDocumentListeners();
 675:   }
 676: 
 677:   /**
 678:    * Installs the document listeners on the textComponent's model.
 679:    */
 680:   private void installDocumentListeners()
 681:   {
 682:     Document doc = textComponent.getDocument();
 683:     if (doc != null)
 684:       doc.addDocumentListener(documentHandler);
 685:   }
 686: 
 687:   /**
 688:    * Returns the name of the keymap for this type of TextUI.
 689:    * 
 690:    * This is implemented so that the classname of this TextUI
 691:    * without the package prefix is returned. This way subclasses
 692:    * don't have to override this method.
 693:    * 
 694:    * @return the name of the keymap for this TextUI
 695:    */
 696:   protected String getKeymapName()
 697:   {
 698:     String fullClassName = getClass().getName();
 699:     int index = fullClassName.lastIndexOf('.');
 700:     String className = fullClassName.substring(index + 1);
 701:     return className;
 702:   }
 703: 
 704:   /**
 705:    * Creates the {@link Keymap} that is installed on the text component.
 706:    *
 707:    * @return the {@link Keymap} that is installed on the text component
 708:    */
 709:   protected Keymap createKeymap()
 710:   {
 711:     String keymapName = getKeymapName();
 712:     Keymap keymap = JTextComponent.getKeymap(keymapName);
 713:     if (keymap == null)
 714:       {
 715:         Keymap parentMap =
 716:           JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
 717:         keymap = JTextComponent.addKeymap(keymapName, parentMap);
 718:         Object val = UIManager.get(getPropertyPrefix() + ".keyBindings");
 719:         if (val != null && val instanceof JTextComponent.KeyBinding[])
 720:           {
 721:             JTextComponent.KeyBinding[] bindings =
 722:               (JTextComponent.KeyBinding[]) val;
 723:             JTextComponent.loadKeymap(keymap, bindings,
 724:                                       getComponent().getActions());
 725:           }
 726:       }
 727:     return keymap;
 728:   }
 729: 
 730:   /**
 731:    * Installs the keyboard actions on the text components.
 732:    */
 733:   protected void installKeyboardActions()
 734:   {
 735:     // This is only there for backwards compatibility.
 736:     textComponent.setKeymap(createKeymap());
 737: 
 738:     // load any bindings for the newer InputMap / ActionMap interface
 739:     SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED,
 740:                                      getInputMap());
 741:     SwingUtilities.replaceUIActionMap(textComponent, getActionMap());
 742:   }
 743:   
 744:   /**
 745:    * Creates an ActionMap to be installed on the text component.
 746:    * 
 747:    * @return an ActionMap to be installed on the text component
 748:    */
 749:   private ActionMap getActionMap()
 750:   {
 751:     // Note: There are no .actionMap entries in the standard L&Fs. However,
 752:     // with the RI it is possible to install action maps via such keys, so
 753:     // we must load them too. It can be observed that when there is no
 754:     // .actionMap entry in the UIManager, one gets installed after a text
 755:     // component of that type has been loaded.
 756:     String prefix = getPropertyPrefix();
 757:     String amName = prefix + ".actionMap";
 758:     ActionMap am = (ActionMap) UIManager.get(amName);
 759:     if (am == null)
 760:       {
 761:         am = createActionMap();
 762:         UIManager.put(amName, am);
 763:       }
 764: 
 765:     ActionMap map = new ActionMapUIResource();
 766:     map.setParent(am);
 767: 
 768:     return map;
 769:   }
 770: 
 771:   /**
 772:    * Creates a default ActionMap for text components that have no UI default
 773:    * for this (the standard for the built-in L&Fs). The ActionMap is copied
 774:    * from the text component's getActions() method.
 775:    *
 776:    * @returna default ActionMap
 777:    */
 778:   private ActionMap createActionMap()
 779:   {
 780:     ActionMap am = new ActionMapUIResource();
 781:     Action[] actions = textComponent.getActions();
 782:     for (int i = actions.length - 1; i >= 0; i--)
 783:       {
 784:         Action action = actions[i];
 785:         am.put(action.getValue(Action.NAME), action);
 786:       }
 787:     // Add TransferHandler's actions here. They don't seem to be in the
 788:     // JTextComponent's default actions, and I can't make up a better place
 789:     // to add them.
 790:     Action copyAction = TransferHandler.getCopyAction();
 791:     am.put(copyAction.getValue(Action.NAME), copyAction);
 792:     Action cutAction = TransferHandler.getCutAction();
 793:     am.put(cutAction.getValue(Action.NAME), cutAction);
 794:     Action pasteAction = TransferHandler.getPasteAction();
 795:     am.put(pasteAction.getValue(Action.NAME), pasteAction);
 796: 
 797:     return am;
 798:   }
 799: 
 800:   /**
 801:    * Gets the input map for the specified <code>condition</code>.
 802:    *
 803:    * @return the InputMap for the specified condition
 804:    */
 805:   private InputMap getInputMap()
 806:   {
 807:     InputMap im = new InputMapUIResource();
 808:     String prefix = getPropertyPrefix();
 809:     InputMap shared =
 810:       (InputMap) SharedUIDefaults.get(prefix + ".focusInputMap");
 811:     if (shared != null)
 812:       im.setParent(shared);
 813:     return im;
 814:   }
 815: 
 816:   /**
 817:    * Uninstalls this TextUI from the text component.
 818:    *
 819:    * @param component the text component to uninstall the UI from
 820:    */
 821:   public void uninstallUI(final JComponent component)
 822:   {
 823:     super.uninstallUI(component);
 824:     rootView.setView(null);
 825: 
 826:     uninstallDefaults();
 827:     uninstallListeners();
 828:     uninstallKeyboardActions();
 829: 
 830:     textComponent = null;
 831:   }
 832: 
 833:   /**
 834:    * Uninstalls all default properties that have previously been installed by
 835:    * this UI.
 836:    */
 837:   protected void uninstallDefaults()
 838:   {
 839:     // Do nothing here.
 840:   }
 841: 
 842:   /**
 843:    * Uninstalls all listeners that have previously been installed by
 844:    * this UI.
 845:    */
 846:   protected void uninstallListeners()
 847:   {
 848:     textComponent.removePropertyChangeListener(updateHandler);
 849:     textComponent.removeFocusListener(focuslistener);
 850:     textComponent.getDocument().removeDocumentListener(documentHandler);
 851:   }
 852: 
 853:   /**
 854:    * Uninstalls all keyboard actions that have previously been installed by
 855:    * this UI.
 856:    */
 857:   protected void uninstallKeyboardActions()
 858:   {
 859:     SwingUtilities.replaceUIInputMap(textComponent, JComponent.WHEN_FOCUSED, 
 860:                                      null);
 861:     SwingUtilities.replaceUIActionMap(textComponent, null);
 862:   }
 863: 
 864:   /**
 865:    * Returns the property prefix by which the text component's UIDefaults
 866:    * are looked up.
 867:    *
 868:    * @return the property prefix by which the text component's UIDefaults
 869:    *     are looked up
 870:    */
 871:   protected abstract String getPropertyPrefix();
 872: 
 873:   /**
 874:    * Returns the preferred size of the text component.
 875:    *
 876:    * @param c not used here
 877:    *
 878:    * @return the preferred size of the text component
 879:    */
 880:   public Dimension getPreferredSize(JComponent c)
 881:   {
 882:     View v = getRootView(textComponent);
 883: 
 884:     float w = v.getPreferredSpan(View.X_AXIS);
 885:     float h = v.getPreferredSpan(View.Y_AXIS);
 886: 
 887:     Insets i = c.getInsets();
 888:     return new Dimension((int) w + i.left + i.right,
 889:                          (int) h + i.top + i.bottom);
 890:   }
 891: 
 892:   /**
 893:    * Returns the maximum size for text components that use this UI.
 894:    *
 895:    * This returns (Integer.MAX_VALUE, Integer.MAX_VALUE).
 896:    *
 897:    * @param c not used here
 898:    *
 899:    * @return the maximum size for text components that use this UI
 900:    */
 901:   public Dimension getMaximumSize(JComponent c)
 902:   {
 903:     // Sun's implementation returns Integer.MAX_VALUE here, so do we.
 904:     return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 905:   }
 906: 
 907:   /**
 908:    * Returns the minimum size for text components. This returns the size
 909:    * of the component's insets.
 910:    *
 911:    * @return the minimum size for text components
 912:    */
 913:   public Dimension getMinimumSize(JComponent c)
 914:   {
 915:     Insets i = c.getInsets();
 916:     return new Dimension(i.left + i.right, i.top + i.bottom);
 917:   }
 918: 
 919:   /**
 920:    * Paints the text component. This acquires a read lock on the model and then
 921:    * calls {@link #paintSafely(Graphics)} in order to actually perform the
 922:    * painting.
 923:    *
 924:    * @param g the <code>Graphics</code> context to paint to
 925:    * @param c not used here
 926:    */
 927:   public final void paint(Graphics g, JComponent c)
 928:   {
 929:     try
 930:       {
 931:         Document doc = textComponent.getDocument();
 932:         if (doc instanceof AbstractDocument)
 933:           {
 934:             AbstractDocument aDoc = (AbstractDocument) doc;
 935:             aDoc.readLock();
 936:           }
 937:         
 938:         paintSafely(g);
 939:       }
 940:     finally
 941:       {
 942:         Document doc = textComponent.getDocument();
 943:         if (doc instanceof AbstractDocument)
 944:           {
 945:             AbstractDocument aDoc = (AbstractDocument) doc;
 946:             aDoc.readUnlock();
 947:           }
 948:       }
 949:   }
 950: 
 951:   /**
 952:    * This paints the text component while beeing sure that the model is not
 953:    * modified while painting.
 954:    *
 955:    * The following is performed in this order:
 956:    * <ol>
 957:    * <li>If the text component is opaque, the background is painted by
 958:    * calling {@link #paintBackground(Graphics)}.</li>
 959:    * <li>If there is a highlighter, the highlighter is painted.</li>
 960:    * <li>The view hierarchy is painted.</li>
 961:    * <li>The Caret is painter.</li>
 962:    * </ol>
 963:    *
 964:    * @param g the <code>Graphics</code> context to paint to
 965:    */
 966:   protected void paintSafely(Graphics g)
 967:   {
 968:     Caret caret = textComponent.getCaret();
 969:     Highlighter highlighter = textComponent.getHighlighter();
 970: 
 971:     if (textComponent.isOpaque())
 972:       paintBackground(g);
 973: 
 974:     // Try painting with the highlighter without checking whether there
 975:     // is a selection because a highlighter can be used to do more than
 976:     // marking selected text.
 977:     if (highlighter != null)
 978:       {
 979:         // Handle restoring of the color here to prevent
 980:         // drawing problems when the Highlighter implementor
 981:         // forgets to restore it.
 982:         Color oldColor = g.getColor();
 983:         highlighter.paint(g);
 984:         g.setColor(oldColor);
 985:       }
 986:       
 987: 
 988:     rootView.paint(g, getVisibleEditorRect());
 989: 
 990:     if (caret != null && textComponent.hasFocus())
 991:       caret.paint(g);
 992:   }
 993: 
 994:   /**
 995:    * Paints the background of the text component.
 996:    *
 997:    * @param g the <code>Graphics</code> context to paint to
 998:    */
 999:   protected void paintBackground(Graphics g)
1000:   {
1001:     Color old = g.getColor();
1002:     g.setColor(textComponent.getBackground());
1003:     g.fillRect(0, 0, textComponent.getWidth(), textComponent.getHeight());
1004:     g.setColor(old);
1005:   }
1006: 
1007:   /**
1008:    * Overridden for better control over background painting. This now simply
1009:    * calls {@link #paint} and this delegates the background painting to
1010:    * {@link #paintBackground}.
1011:    *
1012:    * @param g the graphics to use
1013:    * @param c the component to be painted
1014:    */
1015:   public void update(Graphics g, JComponent c)
1016:   {
1017:     paint(g, c);
1018:   }
1019: 
1020:   /**
1021:    * Marks the specified range inside the text component's model as
1022:    * damaged and queues a repaint request.
1023:    *
1024:    * @param t the text component
1025:    * @param p0 the start location inside the document model of the range that
1026:    *        is damaged
1027:    * @param p1 the end location inside the document model of the range that
1028:    *        is damaged
1029:    */
1030:   public void damageRange(JTextComponent t, int p0, int p1)
1031:   {
1032:     damageRange(t, p0, p1, null, null);
1033:   }
1034: 
1035:   /**
1036:    * Marks the specified range inside the text component's model as
1037:    * damaged and queues a repaint request. This variant of this method
1038:    * allows a {@link Position.Bias} object to be specified for the start
1039:    * and end location of the range.
1040:    *
1041:    * @param t the text component
1042:    * @param p0 the start location inside the document model of the range that
1043:    *        is damaged
1044:    * @param p1 the end location inside the document model of the range that
1045:    *        is damaged
1046:    * @param firstBias the bias for the start location
1047:    * @param secondBias the bias for the end location
1048:    */
1049:   public void damageRange(JTextComponent t, int p0, int p1,
1050:                           Position.Bias firstBias, Position.Bias secondBias)
1051:   {
1052:     // Do nothing if the component cannot be properly displayed.
1053:     if (t.getWidth() == 0 || t.getHeight() == 0)
1054:       return;
1055:     
1056:     try
1057:       {
1058:         // Limit p0 and p1 to sane values to prevent unfriendly
1059:         // BadLocationExceptions. This makes it possible for the highlighter
1060:         // to send us illegal values which can happen when a large number
1061:         // of selected characters are removed (eg. by pressing delete
1062:         // or backspace).
1063:         // The reference implementation does not throw an exception, too.
1064:         p0 = Math.min(p0, t.getDocument().getLength());
1065:         p1 = Math.min(p1, t.getDocument().getLength());
1066: 
1067:         Rectangle l1 = modelToView(t, p0, firstBias);
1068:         Rectangle l2 = modelToView(t, p1, secondBias);
1069:         if (l1 == null || l2 == null)
1070:           {
1071:             // Unable to determine the start or end of the selection.
1072:             t.repaint();
1073:           }
1074:         else if (l1.y == l2.y)
1075:           {
1076:             SwingUtilities.computeUnion(l2.x, l2.y, l2.width, l2.height, l1);
1077:             t.repaint(l1);
1078:           }
1079:         else
1080:           {
1081:             // The two rectangles lie on different lines and we need a
1082:             // different algorithm to calculate the damaged area:
1083:             // 1. The line of p0 is damaged from the position of p0
1084:             // to the right border.
1085:             // 2. All lines between the ones where p0 and p1 lie on
1086:             // are completely damaged. Use the allocation area to find
1087:             // out the bounds.
1088:             // 3. The final line is damaged from the left bound to the
1089:             // position of p1.
1090:             Insets insets = t.getInsets();
1091: 
1092:             // Damage first line until the end.
1093:             l1.width = insets.right + t.getWidth() - l1.x;
1094:             t.repaint(l1);
1095:             
1096:             // Note: Utilities.getPositionBelow() may return the offset
1097:             // that was put in. In that case there is no next line and
1098:             // we should stop searching for one.
1099:             
1100:             int posBelow = Utilities.getPositionBelow(t, p0, l1.x);
1101:             int p1RowStart = Utilities.getRowStart(t, p1);
1102:             
1103:             if (posBelow != -1
1104:                 && posBelow != p0
1105:                 && Utilities.getRowStart(t, posBelow) != p1RowStart)
1106:               {
1107:                 // Take the rectangle of the offset we just found and grow it
1108:                 // to the maximum width. Retain y because this is our start
1109:                 // height.
1110:                 Rectangle grow = modelToView(t, posBelow);
1111:                 grow.x = insets.left;
1112:                 grow.width = t.getWidth() + insets.right;
1113:                 
1114:                 // Find further lines which have to be damaged completely.
1115:                 int nextPosBelow = posBelow;
1116:                 while (nextPosBelow != -1
1117:                        && posBelow != nextPosBelow
1118:                        && Utilities.getRowStart(t, nextPosBelow) != p1RowStart)
1119:                   {
1120:                     posBelow = nextPosBelow;
1121:                     nextPosBelow = Utilities.getPositionBelow(t, posBelow, 
1122:                                                               l1.x);
1123:                     
1124:                     if (posBelow == nextPosBelow)
1125:                       break;
1126:                   }
1127:                 // Now posBelow is an offset on the last line which has to be 
1128:                 // damaged completely. (newPosBelow is on the same line as p1)
1129:                  
1130:                 // Retrieve the rectangle of posBelow and use its y and height
1131:                 // value to calculate the final height of the multiple line
1132:                 // spanning rectangle.
1133:                 Rectangle end = modelToView(t, posBelow);
1134:                 grow.height = end.y + end.height - grow.y;
1135:                 
1136:                 // Mark that area as damage.
1137:                 t.repaint(grow);
1138:               }
1139:             
1140:             // Damage last line from its beginning to the position of p1.
1141:             l2.width += l2.x;
1142:             l2.x = insets.left;
1143:             t.repaint(l2);
1144:           }
1145:       }
1146:     catch (BadLocationException ex)
1147:       {
1148:         AssertionError err = new AssertionError("Unexpected bad location");
1149:         err.initCause(ex);
1150:         throw err;
1151:       }
1152:   }
1153: 
1154:   /**
1155:    * Returns the {@link EditorKit} used for the text component that is managed
1156:    * by this UI.
1157:    *
1158:    * @param t the text component
1159:    *
1160:    * @return the {@link EditorKit} used for the text component that is managed
1161:    *         by this UI
1162:    */
1163:   public EditorKit getEditorKit(JTextComponent t)
1164:   {
1165:     return kit;
1166:   }
1167: 
1168:   /**
1169:    * Gets the next position inside the document model that is visible on
1170:    * screen, starting from <code>pos</code>.
1171:    *
1172:    * @param t the text component
1173:    * @param pos the start positionn
1174:    * @param b the bias for pos
1175:    * @param direction the search direction
1176:    * @param biasRet filled by the method to indicate the bias of the return
1177:    *        value
1178:    *
1179:    * @return the next position inside the document model that is visible on
1180:    *         screen
1181:    */
1182:   public int getNextVisualPositionFrom(JTextComponent t, int pos,
1183:                                        Position.Bias b, int direction,
1184:                                        Position.Bias[] biasRet)
1185:     throws BadLocationException
1186:   {
1187:     // A comment in the spec of NavigationFilter.getNextVisualPositionFrom()
1188:     // suggests that this method should be implemented by forwarding the call
1189:     // the root view.
1190:     return rootView.getNextVisualPositionFrom(pos, b,
1191:                                               getVisibleEditorRect(),
1192:                                               direction, biasRet);
1193:   }
1194: 
1195:   /**
1196:    * Returns the root {@link View} of a text component.
1197:    *
1198:    * @return the root {@link View} of a text component
1199:    */
1200:   public View getRootView(JTextComponent t)
1201:   {
1202:     return rootView;
1203:   }
1204: 
1205:   /**
1206:    * Maps a position in the document into the coordinate space of the View.
1207:    * The output rectangle usually reflects the font height but has a width
1208:    * of zero. A bias of {@link Position.Bias#Forward} is used in this method.
1209:    *
1210:    * @param t the text component
1211:    * @param pos the position of the character in the model
1212:    *
1213:    * @return a rectangle that gives the location of the document position
1214:    *         inside the view coordinate space
1215:    *
1216:    * @throws BadLocationException if <code>pos</code> is invalid
1217:    * @throws IllegalArgumentException if b is not one of the above listed
1218:    *         valid values
1219:    */
1220:   public Rectangle modelToView(JTextComponent t, int pos)
1221:     throws BadLocationException
1222:   {
1223:     return modelToView(t, pos, Position.Bias.Forward);
1224:   }
1225: 
1226:   /**
1227:    * Maps a position in the document into the coordinate space of the View.
1228:    * The output rectangle usually reflects the font height but has a width
1229:    * of zero.
1230:    *
1231:    * @param t the text component
1232:    * @param pos the position of the character in the model
1233:    * @param bias either {@link Position.Bias#Forward} or
1234:    *        {@link Position.Bias#Backward} depending on the preferred
1235:    *        direction bias. If <code>null</code> this defaults to
1236:    *        <code>Position.Bias.Forward</code>
1237:    *
1238:    * @return a rectangle that gives the location of the document position
1239:    *         inside the view coordinate space
1240:    *
1241:    * @throws BadLocationException if <code>pos</code> is invalid
1242:    * @throws IllegalArgumentException if b is not one of the above listed
1243:    *         valid values
1244:    */
1245:   public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias)
1246:     throws BadLocationException
1247:   {
1248:     Rectangle r = getVisibleEditorRect();
1249:     
1250:     return (r != null) ? rootView.modelToView(pos, r, bias).getBounds()
1251:                        : null;
1252:   }
1253: 
1254:   /**
1255:    * Maps a point in the <code>View</code> coordinate space to a position
1256:    * inside a document model.
1257:    *
1258:    * @param t the text component
1259:    * @param pt the point to be mapped
1260:    *
1261:    * @return the position inside the document model that corresponds to
1262:    *     <code>pt</code>
1263:    */
1264:   public int viewToModel(JTextComponent t, Point pt)
1265:   {
1266:     return viewToModel(t, pt, null);
1267:   }
1268: 
1269:   /**
1270:    * Maps a point in the <code>View</code> coordinate space to a position
1271:    * inside a document model.
1272:    *
1273:    * @param t the text component
1274:    * @param pt the point to be mapped
1275:    * @param biasReturn filled in by the method to indicate the bias of the
1276:    *        return value
1277:    *
1278:    * @return the position inside the document model that corresponds to
1279:    *     <code>pt</code>
1280:    */
1281:   public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn)
1282:   {
1283:     return rootView.viewToModel(pt.x, pt.y, getVisibleEditorRect(), biasReturn);
1284:   }
1285: 
1286:   /**
1287:    * Creates a {@link View} for the specified {@link Element}.
1288:    *
1289:    * @param elem the <code>Element</code> to create a <code>View</code> for
1290:    *
1291:    * @see ViewFactory
1292:    */
1293:   public View create(Element elem)
1294:   {
1295:     // Subclasses have to implement this to get this functionality.
1296:     return null;
1297:   }
1298: 
1299:   /**
1300:    * Creates a {@link View} for the specified {@link Element}.
1301:    *
1302:    * @param elem the <code>Element</code> to create a <code>View</code> for
1303:    * @param p0 the start offset
1304:    * @param p1 the end offset
1305:    *
1306:    * @see ViewFactory
1307:    */
1308:   public View create(Element elem, int p0, int p1)
1309:   {
1310:     // Subclasses have to implement this to get this functionality.
1311:     return null;
1312:   }
1313: 
1314:   /**
1315:    * Returns the allocation to give the root view.
1316:    *
1317:    * @return the allocation to give the root view
1318:    *
1319:    * @specnote The allocation has nothing to do with visibility. According
1320:    *           to the specs the naming of this method is unfortunate and
1321:    *           has historical reasons
1322:    */
1323:   protected Rectangle getVisibleEditorRect()
1324:   {
1325:     int width = textComponent.getWidth();
1326:     int height = textComponent.getHeight();
1327: 
1328:     // Return null if the component has no valid size.
1329:     if (width <= 0 || height <= 0)
1330:       return null;
1331:     
1332:     Insets insets = textComponent.getInsets();
1333:     return new Rectangle(insets.left, insets.top,
1334:              width - insets.left - insets.right,
1335:              height - insets.top - insets.bottom);
1336:   }
1337: 
1338:   /**
1339:    * Sets the root view for the text component.
1340:    *
1341:    * @param view the <code>View</code> to be set as root view
1342:    */
1343:   protected final void setView(View view)
1344:   {
1345:     rootView.setView(view);
1346:     textComponent.revalidate();
1347:     textComponent.repaint();
1348:   }
1349: 
1350:   /**
1351:    * Indicates that the model of a text component has changed. This
1352:    * triggers a rebuild of the view hierarchy.
1353:    */
1354:   protected void modelChanged()
1355:   {
1356:     if (textComponent == null || rootView == null) 
1357:       return;
1358:     ViewFactory factory = rootView.getViewFactory();
1359:     if (factory == null) 
1360:       return;
1361:     Document doc = textComponent.getDocument();
1362:     if (doc == null)
1363:       return;
1364:     installDocumentListeners();
1365:     Element elem = doc.getDefaultRootElement();
1366:     if (elem == null)
1367:       return;
1368:     View view = factory.create(elem);
1369:     setView(view);
1370:   }
1371: 
1372:   /**
1373:    * Receives notification whenever one of the text component's bound
1374:    * properties changes. This default implementation does nothing.
1375:    * It is a hook that enables subclasses to react to property changes
1376:    * on the text component.
1377:    *
1378:    * @param ev the property change event
1379:    */
1380:   protected void propertyChange(PropertyChangeEvent ev)
1381:   {
1382:     // The default implementation does nothing.
1383:   }
1384: }