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