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