Source for javax.swing.plaf.basic.BasicTextUI

   1: /* BasicTextUI.java --
   2:    Copyright (C) 2002, 2003, 2004, 2005  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.Container;
  42: import java.awt.Dimension;
  43: import java.awt.Graphics;
  44: import java.awt.Insets;
  45: import java.awt.Point;
  46: import java.awt.Rectangle;
  47: import java.awt.Shape;
  48: import java.awt.event.FocusEvent;
  49: import java.awt.event.FocusListener;
  50: import java.beans.PropertyChangeEvent;
  51: import java.beans.PropertyChangeListener;
  52: 
  53: import javax.swing.Action;
  54: import javax.swing.ActionMap;
  55: import javax.swing.InputMap;
  56: import javax.swing.JComponent;
  57: import javax.swing.SwingUtilities;
  58: import javax.swing.UIDefaults;
  59: import javax.swing.UIManager;
  60: import javax.swing.event.DocumentEvent;
  61: import javax.swing.event.DocumentListener;
  62: import javax.swing.plaf.ActionMapUIResource;
  63: import javax.swing.plaf.TextUI;
  64: import javax.swing.plaf.UIResource;
  65: import javax.swing.text.BadLocationException;
  66: import javax.swing.text.Caret;
  67: import javax.swing.text.DefaultCaret;
  68: import javax.swing.text.DefaultEditorKit;
  69: import javax.swing.text.DefaultHighlighter;
  70: import javax.swing.text.Document;
  71: import javax.swing.text.EditorKit;
  72: import javax.swing.text.Element;
  73: import javax.swing.text.Highlighter;
  74: import javax.swing.text.JTextComponent;
  75: import javax.swing.text.Keymap;
  76: import javax.swing.text.PlainView;
  77: import javax.swing.text.Position;
  78: import javax.swing.text.View;
  79: import javax.swing.text.ViewFactory;
  80: 
  81: 
  82: public abstract class BasicTextUI extends TextUI
  83:   implements ViewFactory
  84: {
  85:   public static class BasicCaret extends DefaultCaret
  86:     implements UIResource
  87:   {
  88:     public BasicCaret()
  89:     {
  90:     }
  91:   }
  92: 
  93:   public static class BasicHighlighter extends DefaultHighlighter
  94:     implements UIResource
  95:   {
  96:     public BasicHighlighter()
  97:     {
  98:     }
  99:   }
 100:   
 101:   private class RootView extends View
 102:   {
 103:     private View view;
 104:     
 105:     public RootView()
 106:     {
 107:       super(null);
 108:     }
 109: 
 110:     // View methods.
 111: 
 112:     public ViewFactory getViewFactory()
 113:     {
 114:       // FIXME: Handle EditorKit somehow.
 115:       return BasicTextUI.this;
 116:     }
 117: 
 118:     public void setView(View v)
 119:     {
 120:       if (view != null)
 121:     view.setParent(null);
 122:       
 123:       if (v != null)
 124:     v.setParent(null);
 125: 
 126:       view = v;
 127:     }
 128: 
 129:     public Container getContainer()
 130:     {
 131:       return textComponent;
 132:     }
 133:     
 134:     public float getPreferredSpan(int axis)
 135:     {
 136:       if (view != null)
 137:     return view.getPreferredSpan(axis);
 138: 
 139:       return Integer.MAX_VALUE;
 140:     }
 141: 
 142:     public void paint(Graphics g, Shape s)
 143:     {
 144:       if (view != null)
 145:     view.paint(g, s);
 146:     }
 147: 
 148:     public Shape modelToView(int position, Shape a, Position.Bias bias)
 149:       throws BadLocationException
 150:     {
 151:       if (view == null)
 152:     return null;
 153:       
 154:       return ((PlainView) view).modelToView(position, a, bias).getBounds();
 155:     }
 156: 
 157:     /**
 158:      * Notification about text insertions. These are forwarded to the
 159:      * real root view.
 160:      *
 161:      * @param ev the DocumentEvent describing the change
 162:      * @param shape the current allocation of the view's display
 163:      * @param vf the ViewFactory to use for creating new Views
 164:      */
 165:     public void insertUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
 166:     {
 167:       view.insertUpdate(ev, shape, vf);
 168:     }
 169: 
 170:     /**
 171:      * Notification about text removals. These are forwarded to the
 172:      * real root view.
 173:      *
 174:      * @param ev the DocumentEvent describing the change
 175:      * @param shape the current allocation of the view's display
 176:      * @param vf the ViewFactory to use for creating new Views
 177:      */
 178:     public void removeUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
 179:     {
 180:       view.removeUpdate(ev, shape, vf);
 181:     }
 182: 
 183:     /**
 184:      * Notification about text changes. These are forwarded to the
 185:      * real root view.
 186:      *
 187:      * @param ev the DocumentEvent describing the change
 188:      * @param shape the current allocation of the view's display
 189:      * @param vf the ViewFactory to use for creating new Views
 190:      */
 191:     public void changedUpdate(DocumentEvent ev, Shape shape, ViewFactory vf)
 192:     {
 193:       view.changedUpdate(ev, shape, vf);
 194:     }
 195:   }
 196: 
 197:   class UpdateHandler implements PropertyChangeListener
 198:   {
 199:     public void propertyChange(PropertyChangeEvent event)
 200:     {
 201:       if (event.getPropertyName().equals("document"))
 202:     {
 203:           // Document changed.
 204:       modelChanged();
 205:     }
 206:     }
 207:   }
 208: 
 209:   /**
 210:    * Listens for changes on the underlying model and forwards notifications
 211:    * to the View. This also updates the caret position of the text component.
 212:    *
 213:    * TODO: Maybe this should somehow be handled through EditorKits
 214:    */
 215:   class DocumentHandler implements DocumentListener
 216:   {
 217:     /**
 218:      * Notification about a document change event.
 219:      *
 220:      * @param ev the DocumentEvent describing the change
 221:      */
 222:     public void changedUpdate(DocumentEvent ev)
 223:     {
 224:       Dimension size = textComponent.getSize();
 225:       rootView.changedUpdate(ev, new Rectangle(0, 0, size.width, size.height),
 226:                              BasicTextUI.this);
 227:     }
 228:     
 229:     /**
 230:      * Notification about a document insert event.
 231:      *
 232:      * @param ev the DocumentEvent describing the insertion
 233:      */
 234:     public void insertUpdate(DocumentEvent ev)
 235:     {
 236:       Dimension size = textComponent.getSize();
 237:       rootView.insertUpdate(ev, new Rectangle(0, 0, size.width, size.height),
 238:                             BasicTextUI.this);
 239:       int caretPos = textComponent.getCaretPosition();
 240:       if (caretPos >= ev.getOffset())
 241:         textComponent.setCaretPosition(caretPos + ev.getLength());
 242:     }
 243: 
 244:     /**
 245:      * Notification about a document removal event.
 246:      *
 247:      * @param ev the DocumentEvent describing the removal
 248:      */
 249:     public void removeUpdate(DocumentEvent ev)
 250:     {
 251:       Dimension size = textComponent.getSize();
 252:       rootView.removeUpdate(ev, new Rectangle(0, 0, size.width, size.height),
 253:                             BasicTextUI.this);
 254:       int caretPos = textComponent.getCaretPosition();
 255:       if (caretPos >= ev.getOffset())
 256:         textComponent.setCaretPosition(caretPos - ev.getLength());
 257:     }
 258:   }
 259: 
 260:   static EditorKit kit = new DefaultEditorKit();
 261: 
 262:   RootView rootView = new RootView();
 263:   JTextComponent textComponent;
 264:   UpdateHandler updateHandler = new UpdateHandler();
 265: 
 266:   /** The DocumentEvent handler. */
 267:   DocumentHandler documentHandler = new DocumentHandler();
 268: 
 269:   public BasicTextUI()
 270:   {
 271:   }
 272: 
 273:   protected Caret createCaret()
 274:   {
 275:     return new BasicCaret();
 276:   }
 277: 
 278:   protected Highlighter createHighlighter()
 279:   {
 280:     return new BasicHighlighter();
 281:   }
 282:   
 283:   protected final JTextComponent getComponent()
 284:   {
 285:     return textComponent;
 286:   }
 287:   
 288:   public void installUI(final JComponent c)
 289:   {
 290:     super.installUI(c);
 291:     c.setOpaque(true);
 292: 
 293:     textComponent = (JTextComponent) c;
 294: 
 295:     Document doc = textComponent.getDocument();
 296:     if (doc == null)
 297:       {
 298:     doc = getEditorKit(textComponent).createDefaultDocument();
 299:     textComponent.setDocument(doc);
 300:       }
 301:     
 302:     textComponent.addPropertyChangeListener(updateHandler);
 303:     modelChanged();
 304:     
 305:     installDefaults();
 306:     installListeners();
 307:     installKeyboardActions();
 308:   }
 309: 
 310:   protected void installDefaults()
 311:   {
 312:     Caret caret = textComponent.getCaret();
 313:     if (caret == null)
 314:       {
 315:         caret = createCaret();
 316:         textComponent.setCaret(caret);
 317:       }
 318: 
 319:     Highlighter highlighter = textComponent.getHighlighter();
 320:     if (highlighter == null)
 321:       textComponent.setHighlighter(createHighlighter());
 322: 
 323:     String prefix = getPropertyPrefix();
 324:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 325:     textComponent.setBackground(defaults.getColor(prefix + ".background"));
 326:     textComponent.setForeground(defaults.getColor(prefix + ".foreground"));
 327:     textComponent.setMargin(defaults.getInsets(prefix + ".margin"));
 328:     textComponent.setBorder(defaults.getBorder(prefix + ".border"));
 329:     textComponent.setFont(defaults.getFont(prefix + ".font"));
 330: 
 331:     caret.setBlinkRate(defaults.getInt(prefix + ".caretBlinkRate"));
 332:   }
 333: 
 334:   private FocusListener focuslistener = new FocusListener() {
 335:       public void focusGained(FocusEvent e) 
 336:       {
 337:         textComponent.repaint();
 338:       }
 339:       public void focusLost(FocusEvent e)
 340:       {
 341:         textComponent.repaint();
 342:       }
 343:     };
 344: 
 345:   protected void installListeners()
 346:   {
 347:     textComponent.addFocusListener(focuslistener);
 348:     installDocumentListeners();
 349:   }
 350: 
 351:   /**
 352:    * Installs the document listeners on the textComponent's model.
 353:    */
 354:   private void installDocumentListeners()
 355:   {
 356:     Document doc = textComponent.getDocument();
 357:     if (doc != null)
 358:       doc.addDocumentListener(documentHandler);
 359:   }
 360: 
 361:   /**
 362:    * Returns the name of the keymap for this type of TextUI.
 363:    * 
 364:    * This is implemented so that the classname of this TextUI
 365:    * without the package prefix is returned. This way subclasses
 366:    * don't have to override this method.
 367:    * 
 368:    * @return the name of the keymap for this TextUI
 369:    */
 370:   protected String getKeymapName()
 371:   {
 372:     String fullClassName = getClass().getName();
 373:     int index = fullClassName.lastIndexOf('.');
 374:     String className = fullClassName.substring(index + 1);
 375:     return className;
 376:   }
 377: 
 378:   protected Keymap createKeymap()
 379:   {
 380:     String prefix = getPropertyPrefix();
 381:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 382:     JTextComponent.KeyBinding[] bindings = 
 383:       (JTextComponent.KeyBinding[]) defaults.get(prefix + ".keyBindings");
 384:     if (bindings == null)
 385:       {
 386:         bindings = new JTextComponent.KeyBinding[0];
 387:         defaults.put(prefix + ".keyBindings", bindings);
 388:       }
 389: 
 390:     Keymap km = JTextComponent.addKeymap(getKeymapName(), 
 391:                                          JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP));    
 392:     JTextComponent.loadKeymap(km, bindings, textComponent.getActions());
 393:     return km;    
 394:   }
 395: 
 396:   protected void installKeyboardActions()
 397:   {    
 398:     // load any bindings for the older Keymap interface
 399:     Keymap km = JTextComponent.getKeymap(getKeymapName());
 400:     if (km == null)
 401:       km = createKeymap();
 402:     textComponent.setKeymap(km);
 403: 
 404:     // load any bindings for the newer InputMap / ActionMap interface
 405:     SwingUtilities.replaceUIInputMap(textComponent, 
 406:                                      JComponent.WHEN_FOCUSED,
 407:                                      getInputMap(JComponent.WHEN_FOCUSED));
 408:     SwingUtilities.replaceUIActionMap(textComponent, getActionMap());
 409:   }
 410: 
 411:   InputMap getInputMap(int condition)
 412:   {
 413:     String prefix = getPropertyPrefix();
 414:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();
 415:     switch (condition)
 416:       {
 417:       case JComponent.WHEN_IN_FOCUSED_WINDOW:
 418:         // FIXME: is this the right string? nobody seems to use it.
 419:         return (InputMap) defaults.get(prefix + ".windowInputMap"); 
 420:       case JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT:
 421:         return (InputMap) defaults.get(prefix + ".ancestorInputMap");
 422:       default:
 423:       case JComponent.WHEN_FOCUSED:
 424:         return (InputMap) defaults.get(prefix + ".focusInputMap");
 425:       }
 426:   }
 427: 
 428:   ActionMap getActionMap()
 429:   {
 430:     String prefix = getPropertyPrefix();
 431:     UIDefaults defaults = UIManager.getLookAndFeelDefaults();    
 432:     ActionMap am = (ActionMap) defaults.get(prefix + ".actionMap");
 433:     if (am == null)
 434:       {
 435:         am = createActionMap();
 436:         defaults.put(prefix + ".actionMap", am);
 437:       }
 438:     return am;
 439:   }
 440: 
 441:   ActionMap createActionMap()
 442:   {
 443:     Action[] actions = textComponent.getActions();
 444:     ActionMap am = new ActionMapUIResource();
 445:     for (int i = 0; i < actions.length; ++i)
 446:       {
 447:         String name = (String) actions[i].getValue(Action.NAME);
 448:         if (name != null)
 449:           am.put(name, actions[i]);
 450:       }
 451:     return am;
 452:   }
 453:   
 454:   public void uninstallUI(final JComponent component)
 455:   {
 456:     super.uninstallUI(component);
 457:     rootView.setView(null);
 458: 
 459:     textComponent.removePropertyChangeListener(updateHandler);
 460: 
 461:     uninstallDefaults();
 462:     uninstallListeners();
 463:     uninstallKeyboardActions();
 464: 
 465:     textComponent = null;
 466:   }
 467: 
 468:   protected void uninstallDefaults()
 469:   {
 470:     // Do nothing here.
 471:   }
 472: 
 473:   protected void uninstallListeners()
 474:   {
 475:     textComponent.removeFocusListener(focuslistener);
 476:   }
 477: 
 478:   protected void uninstallKeyboardActions()
 479:   {
 480:     // Do nothing here.
 481:   }
 482:   
 483:   protected abstract String getPropertyPrefix();
 484: 
 485:   public Dimension getPreferredSize(JComponent c)
 486:   {
 487:     View v = getRootView(textComponent);
 488: 
 489:     float w = v.getPreferredSpan(View.X_AXIS);
 490:     float h = v.getPreferredSpan(View.Y_AXIS);
 491: 
 492:     return new Dimension((int) w, (int) h);
 493:   }
 494: 
 495:   /**
 496:    * Returns the maximum size for text components that use this UI.
 497:    *
 498:    * This returns (Integer.MAX_VALUE, Integer.MAX_VALUE).
 499:    *
 500:    * @return the maximum size for text components that use this UI
 501:    */
 502:   public Dimension getMaximumSize(JComponent c)
 503:   {
 504:     // Sun's implementation returns Integer.MAX_VALUE here, so do we.
 505:     return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 506:   }
 507: 
 508:   public final void paint(Graphics g, JComponent c)
 509:   {
 510:     paintSafely(g);
 511:   }
 512: 
 513:   protected void paintSafely(Graphics g)
 514:   {
 515:     Caret caret = textComponent.getCaret();
 516:     Highlighter highlighter = textComponent.getHighlighter();
 517:     
 518:     if (textComponent.isOpaque())
 519:       paintBackground(g);
 520:     
 521:     if (highlighter != null
 522:     && textComponent.getSelectionStart() != textComponent.getSelectionEnd())
 523:       highlighter.paint(g);
 524: 
 525:     rootView.paint(g, getVisibleEditorRect());
 526: 
 527:     if (caret != null && textComponent.hasFocus())
 528:       caret.paint(g);
 529:   }
 530: 
 531:   protected void paintBackground(Graphics g)
 532:   {
 533:     g.setColor(textComponent.getBackground());
 534:     g.fillRect(0, 0, textComponent.getWidth(), textComponent.getHeight());
 535:   }
 536: 
 537:   public void damageRange(JTextComponent t, int p0, int p1)
 538:   {
 539:     damageRange(t, p0, p1, null, null);
 540:   }
 541: 
 542:   public void damageRange(JTextComponent t, int p0, int p1,
 543:                           Position.Bias firstBias, Position.Bias secondBias)
 544:   {
 545:   }
 546: 
 547:   public EditorKit getEditorKit(JTextComponent t)
 548:   {
 549:     return kit;
 550:   }
 551: 
 552:   public int getNextVisualPositionFrom(JTextComponent t, int pos,
 553:                                        Position.Bias b, int direction,
 554:                                        Position.Bias[] biasRet)
 555:     throws BadLocationException
 556:   {
 557:     return 0;
 558:   }
 559: 
 560:   public View getRootView(JTextComponent t)
 561:   {
 562:     return rootView;
 563:   }
 564: 
 565:   public Rectangle modelToView(JTextComponent t, int pos)
 566:     throws BadLocationException
 567:   {
 568:     return modelToView(t, pos, Position.Bias.Forward);
 569:   }
 570: 
 571:   public Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias)
 572:     throws BadLocationException
 573:   {
 574:     return rootView.modelToView(pos, getVisibleEditorRect(), bias).getBounds();
 575:   }
 576: 
 577:   public int viewToModel(JTextComponent t, Point pt)
 578:   {
 579:     return viewToModel(t, pt, null);
 580:   }
 581: 
 582:   public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn)
 583:   {
 584:     return 0;
 585:   }
 586: 
 587:   public View create(Element elem)
 588:   {
 589:     // Subclasses have to implement this to get this functionality.
 590:     return null;
 591:   }
 592: 
 593:   public View create(Element elem, int p0, int p1)
 594:   {
 595:     // Subclasses have to implement this to get this functionality.
 596:     return null;
 597:   }
 598:   
 599:   protected Rectangle getVisibleEditorRect()
 600:   {
 601:     int width = textComponent.getWidth();
 602:     int height = textComponent.getHeight();
 603: 
 604:     if (width <= 0 || height <= 0)
 605:       return null;
 606:     
 607:     Insets insets = textComponent.getInsets();
 608:     return new Rectangle(insets.left, insets.top,
 609:              width - insets.left + insets.right,
 610:              height - insets.top + insets.bottom);
 611:   }
 612: 
 613:   protected final void setView(View view)
 614:   {
 615:     rootView.setView(view);
 616:     view.setParent(rootView);
 617:   }
 618: 
 619:   protected void modelChanged()
 620:   {
 621:     if (textComponent == null || rootView == null) 
 622:       return;
 623:     ViewFactory factory = rootView.getViewFactory();
 624:     if (factory == null) 
 625:       return;
 626:     Document doc = textComponent.getDocument();
 627:     if (doc == null)
 628:       return;
 629:     installDocumentListeners();
 630:     Element elem = doc.getDefaultRootElement();
 631:     if (elem == null)
 632:       return;
 633:     setView(factory.create(elem));
 634:   }
 635: }