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