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