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