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