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