001 /* BasicTextUI.java -- 002 Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.plaf.basic; 040 041 import gnu.classpath.SystemProperties; 042 043 import java.awt.Color; 044 import java.awt.Container; 045 import java.awt.Dimension; 046 import java.awt.Graphics; 047 import java.awt.HeadlessException; 048 import java.awt.Insets; 049 import java.awt.Point; 050 import java.awt.Rectangle; 051 import java.awt.Shape; 052 import java.awt.Toolkit; 053 import java.awt.datatransfer.Clipboard; 054 import java.awt.datatransfer.StringSelection; 055 import java.awt.event.FocusEvent; 056 import java.awt.event.FocusListener; 057 import java.beans.PropertyChangeEvent; 058 import java.beans.PropertyChangeListener; 059 060 import javax.swing.Action; 061 import javax.swing.ActionMap; 062 import javax.swing.InputMap; 063 import javax.swing.JComponent; 064 import javax.swing.LookAndFeel; 065 import javax.swing.SwingConstants; 066 import javax.swing.SwingUtilities; 067 import javax.swing.TransferHandler; 068 import javax.swing.UIManager; 069 import javax.swing.event.DocumentEvent; 070 import javax.swing.event.DocumentListener; 071 import javax.swing.plaf.ActionMapUIResource; 072 import javax.swing.plaf.InputMapUIResource; 073 import javax.swing.plaf.TextUI; 074 import javax.swing.plaf.UIResource; 075 import javax.swing.text.AbstractDocument; 076 import javax.swing.text.AttributeSet; 077 import javax.swing.text.BadLocationException; 078 import javax.swing.text.Caret; 079 import javax.swing.text.DefaultCaret; 080 import javax.swing.text.DefaultEditorKit; 081 import javax.swing.text.DefaultHighlighter; 082 import javax.swing.text.Document; 083 import javax.swing.text.EditorKit; 084 import javax.swing.text.Element; 085 import javax.swing.text.Highlighter; 086 import javax.swing.text.JTextComponent; 087 import javax.swing.text.Keymap; 088 import javax.swing.text.Position; 089 import javax.swing.text.View; 090 import javax.swing.text.ViewFactory; 091 092 /** 093 * The abstract base class from which the UI classes for Swings text 094 * components are derived. This provides most of the functionality for 095 * the UI classes. 096 * 097 * @author original author unknown 098 * @author Roman Kennke (roman@kennke.org) 099 */ 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 }