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