1 /* 2 * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.plaf.basic; 26 27 import java.util.*; 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.awt.datatransfer.*; 31 import java.awt.im.InputContext; 32 import java.beans.*; 33 import java.io.*; 34 import javax.swing.*; 35 import javax.swing.plaf.*; 36 import javax.swing.text.*; 37 import javax.swing.event.*; 38 import javax.swing.border.Border; 39 import javax.swing.plaf.UIResource; 40 import javax.swing.plaf.synth.SynthUI; 41 import sun.swing.DefaultLookup; 42 import sun.awt.AppContext; 43 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag; 44 45 /** 46 * <p> 47 * Basis of a text components look-and-feel. This provides the 48 * basic editor view and controller services that may be useful 49 * when creating a look-and-feel for an extension of 50 * <code>JTextComponent</code>. 51 * <p> 52 * Most state is held in the associated <code>JTextComponent</code> 53 * as bound properties, and the UI installs default values for the 54 * various properties. This default will install something for 55 * all of the properties. Typically, a LAF implementation will 56 * do more however. At a minimum, a LAF would generally install 57 * key bindings. 58 * <p> 59 * This class also provides some concurrency support if the 60 * <code>Document</code> associated with the JTextComponent is a subclass of 61 * <code>AbstractDocument</code>. Access to the View (or View hierarchy) is 62 * serialized between any thread mutating the model and the Swing 63 * event thread (which is expected to render, do model/view coordinate 64 * translation, etc). <em>Any access to the root view should first 65 * acquire a read-lock on the AbstractDocument and release that lock 66 * in a finally block.</em> 67 * <p> 68 * An important method to define is the {@link #getPropertyPrefix} method 69 * which is used as the basis of the keys used to fetch defaults 70 * from the UIManager. The string should reflect the type of 71 * TextUI (eg. TextField, TextArea, etc) without the particular 72 * LAF part of the name (eg Metal, Motif, etc). 73 * <p> 74 * To build a view of the model, one of the following strategies 75 * can be employed. 76 * <ol> 77 * <li> 78 * One strategy is to simply redefine the 79 * ViewFactory interface in the UI. By default, this UI itself acts 80 * as the factory for View implementations. This is useful 81 * for simple factories. To do this reimplement the 82 * {@link #create} method. 83 * <li> 84 * A common strategy for creating more complex types of documents 85 * is to have the EditorKit implementation return a factory. Since 86 * the EditorKit ties all of the pieces necessary to maintain a type 87 * of document, the factory is typically an important part of that 88 * and should be produced by the EditorKit implementation. 89 * </ol> 90 * <p> 91 * <strong>Warning:</strong> 92 * Serialized objects of this class will not be compatible with 93 * future Swing releases. The current serialization support is 94 * appropriate for short term storage or RMI between applications running 95 * the same version of Swing. As of 1.4, support for long term storage 96 * of all JavaBeans<sup><font size="-2">TM</font></sup> 97 * has been added to the <code>java.beans</code> package. 98 * Please see {@link java.beans.XMLEncoder}. 99 * 100 * @author Timothy Prinzing 101 * @author Shannon Hickey (drag and drop) 102 */ 103 public abstract class BasicTextUI extends TextUI implements ViewFactory { 104 105 /** 106 * Creates a new UI. 107 */ 108 public BasicTextUI() { 109 painted = false; 110 } 111 112 /** 113 * Creates the object to use for a caret. By default an 114 * instance of BasicCaret is created. This method 115 * can be redefined to provide something else that implements 116 * the InputPosition interface or a subclass of JCaret. 117 * 118 * @return the caret object 119 */ 120 protected Caret createCaret() { 121 return new BasicCaret(); 122 } 123 124 /** 125 * Creates the object to use for adding highlights. By default 126 * an instance of BasicHighlighter is created. This method 127 * can be redefined to provide something else that implements 128 * the Highlighter interface or a subclass of DefaultHighlighter. 129 * 130 * @return the highlighter 131 */ 132 protected Highlighter createHighlighter() { 133 return new BasicHighlighter(); 134 } 135 136 /** 137 * Fetches the name of the keymap that will be installed/used 138 * by default for this UI. This is implemented to create a 139 * name based upon the classname. The name is the the name 140 * of the class with the package prefix removed. 141 * 142 * @return the name 143 */ 144 protected String getKeymapName() { 145 String nm = getClass().getName(); 146 int index = nm.lastIndexOf('.'); 147 if (index >= 0) { 148 nm = nm.substring(index+1, nm.length()); 149 } 150 return nm; 151 } 152 153 /** 154 * Creates the keymap to use for the text component, and installs 155 * any necessary bindings into it. By default, the keymap is 156 * shared between all instances of this type of TextUI. The 157 * keymap has the name defined by the getKeymapName method. If the 158 * keymap is not found, then DEFAULT_KEYMAP from JTextComponent is used. 159 * <p> 160 * The set of bindings used to create the keymap is fetched 161 * from the UIManager using a key formed by combining the 162 * {@link #getPropertyPrefix} method 163 * and the string <code>.keyBindings</code>. The type is expected 164 * to be <code>JTextComponent.KeyBinding[]</code>. 165 * 166 * @return the keymap 167 * @see #getKeymapName 168 * @see javax.swing.text.JTextComponent 169 */ 170 protected Keymap createKeymap() { 171 String nm = getKeymapName(); 172 Keymap map = JTextComponent.getKeymap(nm); 173 if (map == null) { 174 Keymap parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP); 175 map = JTextComponent.addKeymap(nm, parent); 176 String prefix = getPropertyPrefix(); 177 Object o = DefaultLookup.get(editor, this, 178 prefix + ".keyBindings"); 179 if ((o != null) && (o instanceof JTextComponent.KeyBinding[])) { 180 JTextComponent.KeyBinding[] bindings = (JTextComponent.KeyBinding[]) o; 181 JTextComponent.loadKeymap(map, bindings, getComponent().getActions()); 182 } 183 } 184 return map; 185 } 186 187 /** 188 * This method gets called when a bound property is changed 189 * on the associated JTextComponent. This is a hook 190 * which UI implementations may change to reflect how the 191 * UI displays bound properties of JTextComponent subclasses. 192 * This is implemented to do nothing (i.e. the response to 193 * properties in JTextComponent itself are handled prior 194 * to calling this method). 195 * 196 * This implementation updates the background of the text 197 * component if the editable and/or enabled state changes. 198 * 199 * @param evt the property change event 200 */ 201 protected void propertyChange(PropertyChangeEvent evt) { 202 if (evt.getPropertyName().equals("editable") || 203 evt.getPropertyName().equals("enabled")) { 204 205 updateBackground((JTextComponent)evt.getSource()); 206 } 207 } 208 209 /** 210 * Updates the background of the text component based on whether the 211 * text component is editable and/or enabled. 212 * 213 * @param c the JTextComponent that needs its background color updated 214 */ 215 private void updateBackground(JTextComponent c) { 216 // This is a temporary workaround. 217 // This code does not correctly deal with Synth (Synth doesn't use 218 // properties like this), nor does it deal with the situation where 219 // the developer grabs the color from a JLabel and sets it as 220 // the background for a JTextArea in all look and feels. The problem 221 // scenario results if the Color obtained for the Label and TextArea 222 // is ==, which is the case for the windows look and feel. 223 // Until an appropriate solution is found, the code is being 224 // reverted to what it was before the original fix. 225 if (this instanceof SynthUI || (c instanceof JTextArea)) { 226 return; 227 } 228 Color background = c.getBackground(); 229 if (background instanceof UIResource) { 230 String prefix = getPropertyPrefix(); 231 232 Color disabledBG = 233 DefaultLookup.getColor(c, this, prefix + ".disabledBackground", null); 234 Color inactiveBG = 235 DefaultLookup.getColor(c, this, prefix + ".inactiveBackground", null); 236 Color bg = 237 DefaultLookup.getColor(c, this, prefix + ".background", null); 238 239 /* In an ideal situation, the following check would not be necessary 240 * and we would replace the color any time the previous color was a 241 * UIResouce. However, it turns out that there is existing code that 242 * uses the following inadvisable pattern to turn a text area into 243 * what appears to be a multi-line label: 244 * 245 * JLabel label = new JLabel(); 246 * JTextArea area = new JTextArea(); 247 * area.setBackground(label.getBackground()); 248 * area.setEditable(false); 249 * 250 * JLabel's default background is a UIResource. As such, just 251 * checking for UIResource would have us always changing the 252 * background away from what the developer wanted. 253 * 254 * Therefore, for JTextArea/JEditorPane, we'll additionally check 255 * that the color we're about to replace matches one that was 256 * installed by us from the UIDefaults. 257 */ 258 if ((c instanceof JTextArea || c instanceof JEditorPane) 259 && background != disabledBG 260 && background != inactiveBG 261 && background != bg) { 262 263 return; 264 } 265 266 Color newColor = null; 267 if (!c.isEnabled()) { 268 newColor = disabledBG; 269 } 270 if (newColor == null && !c.isEditable()) { 271 newColor = inactiveBG; 272 } 273 if (newColor == null) { 274 newColor = bg; 275 } 276 if (newColor != null && newColor != background) { 277 c.setBackground(newColor); 278 } 279 } 280 } 281 282 /** 283 * Gets the name used as a key to look up properties through the 284 * UIManager. This is used as a prefix to all the standard 285 * text properties. 286 * 287 * @return the name 288 */ 289 protected abstract String getPropertyPrefix(); 290 291 /** 292 * Initializes component properties, such as font, foreground, 293 * background, caret color, selection color, selected text color, 294 * disabled text color, and border color. The font, foreground, and 295 * background properties are only set if their current value is either null 296 * or a UIResource, other properties are set if the current 297 * value is null. 298 * 299 * @see #uninstallDefaults 300 * @see #installUI 301 */ 302 protected void installDefaults() 303 { 304 String prefix = getPropertyPrefix(); 305 Font f = editor.getFont(); 306 if ((f == null) || (f instanceof UIResource)) { 307 editor.setFont(UIManager.getFont(prefix + ".font")); 308 } 309 310 Color bg = editor.getBackground(); 311 if ((bg == null) || (bg instanceof UIResource)) { 312 editor.setBackground(UIManager.getColor(prefix + ".background")); 313 } 314 315 Color fg = editor.getForeground(); 316 if ((fg == null) || (fg instanceof UIResource)) { 317 editor.setForeground(UIManager.getColor(prefix + ".foreground")); 318 } 319 320 Color color = editor.getCaretColor(); 321 if ((color == null) || (color instanceof UIResource)) { 322 editor.setCaretColor(UIManager.getColor(prefix + ".caretForeground")); 323 } 324 325 Color s = editor.getSelectionColor(); 326 if ((s == null) || (s instanceof UIResource)) { 327 editor.setSelectionColor(UIManager.getColor(prefix + ".selectionBackground")); 328 } 329 330 Color sfg = editor.getSelectedTextColor(); 331 if ((sfg == null) || (sfg instanceof UIResource)) { 332 editor.setSelectedTextColor(UIManager.getColor(prefix + ".selectionForeground")); 333 } 334 335 Color dfg = editor.getDisabledTextColor(); 336 if ((dfg == null) || (dfg instanceof UIResource)) { 337 editor.setDisabledTextColor(UIManager.getColor(prefix + ".inactiveForeground")); 338 } 339 340 Border b = editor.getBorder(); 341 if ((b == null) || (b instanceof UIResource)) { 342 editor.setBorder(UIManager.getBorder(prefix + ".border")); 343 } 344 345 Insets margin = editor.getMargin(); 346 if (margin == null || margin instanceof UIResource) { 347 editor.setMargin(UIManager.getInsets(prefix + ".margin")); 348 } 349 350 updateCursor(); 351 } 352 353 private void installDefaults2() { 354 editor.addMouseListener(dragListener); 355 editor.addMouseMotionListener(dragListener); 356 357 String prefix = getPropertyPrefix(); 358 359 Caret caret = editor.getCaret(); 360 if (caret == null || caret instanceof UIResource) { 361 caret = createCaret(); 362 editor.setCaret(caret); 363 364 int rate = DefaultLookup.getInt(getComponent(), this, prefix + ".caretBlinkRate", 500); 365 caret.setBlinkRate(rate); 366 } 367 368 Highlighter highlighter = editor.getHighlighter(); 369 if (highlighter == null || highlighter instanceof UIResource) { 370 editor.setHighlighter(createHighlighter()); 371 } 372 373 TransferHandler th = editor.getTransferHandler(); 374 if (th == null || th instanceof UIResource) { 375 editor.setTransferHandler(getTransferHandler()); 376 } 377 } 378 379 /** 380 * Sets the component properties that have not been explicitly overridden 381 * to {@code null}. A property is considered overridden if its current 382 * value is not a {@code UIResource}. 383 * 384 * @see #installDefaults 385 * @see #uninstallUI 386 */ 387 protected void uninstallDefaults() 388 { 389 editor.removeMouseListener(dragListener); 390 editor.removeMouseMotionListener(dragListener); 391 392 if (editor.getCaretColor() instanceof UIResource) { 393 editor.setCaretColor(null); 394 } 395 396 if (editor.getSelectionColor() instanceof UIResource) { 397 editor.setSelectionColor(null); 398 } 399 400 if (editor.getDisabledTextColor() instanceof UIResource) { 401 editor.setDisabledTextColor(null); 402 } 403 404 if (editor.getSelectedTextColor() instanceof UIResource) { 405 editor.setSelectedTextColor(null); 406 } 407 408 if (editor.getBorder() instanceof UIResource) { 409 editor.setBorder(null); 410 } 411 412 if (editor.getMargin() instanceof UIResource) { 413 editor.setMargin(null); 414 } 415 416 if (editor.getCaret() instanceof UIResource) { 417 editor.setCaret(null); 418 } 419 420 if (editor.getHighlighter() instanceof UIResource) { 421 editor.setHighlighter(null); 422 } 423 424 if (editor.getTransferHandler() instanceof UIResource) { 425 editor.setTransferHandler(null); 426 } 427 428 if (editor.getCursor() instanceof UIResource) { 429 editor.setCursor(null); 430 } 431 } 432 433 /** 434 * Installs listeners for the UI. 435 */ 436 protected void installListeners() { 437 } 438 439 /** 440 * Uninstalls listeners for the UI. 441 */ 442 protected void uninstallListeners() { 443 } 444 445 protected void installKeyboardActions() { 446 // backward compatibility support... keymaps for the UI 447 // are now installed in the more friendly input map. 448 editor.setKeymap(createKeymap()); 449 450 InputMap km = getInputMap(); 451 if (km != null) { 452 SwingUtilities.replaceUIInputMap(editor, JComponent.WHEN_FOCUSED, 453 km); 454 } 455 456 ActionMap map = getActionMap(); 457 if (map != null) { 458 SwingUtilities.replaceUIActionMap(editor, map); 459 } 460 461 updateFocusAcceleratorBinding(false); 462 } 463 464 /** 465 * Get the InputMap to use for the UI. 466 */ 467 InputMap getInputMap() { 468 InputMap map = new InputMapUIResource(); 469 470 InputMap shared = 471 (InputMap)DefaultLookup.get(editor, this, 472 getPropertyPrefix() + ".focusInputMap"); 473 if (shared != null) { 474 map.setParent(shared); 475 } 476 return map; 477 } 478 479 /** 480 * Invoked when the focus accelerator changes, this will update the 481 * key bindings as necessary. 482 */ 483 void updateFocusAcceleratorBinding(boolean changed) { 484 char accelerator = editor.getFocusAccelerator(); 485 486 if (changed || accelerator != '\0') { 487 InputMap km = SwingUtilities.getUIInputMap 488 (editor, JComponent.WHEN_IN_FOCUSED_WINDOW); 489 490 if (km == null && accelerator != '\0') { 491 km = new ComponentInputMapUIResource(editor); 492 SwingUtilities.replaceUIInputMap(editor, JComponent. 493 WHEN_IN_FOCUSED_WINDOW, km); 494 ActionMap am = getActionMap(); 495 SwingUtilities.replaceUIActionMap(editor, am); 496 } 497 if (km != null) { 498 km.clear(); 499 if (accelerator != '\0') { 500 km.put(KeyStroke.getKeyStroke(accelerator, 501 ActionEvent.ALT_MASK), 502 "requestFocus"); 503 } 504 } 505 } 506 } 507 508 509 /** 510 * Invoked when editable property is changed. 511 * 512 * removing 'TAB' and 'SHIFT-TAB' from traversalKeysSet in case 513 * editor is editable 514 * adding 'TAB' and 'SHIFT-TAB' to traversalKeysSet in case 515 * editor is non editable 516 */ 517 518 void updateFocusTraversalKeys() { 519 /* 520 * Fix for 4514331 Non-editable JTextArea and similar 521 * should allow Tab to keyboard - accessibility 522 */ 523 EditorKit editorKit = getEditorKit(editor); 524 if ( editorKit != null 525 && editorKit instanceof DefaultEditorKit) { 526 Set<AWTKeyStroke> storedForwardTraversalKeys = editor. 527 getFocusTraversalKeys(KeyboardFocusManager. 528 FORWARD_TRAVERSAL_KEYS); 529 Set<AWTKeyStroke> storedBackwardTraversalKeys = editor. 530 getFocusTraversalKeys(KeyboardFocusManager. 531 BACKWARD_TRAVERSAL_KEYS); 532 Set<AWTKeyStroke> forwardTraversalKeys = 533 new HashSet<AWTKeyStroke>(storedForwardTraversalKeys); 534 Set<AWTKeyStroke> backwardTraversalKeys = 535 new HashSet<AWTKeyStroke>(storedBackwardTraversalKeys); 536 if (editor.isEditable()) { 537 forwardTraversalKeys. 538 remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); 539 backwardTraversalKeys. 540 remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 541 InputEvent.SHIFT_MASK)); 542 } else { 543 forwardTraversalKeys.add(KeyStroke. 544 getKeyStroke(KeyEvent.VK_TAB, 0)); 545 backwardTraversalKeys. 546 add(KeyStroke. 547 getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK)); 548 } 549 LookAndFeel.installProperty(editor, 550 "focusTraversalKeysForward", 551 forwardTraversalKeys); 552 LookAndFeel.installProperty(editor, 553 "focusTraversalKeysBackward", 554 backwardTraversalKeys); 555 } 556 557 } 558 559 /** 560 * As needed updates cursor for the target editor. 561 */ 562 private void updateCursor() { 563 if ((! editor.isCursorSet()) 564 || editor.getCursor() instanceof UIResource) { 565 Cursor cursor = (editor.isEditable()) ? textCursor : null; 566 editor.setCursor(cursor); 567 } 568 } 569 570 /** 571 * Returns the <code>TransferHandler</code> that will be installed if 572 * their isn't one installed on the <code>JTextComponent</code>. 573 */ 574 TransferHandler getTransferHandler() { 575 return defaultTransferHandler; 576 } 577 578 /** 579 * Fetch an action map to use. 580 */ 581 ActionMap getActionMap() { 582 String mapName = getPropertyPrefix() + ".actionMap"; 583 ActionMap map = (ActionMap)UIManager.get(mapName); 584 585 if (map == null) { 586 map = createActionMap(); 587 if (map != null) { 588 UIManager.getLookAndFeelDefaults().put(mapName, map); 589 } 590 } 591 ActionMap componentMap = new ActionMapUIResource(); 592 componentMap.put("requestFocus", new FocusAction()); 593 /* 594 * fix for bug 4515750 595 * JTextField & non-editable JTextArea bind return key - default btn not accessible 596 * 597 * Wrap the return action so that it is only enabled when the 598 * component is editable. This allows the default button to be 599 * processed when the text component has focus and isn't editable. 600 * 601 */ 602 if (getEditorKit(editor) instanceof DefaultEditorKit) { 603 if (map != null) { 604 Object obj = map.get(DefaultEditorKit.insertBreakAction); 605 if (obj != null 606 && obj instanceof DefaultEditorKit.InsertBreakAction) { 607 Action action = new TextActionWrapper((TextAction)obj); 608 componentMap.put(action.getValue(Action.NAME),action); 609 } 610 } 611 } 612 if (map != null) { 613 componentMap.setParent(map); 614 } 615 return componentMap; 616 } 617 618 /** 619 * Create a default action map. This is basically the 620 * set of actions found exported by the component. 621 */ 622 ActionMap createActionMap() { 623 ActionMap map = new ActionMapUIResource(); 624 Action[] actions = editor.getActions(); 625 //System.out.println("building map for UI: " + getPropertyPrefix()); 626 int n = actions.length; 627 for (int i = 0; i < n; i++) { 628 Action a = actions[i]; 629 map.put(a.getValue(Action.NAME), a); 630 //System.out.println(" " + a.getValue(Action.NAME)); 631 } 632 map.put(TransferHandler.getCutAction().getValue(Action.NAME), 633 TransferHandler.getCutAction()); 634 map.put(TransferHandler.getCopyAction().getValue(Action.NAME), 635 TransferHandler.getCopyAction()); 636 map.put(TransferHandler.getPasteAction().getValue(Action.NAME), 637 TransferHandler.getPasteAction()); 638 return map; 639 } 640 641 protected void uninstallKeyboardActions() { 642 editor.setKeymap(null); 643 SwingUtilities.replaceUIInputMap(editor, JComponent. 644 WHEN_IN_FOCUSED_WINDOW, null); 645 SwingUtilities.replaceUIActionMap(editor, null); 646 } 647 648 /** 649 * Paints a background for the view. This will only be 650 * called if isOpaque() on the associated component is 651 * true. The default is to paint the background color 652 * of the component. 653 * 654 * @param g the graphics context 655 */ 656 protected void paintBackground(Graphics g) { 657 g.setColor(editor.getBackground()); 658 g.fillRect(0, 0, editor.getWidth(), editor.getHeight()); 659 } 660 661 /** 662 * Fetches the text component associated with this 663 * UI implementation. This will be null until 664 * the ui has been installed. 665 * 666 * @return the editor component 667 */ 668 protected final JTextComponent getComponent() { 669 return editor; 670 } 671 672 /** 673 * Flags model changes. 674 * This is called whenever the model has changed. 675 * It is implemented to rebuild the view hierarchy 676 * to represent the default root element of the 677 * associated model. 678 */ 679 protected void modelChanged() { 680 // create a view hierarchy 681 ViewFactory f = rootView.getViewFactory(); 682 Document doc = editor.getDocument(); 683 Element elem = doc.getDefaultRootElement(); 684 setView(f.create(elem)); 685 } 686 687 /** 688 * Sets the current root of the view hierarchy and calls invalidate(). 689 * If there were any child components, they will be removed (i.e. 690 * there are assumed to have come from components embedded in views). 691 * 692 * @param v the root view 693 */ 694 protected final void setView(View v) { 695 rootView.setView(v); 696 painted = false; 697 editor.revalidate(); 698 editor.repaint(); 699 } 700 701 /** 702 * Paints the interface safely with a guarantee that 703 * the model won't change from the view of this thread. 704 * This does the following things, rendering from 705 * back to front. 706 * <ol> 707 * <li> 708 * If the component is marked as opaque, the background 709 * is painted in the current background color of the 710 * component. 711 * <li> 712 * The highlights (if any) are painted. 713 * <li> 714 * The view hierarchy is painted. 715 * <li> 716 * The caret is painted. 717 * </ol> 718 * 719 * @param g the graphics context 720 */ 721 protected void paintSafely(Graphics g) { 722 painted = true; 723 Highlighter highlighter = editor.getHighlighter(); 724 Caret caret = editor.getCaret(); 725 726 // paint the background 727 if (editor.isOpaque()) { 728 paintBackground(g); 729 } 730 731 // paint the highlights 732 if (highlighter != null) { 733 highlighter.paint(g); 734 } 735 736 // paint the view hierarchy 737 Rectangle alloc = getVisibleEditorRect(); 738 if (alloc != null) { 739 rootView.paint(g, alloc); 740 } 741 742 // paint the caret 743 if (caret != null) { 744 caret.paint(g); 745 } 746 747 if (dropCaret != null) { 748 dropCaret.paint(g); 749 } 750 } 751 752 // --- ComponentUI methods -------------------------------------------- 753 754 /** 755 * Installs the UI for a component. This does the following 756 * things. 757 * <ol> 758 * <li> 759 * Sets the associated component to opaque if the opaque property 760 * has not already been set by the client program. This will cause the 761 * component's background color to be painted. 762 * <li> 763 * Installs the default caret and highlighter into the 764 * associated component. These properties are only set if their 765 * current value is either {@code null} or an instance of 766 * {@link UIResource}. 767 * <li> 768 * Attaches to the editor and model. If there is no 769 * model, a default one is created. 770 * <li> 771 * Creates the view factory and the view hierarchy used 772 * to represent the model. 773 * </ol> 774 * 775 * @param c the editor component 776 * @see ComponentUI#installUI 777 */ 778 public void installUI(JComponent c) { 779 if (c instanceof JTextComponent) { 780 editor = (JTextComponent) c; 781 782 // common case is background painted... this can 783 // easily be changed by subclasses or from outside 784 // of the component. 785 LookAndFeel.installProperty(editor, "opaque", Boolean.TRUE); 786 LookAndFeel.installProperty(editor, "autoscrolls", Boolean.TRUE); 787 788 // install defaults 789 installDefaults(); 790 installDefaults2(); 791 792 // attach to the model and editor 793 editor.addPropertyChangeListener(updateHandler); 794 Document doc = editor.getDocument(); 795 if (doc == null) { 796 // no model, create a default one. This will 797 // fire a notification to the updateHandler 798 // which takes care of the rest. 799 editor.setDocument(getEditorKit(editor).createDefaultDocument()); 800 } else { 801 doc.addDocumentListener(updateHandler); 802 modelChanged(); 803 } 804 805 // install keymap 806 installListeners(); 807 installKeyboardActions(); 808 809 LayoutManager oldLayout = editor.getLayout(); 810 if ((oldLayout == null) || (oldLayout instanceof UIResource)) { 811 // by default, use default LayoutManger implementation that 812 // will position the components associated with a View object. 813 editor.setLayout(updateHandler); 814 } 815 816 updateBackground(editor); 817 } else { 818 throw new Error("TextUI needs JTextComponent"); 819 } 820 } 821 822 /** 823 * Deinstalls the UI for a component. This removes the listeners, 824 * uninstalls the highlighter, removes views, and nulls out the keymap. 825 * 826 * @param c the editor component 827 * @see ComponentUI#uninstallUI 828 */ 829 public void uninstallUI(JComponent c) { 830 // detach from the model 831 editor.removePropertyChangeListener(updateHandler); 832 editor.getDocument().removeDocumentListener(updateHandler); 833 834 // view part 835 painted = false; 836 uninstallDefaults(); 837 rootView.setView(null); 838 c.removeAll(); 839 LayoutManager lm = c.getLayout(); 840 if (lm instanceof UIResource) { 841 c.setLayout(null); 842 } 843 844 // controller part 845 uninstallKeyboardActions(); 846 uninstallListeners(); 847 848 editor = null; 849 } 850 851 /** 852 * Superclass paints background in an uncontrollable way 853 * (i.e. one might want an image tiled into the background). 854 * To prevent this from happening twice, this method is 855 * reimplemented to simply paint. 856 * <p> 857 * <em>NOTE:</em> NOTE: Superclass is also not thread-safe in its 858 * rendering of the background, although that is not an issue with the 859 * default rendering. 860 */ 861 public void update(Graphics g, JComponent c) { 862 paint(g, c); 863 } 864 865 /** 866 * Paints the interface. This is routed to the 867 * paintSafely method under the guarantee that 868 * the model won't change from the view of this thread 869 * while it's rendering (if the associated model is 870 * derived from AbstractDocument). This enables the 871 * model to potentially be updated asynchronously. 872 * 873 * @param g the graphics context 874 * @param c the editor component 875 */ 876 public final void paint(Graphics g, JComponent c) { 877 if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) { 878 Document doc = editor.getDocument(); 879 if (doc instanceof AbstractDocument) { 880 ((AbstractDocument)doc).readLock(); 881 } 882 try { 883 paintSafely(g); 884 } finally { 885 if (doc instanceof AbstractDocument) { 886 ((AbstractDocument)doc).readUnlock(); 887 } 888 } 889 } 890 } 891 892 /** 893 * Gets the preferred size for the editor component. If the component 894 * has been given a size prior to receiving this request, it will 895 * set the size of the view hierarchy to reflect the size of the component 896 * before requesting the preferred size of the view hierarchy. This 897 * allows formatted views to format to the current component size before 898 * answering the request. Other views don't care about currently formatted 899 * size and give the same answer either way. 900 * 901 * @param c the editor component 902 * @return the size 903 */ 904 public Dimension getPreferredSize(JComponent c) { 905 Document doc = editor.getDocument(); 906 Insets i = c.getInsets(); 907 Dimension d = c.getSize(); 908 909 if (doc instanceof AbstractDocument) { 910 ((AbstractDocument)doc).readLock(); 911 } 912 try { 913 if ((d.width > (i.left + i.right)) && (d.height > (i.top + i.bottom))) { 914 rootView.setSize(d.width - i.left - i.right, d.height - i.top - i.bottom); 915 } 916 else if (d.width == 0 && d.height == 0) { 917 // Probably haven't been layed out yet, force some sort of 918 // initial sizing. 919 rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); 920 } 921 d.width = (int) Math.min((long) rootView.getPreferredSpan(View.X_AXIS) + 922 (long) i.left + (long) i.right, Integer.MAX_VALUE); 923 d.height = (int) Math.min((long) rootView.getPreferredSpan(View.Y_AXIS) + 924 (long) i.top + (long) i.bottom, Integer.MAX_VALUE); 925 } finally { 926 if (doc instanceof AbstractDocument) { 927 ((AbstractDocument)doc).readUnlock(); 928 } 929 } 930 return d; 931 } 932 933 /** 934 * Gets the minimum size for the editor component. 935 * 936 * @param c the editor component 937 * @return the size 938 */ 939 public Dimension getMinimumSize(JComponent c) { 940 Document doc = editor.getDocument(); 941 Insets i = c.getInsets(); 942 Dimension d = new Dimension(); 943 if (doc instanceof AbstractDocument) { 944 ((AbstractDocument)doc).readLock(); 945 } 946 try { 947 d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right; 948 d.height = (int) rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom; 949 } finally { 950 if (doc instanceof AbstractDocument) { 951 ((AbstractDocument)doc).readUnlock(); 952 } 953 } 954 return d; 955 } 956 957 /** 958 * Gets the maximum size for the editor component. 959 * 960 * @param c the editor component 961 * @return the size 962 */ 963 public Dimension getMaximumSize(JComponent c) { 964 Document doc = editor.getDocument(); 965 Insets i = c.getInsets(); 966 Dimension d = new Dimension(); 967 if (doc instanceof AbstractDocument) { 968 ((AbstractDocument)doc).readLock(); 969 } 970 try { 971 d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) + 972 (long) i.left + (long) i.right, Integer.MAX_VALUE); 973 d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) + 974 (long) i.top + (long) i.bottom, Integer.MAX_VALUE); 975 } finally { 976 if (doc instanceof AbstractDocument) { 977 ((AbstractDocument)doc).readUnlock(); 978 } 979 } 980 return d; 981 } 982 983 // ---- TextUI methods ------------------------------------------- 984 985 986 /** 987 * Gets the allocation to give the root View. Due 988 * to an unfortunate set of historical events this 989 * method is inappropriately named. The Rectangle 990 * returned has nothing to do with visibility. 991 * The component must have a non-zero positive size for 992 * this translation to be computed. 993 * 994 * @return the bounding box for the root view 995 */ 996 protected Rectangle getVisibleEditorRect() { 997 Rectangle alloc = editor.getBounds(); 998 if ((alloc.width > 0) && (alloc.height > 0)) { 999 alloc.x = alloc.y = 0; 1000 Insets insets = editor.getInsets(); 1001 alloc.x += insets.left; 1002 alloc.y += insets.top; 1003 alloc.width -= insets.left + insets.right; 1004 alloc.height -= insets.top + insets.bottom; 1005 return alloc; 1006 } 1007 return null; 1008 } 1009 1010 /** 1011 * Converts the given location in the model to a place in 1012 * the view coordinate system. 1013 * The component must have a non-zero positive size for 1014 * this translation to be computed. 1015 * 1016 * @param tc the text component for which this UI is installed 1017 * @param pos the local location in the model to translate >= 0 1018 * @return the coordinates as a rectangle, null if the model is not painted 1019 * @exception BadLocationException if the given position does not 1020 * represent a valid location in the associated document 1021 * @see TextUI#modelToView 1022 */ 1023 public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException { 1024 return modelToView(tc, pos, Position.Bias.Forward); 1025 } 1026 1027 /** 1028 * Converts the given location in the model to a place in 1029 * the view coordinate system. 1030 * The component must have a non-zero positive size for 1031 * this translation to be computed. 1032 * 1033 * @param tc the text component for which this UI is installed 1034 * @param pos the local location in the model to translate >= 0 1035 * @return the coordinates as a rectangle, null if the model is not painted 1036 * @exception BadLocationException if the given position does not 1037 * represent a valid location in the associated document 1038 * @see TextUI#modelToView 1039 */ 1040 public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException { 1041 Document doc = editor.getDocument(); 1042 if (doc instanceof AbstractDocument) { 1043 ((AbstractDocument)doc).readLock(); 1044 } 1045 try { 1046 Rectangle alloc = getVisibleEditorRect(); 1047 if (alloc != null) { 1048 rootView.setSize(alloc.width, alloc.height); 1049 Shape s = rootView.modelToView(pos, alloc, bias); 1050 if (s != null) { 1051 return s.getBounds(); 1052 } 1053 } 1054 } finally { 1055 if (doc instanceof AbstractDocument) { 1056 ((AbstractDocument)doc).readUnlock(); 1057 } 1058 } 1059 return null; 1060 } 1061 1062 /** 1063 * Converts the given place in the view coordinate system 1064 * to the nearest representative location in the model. 1065 * The component must have a non-zero positive size for 1066 * this translation to be computed. 1067 * 1068 * @param tc the text component for which this UI is installed 1069 * @param pt the location in the view to translate. This 1070 * should be in the same coordinate system as the mouse events. 1071 * @return the offset from the start of the document >= 0, 1072 * -1 if not painted 1073 * @see TextUI#viewToModel 1074 */ 1075 public int viewToModel(JTextComponent tc, Point pt) { 1076 return viewToModel(tc, pt, discardBias); 1077 } 1078 1079 /** 1080 * Converts the given place in the view coordinate system 1081 * to the nearest representative location in the model. 1082 * The component must have a non-zero positive size for 1083 * this translation to be computed. 1084 * 1085 * @param tc the text component for which this UI is installed 1086 * @param pt the location in the view to translate. This 1087 * should be in the same coordinate system as the mouse events. 1088 * @return the offset from the start of the document >= 0, 1089 * -1 if the component doesn't yet have a positive size. 1090 * @see TextUI#viewToModel 1091 */ 1092 public int viewToModel(JTextComponent tc, Point pt, 1093 Position.Bias[] biasReturn) { 1094 int offs = -1; 1095 Document doc = editor.getDocument(); 1096 if (doc instanceof AbstractDocument) { 1097 ((AbstractDocument)doc).readLock(); 1098 } 1099 try { 1100 Rectangle alloc = getVisibleEditorRect(); 1101 if (alloc != null) { 1102 rootView.setSize(alloc.width, alloc.height); 1103 offs = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn); 1104 } 1105 } finally { 1106 if (doc instanceof AbstractDocument) { 1107 ((AbstractDocument)doc).readUnlock(); 1108 } 1109 } 1110 return offs; 1111 } 1112 1113 /** 1114 * {@inheritDoc} 1115 */ 1116 public int getNextVisualPositionFrom(JTextComponent t, int pos, 1117 Position.Bias b, int direction, Position.Bias[] biasRet) 1118 throws BadLocationException{ 1119 Document doc = editor.getDocument(); 1120 if (doc instanceof AbstractDocument) { 1121 ((AbstractDocument)doc).readLock(); 1122 } 1123 try { 1124 if (painted) { 1125 Rectangle alloc = getVisibleEditorRect(); 1126 if (alloc != null) { 1127 rootView.setSize(alloc.width, alloc.height); 1128 } 1129 return rootView.getNextVisualPositionFrom(pos, b, alloc, direction, 1130 biasRet); 1131 } 1132 } finally { 1133 if (doc instanceof AbstractDocument) { 1134 ((AbstractDocument)doc).readUnlock(); 1135 } 1136 } 1137 return -1; 1138 } 1139 1140 /** 1141 * Causes the portion of the view responsible for the 1142 * given part of the model to be repainted. Does nothing if 1143 * the view is not currently painted. 1144 * 1145 * @param tc the text component for which this UI is installed 1146 * @param p0 the beginning of the range >= 0 1147 * @param p1 the end of the range >= p0 1148 * @see TextUI#damageRange 1149 */ 1150 public void damageRange(JTextComponent tc, int p0, int p1) { 1151 damageRange(tc, p0, p1, Position.Bias.Forward, Position.Bias.Backward); 1152 } 1153 1154 /** 1155 * Causes the portion of the view responsible for the 1156 * given part of the model to be repainted. 1157 * 1158 * @param p0 the beginning of the range >= 0 1159 * @param p1 the end of the range >= p0 1160 */ 1161 public void damageRange(JTextComponent t, int p0, int p1, 1162 Position.Bias p0Bias, Position.Bias p1Bias) { 1163 if (painted) { 1164 Rectangle alloc = getVisibleEditorRect(); 1165 if (alloc != null) { 1166 Document doc = t.getDocument(); 1167 if (doc instanceof AbstractDocument) { 1168 ((AbstractDocument)doc).readLock(); 1169 } 1170 try { 1171 rootView.setSize(alloc.width, alloc.height); 1172 Shape toDamage = rootView.modelToView(p0, p0Bias, 1173 p1, p1Bias, alloc); 1174 Rectangle rect = (toDamage instanceof Rectangle) ? 1175 (Rectangle)toDamage : toDamage.getBounds(); 1176 editor.repaint(rect.x, rect.y, rect.width, rect.height); 1177 } catch (BadLocationException e) { 1178 } finally { 1179 if (doc instanceof AbstractDocument) { 1180 ((AbstractDocument)doc).readUnlock(); 1181 } 1182 } 1183 } 1184 } 1185 } 1186 1187 /** 1188 * Fetches the EditorKit for the UI. 1189 * 1190 * @param tc the text component for which this UI is installed 1191 * @return the editor capabilities 1192 * @see TextUI#getEditorKit 1193 */ 1194 public EditorKit getEditorKit(JTextComponent tc) { 1195 return defaultKit; 1196 } 1197 1198 /** 1199 * Fetches a View with the allocation of the associated 1200 * text component (i.e. the root of the hierarchy) that 1201 * can be traversed to determine how the model is being 1202 * represented spatially. 1203 * <p> 1204 * <font color=red><b>NOTE:</b>The View hierarchy can 1205 * be traversed from the root view, and other things 1206 * can be done as well. Things done in this way cannot 1207 * be protected like simple method calls through the TextUI. 1208 * Therefore, proper operation in the presence of concurrency 1209 * must be arranged by any logic that calls this method! 1210 * </font> 1211 * 1212 * @param tc the text component for which this UI is installed 1213 * @return the view 1214 * @see TextUI#getRootView 1215 */ 1216 public View getRootView(JTextComponent tc) { 1217 return rootView; 1218 } 1219 1220 1221 /** 1222 * Returns the string to be used as the tooltip at the passed in location. 1223 * This forwards the method onto the root View. 1224 * 1225 * @see javax.swing.text.JTextComponent#getToolTipText 1226 * @see javax.swing.text.View#getToolTipText 1227 * @since 1.4 1228 */ 1229 public String getToolTipText(JTextComponent t, Point pt) { 1230 if (!painted) { 1231 return null; 1232 } 1233 Document doc = editor.getDocument(); 1234 String tt = null; 1235 Rectangle alloc = getVisibleEditorRect(); 1236 1237 if (alloc != null) { 1238 if (doc instanceof AbstractDocument) { 1239 ((AbstractDocument)doc).readLock(); 1240 } 1241 try { 1242 tt = rootView.getToolTipText(pt.x, pt.y, alloc); 1243 } finally { 1244 if (doc instanceof AbstractDocument) { 1245 ((AbstractDocument)doc).readUnlock(); 1246 } 1247 } 1248 } 1249 return tt; 1250 } 1251 1252 // --- ViewFactory methods ------------------------------ 1253 1254 /** 1255 * Creates a view for an element. 1256 * If a subclass wishes to directly implement the factory 1257 * producing the view(s), it should reimplement this 1258 * method. By default it simply returns null indicating 1259 * it is unable to represent the element. 1260 * 1261 * @param elem the element 1262 * @return the view 1263 */ 1264 public View create(Element elem) { 1265 return null; 1266 } 1267 1268 /** 1269 * Creates a view for an element. 1270 * If a subclass wishes to directly implement the factory 1271 * producing the view(s), it should reimplement this 1272 * method. By default it simply returns null indicating 1273 * it is unable to represent the part of the element. 1274 * 1275 * @param elem the element 1276 * @param p0 the starting offset >= 0 1277 * @param p1 the ending offset >= p0 1278 * @return the view 1279 */ 1280 public View create(Element elem, int p0, int p1) { 1281 return null; 1282 } 1283 1284 public static class BasicCaret extends DefaultCaret implements UIResource {} 1285 1286 public static class BasicHighlighter extends DefaultHighlighter implements UIResource {} 1287 1288 static class BasicCursor extends Cursor implements UIResource { 1289 BasicCursor(int type) { 1290 super(type); 1291 } 1292 1293 BasicCursor(String name) { 1294 super(name); 1295 } 1296 } 1297 1298 private static BasicCursor textCursor = new BasicCursor(Cursor.TEXT_CURSOR); 1299 // ----- member variables --------------------------------------- 1300 1301 private static final EditorKit defaultKit = new DefaultEditorKit(); 1302 transient JTextComponent editor; 1303 transient boolean painted; 1304 transient RootView rootView = new RootView(); 1305 transient UpdateHandler updateHandler = new UpdateHandler(); 1306 private static final TransferHandler defaultTransferHandler = new TextTransferHandler(); 1307 private final DragListener dragListener = getDragListener(); 1308 private static final Position.Bias[] discardBias = new Position.Bias[1]; 1309 private DefaultCaret dropCaret; 1310 1311 /** 1312 * Root view that acts as a gateway between the component 1313 * and the View hierarchy. 1314 */ 1315 class RootView extends View { 1316 1317 RootView() { 1318 super(null); 1319 } 1320 1321 void setView(View v) { 1322 View oldView = view; 1323 view = null; 1324 if (oldView != null) { 1325 // get rid of back reference so that the old 1326 // hierarchy can be garbage collected. 1327 oldView.setParent(null); 1328 } 1329 if (v != null) { 1330 v.setParent(this); 1331 } 1332 view = v; 1333 } 1334 1335 /** 1336 * Fetches the attributes to use when rendering. At the root 1337 * level there are no attributes. If an attribute is resolved 1338 * up the view hierarchy this is the end of the line. 1339 */ 1340 public AttributeSet getAttributes() { 1341 return null; 1342 } 1343 1344 /** 1345 * Determines the preferred span for this view along an axis. 1346 * 1347 * @param axis may be either X_AXIS or Y_AXIS 1348 * @return the span the view would like to be rendered into. 1349 * Typically the view is told to render into the span 1350 * that is returned, although there is no guarantee. 1351 * The parent may choose to resize or break the view. 1352 */ 1353 public float getPreferredSpan(int axis) { 1354 if (view != null) { 1355 return view.getPreferredSpan(axis); 1356 } 1357 return 10; 1358 } 1359 1360 /** 1361 * Determines the minimum span for this view along an axis. 1362 * 1363 * @param axis may be either X_AXIS or Y_AXIS 1364 * @return the span the view would like to be rendered into. 1365 * Typically the view is told to render into the span 1366 * that is returned, although there is no guarantee. 1367 * The parent may choose to resize or break the view. 1368 */ 1369 public float getMinimumSpan(int axis) { 1370 if (view != null) { 1371 return view.getMinimumSpan(axis); 1372 } 1373 return 10; 1374 } 1375 1376 /** 1377 * Determines the maximum span for this view along an axis. 1378 * 1379 * @param axis may be either X_AXIS or Y_AXIS 1380 * @return the span the view would like to be rendered into. 1381 * Typically the view is told to render into the span 1382 * that is returned, although there is no guarantee. 1383 * The parent may choose to resize or break the view. 1384 */ 1385 public float getMaximumSpan(int axis) { 1386 return Integer.MAX_VALUE; 1387 } 1388 1389 /** 1390 * Specifies that a preference has changed. 1391 * Child views can call this on the parent to indicate that 1392 * the preference has changed. The root view routes this to 1393 * invalidate on the hosting component. 1394 * <p> 1395 * This can be called on a different thread from the 1396 * event dispatching thread and is basically unsafe to 1397 * propagate into the component. To make this safe, 1398 * the operation is transferred over to the event dispatching 1399 * thread for completion. It is a design goal that all view 1400 * methods be safe to call without concern for concurrency, 1401 * and this behavior helps make that true. 1402 * 1403 * @param child the child view 1404 * @param width true if the width preference has changed 1405 * @param height true if the height preference has changed 1406 */ 1407 public void preferenceChanged(View child, boolean width, boolean height) { 1408 editor.revalidate(); 1409 } 1410 1411 /** 1412 * Determines the desired alignment for this view along an axis. 1413 * 1414 * @param axis may be either X_AXIS or Y_AXIS 1415 * @return the desired alignment, where 0.0 indicates the origin 1416 * and 1.0 the full span away from the origin 1417 */ 1418 public float getAlignment(int axis) { 1419 if (view != null) { 1420 return view.getAlignment(axis); 1421 } 1422 return 0; 1423 } 1424 1425 /** 1426 * Renders the view. 1427 * 1428 * @param g the graphics context 1429 * @param allocation the region to render into 1430 */ 1431 public void paint(Graphics g, Shape allocation) { 1432 if (view != null) { 1433 Rectangle alloc = (allocation instanceof Rectangle) ? 1434 (Rectangle)allocation : allocation.getBounds(); 1435 setSize(alloc.width, alloc.height); 1436 view.paint(g, allocation); 1437 } 1438 } 1439 1440 /** 1441 * Sets the view parent. 1442 * 1443 * @param parent the parent view 1444 */ 1445 public void setParent(View parent) { 1446 throw new Error("Can't set parent on root view"); 1447 } 1448 1449 /** 1450 * Returns the number of views in this view. Since 1451 * this view simply wraps the root of the view hierarchy 1452 * it has exactly one child. 1453 * 1454 * @return the number of views 1455 * @see #getView 1456 */ 1457 public int getViewCount() { 1458 return 1; 1459 } 1460 1461 /** 1462 * Gets the n-th view in this container. 1463 * 1464 * @param n the number of the view to get 1465 * @return the view 1466 */ 1467 public View getView(int n) { 1468 return view; 1469 } 1470 1471 /** 1472 * Returns the child view index representing the given position in 1473 * the model. This is implemented to return the index of the only 1474 * child. 1475 * 1476 * @param pos the position >= 0 1477 * @return index of the view representing the given position, or 1478 * -1 if no view represents that position 1479 * @since 1.3 1480 */ 1481 public int getViewIndex(int pos, Position.Bias b) { 1482 return 0; 1483 } 1484 1485 /** 1486 * Fetches the allocation for the given child view. 1487 * This enables finding out where various views 1488 * are located, without assuming the views store 1489 * their location. This returns the given allocation 1490 * since this view simply acts as a gateway between 1491 * the view hierarchy and the associated component. 1492 * 1493 * @param index the index of the child 1494 * @param a the allocation to this view. 1495 * @return the allocation to the child 1496 */ 1497 public Shape getChildAllocation(int index, Shape a) { 1498 return a; 1499 } 1500 1501 /** 1502 * Provides a mapping from the document model coordinate space 1503 * to the coordinate space of the view mapped to it. 1504 * 1505 * @param pos the position to convert 1506 * @param a the allocated region to render into 1507 * @return the bounding box of the given position 1508 */ 1509 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 1510 if (view != null) { 1511 return view.modelToView(pos, a, b); 1512 } 1513 return null; 1514 } 1515 1516 /** 1517 * Provides a mapping from the document model coordinate space 1518 * to the coordinate space of the view mapped to it. 1519 * 1520 * @param p0 the position to convert >= 0 1521 * @param b0 the bias toward the previous character or the 1522 * next character represented by p0, in case the 1523 * position is a boundary of two views. 1524 * @param p1 the position to convert >= 0 1525 * @param b1 the bias toward the previous character or the 1526 * next character represented by p1, in case the 1527 * position is a boundary of two views. 1528 * @param a the allocated region to render into 1529 * @return the bounding box of the given position is returned 1530 * @exception BadLocationException if the given position does 1531 * not represent a valid location in the associated document 1532 * @exception IllegalArgumentException for an invalid bias argument 1533 * @see View#viewToModel 1534 */ 1535 public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException { 1536 if (view != null) { 1537 return view.modelToView(p0, b0, p1, b1, a); 1538 } 1539 return null; 1540 } 1541 1542 /** 1543 * Provides a mapping from the view coordinate space to the logical 1544 * coordinate space of the model. 1545 * 1546 * @param x x coordinate of the view location to convert 1547 * @param y y coordinate of the view location to convert 1548 * @param a the allocated region to render into 1549 * @return the location within the model that best represents the 1550 * given point in the view 1551 */ 1552 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { 1553 if (view != null) { 1554 int retValue = view.viewToModel(x, y, a, bias); 1555 return retValue; 1556 } 1557 return -1; 1558 } 1559 1560 /** 1561 * Provides a way to determine the next visually represented model 1562 * location that one might place a caret. Some views may not be visible, 1563 * they might not be in the same order found in the model, or they just 1564 * might not allow access to some of the locations in the model. 1565 * 1566 * @param pos the position to convert >= 0 1567 * @param a the allocated region to render into 1568 * @param direction the direction from the current position that can 1569 * be thought of as the arrow keys typically found on a keyboard. 1570 * This may be SwingConstants.WEST, SwingConstants.EAST, 1571 * SwingConstants.NORTH, or SwingConstants.SOUTH. 1572 * @return the location within the model that best represents the next 1573 * location visual position. 1574 * @exception BadLocationException 1575 * @exception IllegalArgumentException for an invalid direction 1576 */ 1577 public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 1578 int direction, 1579 Position.Bias[] biasRet) 1580 throws BadLocationException { 1581 if( view != null ) { 1582 int nextPos = view.getNextVisualPositionFrom(pos, b, a, 1583 direction, biasRet); 1584 if(nextPos != -1) { 1585 pos = nextPos; 1586 } 1587 else { 1588 biasRet[0] = b; 1589 } 1590 } 1591 return pos; 1592 } 1593 1594 /** 1595 * Gives notification that something was inserted into the document 1596 * in a location that this view is responsible for. 1597 * 1598 * @param e the change information from the associated document 1599 * @param a the current allocation of the view 1600 * @param f the factory to use to rebuild if the view has children 1601 */ 1602 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) { 1603 if (view != null) { 1604 view.insertUpdate(e, a, f); 1605 } 1606 } 1607 1608 /** 1609 * Gives notification that something was removed from the document 1610 * in a location that this view is responsible for. 1611 * 1612 * @param e the change information from the associated document 1613 * @param a the current allocation of the view 1614 * @param f the factory to use to rebuild if the view has children 1615 */ 1616 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) { 1617 if (view != null) { 1618 view.removeUpdate(e, a, f); 1619 } 1620 } 1621 1622 /** 1623 * Gives notification from the document that attributes were changed 1624 * in a location that this view is responsible for. 1625 * 1626 * @param e the change information from the associated document 1627 * @param a the current allocation of the view 1628 * @param f the factory to use to rebuild if the view has children 1629 */ 1630 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { 1631 if (view != null) { 1632 view.changedUpdate(e, a, f); 1633 } 1634 } 1635 1636 /** 1637 * Returns the document model underlying the view. 1638 * 1639 * @return the model 1640 */ 1641 public Document getDocument() { 1642 return editor.getDocument(); 1643 } 1644 1645 /** 1646 * Returns the starting offset into the model for this view. 1647 * 1648 * @return the starting offset 1649 */ 1650 public int getStartOffset() { 1651 if (view != null) { 1652 return view.getStartOffset(); 1653 } 1654 return getElement().getStartOffset(); 1655 } 1656 1657 /** 1658 * Returns the ending offset into the model for this view. 1659 * 1660 * @return the ending offset 1661 */ 1662 public int getEndOffset() { 1663 if (view != null) { 1664 return view.getEndOffset(); 1665 } 1666 return getElement().getEndOffset(); 1667 } 1668 1669 /** 1670 * Gets the element that this view is mapped to. 1671 * 1672 * @return the view 1673 */ 1674 public Element getElement() { 1675 if (view != null) { 1676 return view.getElement(); 1677 } 1678 return editor.getDocument().getDefaultRootElement(); 1679 } 1680 1681 /** 1682 * Breaks this view on the given axis at the given length. 1683 * 1684 * @param axis may be either X_AXIS or Y_AXIS 1685 * @param len specifies where a break is desired in the span 1686 * @param the current allocation of the view 1687 * @return the fragment of the view that represents the given span 1688 * if the view can be broken, otherwise null 1689 */ 1690 public View breakView(int axis, float len, Shape a) { 1691 throw new Error("Can't break root view"); 1692 } 1693 1694 /** 1695 * Determines the resizability of the view along the 1696 * given axis. A value of 0 or less is not resizable. 1697 * 1698 * @param axis may be either X_AXIS or Y_AXIS 1699 * @return the weight 1700 */ 1701 public int getResizeWeight(int axis) { 1702 if (view != null) { 1703 return view.getResizeWeight(axis); 1704 } 1705 return 0; 1706 } 1707 1708 /** 1709 * Sets the view size. 1710 * 1711 * @param width the width 1712 * @param height the height 1713 */ 1714 public void setSize(float width, float height) { 1715 if (view != null) { 1716 view.setSize(width, height); 1717 } 1718 } 1719 1720 /** 1721 * Fetches the container hosting the view. This is useful for 1722 * things like scheduling a repaint, finding out the host 1723 * components font, etc. The default implementation 1724 * of this is to forward the query to the parent view. 1725 * 1726 * @return the container 1727 */ 1728 public Container getContainer() { 1729 return editor; 1730 } 1731 1732 /** 1733 * Fetches the factory to be used for building the 1734 * various view fragments that make up the view that 1735 * represents the model. This is what determines 1736 * how the model will be represented. This is implemented 1737 * to fetch the factory provided by the associated 1738 * EditorKit unless that is null, in which case this 1739 * simply returns the BasicTextUI itself which allows 1740 * subclasses to implement a simple factory directly without 1741 * creating extra objects. 1742 * 1743 * @return the factory 1744 */ 1745 public ViewFactory getViewFactory() { 1746 EditorKit kit = getEditorKit(editor); 1747 ViewFactory f = kit.getViewFactory(); 1748 if (f != null) { 1749 return f; 1750 } 1751 return BasicTextUI.this; 1752 } 1753 1754 private View view; 1755 1756 } 1757 1758 /** 1759 * Handles updates from various places. If the model is changed, 1760 * this class unregisters as a listener to the old model and 1761 * registers with the new model. If the document model changes, 1762 * the change is forwarded to the root view. If the focus 1763 * accelerator changes, a new keystroke is registered to request 1764 * focus. 1765 */ 1766 class UpdateHandler implements PropertyChangeListener, DocumentListener, LayoutManager2, UIResource { 1767 1768 // --- PropertyChangeListener methods ----------------------- 1769 1770 /** 1771 * This method gets called when a bound property is changed. 1772 * We are looking for document changes on the editor. 1773 */ 1774 public final void propertyChange(PropertyChangeEvent evt) { 1775 Object oldValue = evt.getOldValue(); 1776 Object newValue = evt.getNewValue(); 1777 String propertyName = evt.getPropertyName(); 1778 if ((oldValue instanceof Document) || (newValue instanceof Document)) { 1779 if (oldValue != null) { 1780 ((Document)oldValue).removeDocumentListener(this); 1781 i18nView = false; 1782 } 1783 if (newValue != null) { 1784 ((Document)newValue).addDocumentListener(this); 1785 if ("document" == propertyName) { 1786 setView(null); 1787 BasicTextUI.this.propertyChange(evt); 1788 modelChanged(); 1789 return; 1790 } 1791 } 1792 modelChanged(); 1793 } 1794 if ("focusAccelerator" == propertyName) { 1795 updateFocusAcceleratorBinding(true); 1796 } else if ("componentOrientation" == propertyName) { 1797 // Changes in ComponentOrientation require the views to be 1798 // rebuilt. 1799 modelChanged(); 1800 } else if ("font" == propertyName) { 1801 modelChanged(); 1802 } else if ("dropLocation" == propertyName) { 1803 dropIndexChanged(); 1804 } else if ("editable" == propertyName) { 1805 updateCursor(); 1806 modelChanged(); 1807 } 1808 BasicTextUI.this.propertyChange(evt); 1809 } 1810 1811 private void dropIndexChanged() { 1812 if (editor.getDropMode() == DropMode.USE_SELECTION) { 1813 return; 1814 } 1815 1816 JTextComponent.DropLocation dropLocation = editor.getDropLocation(); 1817 1818 if (dropLocation == null) { 1819 if (dropCaret != null) { 1820 dropCaret.deinstall(editor); 1821 editor.repaint(dropCaret); 1822 dropCaret = null; 1823 } 1824 } else { 1825 if (dropCaret == null) { 1826 dropCaret = new BasicCaret(); 1827 dropCaret.install(editor); 1828 dropCaret.setVisible(true); 1829 } 1830 1831 dropCaret.setDot(dropLocation.getIndex(), 1832 dropLocation.getBias()); 1833 } 1834 } 1835 1836 // --- DocumentListener methods ----------------------- 1837 1838 /** 1839 * The insert notification. Gets sent to the root of the view structure 1840 * that represents the portion of the model being represented by the 1841 * editor. The factory is added as an argument to the update so that 1842 * the views can update themselves in a dynamic (not hardcoded) way. 1843 * 1844 * @param e The change notification from the currently associated 1845 * document. 1846 * @see DocumentListener#insertUpdate 1847 */ 1848 public final void insertUpdate(DocumentEvent e) { 1849 Document doc = e.getDocument(); 1850 Object o = doc.getProperty("i18n"); 1851 if (o instanceof Boolean) { 1852 Boolean i18nFlag = (Boolean) o; 1853 if (i18nFlag.booleanValue() != i18nView) { 1854 // i18n flag changed, rebuild the view 1855 i18nView = i18nFlag.booleanValue(); 1856 modelChanged(); 1857 return; 1858 } 1859 } 1860 1861 // normal insert update 1862 Rectangle alloc = (painted) ? getVisibleEditorRect() : null; 1863 rootView.insertUpdate(e, alloc, rootView.getViewFactory()); 1864 } 1865 1866 /** 1867 * The remove notification. Gets sent to the root of the view structure 1868 * that represents the portion of the model being represented by the 1869 * editor. The factory is added as an argument to the update so that 1870 * the views can update themselves in a dynamic (not hardcoded) way. 1871 * 1872 * @param e The change notification from the currently associated 1873 * document. 1874 * @see DocumentListener#removeUpdate 1875 */ 1876 public final void removeUpdate(DocumentEvent e) { 1877 Rectangle alloc = (painted) ? getVisibleEditorRect() : null; 1878 rootView.removeUpdate(e, alloc, rootView.getViewFactory()); 1879 } 1880 1881 /** 1882 * The change notification. Gets sent to the root of the view structure 1883 * that represents the portion of the model being represented by the 1884 * editor. The factory is added as an argument to the update so that 1885 * the views can update themselves in a dynamic (not hardcoded) way. 1886 * 1887 * @param e The change notification from the currently associated 1888 * document. 1889 * @see DocumentListener#changedUpdate(DocumentEvent) 1890 */ 1891 public final void changedUpdate(DocumentEvent e) { 1892 Rectangle alloc = (painted) ? getVisibleEditorRect() : null; 1893 rootView.changedUpdate(e, alloc, rootView.getViewFactory()); 1894 } 1895 1896 // --- LayoutManager2 methods -------------------------------- 1897 1898 /** 1899 * Adds the specified component with the specified name to 1900 * the layout. 1901 * @param name the component name 1902 * @param comp the component to be added 1903 */ 1904 public void addLayoutComponent(String name, Component comp) { 1905 // not supported 1906 } 1907 1908 /** 1909 * Removes the specified component from the layout. 1910 * @param comp the component to be removed 1911 */ 1912 public void removeLayoutComponent(Component comp) { 1913 if (constraints != null) { 1914 // remove the constraint record 1915 constraints.remove(comp); 1916 } 1917 } 1918 1919 /** 1920 * Calculates the preferred size dimensions for the specified 1921 * panel given the components in the specified parent container. 1922 * @param parent the component to be laid out 1923 * 1924 * @see #minimumLayoutSize 1925 */ 1926 public Dimension preferredLayoutSize(Container parent) { 1927 // should not be called (JComponent uses UI instead) 1928 return null; 1929 } 1930 1931 /** 1932 * Calculates the minimum size dimensions for the specified 1933 * panel given the components in the specified parent container. 1934 * @param parent the component to be laid out 1935 * @see #preferredLayoutSize 1936 */ 1937 public Dimension minimumLayoutSize(Container parent) { 1938 // should not be called (JComponent uses UI instead) 1939 return null; 1940 } 1941 1942 /** 1943 * Lays out the container in the specified panel. This is 1944 * implemented to position all components that were added 1945 * with a View object as a constraint. The current allocation 1946 * of the associated View is used as the location of the 1947 * component. 1948 * <p> 1949 * A read-lock is acquired on the document to prevent the 1950 * view tree from being modified while the layout process 1951 * is active. 1952 * 1953 * @param parent the component which needs to be laid out 1954 */ 1955 public void layoutContainer(Container parent) { 1956 if ((constraints != null) && (! constraints.isEmpty())) { 1957 Rectangle alloc = getVisibleEditorRect(); 1958 if (alloc != null) { 1959 Document doc = editor.getDocument(); 1960 if (doc instanceof AbstractDocument) { 1961 ((AbstractDocument)doc).readLock(); 1962 } 1963 try { 1964 rootView.setSize(alloc.width, alloc.height); 1965 Enumeration<Component> components = constraints.keys(); 1966 while (components.hasMoreElements()) { 1967 Component comp = components.nextElement(); 1968 View v = (View) constraints.get(comp); 1969 Shape ca = calculateViewPosition(alloc, v); 1970 if (ca != null) { 1971 Rectangle compAlloc = (ca instanceof Rectangle) ? 1972 (Rectangle) ca : ca.getBounds(); 1973 comp.setBounds(compAlloc); 1974 } 1975 } 1976 } finally { 1977 if (doc instanceof AbstractDocument) { 1978 ((AbstractDocument)doc).readUnlock(); 1979 } 1980 } 1981 } 1982 } 1983 } 1984 1985 /** 1986 * Find the Shape representing the given view. 1987 */ 1988 Shape calculateViewPosition(Shape alloc, View v) { 1989 int pos = v.getStartOffset(); 1990 View child = null; 1991 for (View parent = rootView; (parent != null) && (parent != v); parent = child) { 1992 int index = parent.getViewIndex(pos, Position.Bias.Forward); 1993 alloc = parent.getChildAllocation(index, alloc); 1994 child = parent.getView(index); 1995 } 1996 return (child != null) ? alloc : null; 1997 } 1998 1999 /** 2000 * Adds the specified component to the layout, using the specified 2001 * constraint object. We only store those components that were added 2002 * with a constraint that is of type View. 2003 * 2004 * @param comp the component to be added 2005 * @param constraint where/how the component is added to the layout. 2006 */ 2007 public void addLayoutComponent(Component comp, Object constraint) { 2008 if (constraint instanceof View) { 2009 if (constraints == null) { 2010 constraints = new Hashtable<Component, Object>(7); 2011 } 2012 constraints.put(comp, constraint); 2013 } 2014 } 2015 2016 /** 2017 * Returns the maximum size of this component. 2018 * @see java.awt.Component#getMinimumSize() 2019 * @see java.awt.Component#getPreferredSize() 2020 * @see LayoutManager 2021 */ 2022 public Dimension maximumLayoutSize(Container target) { 2023 // should not be called (JComponent uses UI instead) 2024 return null; 2025 } 2026 2027 /** 2028 * Returns the alignment along the x axis. This specifies how 2029 * the component would like to be aligned relative to other 2030 * components. The value should be a number between 0 and 1 2031 * where 0 represents alignment along the origin, 1 is aligned 2032 * the furthest away from the origin, 0.5 is centered, etc. 2033 */ 2034 public float getLayoutAlignmentX(Container target) { 2035 return 0.5f; 2036 } 2037 2038 /** 2039 * Returns the alignment along the y axis. This specifies how 2040 * the component would like to be aligned relative to other 2041 * components. The value should be a number between 0 and 1 2042 * where 0 represents alignment along the origin, 1 is aligned 2043 * the furthest away from the origin, 0.5 is centered, etc. 2044 */ 2045 public float getLayoutAlignmentY(Container target) { 2046 return 0.5f; 2047 } 2048 2049 /** 2050 * Invalidates the layout, indicating that if the layout manager 2051 * has cached information it should be discarded. 2052 */ 2053 public void invalidateLayout(Container target) { 2054 } 2055 2056 /** 2057 * The "layout constraints" for the LayoutManager2 implementation. 2058 * These are View objects for those components that are represented 2059 * by a View in the View tree. 2060 */ 2061 private Hashtable<Component, Object> constraints; 2062 2063 private boolean i18nView = false; 2064 } 2065 2066 /** 2067 * Wrapper for text actions to return isEnabled false in case editor is non editable 2068 */ 2069 class TextActionWrapper extends TextAction { 2070 public TextActionWrapper(TextAction action) { 2071 super((String)action.getValue(Action.NAME)); 2072 this.action = action; 2073 } 2074 /** 2075 * The operation to perform when this action is triggered. 2076 * 2077 * @param e the action event 2078 */ 2079 public void actionPerformed(ActionEvent e) { 2080 action.actionPerformed(e); 2081 } 2082 public boolean isEnabled() { 2083 return (editor == null || editor.isEditable()) ? action.isEnabled() : false; 2084 } 2085 TextAction action = null; 2086 } 2087 2088 2089 /** 2090 * Registered in the ActionMap. 2091 */ 2092 class FocusAction extends AbstractAction { 2093 2094 public void actionPerformed(ActionEvent e) { 2095 editor.requestFocus(); 2096 } 2097 2098 public boolean isEnabled() { 2099 return editor.isEditable(); 2100 } 2101 } 2102 2103 private static DragListener getDragListener() { 2104 synchronized(DragListener.class) { 2105 DragListener listener = 2106 (DragListener)AppContext.getAppContext(). 2107 get(DragListener.class); 2108 2109 if (listener == null) { 2110 listener = new DragListener(); 2111 AppContext.getAppContext().put(DragListener.class, listener); 2112 } 2113 2114 return listener; 2115 } 2116 } 2117 2118 /** 2119 * Listens for mouse events for the purposes of detecting drag gestures. 2120 * BasicTextUI will maintain one of these per AppContext. 2121 */ 2122 static class DragListener extends MouseInputAdapter 2123 implements BeforeDrag { 2124 2125 private boolean dragStarted; 2126 2127 public void dragStarting(MouseEvent me) { 2128 dragStarted = true; 2129 } 2130 2131 public void mousePressed(MouseEvent e) { 2132 JTextComponent c = (JTextComponent)e.getSource(); 2133 if (c.getDragEnabled()) { 2134 dragStarted = false; 2135 if (isDragPossible(e) && DragRecognitionSupport.mousePressed(e)) { 2136 e.consume(); 2137 } 2138 } 2139 } 2140 2141 public void mouseReleased(MouseEvent e) { 2142 JTextComponent c = (JTextComponent)e.getSource(); 2143 if (c.getDragEnabled()) { 2144 if (dragStarted) { 2145 e.consume(); 2146 } 2147 2148 DragRecognitionSupport.mouseReleased(e); 2149 } 2150 } 2151 2152 public void mouseDragged(MouseEvent e) { 2153 JTextComponent c = (JTextComponent)e.getSource(); 2154 if (c.getDragEnabled()) { 2155 if (dragStarted || DragRecognitionSupport.mouseDragged(e, this)) { 2156 e.consume(); 2157 } 2158 } 2159 } 2160 2161 /** 2162 * Determines if the following are true: 2163 * <ul> 2164 * <li>the component is enabled 2165 * <li>the press event is located over a selection 2166 * </ul> 2167 */ 2168 protected boolean isDragPossible(MouseEvent e) { 2169 JTextComponent c = (JTextComponent)e.getSource(); 2170 if (c.isEnabled()) { 2171 Caret caret = c.getCaret(); 2172 int dot = caret.getDot(); 2173 int mark = caret.getMark(); 2174 if (dot != mark) { 2175 Point p = new Point(e.getX(), e.getY()); 2176 int pos = c.viewToModel(p); 2177 2178 int p0 = Math.min(dot, mark); 2179 int p1 = Math.max(dot, mark); 2180 if ((pos >= p0) && (pos < p1)) { 2181 return true; 2182 } 2183 } 2184 } 2185 return false; 2186 } 2187 } 2188 2189 static class TextTransferHandler extends TransferHandler implements UIResource { 2190 2191 private JTextComponent exportComp; 2192 private boolean shouldRemove; 2193 private int p0; 2194 private int p1; 2195 2196 /** 2197 * Whether or not this is a drop using 2198 * <code>DropMode.INSERT</code>. 2199 */ 2200 private boolean modeBetween = false; 2201 2202 /** 2203 * Whether or not this is a drop. 2204 */ 2205 private boolean isDrop = false; 2206 2207 /** 2208 * The drop action. 2209 */ 2210 private int dropAction = MOVE; 2211 2212 /** 2213 * The drop bias. 2214 */ 2215 private Position.Bias dropBias; 2216 2217 /** 2218 * Try to find a flavor that can be used to import a Transferable. 2219 * The set of usable flavors are tried in the following order: 2220 * <ol> 2221 * <li>First, an attempt is made to find a flavor matching the content type 2222 * of the EditorKit for the component. 2223 * <li>Second, an attempt to find a text/plain flavor is made. 2224 * <li>Third, an attempt to find a flavor representing a String reference 2225 * in the same VM is made. 2226 * <li>Lastly, DataFlavor.stringFlavor is searched for. 2227 * </ol> 2228 */ 2229 protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) { 2230 DataFlavor plainFlavor = null; 2231 DataFlavor refFlavor = null; 2232 DataFlavor stringFlavor = null; 2233 2234 if (c instanceof JEditorPane) { 2235 for (int i = 0; i < flavors.length; i++) { 2236 String mime = flavors[i].getMimeType(); 2237 if (mime.startsWith(((JEditorPane)c).getEditorKit().getContentType())) { 2238 return flavors[i]; 2239 } else if (plainFlavor == null && mime.startsWith("text/plain")) { 2240 plainFlavor = flavors[i]; 2241 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref") 2242 && flavors[i].getRepresentationClass() == java.lang.String.class) { 2243 refFlavor = flavors[i]; 2244 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) { 2245 stringFlavor = flavors[i]; 2246 } 2247 } 2248 if (plainFlavor != null) { 2249 return plainFlavor; 2250 } else if (refFlavor != null) { 2251 return refFlavor; 2252 } else if (stringFlavor != null) { 2253 return stringFlavor; 2254 } 2255 return null; 2256 } 2257 2258 2259 for (int i = 0; i < flavors.length; i++) { 2260 String mime = flavors[i].getMimeType(); 2261 if (mime.startsWith("text/plain")) { 2262 return flavors[i]; 2263 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref") 2264 && flavors[i].getRepresentationClass() == java.lang.String.class) { 2265 refFlavor = flavors[i]; 2266 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) { 2267 stringFlavor = flavors[i]; 2268 } 2269 } 2270 if (refFlavor != null) { 2271 return refFlavor; 2272 } else if (stringFlavor != null) { 2273 return stringFlavor; 2274 } 2275 return null; 2276 } 2277 2278 /** 2279 * Import the given stream data into the text component. 2280 */ 2281 protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead) 2282 throws BadLocationException, IOException { 2283 if (useRead) { 2284 int startPosition = c.getSelectionStart(); 2285 int endPosition = c.getSelectionEnd(); 2286 int length = endPosition - startPosition; 2287 EditorKit kit = c.getUI().getEditorKit(c); 2288 Document doc = c.getDocument(); 2289 if (length > 0) { 2290 doc.remove(startPosition, length); 2291 } 2292 kit.read(in, doc, startPosition); 2293 } else { 2294 char[] buff = new char[1024]; 2295 int nch; 2296 boolean lastWasCR = false; 2297 int last; 2298 StringBuffer sbuff = null; 2299 2300 // Read in a block at a time, mapping \r\n to \n, as well as single 2301 // \r to \n. 2302 while ((nch = in.read(buff, 0, buff.length)) != -1) { 2303 if (sbuff == null) { 2304 sbuff = new StringBuffer(nch); 2305 } 2306 last = 0; 2307 for(int counter = 0; counter < nch; counter++) { 2308 switch(buff[counter]) { 2309 case '\r': 2310 if (lastWasCR) { 2311 if (counter == 0) { 2312 sbuff.append('\n'); 2313 } else { 2314 buff[counter - 1] = '\n'; 2315 } 2316 } else { 2317 lastWasCR = true; 2318 } 2319 break; 2320 case '\n': 2321 if (lastWasCR) { 2322 if (counter > (last + 1)) { 2323 sbuff.append(buff, last, counter - last - 1); 2324 } 2325 // else nothing to do, can skip \r, next write will 2326 // write \n 2327 lastWasCR = false; 2328 last = counter; 2329 } 2330 break; 2331 default: 2332 if (lastWasCR) { 2333 if (counter == 0) { 2334 sbuff.append('\n'); 2335 } else { 2336 buff[counter - 1] = '\n'; 2337 } 2338 lastWasCR = false; 2339 } 2340 break; 2341 } 2342 } 2343 if (last < nch) { 2344 if (lastWasCR) { 2345 if (last < (nch - 1)) { 2346 sbuff.append(buff, last, nch - last - 1); 2347 } 2348 } else { 2349 sbuff.append(buff, last, nch - last); 2350 } 2351 } 2352 } 2353 if (lastWasCR) { 2354 sbuff.append('\n'); 2355 } 2356 c.replaceSelection(sbuff != null ? sbuff.toString() : ""); 2357 } 2358 } 2359 2360 // --- TransferHandler methods ------------------------------------ 2361 2362 /** 2363 * This is the type of transfer actions supported by the source. Some models are 2364 * not mutable, so a transfer operation of COPY only should 2365 * be advertised in that case. 2366 * 2367 * @param c The component holding the data to be transfered. This 2368 * argument is provided to enable sharing of TransferHandlers by 2369 * multiple components. 2370 * @return This is implemented to return NONE if the component is a JPasswordField 2371 * since exporting data via user gestures is not allowed. If the text component is 2372 * editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed. 2373 */ 2374 public int getSourceActions(JComponent c) { 2375 if (c instanceof JPasswordField && 2376 c.getClientProperty("JPasswordField.cutCopyAllowed") != 2377 Boolean.TRUE) { 2378 return NONE; 2379 } 2380 2381 return ((JTextComponent)c).isEditable() ? COPY_OR_MOVE : COPY; 2382 } 2383 2384 /** 2385 * Create a Transferable to use as the source for a data transfer. 2386 * 2387 * @param comp The component holding the data to be transfered. This 2388 * argument is provided to enable sharing of TransferHandlers by 2389 * multiple components. 2390 * @return The representation of the data to be transfered. 2391 * 2392 */ 2393 protected Transferable createTransferable(JComponent comp) { 2394 exportComp = (JTextComponent)comp; 2395 shouldRemove = true; 2396 p0 = exportComp.getSelectionStart(); 2397 p1 = exportComp.getSelectionEnd(); 2398 return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1)) : null; 2399 } 2400 2401 /** 2402 * This method is called after data has been exported. This method should remove 2403 * the data that was transfered if the action was MOVE. 2404 * 2405 * @param source The component that was the source of the data. 2406 * @param data The data that was transferred or possibly null 2407 * if the action is <code>NONE</code>. 2408 * @param action The actual action that was performed. 2409 */ 2410 protected void exportDone(JComponent source, Transferable data, int action) { 2411 // only remove the text if shouldRemove has not been set to 2412 // false by importData and only if the action is a move 2413 if (shouldRemove && action == MOVE) { 2414 TextTransferable t = (TextTransferable)data; 2415 t.removeText(); 2416 } 2417 2418 exportComp = null; 2419 } 2420 2421 public boolean importData(TransferSupport support) { 2422 isDrop = support.isDrop(); 2423 2424 if (isDrop) { 2425 modeBetween = 2426 ((JTextComponent)support.getComponent()).getDropMode() == DropMode.INSERT; 2427 2428 dropBias = ((JTextComponent.DropLocation)support.getDropLocation()).getBias(); 2429 2430 dropAction = support.getDropAction(); 2431 } 2432 2433 try { 2434 return super.importData(support); 2435 } finally { 2436 isDrop = false; 2437 modeBetween = false; 2438 dropBias = null; 2439 dropAction = MOVE; 2440 } 2441 } 2442 2443 /** 2444 * This method causes a transfer to a component from a clipboard or a 2445 * DND drop operation. The Transferable represents the data to be 2446 * imported into the component. 2447 * 2448 * @param comp The component to receive the transfer. This 2449 * argument is provided to enable sharing of TransferHandlers by 2450 * multiple components. 2451 * @param t The data to import 2452 * @return true if the data was inserted into the component, false otherwise. 2453 */ 2454 public boolean importData(JComponent comp, Transferable t) { 2455 JTextComponent c = (JTextComponent)comp; 2456 2457 int pos = modeBetween 2458 ? c.getDropLocation().getIndex() : c.getCaretPosition(); 2459 2460 // if we are importing to the same component that we exported from 2461 // then don't actually do anything if the drop location is inside 2462 // the drag location and set shouldRemove to false so that exportDone 2463 // knows not to remove any data 2464 if (dropAction == MOVE && c == exportComp && pos >= p0 && pos <= p1) { 2465 shouldRemove = false; 2466 return true; 2467 } 2468 2469 boolean imported = false; 2470 DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c); 2471 if (importFlavor != null) { 2472 try { 2473 boolean useRead = false; 2474 if (comp instanceof JEditorPane) { 2475 JEditorPane ep = (JEditorPane)comp; 2476 if (!ep.getContentType().startsWith("text/plain") && 2477 importFlavor.getMimeType().startsWith(ep.getContentType())) { 2478 useRead = true; 2479 } 2480 } 2481 InputContext ic = c.getInputContext(); 2482 if (ic != null) { 2483 ic.endComposition(); 2484 } 2485 Reader r = importFlavor.getReaderForText(t); 2486 2487 if (modeBetween) { 2488 Caret caret = c.getCaret(); 2489 if (caret instanceof DefaultCaret) { 2490 ((DefaultCaret)caret).setDot(pos, dropBias); 2491 } else { 2492 c.setCaretPosition(pos); 2493 } 2494 } 2495 2496 handleReaderImport(r, c, useRead); 2497 2498 if (isDrop) { 2499 c.requestFocus(); 2500 Caret caret = c.getCaret(); 2501 if (caret instanceof DefaultCaret) { 2502 int newPos = caret.getDot(); 2503 Position.Bias newBias = ((DefaultCaret)caret).getDotBias(); 2504 2505 ((DefaultCaret)caret).setDot(pos, dropBias); 2506 ((DefaultCaret)caret).moveDot(newPos, newBias); 2507 } else { 2508 c.select(pos, c.getCaretPosition()); 2509 } 2510 } 2511 2512 imported = true; 2513 } catch (UnsupportedFlavorException ufe) { 2514 } catch (BadLocationException ble) { 2515 } catch (IOException ioe) { 2516 } 2517 } 2518 return imported; 2519 } 2520 2521 /** 2522 * This method indicates if a component would accept an import of the given 2523 * set of data flavors prior to actually attempting to import it. 2524 * 2525 * @param comp The component to receive the transfer. This 2526 * argument is provided to enable sharing of TransferHandlers by 2527 * multiple components. 2528 * @param flavors The data formats available 2529 * @return true if the data can be inserted into the component, false otherwise. 2530 */ 2531 public boolean canImport(JComponent comp, DataFlavor[] flavors) { 2532 JTextComponent c = (JTextComponent)comp; 2533 if (!(c.isEditable() && c.isEnabled())) { 2534 return false; 2535 } 2536 return (getImportFlavor(flavors, c) != null); 2537 } 2538 2539 /** 2540 * A possible implementation of the Transferable interface 2541 * for text components. For a JEditorPane with a rich set 2542 * of EditorKit implementations, conversions could be made 2543 * giving a wider set of formats. This is implemented to 2544 * offer up only the active content type and text/plain 2545 * (if that is not the active format) since that can be 2546 * extracted from other formats. 2547 */ 2548 static class TextTransferable extends BasicTransferable { 2549 2550 TextTransferable(JTextComponent c, int start, int end) { 2551 super(null, null); 2552 2553 this.c = c; 2554 2555 Document doc = c.getDocument(); 2556 2557 try { 2558 p0 = doc.createPosition(start); 2559 p1 = doc.createPosition(end); 2560 2561 plainData = c.getSelectedText(); 2562 2563 if (c instanceof JEditorPane) { 2564 JEditorPane ep = (JEditorPane)c; 2565 2566 mimeType = ep.getContentType(); 2567 2568 if (mimeType.startsWith("text/plain")) { 2569 return; 2570 } 2571 2572 StringWriter sw = new StringWriter(p1.getOffset() - p0.getOffset()); 2573 ep.getEditorKit().write(sw, doc, p0.getOffset(), p1.getOffset() - p0.getOffset()); 2574 2575 if (mimeType.startsWith("text/html")) { 2576 htmlData = sw.toString(); 2577 } else { 2578 richText = sw.toString(); 2579 } 2580 } 2581 } catch (BadLocationException ble) { 2582 } catch (IOException ioe) { 2583 } 2584 } 2585 2586 void removeText() { 2587 if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) { 2588 try { 2589 Document doc = c.getDocument(); 2590 doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset()); 2591 } catch (BadLocationException e) { 2592 } 2593 } 2594 } 2595 2596 // ---- EditorKit other than plain or HTML text ----------------------- 2597 2598 /** 2599 * If the EditorKit is not for text/plain or text/html, that format 2600 * is supported through the "richer flavors" part of BasicTransferable. 2601 */ 2602 protected DataFlavor[] getRicherFlavors() { 2603 if (richText == null) { 2604 return null; 2605 } 2606 2607 try { 2608 DataFlavor[] flavors = new DataFlavor[3]; 2609 flavors[0] = new DataFlavor(mimeType + ";class=java.lang.String"); 2610 flavors[1] = new DataFlavor(mimeType + ";class=java.io.Reader"); 2611 flavors[2] = new DataFlavor(mimeType + ";class=java.io.InputStream;charset=unicode"); 2612 return flavors; 2613 } catch (ClassNotFoundException cle) { 2614 // fall through to unsupported (should not happen) 2615 } 2616 2617 return null; 2618 } 2619 2620 /** 2621 * The only richer format supported is the file list flavor 2622 */ 2623 protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException { 2624 if (richText == null) { 2625 return null; 2626 } 2627 2628 if (String.class.equals(flavor.getRepresentationClass())) { 2629 return richText; 2630 } else if (Reader.class.equals(flavor.getRepresentationClass())) { 2631 return new StringReader(richText); 2632 } else if (InputStream.class.equals(flavor.getRepresentationClass())) { 2633 return new StringBufferInputStream(richText); 2634 } 2635 throw new UnsupportedFlavorException(flavor); 2636 } 2637 2638 Position p0; 2639 Position p1; 2640 String mimeType; 2641 String richText; 2642 JTextComponent c; 2643 } 2644 2645 } 2646 2647 }