1 /* 2 * Copyright (c) 1997, 2013, 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™ 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, BasicLookAndFeel.getFocusAcceleratorKeyMask()), "requestFocus"); 501 } 502 } 503 } 504 } 505 506 507 /** 508 * Invoked when editable property is changed. 509 * 510 * removing 'TAB' and 'SHIFT-TAB' from traversalKeysSet in case 511 * editor is editable 512 * adding 'TAB' and 'SHIFT-TAB' to traversalKeysSet in case 513 * editor is non editable 514 */ 515 516 void updateFocusTraversalKeys() { 517 /* 518 * Fix for 4514331 Non-editable JTextArea and similar 519 * should allow Tab to keyboard - accessibility 520 */ 521 EditorKit editorKit = getEditorKit(editor); 522 if ( editorKit != null 523 && editorKit instanceof DefaultEditorKit) { 524 Set<AWTKeyStroke> storedForwardTraversalKeys = editor. 525 getFocusTraversalKeys(KeyboardFocusManager. 526 FORWARD_TRAVERSAL_KEYS); 527 Set<AWTKeyStroke> storedBackwardTraversalKeys = editor. 528 getFocusTraversalKeys(KeyboardFocusManager. 529 BACKWARD_TRAVERSAL_KEYS); 530 Set<AWTKeyStroke> forwardTraversalKeys = 531 new HashSet<AWTKeyStroke>(storedForwardTraversalKeys); 532 Set<AWTKeyStroke> backwardTraversalKeys = 533 new HashSet<AWTKeyStroke>(storedBackwardTraversalKeys); 534 if (editor.isEditable()) { 535 forwardTraversalKeys. 536 remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); 537 backwardTraversalKeys. 538 remove(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 539 InputEvent.SHIFT_MASK)); 540 } else { 541 forwardTraversalKeys.add(KeyStroke. 542 getKeyStroke(KeyEvent.VK_TAB, 0)); 543 backwardTraversalKeys. 544 add(KeyStroke. 545 getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK)); 546 } 547 LookAndFeel.installProperty(editor, 548 "focusTraversalKeysForward", 549 forwardTraversalKeys); 550 LookAndFeel.installProperty(editor, 551 "focusTraversalKeysBackward", 552 backwardTraversalKeys); 553 } 554 555 } 556 557 /** 558 * As needed updates cursor for the target editor. 559 */ 560 private void updateCursor() { 561 if ((! editor.isCursorSet()) 562 || editor.getCursor() instanceof UIResource) { 563 Cursor cursor = (editor.isEditable()) ? textCursor : null; 564 editor.setCursor(cursor); 565 } 566 } 567 568 /** 569 * Returns the <code>TransferHandler</code> that will be installed if 570 * their isn't one installed on the <code>JTextComponent</code>. 571 */ 572 TransferHandler getTransferHandler() { 573 return defaultTransferHandler; 574 } 575 576 /** 577 * Fetch an action map to use. 578 */ 579 ActionMap getActionMap() { 580 String mapName = getPropertyPrefix() + ".actionMap"; 581 ActionMap map = (ActionMap)UIManager.get(mapName); 582 583 if (map == null) { 584 map = createActionMap(); 585 if (map != null) { 586 UIManager.getLookAndFeelDefaults().put(mapName, map); 587 } 588 } 589 ActionMap componentMap = new ActionMapUIResource(); 590 componentMap.put("requestFocus", new FocusAction()); 591 /* 592 * fix for bug 4515750 593 * JTextField & non-editable JTextArea bind return key - default btn not accessible 594 * 595 * Wrap the return action so that it is only enabled when the 596 * component is editable. This allows the default button to be 597 * processed when the text component has focus and isn't editable. 598 * 599 */ 600 if (getEditorKit(editor) instanceof DefaultEditorKit) { 601 if (map != null) { 602 Object obj = map.get(DefaultEditorKit.insertBreakAction); 603 if (obj != null 604 && obj instanceof DefaultEditorKit.InsertBreakAction) { 605 Action action = new TextActionWrapper((TextAction)obj); 606 componentMap.put(action.getValue(Action.NAME),action); 607 } 608 } 609 } 610 if (map != null) { 611 componentMap.setParent(map); 612 } 613 return componentMap; 614 } 615 616 /** 617 * Create a default action map. This is basically the 618 * set of actions found exported by the component. 619 */ 620 ActionMap createActionMap() { 621 ActionMap map = new ActionMapUIResource(); 622 Action[] actions = editor.getActions(); 623 //System.out.println("building map for UI: " + getPropertyPrefix()); 624 int n = actions.length; 625 for (int i = 0; i < n; i++) { 626 Action a = actions[i]; 627 map.put(a.getValue(Action.NAME), a); 628 //System.out.println(" " + a.getValue(Action.NAME)); 629 } 630 map.put(TransferHandler.getCutAction().getValue(Action.NAME), 631 TransferHandler.getCutAction()); 632 map.put(TransferHandler.getCopyAction().getValue(Action.NAME), 633 TransferHandler.getCopyAction()); 634 map.put(TransferHandler.getPasteAction().getValue(Action.NAME), 635 TransferHandler.getPasteAction()); 636 return map; 637 } 638 639 protected void uninstallKeyboardActions() { 640 editor.setKeymap(null); 641 SwingUtilities.replaceUIInputMap(editor, JComponent. 642 WHEN_IN_FOCUSED_WINDOW, null); 643 SwingUtilities.replaceUIActionMap(editor, null); 644 } 645 646 /** 647 * Paints a background for the view. This will only be 648 * called if isOpaque() on the associated component is 649 * true. The default is to paint the background color 650 * of the component. 651 * 652 * @param g the graphics context 653 */ 654 protected void paintBackground(Graphics g) { 655 g.setColor(editor.getBackground()); 656 g.fillRect(0, 0, editor.getWidth(), editor.getHeight()); 657 } 658 659 /** 660 * Fetches the text component associated with this 661 * UI implementation. This will be null until 662 * the ui has been installed. 663 * 664 * @return the editor component 665 */ 666 protected final JTextComponent getComponent() { 667 return editor; 668 } 669 670 /** 671 * Flags model changes. 672 * This is called whenever the model has changed. 673 * It is implemented to rebuild the view hierarchy 674 * to represent the default root element of the 675 * associated model. 676 */ 677 protected void modelChanged() { 678 // create a view hierarchy 679 ViewFactory f = rootView.getViewFactory(); 680 Document doc = editor.getDocument(); 681 Element elem = doc.getDefaultRootElement(); 682 setView(f.create(elem)); 683 } 684 685 /** 686 * Sets the current root of the view hierarchy and calls invalidate(). 687 * If there were any child components, they will be removed (i.e. 688 * there are assumed to have come from components embedded in views). 689 * 690 * @param v the root view 691 */ 692 protected final void setView(View v) { 693 rootView.setView(v); 694 painted = false; 695 editor.revalidate(); 696 editor.repaint(); 697 } 698 699 /** 700 * Paints the interface safely with a guarantee that 701 * the model won't change from the view of this thread. 702 * This does the following things, rendering from 703 * back to front. 704 * <ol> 705 * <li> 706 * If the component is marked as opaque, the background 707 * is painted in the current background color of the 708 * component. 709 * <li> 710 * The highlights (if any) are painted. 711 * <li> 712 * The view hierarchy is painted. 713 * <li> 714 * The caret is painted. 715 * </ol> 716 * 717 * @param g the graphics context 718 */ 719 protected void paintSafely(Graphics g) { 720 painted = true; 721 Highlighter highlighter = editor.getHighlighter(); 722 Caret caret = editor.getCaret(); 723 724 // paint the background 725 if (editor.isOpaque()) { 726 paintBackground(g); 727 } 728 729 // paint the highlights 730 if (highlighter != null) { 731 highlighter.paint(g); 732 } 733 734 // paint the view hierarchy 735 Rectangle alloc = getVisibleEditorRect(); 736 if (alloc != null) { 737 rootView.paint(g, alloc); 738 } 739 740 // paint the caret 741 if (caret != null) { 742 caret.paint(g); 743 } 744 745 if (dropCaret != null) { 746 dropCaret.paint(g); 747 } 748 } 749 750 // --- ComponentUI methods -------------------------------------------- 751 752 /** 753 * Installs the UI for a component. This does the following 754 * things. 755 * <ol> 756 * <li> 757 * Sets the associated component to opaque if the opaque property 758 * has not already been set by the client program. This will cause the 759 * component's background color to be painted. 760 * <li> 761 * Installs the default caret and highlighter into the 762 * associated component. These properties are only set if their 763 * current value is either {@code null} or an instance of 764 * {@link UIResource}. 765 * <li> 766 * Attaches to the editor and model. If there is no 767 * model, a default one is created. 768 * <li> 769 * Creates the view factory and the view hierarchy used 770 * to represent the model. 771 * </ol> 772 * 773 * @param c the editor component 774 * @see ComponentUI#installUI 775 */ 776 public void installUI(JComponent c) { 777 if (c instanceof JTextComponent) { 778 editor = (JTextComponent) c; 779 780 // common case is background painted... this can 781 // easily be changed by subclasses or from outside 782 // of the component. 783 LookAndFeel.installProperty(editor, "opaque", Boolean.TRUE); 784 LookAndFeel.installProperty(editor, "autoscrolls", Boolean.TRUE); 785 786 // install defaults 787 installDefaults(); 788 installDefaults2(); 789 790 // attach to the model and editor 791 editor.addPropertyChangeListener(updateHandler); 792 Document doc = editor.getDocument(); 793 if (doc == null) { 794 // no model, create a default one. This will 795 // fire a notification to the updateHandler 796 // which takes care of the rest. 797 editor.setDocument(getEditorKit(editor).createDefaultDocument()); 798 } else { 799 doc.addDocumentListener(updateHandler); 800 modelChanged(); 801 } 802 803 // install keymap 804 installListeners(); 805 installKeyboardActions(); 806 807 LayoutManager oldLayout = editor.getLayout(); 808 if ((oldLayout == null) || (oldLayout instanceof UIResource)) { 809 // by default, use default LayoutManger implementation that 810 // will position the components associated with a View object. 811 editor.setLayout(updateHandler); 812 } 813 814 updateBackground(editor); 815 } else { 816 throw new Error("TextUI needs JTextComponent"); 817 } 818 } 819 820 /** 821 * Deinstalls the UI for a component. This removes the listeners, 822 * uninstalls the highlighter, removes views, and nulls out the keymap. 823 * 824 * @param c the editor component 825 * @see ComponentUI#uninstallUI 826 */ 827 public void uninstallUI(JComponent c) { 828 // detach from the model 829 editor.removePropertyChangeListener(updateHandler); 830 editor.getDocument().removeDocumentListener(updateHandler); 831 832 // view part 833 painted = false; 834 uninstallDefaults(); 835 rootView.setView(null); 836 c.removeAll(); 837 LayoutManager lm = c.getLayout(); 838 if (lm instanceof UIResource) { 839 c.setLayout(null); 840 } 841 842 // controller part 843 uninstallKeyboardActions(); 844 uninstallListeners(); 845 846 editor = null; 847 } 848 849 /** 850 * Superclass paints background in an uncontrollable way 851 * (i.e. one might want an image tiled into the background). 852 * To prevent this from happening twice, this method is 853 * reimplemented to simply paint. 854 * <p> 855 * <em>NOTE:</em> NOTE: Superclass is also not thread-safe in its 856 * rendering of the background, although that is not an issue with the 857 * default rendering. 858 */ 859 public void update(Graphics g, JComponent c) { 860 paint(g, c); 861 } 862 863 /** 864 * Paints the interface. This is routed to the 865 * paintSafely method under the guarantee that 866 * the model won't change from the view of this thread 867 * while it's rendering (if the associated model is 868 * derived from AbstractDocument). This enables the 869 * model to potentially be updated asynchronously. 870 * 871 * @param g the graphics context 872 * @param c the editor component 873 */ 874 public final void paint(Graphics g, JComponent c) { 875 if ((rootView.getViewCount() > 0) && (rootView.getView(0) != null)) { 876 Document doc = editor.getDocument(); 877 if (doc instanceof AbstractDocument) { 878 ((AbstractDocument)doc).readLock(); 879 } 880 try { 881 paintSafely(g); 882 } finally { 883 if (doc instanceof AbstractDocument) { 884 ((AbstractDocument)doc).readUnlock(); 885 } 886 } 887 } 888 } 889 890 /** 891 * Gets the preferred size for the editor component. If the component 892 * has been given a size prior to receiving this request, it will 893 * set the size of the view hierarchy to reflect the size of the component 894 * before requesting the preferred size of the view hierarchy. This 895 * allows formatted views to format to the current component size before 896 * answering the request. Other views don't care about currently formatted 897 * size and give the same answer either way. 898 * 899 * @param c the editor component 900 * @return the size 901 */ 902 public Dimension getPreferredSize(JComponent c) { 903 Document doc = editor.getDocument(); 904 Insets i = c.getInsets(); 905 Dimension d = c.getSize(); 906 907 if (doc instanceof AbstractDocument) { 908 ((AbstractDocument)doc).readLock(); 909 } 910 try { 911 if ((d.width > (i.left + i.right)) && (d.height > (i.top + i.bottom))) { 912 rootView.setSize(d.width - i.left - i.right, d.height - i.top - i.bottom); 913 } 914 else if (d.width == 0 && d.height == 0) { 915 // Probably haven't been layed out yet, force some sort of 916 // initial sizing. 917 rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE); 918 } 919 d.width = (int) Math.min((long) rootView.getPreferredSpan(View.X_AXIS) + 920 (long) i.left + (long) i.right, Integer.MAX_VALUE); 921 d.height = (int) Math.min((long) rootView.getPreferredSpan(View.Y_AXIS) + 922 (long) i.top + (long) i.bottom, Integer.MAX_VALUE); 923 } finally { 924 if (doc instanceof AbstractDocument) { 925 ((AbstractDocument)doc).readUnlock(); 926 } 927 } 928 return d; 929 } 930 931 /** 932 * Gets the minimum size for the editor component. 933 * 934 * @param c the editor component 935 * @return the size 936 */ 937 public Dimension getMinimumSize(JComponent c) { 938 Document doc = editor.getDocument(); 939 Insets i = c.getInsets(); 940 Dimension d = new Dimension(); 941 if (doc instanceof AbstractDocument) { 942 ((AbstractDocument)doc).readLock(); 943 } 944 try { 945 d.width = (int) rootView.getMinimumSpan(View.X_AXIS) + i.left + i.right; 946 d.height = (int) rootView.getMinimumSpan(View.Y_AXIS) + i.top + i.bottom; 947 } finally { 948 if (doc instanceof AbstractDocument) { 949 ((AbstractDocument)doc).readUnlock(); 950 } 951 } 952 return d; 953 } 954 955 /** 956 * Gets the maximum size for the editor component. 957 * 958 * @param c the editor component 959 * @return the size 960 */ 961 public Dimension getMaximumSize(JComponent c) { 962 Document doc = editor.getDocument(); 963 Insets i = c.getInsets(); 964 Dimension d = new Dimension(); 965 if (doc instanceof AbstractDocument) { 966 ((AbstractDocument)doc).readLock(); 967 } 968 try { 969 d.width = (int) Math.min((long) rootView.getMaximumSpan(View.X_AXIS) + 970 (long) i.left + (long) i.right, Integer.MAX_VALUE); 971 d.height = (int) Math.min((long) rootView.getMaximumSpan(View.Y_AXIS) + 972 (long) i.top + (long) i.bottom, Integer.MAX_VALUE); 973 } finally { 974 if (doc instanceof AbstractDocument) { 975 ((AbstractDocument)doc).readUnlock(); 976 } 977 } 978 return d; 979 } 980 981 // ---- TextUI methods ------------------------------------------- 982 983 984 /** 985 * Gets the allocation to give the root View. Due 986 * to an unfortunate set of historical events this 987 * method is inappropriately named. The Rectangle 988 * returned has nothing to do with visibility. 989 * The component must have a non-zero positive size for 990 * this translation to be computed. 991 * 992 * @return the bounding box for the root view 993 */ 994 protected Rectangle getVisibleEditorRect() { 995 Rectangle alloc = editor.getBounds(); 996 if ((alloc.width > 0) && (alloc.height > 0)) { 997 alloc.x = alloc.y = 0; 998 Insets insets = editor.getInsets(); 999 alloc.x += insets.left; 1000 alloc.y += insets.top; 1001 alloc.width -= insets.left + insets.right; 1002 alloc.height -= insets.top + insets.bottom; 1003 return alloc; 1004 } 1005 return null; 1006 } 1007 1008 /** 1009 * Converts the given location in the model to a place in 1010 * the view coordinate system. 1011 * The component must have a non-zero positive size for 1012 * this translation to be computed. 1013 * 1014 * @param tc the text component for which this UI is installed 1015 * @param pos the local location in the model to translate >= 0 1016 * @return the coordinates as a rectangle, null if the model is not painted 1017 * @exception BadLocationException if the given position does not 1018 * represent a valid location in the associated document 1019 * @see TextUI#modelToView 1020 */ 1021 public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException { 1022 return modelToView(tc, pos, Position.Bias.Forward); 1023 } 1024 1025 /** 1026 * Converts the given location in the model to a place in 1027 * the view coordinate system. 1028 * The component must have a non-zero positive size for 1029 * this translation to be computed. 1030 * 1031 * @param tc the text component for which this UI is installed 1032 * @param pos the local location in the model to translate >= 0 1033 * @return the coordinates as a rectangle, null if the model is not painted 1034 * @exception BadLocationException if the given position does not 1035 * represent a valid location in the associated document 1036 * @see TextUI#modelToView 1037 */ 1038 public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException { 1039 Document doc = editor.getDocument(); 1040 if (doc instanceof AbstractDocument) { 1041 ((AbstractDocument)doc).readLock(); 1042 } 1043 try { 1044 Rectangle alloc = getVisibleEditorRect(); 1045 if (alloc != null) { 1046 rootView.setSize(alloc.width, alloc.height); 1047 Shape s = rootView.modelToView(pos, alloc, bias); 1048 if (s != null) { 1049 return s.getBounds(); 1050 } 1051 } 1052 } finally { 1053 if (doc instanceof AbstractDocument) { 1054 ((AbstractDocument)doc).readUnlock(); 1055 } 1056 } 1057 return null; 1058 } 1059 1060 /** 1061 * Converts the given place in the view coordinate system 1062 * to the nearest representative location in the model. 1063 * The component must have a non-zero positive size for 1064 * this translation to be computed. 1065 * 1066 * @param tc the text component for which this UI is installed 1067 * @param pt the location in the view to translate. This 1068 * should be in the same coordinate system as the mouse events. 1069 * @return the offset from the start of the document >= 0, 1070 * -1 if not painted 1071 * @see TextUI#viewToModel 1072 */ 1073 public int viewToModel(JTextComponent tc, Point pt) { 1074 return viewToModel(tc, pt, discardBias); 1075 } 1076 1077 /** 1078 * Converts the given place in the view coordinate system 1079 * to the nearest representative location in the model. 1080 * The component must have a non-zero positive size for 1081 * this translation to be computed. 1082 * 1083 * @param tc the text component for which this UI is installed 1084 * @param pt the location in the view to translate. This 1085 * should be in the same coordinate system as the mouse events. 1086 * @return the offset from the start of the document >= 0, 1087 * -1 if the component doesn't yet have a positive size. 1088 * @see TextUI#viewToModel 1089 */ 1090 public int viewToModel(JTextComponent tc, Point pt, 1091 Position.Bias[] biasReturn) { 1092 int offs = -1; 1093 Document doc = editor.getDocument(); 1094 if (doc instanceof AbstractDocument) { 1095 ((AbstractDocument)doc).readLock(); 1096 } 1097 try { 1098 Rectangle alloc = getVisibleEditorRect(); 1099 if (alloc != null) { 1100 rootView.setSize(alloc.width, alloc.height); 1101 offs = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn); 1102 } 1103 } finally { 1104 if (doc instanceof AbstractDocument) { 1105 ((AbstractDocument)doc).readUnlock(); 1106 } 1107 } 1108 return offs; 1109 } 1110 1111 /** 1112 * {@inheritDoc} 1113 */ 1114 public int getNextVisualPositionFrom(JTextComponent t, int pos, 1115 Position.Bias b, int direction, Position.Bias[] biasRet) 1116 throws BadLocationException{ 1117 Document doc = editor.getDocument(); 1118 if (doc instanceof AbstractDocument) { 1119 ((AbstractDocument)doc).readLock(); 1120 } 1121 try { 1122 if (painted) { 1123 Rectangle alloc = getVisibleEditorRect(); 1124 if (alloc != null) { 1125 rootView.setSize(alloc.width, alloc.height); 1126 } 1127 return rootView.getNextVisualPositionFrom(pos, b, alloc, direction, 1128 biasRet); 1129 } 1130 } finally { 1131 if (doc instanceof AbstractDocument) { 1132 ((AbstractDocument)doc).readUnlock(); 1133 } 1134 } 1135 return -1; 1136 } 1137 1138 /** 1139 * Causes the portion of the view responsible for the 1140 * given part of the model to be repainted. Does nothing if 1141 * the view is not currently painted. 1142 * 1143 * @param tc the text component for which this UI is installed 1144 * @param p0 the beginning of the range >= 0 1145 * @param p1 the end of the range >= p0 1146 * @see TextUI#damageRange 1147 */ 1148 public void damageRange(JTextComponent tc, int p0, int p1) { 1149 damageRange(tc, p0, p1, Position.Bias.Forward, Position.Bias.Backward); 1150 } 1151 1152 /** 1153 * Causes the portion of the view responsible for the 1154 * given part of the model to be repainted. 1155 * 1156 * @param p0 the beginning of the range >= 0 1157 * @param p1 the end of the range >= p0 1158 */ 1159 public void damageRange(JTextComponent t, int p0, int p1, 1160 Position.Bias p0Bias, Position.Bias p1Bias) { 1161 if (painted) { 1162 Rectangle alloc = getVisibleEditorRect(); 1163 if (alloc != null) { 1164 Document doc = t.getDocument(); 1165 if (doc instanceof AbstractDocument) { 1166 ((AbstractDocument)doc).readLock(); 1167 } 1168 try { 1169 rootView.setSize(alloc.width, alloc.height); 1170 Shape toDamage = rootView.modelToView(p0, p0Bias, 1171 p1, p1Bias, alloc); 1172 Rectangle rect = (toDamage instanceof Rectangle) ? 1173 (Rectangle)toDamage : toDamage.getBounds(); 1174 editor.repaint(rect.x, rect.y, rect.width, rect.height); 1175 } catch (BadLocationException e) { 1176 } finally { 1177 if (doc instanceof AbstractDocument) { 1178 ((AbstractDocument)doc).readUnlock(); 1179 } 1180 } 1181 } 1182 } 1183 } 1184 1185 /** 1186 * Fetches the EditorKit for the UI. 1187 * 1188 * @param tc the text component for which this UI is installed 1189 * @return the editor capabilities 1190 * @see TextUI#getEditorKit 1191 */ 1192 public EditorKit getEditorKit(JTextComponent tc) { 1193 return defaultKit; 1194 } 1195 1196 /** 1197 * Fetches a View with the allocation of the associated 1198 * text component (i.e. the root of the hierarchy) that 1199 * can be traversed to determine how the model is being 1200 * represented spatially. 1201 * <p> 1202 * <font style="color: red;"><b>NOTE:</b>The View hierarchy can 1203 * be traversed from the root view, and other things 1204 * can be done as well. Things done in this way cannot 1205 * be protected like simple method calls through the TextUI. 1206 * Therefore, proper operation in the presence of concurrency 1207 * must be arranged by any logic that calls this method! 1208 * </font> 1209 * 1210 * @param tc the text component for which this UI is installed 1211 * @return the view 1212 * @see TextUI#getRootView 1213 */ 1214 public View getRootView(JTextComponent tc) { 1215 return rootView; 1216 } 1217 1218 1219 /** 1220 * Returns the string to be used as the tooltip at the passed in location. 1221 * This forwards the method onto the root View. 1222 * 1223 * @see javax.swing.text.JTextComponent#getToolTipText 1224 * @see javax.swing.text.View#getToolTipText 1225 * @since 1.4 1226 */ 1227 public String getToolTipText(JTextComponent t, Point pt) { 1228 if (!painted) { 1229 return null; 1230 } 1231 Document doc = editor.getDocument(); 1232 String tt = null; 1233 Rectangle alloc = getVisibleEditorRect(); 1234 1235 if (alloc != null) { 1236 if (doc instanceof AbstractDocument) { 1237 ((AbstractDocument)doc).readLock(); 1238 } 1239 try { 1240 tt = rootView.getToolTipText(pt.x, pt.y, alloc); 1241 } finally { 1242 if (doc instanceof AbstractDocument) { 1243 ((AbstractDocument)doc).readUnlock(); 1244 } 1245 } 1246 } 1247 return tt; 1248 } 1249 1250 // --- ViewFactory methods ------------------------------ 1251 1252 /** 1253 * Creates a view for an element. 1254 * If a subclass wishes to directly implement the factory 1255 * producing the view(s), it should reimplement this 1256 * method. By default it simply returns null indicating 1257 * it is unable to represent the element. 1258 * 1259 * @param elem the element 1260 * @return the view 1261 */ 1262 public View create(Element elem) { 1263 return null; 1264 } 1265 1266 /** 1267 * Creates a view for an element. 1268 * If a subclass wishes to directly implement the factory 1269 * producing the view(s), it should reimplement this 1270 * method. By default it simply returns null indicating 1271 * it is unable to represent the part of the element. 1272 * 1273 * @param elem the element 1274 * @param p0 the starting offset >= 0 1275 * @param p1 the ending offset >= p0 1276 * @return the view 1277 */ 1278 public View create(Element elem, int p0, int p1) { 1279 return null; 1280 } 1281 1282 public static class BasicCaret extends DefaultCaret implements UIResource {} 1283 1284 public static class BasicHighlighter extends DefaultHighlighter implements UIResource {} 1285 1286 static class BasicCursor extends Cursor implements UIResource { 1287 BasicCursor(int type) { 1288 super(type); 1289 } 1290 1291 BasicCursor(String name) { 1292 super(name); 1293 } 1294 } 1295 1296 private static BasicCursor textCursor = new BasicCursor(Cursor.TEXT_CURSOR); 1297 // ----- member variables --------------------------------------- 1298 1299 private static final EditorKit defaultKit = new DefaultEditorKit(); 1300 transient JTextComponent editor; 1301 transient boolean painted; 1302 transient RootView rootView = new RootView(); 1303 transient UpdateHandler updateHandler = new UpdateHandler(); 1304 private static final TransferHandler defaultTransferHandler = new TextTransferHandler(); 1305 private final DragListener dragListener = getDragListener(); 1306 private static final Position.Bias[] discardBias = new Position.Bias[1]; 1307 private DefaultCaret dropCaret; 1308 1309 /** 1310 * Root view that acts as a gateway between the component 1311 * and the View hierarchy. 1312 */ 1313 class RootView extends View { 1314 1315 RootView() { 1316 super(null); 1317 } 1318 1319 void setView(View v) { 1320 View oldView = view; 1321 view = null; 1322 if (oldView != null) { 1323 // get rid of back reference so that the old 1324 // hierarchy can be garbage collected. 1325 oldView.setParent(null); 1326 } 1327 if (v != null) { 1328 v.setParent(this); 1329 } 1330 view = v; 1331 } 1332 1333 /** 1334 * Fetches the attributes to use when rendering. At the root 1335 * level there are no attributes. If an attribute is resolved 1336 * up the view hierarchy this is the end of the line. 1337 */ 1338 public AttributeSet getAttributes() { 1339 return null; 1340 } 1341 1342 /** 1343 * Determines the preferred span for this view along an axis. 1344 * 1345 * @param axis may be either X_AXIS or Y_AXIS 1346 * @return the span the view would like to be rendered into. 1347 * Typically the view is told to render into the span 1348 * that is returned, although there is no guarantee. 1349 * The parent may choose to resize or break the view. 1350 */ 1351 public float getPreferredSpan(int axis) { 1352 if (view != null) { 1353 return view.getPreferredSpan(axis); 1354 } 1355 return 10; 1356 } 1357 1358 /** 1359 * Determines the minimum span for this view along an axis. 1360 * 1361 * @param axis may be either X_AXIS or Y_AXIS 1362 * @return the span the view would like to be rendered into. 1363 * Typically the view is told to render into the span 1364 * that is returned, although there is no guarantee. 1365 * The parent may choose to resize or break the view. 1366 */ 1367 public float getMinimumSpan(int axis) { 1368 if (view != null) { 1369 return view.getMinimumSpan(axis); 1370 } 1371 return 10; 1372 } 1373 1374 /** 1375 * Determines the maximum span for this view along an axis. 1376 * 1377 * @param axis may be either X_AXIS or Y_AXIS 1378 * @return the span the view would like to be rendered into. 1379 * Typically the view is told to render into the span 1380 * that is returned, although there is no guarantee. 1381 * The parent may choose to resize or break the view. 1382 */ 1383 public float getMaximumSpan(int axis) { 1384 return Integer.MAX_VALUE; 1385 } 1386 1387 /** 1388 * Specifies that a preference has changed. 1389 * Child views can call this on the parent to indicate that 1390 * the preference has changed. The root view routes this to 1391 * invalidate on the hosting component. 1392 * <p> 1393 * This can be called on a different thread from the 1394 * event dispatching thread and is basically unsafe to 1395 * propagate into the component. To make this safe, 1396 * the operation is transferred over to the event dispatching 1397 * thread for completion. It is a design goal that all view 1398 * methods be safe to call without concern for concurrency, 1399 * and this behavior helps make that true. 1400 * 1401 * @param child the child view 1402 * @param width true if the width preference has changed 1403 * @param height true if the height preference has changed 1404 */ 1405 public void preferenceChanged(View child, boolean width, boolean height) { 1406 editor.revalidate(); 1407 } 1408 1409 /** 1410 * Determines the desired alignment for this view along an axis. 1411 * 1412 * @param axis may be either X_AXIS or Y_AXIS 1413 * @return the desired alignment, where 0.0 indicates the origin 1414 * and 1.0 the full span away from the origin 1415 */ 1416 public float getAlignment(int axis) { 1417 if (view != null) { 1418 return view.getAlignment(axis); 1419 } 1420 return 0; 1421 } 1422 1423 /** 1424 * Renders the view. 1425 * 1426 * @param g the graphics context 1427 * @param allocation the region to render into 1428 */ 1429 public void paint(Graphics g, Shape allocation) { 1430 if (view != null) { 1431 Rectangle alloc = (allocation instanceof Rectangle) ? 1432 (Rectangle)allocation : allocation.getBounds(); 1433 setSize(alloc.width, alloc.height); 1434 view.paint(g, allocation); 1435 } 1436 } 1437 1438 /** 1439 * Sets the view parent. 1440 * 1441 * @param parent the parent view 1442 */ 1443 public void setParent(View parent) { 1444 throw new Error("Can't set parent on root view"); 1445 } 1446 1447 /** 1448 * Returns the number of views in this view. Since 1449 * this view simply wraps the root of the view hierarchy 1450 * it has exactly one child. 1451 * 1452 * @return the number of views 1453 * @see #getView 1454 */ 1455 public int getViewCount() { 1456 return 1; 1457 } 1458 1459 /** 1460 * Gets the n-th view in this container. 1461 * 1462 * @param n the number of the view to get 1463 * @return the view 1464 */ 1465 public View getView(int n) { 1466 return view; 1467 } 1468 1469 /** 1470 * Returns the child view index representing the given position in 1471 * the model. This is implemented to return the index of the only 1472 * child. 1473 * 1474 * @param pos the position >= 0 1475 * @return index of the view representing the given position, or 1476 * -1 if no view represents that position 1477 * @since 1.3 1478 */ 1479 public int getViewIndex(int pos, Position.Bias b) { 1480 return 0; 1481 } 1482 1483 /** 1484 * Fetches the allocation for the given child view. 1485 * This enables finding out where various views 1486 * are located, without assuming the views store 1487 * their location. This returns the given allocation 1488 * since this view simply acts as a gateway between 1489 * the view hierarchy and the associated component. 1490 * 1491 * @param index the index of the child 1492 * @param a the allocation to this view. 1493 * @return the allocation to the child 1494 */ 1495 public Shape getChildAllocation(int index, Shape a) { 1496 return a; 1497 } 1498 1499 /** 1500 * Provides a mapping from the document model coordinate space 1501 * to the coordinate space of the view mapped to it. 1502 * 1503 * @param pos the position to convert 1504 * @param a the allocated region to render into 1505 * @return the bounding box of the given position 1506 */ 1507 public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { 1508 if (view != null) { 1509 return view.modelToView(pos, a, b); 1510 } 1511 return null; 1512 } 1513 1514 /** 1515 * Provides a mapping from the document model coordinate space 1516 * to the coordinate space of the view mapped to it. 1517 * 1518 * @param p0 the position to convert >= 0 1519 * @param b0 the bias toward the previous character or the 1520 * next character represented by p0, in case the 1521 * position is a boundary of two views. 1522 * @param p1 the position to convert >= 0 1523 * @param b1 the bias toward the previous character or the 1524 * next character represented by p1, in case the 1525 * position is a boundary of two views. 1526 * @param a the allocated region to render into 1527 * @return the bounding box of the given position is returned 1528 * @exception BadLocationException if the given position does 1529 * not represent a valid location in the associated document 1530 * @exception IllegalArgumentException for an invalid bias argument 1531 * @see View#viewToModel 1532 */ 1533 public Shape modelToView(int p0, Position.Bias b0, int p1, Position.Bias b1, Shape a) throws BadLocationException { 1534 if (view != null) { 1535 return view.modelToView(p0, b0, p1, b1, a); 1536 } 1537 return null; 1538 } 1539 1540 /** 1541 * Provides a mapping from the view coordinate space to the logical 1542 * coordinate space of the model. 1543 * 1544 * @param x x coordinate of the view location to convert 1545 * @param y y coordinate of the view location to convert 1546 * @param a the allocated region to render into 1547 * @return the location within the model that best represents the 1548 * given point in the view 1549 */ 1550 public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { 1551 if (view != null) { 1552 int retValue = view.viewToModel(x, y, a, bias); 1553 return retValue; 1554 } 1555 return -1; 1556 } 1557 1558 /** 1559 * Provides a way to determine the next visually represented model 1560 * location that one might place a caret. Some views may not be visible, 1561 * they might not be in the same order found in the model, or they just 1562 * might not allow access to some of the locations in the model. 1563 * This method enables specifying a position to convert 1564 * within the range of >=0. If the value is -1, a position 1565 * will be calculated automatically. If the value < -1, 1566 * the {@code BadLocationException} will be thrown. 1567 * 1568 * @param pos the position to convert >= 0 1569 * @param a the allocated region to render into 1570 * @param direction the direction from the current position that can 1571 * be thought of as the arrow keys typically found on a keyboard. 1572 * This may be SwingConstants.WEST, SwingConstants.EAST, 1573 * SwingConstants.NORTH, or SwingConstants.SOUTH. 1574 * @return the location within the model that best represents the next 1575 * location visual position. 1576 * @exception BadLocationException the given position is not a valid 1577 * position within the document 1578 * @exception IllegalArgumentException for an invalid direction 1579 */ 1580 public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a, 1581 int direction, 1582 Position.Bias[] biasRet) 1583 throws BadLocationException { 1584 if (pos < -1) { 1585 throw new BadLocationException("invalid position", pos); 1586 } 1587 if( view != null ) { 1588 int nextPos = view.getNextVisualPositionFrom(pos, b, a, 1589 direction, biasRet); 1590 if(nextPos != -1) { 1591 pos = nextPos; 1592 } 1593 else { 1594 biasRet[0] = b; 1595 } 1596 } 1597 return pos; 1598 } 1599 1600 /** 1601 * Gives notification that something was inserted into the document 1602 * in a location that this view is responsible for. 1603 * 1604 * @param e the change information from the associated document 1605 * @param a the current allocation of the view 1606 * @param f the factory to use to rebuild if the view has children 1607 */ 1608 public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) { 1609 if (view != null) { 1610 view.insertUpdate(e, a, f); 1611 } 1612 } 1613 1614 /** 1615 * Gives notification that something was removed from the document 1616 * in a location that this view is responsible for. 1617 * 1618 * @param e the change information from the associated document 1619 * @param a the current allocation of the view 1620 * @param f the factory to use to rebuild if the view has children 1621 */ 1622 public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) { 1623 if (view != null) { 1624 view.removeUpdate(e, a, f); 1625 } 1626 } 1627 1628 /** 1629 * Gives notification from the document that attributes were changed 1630 * in a location that this view is responsible for. 1631 * 1632 * @param e the change information from the associated document 1633 * @param a the current allocation of the view 1634 * @param f the factory to use to rebuild if the view has children 1635 */ 1636 public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) { 1637 if (view != null) { 1638 view.changedUpdate(e, a, f); 1639 } 1640 } 1641 1642 /** 1643 * Returns the document model underlying the view. 1644 * 1645 * @return the model 1646 */ 1647 public Document getDocument() { 1648 return editor.getDocument(); 1649 } 1650 1651 /** 1652 * Returns the starting offset into the model for this view. 1653 * 1654 * @return the starting offset 1655 */ 1656 public int getStartOffset() { 1657 if (view != null) { 1658 return view.getStartOffset(); 1659 } 1660 return getElement().getStartOffset(); 1661 } 1662 1663 /** 1664 * Returns the ending offset into the model for this view. 1665 * 1666 * @return the ending offset 1667 */ 1668 public int getEndOffset() { 1669 if (view != null) { 1670 return view.getEndOffset(); 1671 } 1672 return getElement().getEndOffset(); 1673 } 1674 1675 /** 1676 * Gets the element that this view is mapped to. 1677 * 1678 * @return the view 1679 */ 1680 public Element getElement() { 1681 if (view != null) { 1682 return view.getElement(); 1683 } 1684 return editor.getDocument().getDefaultRootElement(); 1685 } 1686 1687 /** 1688 * Breaks this view on the given axis at the given length. 1689 * 1690 * @param axis may be either X_AXIS or Y_AXIS 1691 * @param len specifies where a break is desired in the span 1692 * @param the current allocation of the view 1693 * @return the fragment of the view that represents the given span 1694 * if the view can be broken, otherwise null 1695 */ 1696 public View breakView(int axis, float len, Shape a) { 1697 throw new Error("Can't break root view"); 1698 } 1699 1700 /** 1701 * Determines the resizability of the view along the 1702 * given axis. A value of 0 or less is not resizable. 1703 * 1704 * @param axis may be either X_AXIS or Y_AXIS 1705 * @return the weight 1706 */ 1707 public int getResizeWeight(int axis) { 1708 if (view != null) { 1709 return view.getResizeWeight(axis); 1710 } 1711 return 0; 1712 } 1713 1714 /** 1715 * Sets the view size. 1716 * 1717 * @param width the width 1718 * @param height the height 1719 */ 1720 public void setSize(float width, float height) { 1721 if (view != null) { 1722 view.setSize(width, height); 1723 } 1724 } 1725 1726 /** 1727 * Fetches the container hosting the view. This is useful for 1728 * things like scheduling a repaint, finding out the host 1729 * components font, etc. The default implementation 1730 * of this is to forward the query to the parent view. 1731 * 1732 * @return the container 1733 */ 1734 public Container getContainer() { 1735 return editor; 1736 } 1737 1738 /** 1739 * Fetches the factory to be used for building the 1740 * various view fragments that make up the view that 1741 * represents the model. This is what determines 1742 * how the model will be represented. This is implemented 1743 * to fetch the factory provided by the associated 1744 * EditorKit unless that is null, in which case this 1745 * simply returns the BasicTextUI itself which allows 1746 * subclasses to implement a simple factory directly without 1747 * creating extra objects. 1748 * 1749 * @return the factory 1750 */ 1751 public ViewFactory getViewFactory() { 1752 EditorKit kit = getEditorKit(editor); 1753 ViewFactory f = kit.getViewFactory(); 1754 if (f != null) { 1755 return f; 1756 } 1757 return BasicTextUI.this; 1758 } 1759 1760 private View view; 1761 1762 } 1763 1764 /** 1765 * Handles updates from various places. If the model is changed, 1766 * this class unregisters as a listener to the old model and 1767 * registers with the new model. If the document model changes, 1768 * the change is forwarded to the root view. If the focus 1769 * accelerator changes, a new keystroke is registered to request 1770 * focus. 1771 */ 1772 class UpdateHandler implements PropertyChangeListener, DocumentListener, LayoutManager2, UIResource { 1773 1774 // --- PropertyChangeListener methods ----------------------- 1775 1776 /** 1777 * This method gets called when a bound property is changed. 1778 * We are looking for document changes on the editor. 1779 */ 1780 public final void propertyChange(PropertyChangeEvent evt) { 1781 Object oldValue = evt.getOldValue(); 1782 Object newValue = evt.getNewValue(); 1783 String propertyName = evt.getPropertyName(); 1784 if ((oldValue instanceof Document) || (newValue instanceof Document)) { 1785 if (oldValue != null) { 1786 ((Document)oldValue).removeDocumentListener(this); 1787 i18nView = false; 1788 } 1789 if (newValue != null) { 1790 ((Document)newValue).addDocumentListener(this); 1791 if ("document" == propertyName) { 1792 setView(null); 1793 BasicTextUI.this.propertyChange(evt); 1794 modelChanged(); 1795 return; 1796 } 1797 } 1798 modelChanged(); 1799 } 1800 if ("focusAccelerator" == propertyName) { 1801 updateFocusAcceleratorBinding(true); 1802 } else if ("componentOrientation" == propertyName) { 1803 // Changes in ComponentOrientation require the views to be 1804 // rebuilt. 1805 modelChanged(); 1806 } else if ("font" == propertyName) { 1807 modelChanged(); 1808 } else if ("dropLocation" == propertyName) { 1809 dropIndexChanged(); 1810 } else if ("editable" == propertyName) { 1811 updateCursor(); 1812 modelChanged(); 1813 } 1814 BasicTextUI.this.propertyChange(evt); 1815 } 1816 1817 private void dropIndexChanged() { 1818 if (editor.getDropMode() == DropMode.USE_SELECTION) { 1819 return; 1820 } 1821 1822 JTextComponent.DropLocation dropLocation = editor.getDropLocation(); 1823 1824 if (dropLocation == null) { 1825 if (dropCaret != null) { 1826 dropCaret.deinstall(editor); 1827 editor.repaint(dropCaret); 1828 dropCaret = null; 1829 } 1830 } else { 1831 if (dropCaret == null) { 1832 dropCaret = new BasicCaret(); 1833 dropCaret.install(editor); 1834 dropCaret.setVisible(true); 1835 } 1836 1837 dropCaret.setDot(dropLocation.getIndex(), 1838 dropLocation.getBias()); 1839 } 1840 } 1841 1842 // --- DocumentListener methods ----------------------- 1843 1844 /** 1845 * The insert notification. Gets sent to the root of the view structure 1846 * that represents the portion of the model being represented by the 1847 * editor. The factory is added as an argument to the update so that 1848 * the views can update themselves in a dynamic (not hardcoded) way. 1849 * 1850 * @param e The change notification from the currently associated 1851 * document. 1852 * @see DocumentListener#insertUpdate 1853 */ 1854 public final void insertUpdate(DocumentEvent e) { 1855 Document doc = e.getDocument(); 1856 Object o = doc.getProperty("i18n"); 1857 if (o instanceof Boolean) { 1858 Boolean i18nFlag = (Boolean) o; 1859 if (i18nFlag.booleanValue() != i18nView) { 1860 // i18n flag changed, rebuild the view 1861 i18nView = i18nFlag.booleanValue(); 1862 modelChanged(); 1863 return; 1864 } 1865 } 1866 1867 // normal insert update 1868 Rectangle alloc = (painted) ? getVisibleEditorRect() : null; 1869 rootView.insertUpdate(e, alloc, rootView.getViewFactory()); 1870 } 1871 1872 /** 1873 * The remove notification. Gets sent to the root of the view structure 1874 * that represents the portion of the model being represented by the 1875 * editor. The factory is added as an argument to the update so that 1876 * the views can update themselves in a dynamic (not hardcoded) way. 1877 * 1878 * @param e The change notification from the currently associated 1879 * document. 1880 * @see DocumentListener#removeUpdate 1881 */ 1882 public final void removeUpdate(DocumentEvent e) { 1883 Rectangle alloc = (painted) ? getVisibleEditorRect() : null; 1884 rootView.removeUpdate(e, alloc, rootView.getViewFactory()); 1885 } 1886 1887 /** 1888 * The change notification. Gets sent to the root of the view structure 1889 * that represents the portion of the model being represented by the 1890 * editor. The factory is added as an argument to the update so that 1891 * the views can update themselves in a dynamic (not hardcoded) way. 1892 * 1893 * @param e The change notification from the currently associated 1894 * document. 1895 * @see DocumentListener#changedUpdate(DocumentEvent) 1896 */ 1897 public final void changedUpdate(DocumentEvent e) { 1898 Rectangle alloc = (painted) ? getVisibleEditorRect() : null; 1899 rootView.changedUpdate(e, alloc, rootView.getViewFactory()); 1900 } 1901 1902 // --- LayoutManager2 methods -------------------------------- 1903 1904 /** 1905 * Adds the specified component with the specified name to 1906 * the layout. 1907 * @param name the component name 1908 * @param comp the component to be added 1909 */ 1910 public void addLayoutComponent(String name, Component comp) { 1911 // not supported 1912 } 1913 1914 /** 1915 * Removes the specified component from the layout. 1916 * @param comp the component to be removed 1917 */ 1918 public void removeLayoutComponent(Component comp) { 1919 if (constraints != null) { 1920 // remove the constraint record 1921 constraints.remove(comp); 1922 } 1923 } 1924 1925 /** 1926 * Calculates the preferred size dimensions for the specified 1927 * panel given the components in the specified parent container. 1928 * @param parent the component to be laid out 1929 * 1930 * @see #minimumLayoutSize 1931 */ 1932 public Dimension preferredLayoutSize(Container parent) { 1933 // should not be called (JComponent uses UI instead) 1934 return null; 1935 } 1936 1937 /** 1938 * Calculates the minimum size dimensions for the specified 1939 * panel given the components in the specified parent container. 1940 * @param parent the component to be laid out 1941 * @see #preferredLayoutSize 1942 */ 1943 public Dimension minimumLayoutSize(Container parent) { 1944 // should not be called (JComponent uses UI instead) 1945 return null; 1946 } 1947 1948 /** 1949 * Lays out the container in the specified panel. This is 1950 * implemented to position all components that were added 1951 * with a View object as a constraint. The current allocation 1952 * of the associated View is used as the location of the 1953 * component. 1954 * <p> 1955 * A read-lock is acquired on the document to prevent the 1956 * view tree from being modified while the layout process 1957 * is active. 1958 * 1959 * @param parent the component which needs to be laid out 1960 */ 1961 public void layoutContainer(Container parent) { 1962 if ((constraints != null) && (! constraints.isEmpty())) { 1963 Rectangle alloc = getVisibleEditorRect(); 1964 if (alloc != null) { 1965 Document doc = editor.getDocument(); 1966 if (doc instanceof AbstractDocument) { 1967 ((AbstractDocument)doc).readLock(); 1968 } 1969 try { 1970 rootView.setSize(alloc.width, alloc.height); 1971 Enumeration<Component> components = constraints.keys(); 1972 while (components.hasMoreElements()) { 1973 Component comp = components.nextElement(); 1974 View v = (View) constraints.get(comp); 1975 Shape ca = calculateViewPosition(alloc, v); 1976 if (ca != null) { 1977 Rectangle compAlloc = (ca instanceof Rectangle) ? 1978 (Rectangle) ca : ca.getBounds(); 1979 comp.setBounds(compAlloc); 1980 } 1981 } 1982 } finally { 1983 if (doc instanceof AbstractDocument) { 1984 ((AbstractDocument)doc).readUnlock(); 1985 } 1986 } 1987 } 1988 } 1989 } 1990 1991 /** 1992 * Find the Shape representing the given view. 1993 */ 1994 Shape calculateViewPosition(Shape alloc, View v) { 1995 int pos = v.getStartOffset(); 1996 View child = null; 1997 for (View parent = rootView; (parent != null) && (parent != v); parent = child) { 1998 int index = parent.getViewIndex(pos, Position.Bias.Forward); 1999 alloc = parent.getChildAllocation(index, alloc); 2000 child = parent.getView(index); 2001 } 2002 return (child != null) ? alloc : null; 2003 } 2004 2005 /** 2006 * Adds the specified component to the layout, using the specified 2007 * constraint object. We only store those components that were added 2008 * with a constraint that is of type View. 2009 * 2010 * @param comp the component to be added 2011 * @param constraint where/how the component is added to the layout. 2012 */ 2013 public void addLayoutComponent(Component comp, Object constraint) { 2014 if (constraint instanceof View) { 2015 if (constraints == null) { 2016 constraints = new Hashtable<Component, Object>(7); 2017 } 2018 constraints.put(comp, constraint); 2019 } 2020 } 2021 2022 /** 2023 * Returns the maximum size of this component. 2024 * @see java.awt.Component#getMinimumSize() 2025 * @see java.awt.Component#getPreferredSize() 2026 * @see LayoutManager 2027 */ 2028 public Dimension maximumLayoutSize(Container target) { 2029 // should not be called (JComponent uses UI instead) 2030 return null; 2031 } 2032 2033 /** 2034 * Returns the alignment along the x axis. This specifies how 2035 * the component would like to be aligned relative to other 2036 * components. The value should be a number between 0 and 1 2037 * where 0 represents alignment along the origin, 1 is aligned 2038 * the furthest away from the origin, 0.5 is centered, etc. 2039 */ 2040 public float getLayoutAlignmentX(Container target) { 2041 return 0.5f; 2042 } 2043 2044 /** 2045 * Returns the alignment along the y axis. This specifies how 2046 * the component would like to be aligned relative to other 2047 * components. The value should be a number between 0 and 1 2048 * where 0 represents alignment along the origin, 1 is aligned 2049 * the furthest away from the origin, 0.5 is centered, etc. 2050 */ 2051 public float getLayoutAlignmentY(Container target) { 2052 return 0.5f; 2053 } 2054 2055 /** 2056 * Invalidates the layout, indicating that if the layout manager 2057 * has cached information it should be discarded. 2058 */ 2059 public void invalidateLayout(Container target) { 2060 } 2061 2062 /** 2063 * The "layout constraints" for the LayoutManager2 implementation. 2064 * These are View objects for those components that are represented 2065 * by a View in the View tree. 2066 */ 2067 private Hashtable<Component, Object> constraints; 2068 2069 private boolean i18nView = false; 2070 } 2071 2072 /** 2073 * Wrapper for text actions to return isEnabled false in case editor is non editable 2074 */ 2075 class TextActionWrapper extends TextAction { 2076 public TextActionWrapper(TextAction action) { 2077 super((String)action.getValue(Action.NAME)); 2078 this.action = action; 2079 } 2080 /** 2081 * The operation to perform when this action is triggered. 2082 * 2083 * @param e the action event 2084 */ 2085 public void actionPerformed(ActionEvent e) { 2086 action.actionPerformed(e); 2087 } 2088 public boolean isEnabled() { 2089 return (editor == null || editor.isEditable()) ? action.isEnabled() : false; 2090 } 2091 TextAction action = null; 2092 } 2093 2094 2095 /** 2096 * Registered in the ActionMap. 2097 */ 2098 class FocusAction extends AbstractAction { 2099 2100 public void actionPerformed(ActionEvent e) { 2101 editor.requestFocus(); 2102 } 2103 2104 public boolean isEnabled() { 2105 return editor.isEditable(); 2106 } 2107 } 2108 2109 private static DragListener getDragListener() { 2110 synchronized(DragListener.class) { 2111 DragListener listener = 2112 (DragListener)AppContext.getAppContext(). 2113 get(DragListener.class); 2114 2115 if (listener == null) { 2116 listener = new DragListener(); 2117 AppContext.getAppContext().put(DragListener.class, listener); 2118 } 2119 2120 return listener; 2121 } 2122 } 2123 2124 /** 2125 * Listens for mouse events for the purposes of detecting drag gestures. 2126 * BasicTextUI will maintain one of these per AppContext. 2127 */ 2128 static class DragListener extends MouseInputAdapter 2129 implements BeforeDrag { 2130 2131 private boolean dragStarted; 2132 2133 public void dragStarting(MouseEvent me) { 2134 dragStarted = true; 2135 } 2136 2137 public void mousePressed(MouseEvent e) { 2138 JTextComponent c = (JTextComponent)e.getSource(); 2139 if (c.getDragEnabled()) { 2140 dragStarted = false; 2141 if (isDragPossible(e) && DragRecognitionSupport.mousePressed(e)) { 2142 e.consume(); 2143 } 2144 } 2145 } 2146 2147 public void mouseReleased(MouseEvent e) { 2148 JTextComponent c = (JTextComponent)e.getSource(); 2149 if (c.getDragEnabled()) { 2150 if (dragStarted) { 2151 e.consume(); 2152 } 2153 2154 DragRecognitionSupport.mouseReleased(e); 2155 } 2156 } 2157 2158 public void mouseDragged(MouseEvent e) { 2159 JTextComponent c = (JTextComponent)e.getSource(); 2160 if (c.getDragEnabled()) { 2161 if (dragStarted || DragRecognitionSupport.mouseDragged(e, this)) { 2162 e.consume(); 2163 } 2164 } 2165 } 2166 2167 /** 2168 * Determines if the following are true: 2169 * <ul> 2170 * <li>the component is enabled 2171 * <li>the press event is located over a selection 2172 * </ul> 2173 */ 2174 protected boolean isDragPossible(MouseEvent e) { 2175 JTextComponent c = (JTextComponent)e.getSource(); 2176 if (c.isEnabled()) { 2177 Caret caret = c.getCaret(); 2178 int dot = caret.getDot(); 2179 int mark = caret.getMark(); 2180 if (dot != mark) { 2181 Point p = new Point(e.getX(), e.getY()); 2182 int pos = c.viewToModel(p); 2183 2184 int p0 = Math.min(dot, mark); 2185 int p1 = Math.max(dot, mark); 2186 if ((pos >= p0) && (pos < p1)) { 2187 return true; 2188 } 2189 } 2190 } 2191 return false; 2192 } 2193 } 2194 2195 static class TextTransferHandler extends TransferHandler implements UIResource { 2196 2197 private JTextComponent exportComp; 2198 private boolean shouldRemove; 2199 private int p0; 2200 private int p1; 2201 2202 /** 2203 * Whether or not this is a drop using 2204 * <code>DropMode.INSERT</code>. 2205 */ 2206 private boolean modeBetween = false; 2207 2208 /** 2209 * Whether or not this is a drop. 2210 */ 2211 private boolean isDrop = false; 2212 2213 /** 2214 * The drop action. 2215 */ 2216 private int dropAction = MOVE; 2217 2218 /** 2219 * The drop bias. 2220 */ 2221 private Position.Bias dropBias; 2222 2223 /** 2224 * Try to find a flavor that can be used to import a Transferable. 2225 * The set of usable flavors are tried in the following order: 2226 * <ol> 2227 * <li>First, an attempt is made to find a flavor matching the content type 2228 * of the EditorKit for the component. 2229 * <li>Second, an attempt to find a text/plain flavor is made. 2230 * <li>Third, an attempt to find a flavor representing a String reference 2231 * in the same VM is made. 2232 * <li>Lastly, DataFlavor.stringFlavor is searched for. 2233 * </ol> 2234 */ 2235 protected DataFlavor getImportFlavor(DataFlavor[] flavors, JTextComponent c) { 2236 DataFlavor plainFlavor = null; 2237 DataFlavor refFlavor = null; 2238 DataFlavor stringFlavor = null; 2239 2240 if (c instanceof JEditorPane) { 2241 for (int i = 0; i < flavors.length; i++) { 2242 String mime = flavors[i].getMimeType(); 2243 if (mime.startsWith(((JEditorPane)c).getEditorKit().getContentType())) { 2244 return flavors[i]; 2245 } else if (plainFlavor == null && mime.startsWith("text/plain")) { 2246 plainFlavor = flavors[i]; 2247 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref") 2248 && flavors[i].getRepresentationClass() == java.lang.String.class) { 2249 refFlavor = flavors[i]; 2250 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) { 2251 stringFlavor = flavors[i]; 2252 } 2253 } 2254 if (plainFlavor != null) { 2255 return plainFlavor; 2256 } else if (refFlavor != null) { 2257 return refFlavor; 2258 } else if (stringFlavor != null) { 2259 return stringFlavor; 2260 } 2261 return null; 2262 } 2263 2264 2265 for (int i = 0; i < flavors.length; i++) { 2266 String mime = flavors[i].getMimeType(); 2267 if (mime.startsWith("text/plain")) { 2268 return flavors[i]; 2269 } else if (refFlavor == null && mime.startsWith("application/x-java-jvm-local-objectref") 2270 && flavors[i].getRepresentationClass() == java.lang.String.class) { 2271 refFlavor = flavors[i]; 2272 } else if (stringFlavor == null && flavors[i].equals(DataFlavor.stringFlavor)) { 2273 stringFlavor = flavors[i]; 2274 } 2275 } 2276 if (refFlavor != null) { 2277 return refFlavor; 2278 } else if (stringFlavor != null) { 2279 return stringFlavor; 2280 } 2281 return null; 2282 } 2283 2284 /** 2285 * Import the given stream data into the text component. 2286 */ 2287 protected void handleReaderImport(Reader in, JTextComponent c, boolean useRead) 2288 throws BadLocationException, IOException { 2289 if (useRead) { 2290 int startPosition = c.getSelectionStart(); 2291 int endPosition = c.getSelectionEnd(); 2292 int length = endPosition - startPosition; 2293 EditorKit kit = c.getUI().getEditorKit(c); 2294 Document doc = c.getDocument(); 2295 if (length > 0) { 2296 doc.remove(startPosition, length); 2297 } 2298 kit.read(in, doc, startPosition); 2299 } else { 2300 char[] buff = new char[1024]; 2301 int nch; 2302 boolean lastWasCR = false; 2303 int last; 2304 StringBuffer sbuff = null; 2305 2306 // Read in a block at a time, mapping \r\n to \n, as well as single 2307 // \r to \n. 2308 while ((nch = in.read(buff, 0, buff.length)) != -1) { 2309 if (sbuff == null) { 2310 sbuff = new StringBuffer(nch); 2311 } 2312 last = 0; 2313 for(int counter = 0; counter < nch; counter++) { 2314 switch(buff[counter]) { 2315 case '\r': 2316 if (lastWasCR) { 2317 if (counter == 0) { 2318 sbuff.append('\n'); 2319 } else { 2320 buff[counter - 1] = '\n'; 2321 } 2322 } else { 2323 lastWasCR = true; 2324 } 2325 break; 2326 case '\n': 2327 if (lastWasCR) { 2328 if (counter > (last + 1)) { 2329 sbuff.append(buff, last, counter - last - 1); 2330 } 2331 // else nothing to do, can skip \r, next write will 2332 // write \n 2333 lastWasCR = false; 2334 last = counter; 2335 } 2336 break; 2337 default: 2338 if (lastWasCR) { 2339 if (counter == 0) { 2340 sbuff.append('\n'); 2341 } else { 2342 buff[counter - 1] = '\n'; 2343 } 2344 lastWasCR = false; 2345 } 2346 break; 2347 } 2348 } 2349 if (last < nch) { 2350 if (lastWasCR) { 2351 if (last < (nch - 1)) { 2352 sbuff.append(buff, last, nch - last - 1); 2353 } 2354 } else { 2355 sbuff.append(buff, last, nch - last); 2356 } 2357 } 2358 } 2359 if (lastWasCR) { 2360 sbuff.append('\n'); 2361 } 2362 c.replaceSelection(sbuff != null ? sbuff.toString() : ""); 2363 } 2364 } 2365 2366 // --- TransferHandler methods ------------------------------------ 2367 2368 /** 2369 * This is the type of transfer actions supported by the source. Some models are 2370 * not mutable, so a transfer operation of COPY only should 2371 * be advertised in that case. 2372 * 2373 * @param c The component holding the data to be transfered. This 2374 * argument is provided to enable sharing of TransferHandlers by 2375 * multiple components. 2376 * @return This is implemented to return NONE if the component is a JPasswordField 2377 * since exporting data via user gestures is not allowed. If the text component is 2378 * editable, COPY_OR_MOVE is returned, otherwise just COPY is allowed. 2379 */ 2380 public int getSourceActions(JComponent c) { 2381 if (c instanceof JPasswordField && 2382 c.getClientProperty("JPasswordField.cutCopyAllowed") != 2383 Boolean.TRUE) { 2384 return NONE; 2385 } 2386 2387 return ((JTextComponent)c).isEditable() ? COPY_OR_MOVE : COPY; 2388 } 2389 2390 /** 2391 * Create a Transferable to use as the source for a data transfer. 2392 * 2393 * @param comp The component holding the data to be transfered. This 2394 * argument is provided to enable sharing of TransferHandlers by 2395 * multiple components. 2396 * @return The representation of the data to be transfered. 2397 * 2398 */ 2399 protected Transferable createTransferable(JComponent comp) { 2400 exportComp = (JTextComponent)comp; 2401 shouldRemove = true; 2402 p0 = exportComp.getSelectionStart(); 2403 p1 = exportComp.getSelectionEnd(); 2404 return (p0 != p1) ? (new TextTransferable(exportComp, p0, p1)) : null; 2405 } 2406 2407 /** 2408 * This method is called after data has been exported. This method should remove 2409 * the data that was transfered if the action was MOVE. 2410 * 2411 * @param source The component that was the source of the data. 2412 * @param data The data that was transferred or possibly null 2413 * if the action is <code>NONE</code>. 2414 * @param action The actual action that was performed. 2415 */ 2416 protected void exportDone(JComponent source, Transferable data, int action) { 2417 // only remove the text if shouldRemove has not been set to 2418 // false by importData and only if the action is a move 2419 if (shouldRemove && action == MOVE) { 2420 TextTransferable t = (TextTransferable)data; 2421 t.removeText(); 2422 } 2423 2424 exportComp = null; 2425 } 2426 2427 public boolean importData(TransferSupport support) { 2428 isDrop = support.isDrop(); 2429 2430 if (isDrop) { 2431 modeBetween = 2432 ((JTextComponent)support.getComponent()).getDropMode() == DropMode.INSERT; 2433 2434 dropBias = ((JTextComponent.DropLocation)support.getDropLocation()).getBias(); 2435 2436 dropAction = support.getDropAction(); 2437 } 2438 2439 try { 2440 return super.importData(support); 2441 } finally { 2442 isDrop = false; 2443 modeBetween = false; 2444 dropBias = null; 2445 dropAction = MOVE; 2446 } 2447 } 2448 2449 /** 2450 * This method causes a transfer to a component from a clipboard or a 2451 * DND drop operation. The Transferable represents the data to be 2452 * imported into the component. 2453 * 2454 * @param comp The component to receive the transfer. This 2455 * argument is provided to enable sharing of TransferHandlers by 2456 * multiple components. 2457 * @param t The data to import 2458 * @return true if the data was inserted into the component, false otherwise. 2459 */ 2460 public boolean importData(JComponent comp, Transferable t) { 2461 JTextComponent c = (JTextComponent)comp; 2462 2463 int pos = modeBetween 2464 ? c.getDropLocation().getIndex() : c.getCaretPosition(); 2465 2466 // if we are importing to the same component that we exported from 2467 // then don't actually do anything if the drop location is inside 2468 // the drag location and set shouldRemove to false so that exportDone 2469 // knows not to remove any data 2470 if (dropAction == MOVE && c == exportComp && pos >= p0 && pos <= p1) { 2471 shouldRemove = false; 2472 return true; 2473 } 2474 2475 boolean imported = false; 2476 DataFlavor importFlavor = getImportFlavor(t.getTransferDataFlavors(), c); 2477 if (importFlavor != null) { 2478 try { 2479 boolean useRead = false; 2480 if (comp instanceof JEditorPane) { 2481 JEditorPane ep = (JEditorPane)comp; 2482 if (!ep.getContentType().startsWith("text/plain") && 2483 importFlavor.getMimeType().startsWith(ep.getContentType())) { 2484 useRead = true; 2485 } 2486 } 2487 InputContext ic = c.getInputContext(); 2488 if (ic != null) { 2489 ic.endComposition(); 2490 } 2491 Reader r = importFlavor.getReaderForText(t); 2492 2493 if (modeBetween) { 2494 Caret caret = c.getCaret(); 2495 if (caret instanceof DefaultCaret) { 2496 ((DefaultCaret)caret).setDot(pos, dropBias); 2497 } else { 2498 c.setCaretPosition(pos); 2499 } 2500 } 2501 2502 handleReaderImport(r, c, useRead); 2503 2504 if (isDrop) { 2505 c.requestFocus(); 2506 Caret caret = c.getCaret(); 2507 if (caret instanceof DefaultCaret) { 2508 int newPos = caret.getDot(); 2509 Position.Bias newBias = ((DefaultCaret)caret).getDotBias(); 2510 2511 ((DefaultCaret)caret).setDot(pos, dropBias); 2512 ((DefaultCaret)caret).moveDot(newPos, newBias); 2513 } else { 2514 c.select(pos, c.getCaretPosition()); 2515 } 2516 } 2517 2518 imported = true; 2519 } catch (UnsupportedFlavorException ufe) { 2520 } catch (BadLocationException ble) { 2521 } catch (IOException ioe) { 2522 } 2523 } 2524 return imported; 2525 } 2526 2527 /** 2528 * This method indicates if a component would accept an import of the given 2529 * set of data flavors prior to actually attempting to import it. 2530 * 2531 * @param comp The component to receive the transfer. This 2532 * argument is provided to enable sharing of TransferHandlers by 2533 * multiple components. 2534 * @param flavors The data formats available 2535 * @return true if the data can be inserted into the component, false otherwise. 2536 */ 2537 public boolean canImport(JComponent comp, DataFlavor[] flavors) { 2538 JTextComponent c = (JTextComponent)comp; 2539 if (!(c.isEditable() && c.isEnabled())) { 2540 return false; 2541 } 2542 return (getImportFlavor(flavors, c) != null); 2543 } 2544 2545 /** 2546 * A possible implementation of the Transferable interface 2547 * for text components. For a JEditorPane with a rich set 2548 * of EditorKit implementations, conversions could be made 2549 * giving a wider set of formats. This is implemented to 2550 * offer up only the active content type and text/plain 2551 * (if that is not the active format) since that can be 2552 * extracted from other formats. 2553 */ 2554 static class TextTransferable extends BasicTransferable { 2555 2556 TextTransferable(JTextComponent c, int start, int end) { 2557 super(null, null); 2558 2559 this.c = c; 2560 2561 Document doc = c.getDocument(); 2562 2563 try { 2564 p0 = doc.createPosition(start); 2565 p1 = doc.createPosition(end); 2566 2567 plainData = c.getSelectedText(); 2568 2569 if (c instanceof JEditorPane) { 2570 JEditorPane ep = (JEditorPane)c; 2571 2572 mimeType = ep.getContentType(); 2573 2574 if (mimeType.startsWith("text/plain")) { 2575 return; 2576 } 2577 2578 StringWriter sw = new StringWriter(p1.getOffset() - p0.getOffset()); 2579 ep.getEditorKit().write(sw, doc, p0.getOffset(), p1.getOffset() - p0.getOffset()); 2580 2581 if (mimeType.startsWith("text/html")) { 2582 htmlData = sw.toString(); 2583 } else { 2584 richText = sw.toString(); 2585 } 2586 } 2587 } catch (BadLocationException ble) { 2588 } catch (IOException ioe) { 2589 } 2590 } 2591 2592 void removeText() { 2593 if ((p0 != null) && (p1 != null) && (p0.getOffset() != p1.getOffset())) { 2594 try { 2595 Document doc = c.getDocument(); 2596 doc.remove(p0.getOffset(), p1.getOffset() - p0.getOffset()); 2597 } catch (BadLocationException e) { 2598 } 2599 } 2600 } 2601 2602 // ---- EditorKit other than plain or HTML text ----------------------- 2603 2604 /** 2605 * If the EditorKit is not for text/plain or text/html, that format 2606 * is supported through the "richer flavors" part of BasicTransferable. 2607 */ 2608 protected DataFlavor[] getRicherFlavors() { 2609 if (richText == null) { 2610 return null; 2611 } 2612 2613 try { 2614 DataFlavor[] flavors = new DataFlavor[3]; 2615 flavors[0] = new DataFlavor(mimeType + ";class=java.lang.String"); 2616 flavors[1] = new DataFlavor(mimeType + ";class=java.io.Reader"); 2617 flavors[2] = new DataFlavor(mimeType + ";class=java.io.InputStream;charset=unicode"); 2618 return flavors; 2619 } catch (ClassNotFoundException cle) { 2620 // fall through to unsupported (should not happen) 2621 } 2622 2623 return null; 2624 } 2625 2626 /** 2627 * The only richer format supported is the file list flavor 2628 */ 2629 protected Object getRicherData(DataFlavor flavor) throws UnsupportedFlavorException { 2630 if (richText == null) { 2631 return null; 2632 } 2633 2634 if (String.class.equals(flavor.getRepresentationClass())) { 2635 return richText; 2636 } else if (Reader.class.equals(flavor.getRepresentationClass())) { 2637 return new StringReader(richText); 2638 } else if (InputStream.class.equals(flavor.getRepresentationClass())) { 2639 return new StringBufferInputStream(richText); 2640 } 2641 throw new UnsupportedFlavorException(flavor); 2642 } 2643 2644 Position p0; 2645 Position p1; 2646 String mimeType; 2647 String richText; 2648 JTextComponent c; 2649 } 2650 2651 } 2652 2653 }