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.text; 26 27 import com.sun.beans.util.Cache; 28 29 import java.security.AccessController; 30 import java.security.PrivilegedAction; 31 32 import java.beans.JavaBean; 33 import java.beans.BeanProperty; 34 import java.beans.Transient; 35 import java.util.HashMap; 36 import java.util.Hashtable; 37 import java.util.Enumeration; 38 import java.util.Vector; 39 40 import java.util.concurrent.*; 41 42 import java.io.*; 43 44 import java.awt.*; 45 import java.awt.event.*; 46 import java.awt.print.*; 47 import java.awt.datatransfer.*; 48 import java.awt.im.InputContext; 49 import java.awt.im.InputMethodRequests; 50 import java.awt.font.TextHitInfo; 51 import java.awt.font.TextAttribute; 52 import java.awt.geom.Point2D; 53 import java.awt.geom.Rectangle2D; 54 55 import java.awt.print.Printable; 56 import java.awt.print.PrinterException; 57 58 import javax.print.PrintService; 59 import javax.print.attribute.PrintRequestAttributeSet; 60 61 import java.text.*; 62 import java.text.AttributedCharacterIterator.Attribute; 63 64 import javax.swing.*; 65 import javax.swing.event.*; 66 import javax.swing.plaf.*; 67 68 import javax.accessibility.*; 69 70 import javax.print.attribute.*; 71 72 import sun.awt.AppContext; 73 74 75 import sun.swing.PrintingStatus; 76 import sun.swing.SwingUtilities2; 77 import sun.swing.text.TextComponentPrintable; 78 import sun.swing.SwingAccessor; 79 80 /** 81 * <code>JTextComponent</code> is the base class for swing text 82 * components. It tries to be compatible with the 83 * <code>java.awt.TextComponent</code> class 84 * where it can reasonably do so. Also provided are other services 85 * for additional flexibility (beyond the pluggable UI and bean 86 * support). 87 * You can find information on how to use the functionality 88 * this class provides in 89 * <a href="https://docs.oracle.com/javase/tutorial/uiswing/components/generaltext.html">General Rules for Using Text Components</a>, 90 * a section in <em>The Java Tutorial.</em> 91 * 92 * <dl> 93 * <dt><b>Caret Changes</b> 94 * <dd> 95 * The caret is a pluggable object in swing text components. 96 * Notification of changes to the caret position and the selection 97 * are sent to implementations of the <code>CaretListener</code> 98 * interface that have been registered with the text component. 99 * The UI will install a default caret unless a customized caret 100 * has been set. <br> 101 * By default the caret tracks all the document changes 102 * performed on the Event Dispatching Thread and updates it's position 103 * accordingly if an insertion occurs before or at the caret position 104 * or a removal occurs before the caret position. <code>DefaultCaret</code> 105 * tries to make itself visible which may lead to scrolling 106 * of a text component within <code>JScrollPane</code>. The default caret 107 * behavior can be changed by the {@link DefaultCaret#setUpdatePolicy} method. 108 * <br> 109 * <b>Note</b>: Non-editable text components also have a caret though 110 * it may not be painted. 111 * 112 * <dt><b>Commands</b> 113 * <dd> 114 * Text components provide a number of commands that can be used 115 * to manipulate the component. This is essentially the way that 116 * the component expresses its capabilities. These are expressed 117 * in terms of the swing <code>Action</code> interface, 118 * using the <code>TextAction</code> implementation. 119 * The set of commands supported by the text component can be 120 * found with the {@link #getActions} method. These actions 121 * can be bound to key events, fired from buttons, etc. 122 * 123 * <dt><b>Text Input</b> 124 * <dd> 125 * The text components support flexible and internationalized text input, using 126 * keymaps and the input method framework, while maintaining compatibility with 127 * the AWT listener model. 128 * <p> 129 * A {@link javax.swing.text.Keymap} lets an application bind key 130 * strokes to actions. 131 * In order to allow keymaps to be shared across multiple text components, they 132 * can use actions that extend <code>TextAction</code>. 133 * <code>TextAction</code> can determine which <code>JTextComponent</code> 134 * most recently has or had focus and therefore is the subject of 135 * the action (In the case that the <code>ActionEvent</code> 136 * sent to the action doesn't contain the target text component as its source). 137 * <p> 138 * The {@extLink imf_overview Input Method Framework} 139 * lets text components interact with input methods, separate software 140 * components that preprocess events to let users enter thousands of 141 * different characters using keyboards with far fewer keys. 142 * <code>JTextComponent</code> is an <em>active client</em> of 143 * the framework, so it implements the preferred user interface for interacting 144 * with input methods. As a consequence, some key events do not reach the text 145 * component because they are handled by an input method, and some text input 146 * reaches the text component as committed text within an {@link 147 * java.awt.event.InputMethodEvent} instead of as a key event. 148 * The complete text input is the combination of the characters in 149 * <code>keyTyped</code> key events and committed text in input method events. 150 * <p> 151 * The AWT listener model lets applications attach event listeners to 152 * components in order to bind events to actions. Swing encourages the 153 * use of keymaps instead of listeners, but maintains compatibility 154 * with listeners by giving the listeners a chance to steal an event 155 * by consuming it. 156 * <p> 157 * Keyboard event and input method events are handled in the following stages, 158 * with each stage capable of consuming the event: 159 * 160 * <table class="striped"> 161 * <caption>Stages of keyboard and input method event handling</caption> 162 * <thead> 163 * <tr> 164 * <th scope="col">Stage 165 * <th scope="col">KeyEvent 166 * <th scope="col">InputMethodEvent 167 * </thead> 168 * <tbody> 169 * <tr> 170 * <th scope="row">1. 171 * <td>input methods 172 * <td>(generated here) 173 * <tr> 174 * <th scope="row">2. 175 * <td>focus manager 176 * <td> 177 * </tr> 178 * <tr> 179 * <th scope="row">3. 180 * <td>registered key listeners 181 * <td>registered input method listeners 182 * <tr> 183 * <th scope="row">4. 184 * <td> 185 * <td>input method handling in JTextComponent 186 * <tr> 187 * <th scope="row">5. 188 * <td colspan=2>keymap handling using the current keymap 189 * <tr> 190 * <th scope="row">6. 191 * <td>keyboard handling in JComponent (e.g. accelerators, component 192 * navigation, etc.) 193 * <td> 194 * </tbody> 195 * </table> 196 * <p> 197 * To maintain compatibility with applications that listen to key 198 * events but are not aware of input method events, the input 199 * method handling in stage 4 provides a compatibility mode for 200 * components that do not process input method events. For these 201 * components, the committed text is converted to keyTyped key events 202 * and processed in the key event pipeline starting at stage 3 203 * instead of in the input method event pipeline. 204 * <p> 205 * By default the component will create a keymap (named <b>DEFAULT_KEYMAP</b>) 206 * that is shared by all JTextComponent instances as the default keymap. 207 * Typically a look-and-feel implementation will install a different keymap 208 * that resolves to the default keymap for those bindings not found in the 209 * different keymap. The minimal bindings include: 210 * <ul> 211 * <li>inserting content into the editor for the 212 * printable keys. 213 * <li>removing content with the backspace and del 214 * keys. 215 * <li>caret movement forward and backward 216 * </ul> 217 * 218 * <dt><b>Model/View Split</b> 219 * <dd> 220 * The text components have a model-view split. A text component pulls 221 * together the objects used to represent the model, view, and controller. 222 * The text document model may be shared by other views which act as observers 223 * of the model (e.g. a document may be shared by multiple components). 224 * 225 * <p style="text-align:center"><img src="doc-files/editor.gif" alt="Diagram showing interaction between Controller, Document, events, and ViewFactory" 226 * HEIGHT=358 WIDTH=587></p> 227 * 228 * <p> 229 * The model is defined by the {@link Document} interface. 230 * This is intended to provide a flexible text storage mechanism 231 * that tracks change during edits and can be extended to more sophisticated 232 * models. The model interfaces are meant to capture the capabilities of 233 * expression given by SGML, a system used to express a wide variety of 234 * content. 235 * Each modification to the document causes notification of the 236 * details of the change to be sent to all observers in the form of a 237 * {@link DocumentEvent} which allows the views to stay up to date with the model. 238 * This event is sent to observers that have implemented the 239 * {@link DocumentListener} 240 * interface and registered interest with the model being observed. 241 * 242 * <dt><b>Location Information</b> 243 * <dd> 244 * The capability of determining the location of text in 245 * the view is provided. There are two methods, {@link #modelToView} 246 * and {@link #viewToModel} for determining this information. 247 * 248 * <dt><b>Undo/Redo support</b> 249 * <dd> 250 * Support for an edit history mechanism is provided to allow 251 * undo/redo operations. The text component does not itself 252 * provide the history buffer by default, but does provide 253 * the <code>UndoableEdit</code> records that can be used in conjunction 254 * with a history buffer to provide the undo/redo support. 255 * The support is provided by the Document model, which allows 256 * one to attach UndoableEditListener implementations. 257 * 258 * <dt><b>Thread Safety</b> 259 * <dd> 260 * The swing text components provide some support of thread 261 * safe operations. Because of the high level of configurability 262 * of the text components, it is possible to circumvent the 263 * protection provided. The protection primarily comes from 264 * the model, so the documentation of <code>AbstractDocument</code> 265 * describes the assumptions of the protection provided. 266 * The methods that are safe to call asynchronously are marked 267 * with comments. 268 * 269 * <dt><b>Newlines</b> 270 * <dd> 271 * For a discussion on how newlines are handled, see 272 * <a href="DefaultEditorKit.html">DefaultEditorKit</a>. 273 * 274 * 275 * <dt><b>Printing support</b> 276 * <dd> 277 * Several {@link #print print} methods are provided for basic 278 * document printing. If more advanced printing is needed, use the 279 * {@link #getPrintable} method. 280 * </dl> 281 * 282 * <p> 283 * <strong>Warning:</strong> 284 * Serialized objects of this class will not be compatible with 285 * future Swing releases. The current serialization support is 286 * appropriate for short term storage or RMI between applications running 287 * the same version of Swing. As of 1.4, support for long term storage 288 * of all JavaBeans 289 * has been added to the <code>java.beans</code> package. 290 * Please see {@link java.beans.XMLEncoder}. 291 * 292 * @author Timothy Prinzing 293 * @author Igor Kushnirskiy (printing support) 294 * @see Document 295 * @see DocumentEvent 296 * @see DocumentListener 297 * @see Caret 298 * @see CaretEvent 299 * @see CaretListener 300 * @see TextUI 301 * @see View 302 * @see ViewFactory 303 */ 304 @JavaBean(defaultProperty = "UI") 305 @SwingContainer(false) 306 @SuppressWarnings("serial") // Same-version serialization only 307 public abstract class JTextComponent extends JComponent implements Scrollable, Accessible 308 { 309 /** 310 * Creates a new <code>JTextComponent</code>. 311 * Listeners for caret events are established, and the pluggable 312 * UI installed. The component is marked as editable. No layout manager 313 * is used, because layout is managed by the view subsystem of text. 314 * The document model is set to <code>null</code>. 315 */ JTextComponent()316 public JTextComponent() { 317 super(); 318 // enable InputMethodEvent for on-the-spot pre-editing 319 enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.INPUT_METHOD_EVENT_MASK); 320 caretEvent = new MutableCaretEvent(this); 321 addMouseListener(caretEvent); 322 addFocusListener(caretEvent); 323 setEditable(true); 324 setDragEnabled(false); 325 setLayout(null); // layout is managed by View hierarchy 326 updateUI(); 327 } 328 329 /** 330 * Fetches the user-interface factory for this text-oriented editor. 331 * 332 * @return the factory 333 */ getUI()334 public TextUI getUI() { return (TextUI)ui; } 335 336 /** 337 * Sets the user-interface factory for this text-oriented editor. 338 * 339 * @param ui the factory 340 */ setUI(TextUI ui)341 public void setUI(TextUI ui) { 342 super.setUI(ui); 343 } 344 345 /** 346 * Reloads the pluggable UI. The key used to fetch the 347 * new interface is <code>getUIClassID()</code>. The type of 348 * the UI is <code>TextUI</code>. <code>invalidate</code> 349 * is called after setting the UI. 350 */ updateUI()351 public void updateUI() { 352 setUI((TextUI)UIManager.getUI(this)); 353 invalidate(); 354 } 355 356 /** 357 * Adds a caret listener for notification of any changes 358 * to the caret. 359 * 360 * @param listener the listener to be added 361 * @see javax.swing.event.CaretEvent 362 */ addCaretListener(CaretListener listener)363 public void addCaretListener(CaretListener listener) { 364 listenerList.add(CaretListener.class, listener); 365 } 366 367 /** 368 * Removes a caret listener. 369 * 370 * @param listener the listener to be removed 371 * @see javax.swing.event.CaretEvent 372 */ removeCaretListener(CaretListener listener)373 public void removeCaretListener(CaretListener listener) { 374 listenerList.remove(CaretListener.class, listener); 375 } 376 377 /** 378 * Returns an array of all the caret listeners 379 * registered on this text component. 380 * 381 * @return all of this component's <code>CaretListener</code>s 382 * or an empty 383 * array if no caret listeners are currently registered 384 * 385 * @see #addCaretListener 386 * @see #removeCaretListener 387 * 388 * @since 1.4 389 */ 390 @BeanProperty(bound = false) getCaretListeners()391 public CaretListener[] getCaretListeners() { 392 return listenerList.getListeners(CaretListener.class); 393 } 394 395 /** 396 * Notifies all listeners that have registered interest for 397 * notification on this event type. The event instance 398 * is lazily created using the parameters passed into 399 * the fire method. The listener list is processed in a 400 * last-to-first manner. 401 * 402 * @param e the event 403 * @see EventListenerList 404 */ fireCaretUpdate(CaretEvent e)405 protected void fireCaretUpdate(CaretEvent e) { 406 // Guaranteed to return a non-null array 407 Object[] listeners = listenerList.getListenerList(); 408 // Process the listeners last to first, notifying 409 // those that are interested in this event 410 for (int i = listeners.length-2; i>=0; i-=2) { 411 if (listeners[i]==CaretListener.class) { 412 ((CaretListener)listeners[i+1]).caretUpdate(e); 413 } 414 } 415 } 416 417 /** 418 * Associates the editor with a text document. 419 * The currently registered factory is used to build a view for 420 * the document, which gets displayed by the editor after revalidation. 421 * A PropertyChange event ("document") is propagated to each listener. 422 * 423 * @param doc the document to display/edit 424 * @see #getDocument 425 */ 426 @BeanProperty(expert = true, description 427 = "the text document model") setDocument(Document doc)428 public void setDocument(Document doc) { 429 Document old = model; 430 431 /* 432 * acquire a read lock on the old model to prevent notification of 433 * mutations while we disconnecting the old model. 434 */ 435 try { 436 if (old instanceof AbstractDocument) { 437 ((AbstractDocument)old).readLock(); 438 } 439 if (accessibleContext != null) { 440 model.removeDocumentListener( 441 ((AccessibleJTextComponent)accessibleContext)); 442 } 443 if (inputMethodRequestsHandler != null) { 444 model.removeDocumentListener((DocumentListener)inputMethodRequestsHandler); 445 } 446 model = doc; 447 448 // Set the document's run direction property to match the 449 // component's ComponentOrientation property. 450 Boolean runDir = getComponentOrientation().isLeftToRight() 451 ? TextAttribute.RUN_DIRECTION_LTR 452 : TextAttribute.RUN_DIRECTION_RTL; 453 if (runDir != doc.getProperty(TextAttribute.RUN_DIRECTION)) { 454 doc.putProperty(TextAttribute.RUN_DIRECTION, runDir ); 455 } 456 firePropertyChange("document", old, doc); 457 } finally { 458 if (old instanceof AbstractDocument) { 459 ((AbstractDocument)old).readUnlock(); 460 } 461 } 462 463 revalidate(); 464 repaint(); 465 if (accessibleContext != null) { 466 model.addDocumentListener( 467 ((AccessibleJTextComponent)accessibleContext)); 468 } 469 if (inputMethodRequestsHandler != null) { 470 model.addDocumentListener((DocumentListener)inputMethodRequestsHandler); 471 } 472 } 473 474 /** 475 * Fetches the model associated with the editor. This is 476 * primarily for the UI to get at the minimal amount of 477 * state required to be a text editor. Subclasses will 478 * return the actual type of the model which will typically 479 * be something that extends Document. 480 * 481 * @return the model 482 */ getDocument()483 public Document getDocument() { 484 return model; 485 } 486 487 // Override of Component.setComponentOrientation setComponentOrientation( ComponentOrientation o )488 public void setComponentOrientation( ComponentOrientation o ) { 489 // Set the document's run direction property to match the 490 // ComponentOrientation property. 491 Document doc = getDocument(); 492 if( doc != null ) { 493 Boolean runDir = o.isLeftToRight() 494 ? TextAttribute.RUN_DIRECTION_LTR 495 : TextAttribute.RUN_DIRECTION_RTL; 496 doc.putProperty( TextAttribute.RUN_DIRECTION, runDir ); 497 } 498 super.setComponentOrientation( o ); 499 } 500 501 /** 502 * Fetches the command list for the editor. This is 503 * the list of commands supported by the plugged-in UI 504 * augmented by the collection of commands that the 505 * editor itself supports. These are useful for binding 506 * to events, such as in a keymap. 507 * 508 * @return the command list 509 */ 510 @BeanProperty(bound = false) getActions()511 public Action[] getActions() { 512 return getUI().getEditorKit(this).getActions(); 513 } 514 515 /** 516 * Sets margin space between the text component's border 517 * and its text. The text component's default <code>Border</code> 518 * object will use this value to create the proper margin. 519 * However, if a non-default border is set on the text component, 520 * it is that <code>Border</code> object's responsibility to create the 521 * appropriate margin space (else this property will effectively 522 * be ignored). This causes a redraw of the component. 523 * A PropertyChange event ("margin") is sent to all listeners. 524 * 525 * @param m the space between the border and the text 526 */ 527 @BeanProperty(description 528 = "desired space between the border and text area") setMargin(Insets m)529 public void setMargin(Insets m) { 530 Insets old = margin; 531 margin = m; 532 firePropertyChange("margin", old, m); 533 invalidate(); 534 } 535 536 /** 537 * Returns the margin between the text component's border and 538 * its text. 539 * 540 * @return the margin 541 */ getMargin()542 public Insets getMargin() { 543 return margin; 544 } 545 546 /** 547 * Sets the <code>NavigationFilter</code>. <code>NavigationFilter</code> 548 * is used by <code>DefaultCaret</code> and the default cursor movement 549 * actions as a way to restrict the cursor movement. 550 * @param filter the filter 551 * 552 * @since 1.4 553 */ setNavigationFilter(NavigationFilter filter)554 public void setNavigationFilter(NavigationFilter filter) { 555 navigationFilter = filter; 556 } 557 558 /** 559 * Returns the <code>NavigationFilter</code>. <code>NavigationFilter</code> 560 * is used by <code>DefaultCaret</code> and the default cursor movement 561 * actions as a way to restrict the cursor movement. A null return value 562 * implies the cursor movement and selection should not be restricted. 563 * 564 * @since 1.4 565 * @return the NavigationFilter 566 */ getNavigationFilter()567 public NavigationFilter getNavigationFilter() { 568 return navigationFilter; 569 } 570 571 /** 572 * Fetches the caret that allows text-oriented navigation over 573 * the view. 574 * 575 * @return the caret 576 */ 577 @Transient getCaret()578 public Caret getCaret() { 579 return caret; 580 } 581 582 /** 583 * Sets the caret to be used. By default this will be set 584 * by the UI that gets installed. This can be changed to 585 * a custom caret if desired. Setting the caret results in a 586 * PropertyChange event ("caret") being fired. 587 * 588 * @param c the caret 589 * @see #getCaret 590 */ 591 @BeanProperty(expert = true, description 592 = "the caret used to select/navigate") setCaret(Caret c)593 public void setCaret(Caret c) { 594 if (caret != null) { 595 caret.removeChangeListener(caretEvent); 596 caret.deinstall(this); 597 } 598 Caret old = caret; 599 caret = c; 600 if (caret != null) { 601 caret.install(this); 602 caret.addChangeListener(caretEvent); 603 } 604 firePropertyChange("caret", old, caret); 605 } 606 607 /** 608 * Fetches the object responsible for making highlights. 609 * 610 * @return the highlighter 611 */ getHighlighter()612 public Highlighter getHighlighter() { 613 return highlighter; 614 } 615 616 /** 617 * Sets the highlighter to be used. By default this will be set 618 * by the UI that gets installed. This can be changed to 619 * a custom highlighter if desired. The highlighter can be set to 620 * <code>null</code> to disable it. 621 * A PropertyChange event ("highlighter") is fired 622 * when a new highlighter is installed. 623 * 624 * @param h the highlighter 625 * @see #getHighlighter 626 */ 627 @BeanProperty(expert = true, description 628 = "object responsible for background highlights") setHighlighter(Highlighter h)629 public void setHighlighter(Highlighter h) { 630 if (highlighter != null) { 631 highlighter.deinstall(this); 632 } 633 Highlighter old = highlighter; 634 highlighter = h; 635 if (highlighter != null) { 636 highlighter.install(this); 637 } 638 firePropertyChange("highlighter", old, h); 639 } 640 641 /** 642 * Sets the keymap to use for binding events to 643 * actions. Setting to <code>null</code> effectively disables 644 * keyboard input. 645 * A PropertyChange event ("keymap") is fired when a new keymap 646 * is installed. 647 * 648 * @param map the keymap 649 * @see #getKeymap 650 */ 651 @BeanProperty(description 652 = "set of key event to action bindings to use") setKeymap(Keymap map)653 public void setKeymap(Keymap map) { 654 Keymap old = keymap; 655 keymap = map; 656 firePropertyChange("keymap", old, keymap); 657 updateInputMap(old, map); 658 } 659 660 /** 661 * Turns on or off automatic drag handling. In order to enable automatic 662 * drag handling, this property should be set to {@code true}, and the 663 * component's {@code TransferHandler} needs to be {@code non-null}. 664 * The default value of the {@code dragEnabled} property is {@code false}. 665 * <p> 666 * The job of honoring this property, and recognizing a user drag gesture, 667 * lies with the look and feel implementation, and in particular, the component's 668 * {@code TextUI}. When automatic drag handling is enabled, most look and 669 * feels (including those that subclass {@code BasicLookAndFeel}) begin a 670 * drag and drop operation whenever the user presses the mouse button over 671 * a selection and then moves the mouse a few pixels. Setting this property to 672 * {@code true} can therefore have a subtle effect on how selections behave. 673 * <p> 674 * If a look and feel is used that ignores this property, you can still 675 * begin a drag and drop operation by calling {@code exportAsDrag} on the 676 * component's {@code TransferHandler}. 677 * 678 * @param b whether or not to enable automatic drag handling 679 * @exception HeadlessException if 680 * <code>b</code> is <code>true</code> and 681 * <code>GraphicsEnvironment.isHeadless()</code> 682 * returns <code>true</code> 683 * @see java.awt.GraphicsEnvironment#isHeadless 684 * @see #getDragEnabled 685 * @see #setTransferHandler 686 * @see TransferHandler 687 * @since 1.4 688 */ 689 @BeanProperty(bound = false, description 690 = "determines whether automatic drag handling is enabled") setDragEnabled(boolean b)691 public void setDragEnabled(boolean b) { 692 checkDragEnabled(b); 693 dragEnabled = b; 694 } 695 checkDragEnabled(boolean b)696 private static void checkDragEnabled(boolean b) { 697 if (b && GraphicsEnvironment.isHeadless()) { 698 throw new HeadlessException(); 699 } 700 } 701 702 /** 703 * Returns whether or not automatic drag handling is enabled. 704 * 705 * @return the value of the {@code dragEnabled} property 706 * @see #setDragEnabled 707 * @since 1.4 708 */ getDragEnabled()709 public boolean getDragEnabled() { 710 return dragEnabled; 711 } 712 713 /** 714 * Sets the drop mode for this component. For backward compatibility, 715 * the default for this property is <code>DropMode.USE_SELECTION</code>. 716 * Usage of <code>DropMode.INSERT</code> is recommended, however, 717 * for an improved user experience. It offers similar behavior of dropping 718 * between text locations, but does so without affecting the actual text 719 * selection and caret location. 720 * <p> 721 * <code>JTextComponents</code> support the following drop modes: 722 * <ul> 723 * <li><code>DropMode.USE_SELECTION</code></li> 724 * <li><code>DropMode.INSERT</code></li> 725 * </ul> 726 * <p> 727 * The drop mode is only meaningful if this component has a 728 * <code>TransferHandler</code> that accepts drops. 729 * 730 * @param dropMode the drop mode to use 731 * @throws IllegalArgumentException if the drop mode is unsupported 732 * or <code>null</code> 733 * @see #getDropMode 734 * @see #getDropLocation 735 * @see #setTransferHandler 736 * @see javax.swing.TransferHandler 737 * @since 1.6 738 */ setDropMode(DropMode dropMode)739 public final void setDropMode(DropMode dropMode) { 740 checkDropMode(dropMode); 741 this.dropMode = dropMode; 742 } 743 checkDropMode(DropMode dropMode)744 private static void checkDropMode(DropMode dropMode) { 745 if (dropMode != null) { 746 switch (dropMode) { 747 case USE_SELECTION: 748 case INSERT: 749 return; 750 } 751 } 752 753 throw new IllegalArgumentException(dropMode + ": Unsupported drop mode for text"); 754 } 755 756 /** 757 * Returns the drop mode for this component. 758 * 759 * @return the drop mode for this component 760 * @see #setDropMode 761 * @since 1.6 762 */ getDropMode()763 public final DropMode getDropMode() { 764 return dropMode; 765 } 766 767 static { SwingAccessor.setJTextComponentAccessor( new SwingAccessor.JTextComponentAccessor() { public TransferHandler.DropLocation dropLocationForPoint(JTextComponent textComp, Point p) { return textComp.dropLocationForPoint(p); } public Object setDropLocation(JTextComponent textComp, TransferHandler.DropLocation location, Object state, boolean forDrop) { return textComp.setDropLocation(location, state, forDrop); } })768 SwingAccessor.setJTextComponentAccessor( 769 new SwingAccessor.JTextComponentAccessor() { 770 public TransferHandler.DropLocation dropLocationForPoint(JTextComponent textComp, 771 Point p) 772 { 773 return textComp.dropLocationForPoint(p); 774 } 775 public Object setDropLocation(JTextComponent textComp, 776 TransferHandler.DropLocation location, 777 Object state, boolean forDrop) 778 { 779 return textComp.setDropLocation(location, state, forDrop); 780 } 781 }); 782 } 783 784 785 /** 786 * Calculates a drop location in this component, representing where a 787 * drop at the given point should insert data. 788 * <p> 789 * Note: This method is meant to override 790 * <code>JComponent.dropLocationForPoint()</code>, which is package-private 791 * in javax.swing. <code>TransferHandler</code> will detect text components 792 * and call this method instead via reflection. It's name should therefore 793 * not be changed. 794 * 795 * @param p the point to calculate a drop location for 796 * @return the drop location, or <code>null</code> 797 */ 798 @SuppressWarnings("deprecation") dropLocationForPoint(Point p)799 DropLocation dropLocationForPoint(Point p) { 800 Position.Bias[] bias = new Position.Bias[1]; 801 int index = getUI().viewToModel(this, p, bias); 802 803 // viewToModel currently returns null for some HTML content 804 // when the point is within the component's top inset 805 if (bias[0] == null) { 806 bias[0] = Position.Bias.Forward; 807 } 808 809 return new DropLocation(p, index, bias[0]); 810 } 811 812 /** 813 * Called to set or clear the drop location during a DnD operation. 814 * In some cases, the component may need to use it's internal selection 815 * temporarily to indicate the drop location. To help facilitate this, 816 * this method returns and accepts as a parameter a state object. 817 * This state object can be used to store, and later restore, the selection 818 * state. Whatever this method returns will be passed back to it in 819 * future calls, as the state parameter. If it wants the DnD system to 820 * continue storing the same state, it must pass it back every time. 821 * Here's how this is used: 822 * <p> 823 * Let's say that on the first call to this method the component decides 824 * to save some state (because it is about to use the selection to show 825 * a drop index). It can return a state object to the caller encapsulating 826 * any saved selection state. On a second call, let's say the drop location 827 * is being changed to something else. The component doesn't need to 828 * restore anything yet, so it simply passes back the same state object 829 * to have the DnD system continue storing it. Finally, let's say this 830 * method is messaged with <code>null</code>. This means DnD 831 * is finished with this component for now, meaning it should restore 832 * state. At this point, it can use the state parameter to restore 833 * said state, and of course return <code>null</code> since there's 834 * no longer anything to store. 835 * <p> 836 * Note: This method is meant to override 837 * <code>JComponent.setDropLocation()</code>, which is package-private 838 * in javax.swing. <code>TransferHandler</code> will detect text components 839 * and call this method instead via reflection. It's name should therefore 840 * not be changed. 841 * 842 * @param location the drop location (as calculated by 843 * <code>dropLocationForPoint</code>) or <code>null</code> 844 * if there's no longer a valid drop location 845 * @param state the state object saved earlier for this component, 846 * or <code>null</code> 847 * @param forDrop whether or not the method is being called because an 848 * actual drop occurred 849 * @return any saved state for this component, or <code>null</code> if none 850 */ setDropLocation(TransferHandler.DropLocation location, Object state, boolean forDrop)851 Object setDropLocation(TransferHandler.DropLocation location, 852 Object state, 853 boolean forDrop) { 854 855 Object retVal = null; 856 DropLocation textLocation = (DropLocation)location; 857 858 if (dropMode == DropMode.USE_SELECTION) { 859 if (textLocation == null) { 860 if (state != null) { 861 /* 862 * This object represents the state saved earlier. 863 * If the caret is a DefaultCaret it will be 864 * an Object array containing, in order: 865 * - the saved caret mark (Integer) 866 * - the saved caret dot (Integer) 867 * - the saved caret visibility (Boolean) 868 * - the saved mark bias (Position.Bias) 869 * - the saved dot bias (Position.Bias) 870 * If the caret is not a DefaultCaret it will 871 * be similar, but will not contain the dot 872 * or mark bias. 873 */ 874 Object[] vals = (Object[])state; 875 876 if (!forDrop) { 877 if (caret instanceof DefaultCaret) { 878 ((DefaultCaret)caret).setDot(((Integer)vals[0]).intValue(), 879 (Position.Bias)vals[3]); 880 ((DefaultCaret)caret).moveDot(((Integer)vals[1]).intValue(), 881 (Position.Bias)vals[4]); 882 } else { 883 caret.setDot(((Integer)vals[0]).intValue()); 884 caret.moveDot(((Integer)vals[1]).intValue()); 885 } 886 } 887 888 caret.setVisible(((Boolean)vals[2]).booleanValue()); 889 } 890 } else { 891 if (dropLocation == null) { 892 boolean visible; 893 894 if (caret instanceof DefaultCaret) { 895 DefaultCaret dc = (DefaultCaret)caret; 896 visible = dc.isActive(); 897 retVal = new Object[] {Integer.valueOf(dc.getMark()), 898 Integer.valueOf(dc.getDot()), 899 Boolean.valueOf(visible), 900 dc.getMarkBias(), 901 dc.getDotBias()}; 902 } else { 903 visible = caret.isVisible(); 904 retVal = new Object[] {Integer.valueOf(caret.getMark()), 905 Integer.valueOf(caret.getDot()), 906 Boolean.valueOf(visible)}; 907 } 908 909 caret.setVisible(true); 910 } else { 911 retVal = state; 912 } 913 914 if (caret instanceof DefaultCaret) { 915 ((DefaultCaret)caret).setDot(textLocation.getIndex(), textLocation.getBias()); 916 } else { 917 caret.setDot(textLocation.getIndex()); 918 } 919 } 920 } else { 921 if (textLocation == null) { 922 if (state != null) { 923 caret.setVisible(((Boolean)state).booleanValue()); 924 } 925 } else { 926 if (dropLocation == null) { 927 boolean visible = caret instanceof DefaultCaret 928 ? ((DefaultCaret)caret).isActive() 929 : caret.isVisible(); 930 retVal = Boolean.valueOf(visible); 931 caret.setVisible(false); 932 } else { 933 retVal = state; 934 } 935 } 936 } 937 938 DropLocation old = dropLocation; 939 dropLocation = textLocation; 940 firePropertyChange("dropLocation", old, dropLocation); 941 942 return retVal; 943 } 944 945 /** 946 * Returns the location that this component should visually indicate 947 * as the drop location during a DnD operation over the component, 948 * or {@code null} if no location is to currently be shown. 949 * <p> 950 * This method is not meant for querying the drop location 951 * from a {@code TransferHandler}, as the drop location is only 952 * set after the {@code TransferHandler}'s <code>canImport</code> 953 * has returned and has allowed for the location to be shown. 954 * <p> 955 * When this property changes, a property change event with 956 * name "dropLocation" is fired by the component. 957 * 958 * @return the drop location 959 * @see #setDropMode 960 * @see TransferHandler#canImport(TransferHandler.TransferSupport) 961 * @since 1.6 962 */ 963 @BeanProperty(bound = false) getDropLocation()964 public final DropLocation getDropLocation() { 965 return dropLocation; 966 } 967 968 969 /** 970 * Updates the <code>InputMap</code>s in response to a 971 * <code>Keymap</code> change. 972 * @param oldKm the old <code>Keymap</code> 973 * @param newKm the new <code>Keymap</code> 974 */ updateInputMap(Keymap oldKm, Keymap newKm)975 void updateInputMap(Keymap oldKm, Keymap newKm) { 976 // Locate the current KeymapWrapper. 977 InputMap km = getInputMap(JComponent.WHEN_FOCUSED); 978 InputMap last = km; 979 while (km != null && !(km instanceof KeymapWrapper)) { 980 last = km; 981 km = km.getParent(); 982 } 983 if (km != null) { 984 // Found it, tweak the InputMap that points to it, as well 985 // as anything it points to. 986 if (newKm == null) { 987 if (last != km) { 988 last.setParent(km.getParent()); 989 } 990 else { 991 last.setParent(null); 992 } 993 } 994 else { 995 InputMap newKM = new KeymapWrapper(newKm); 996 last.setParent(newKM); 997 if (last != km) { 998 newKM.setParent(km.getParent()); 999 } 1000 } 1001 } 1002 else if (newKm != null) { 1003 km = getInputMap(JComponent.WHEN_FOCUSED); 1004 if (km != null) { 1005 // Couldn't find it. 1006 // Set the parent of WHEN_FOCUSED InputMap to be the new one. 1007 InputMap newKM = new KeymapWrapper(newKm); 1008 newKM.setParent(km.getParent()); 1009 km.setParent(newKM); 1010 } 1011 } 1012 1013 // Do the same thing with the ActionMap 1014 ActionMap am = getActionMap(); 1015 ActionMap lastAM = am; 1016 while (am != null && !(am instanceof KeymapActionMap)) { 1017 lastAM = am; 1018 am = am.getParent(); 1019 } 1020 if (am != null) { 1021 // Found it, tweak the Actionap that points to it, as well 1022 // as anything it points to. 1023 if (newKm == null) { 1024 if (lastAM != am) { 1025 lastAM.setParent(am.getParent()); 1026 } 1027 else { 1028 lastAM.setParent(null); 1029 } 1030 } 1031 else { 1032 ActionMap newAM = new KeymapActionMap(newKm); 1033 lastAM.setParent(newAM); 1034 if (lastAM != am) { 1035 newAM.setParent(am.getParent()); 1036 } 1037 } 1038 } 1039 else if (newKm != null) { 1040 am = getActionMap(); 1041 if (am != null) { 1042 // Couldn't find it. 1043 // Set the parent of ActionMap to be the new one. 1044 ActionMap newAM = new KeymapActionMap(newKm); 1045 newAM.setParent(am.getParent()); 1046 am.setParent(newAM); 1047 } 1048 } 1049 } 1050 1051 /** 1052 * Fetches the keymap currently active in this text 1053 * component. 1054 * 1055 * @return the keymap 1056 */ getKeymap()1057 public Keymap getKeymap() { 1058 return keymap; 1059 } 1060 1061 /** 1062 * Adds a new keymap into the keymap hierarchy. Keymap bindings 1063 * resolve from bottom up so an attribute specified in a child 1064 * will override an attribute specified in the parent. 1065 * 1066 * @param nm the name of the keymap (must be unique within the 1067 * collection of named keymaps in the document); the name may 1068 * be <code>null</code> if the keymap is unnamed, 1069 * but the caller is responsible for managing the reference 1070 * returned as an unnamed keymap can't 1071 * be fetched by name 1072 * @param parent the parent keymap; this may be <code>null</code> if 1073 * unspecified bindings need not be resolved in some other keymap 1074 * @return the keymap 1075 */ addKeymap(String nm, Keymap parent)1076 public static Keymap addKeymap(String nm, Keymap parent) { 1077 Keymap map = new DefaultKeymap(nm, parent); 1078 if (nm != null) { 1079 // add a named keymap, a class of bindings 1080 getKeymapTable().put(nm, map); 1081 } 1082 return map; 1083 } 1084 1085 /** 1086 * Removes a named keymap previously added to the document. Keymaps 1087 * with <code>null</code> names may not be removed in this way. 1088 * 1089 * @param nm the name of the keymap to remove 1090 * @return the keymap that was removed 1091 */ removeKeymap(String nm)1092 public static Keymap removeKeymap(String nm) { 1093 return getKeymapTable().remove(nm); 1094 } 1095 1096 /** 1097 * Fetches a named keymap previously added to the document. 1098 * This does not work with <code>null</code>-named keymaps. 1099 * 1100 * @param nm the name of the keymap 1101 * @return the keymap 1102 */ getKeymap(String nm)1103 public static Keymap getKeymap(String nm) { 1104 return getKeymapTable().get(nm); 1105 } 1106 getKeymapTable()1107 private static HashMap<String,Keymap> getKeymapTable() { 1108 synchronized (KEYMAP_TABLE) { 1109 AppContext appContext = AppContext.getAppContext(); 1110 @SuppressWarnings("unchecked") 1111 HashMap<String,Keymap> keymapTable = 1112 (HashMap<String,Keymap>)appContext.get(KEYMAP_TABLE); 1113 if (keymapTable == null) { 1114 keymapTable = new HashMap<String,Keymap>(17); 1115 appContext.put(KEYMAP_TABLE, keymapTable); 1116 //initialize default keymap 1117 Keymap binding = addKeymap(DEFAULT_KEYMAP, null); 1118 binding.setDefaultAction(new 1119 DefaultEditorKit.DefaultKeyTypedAction()); 1120 } 1121 return keymapTable; 1122 } 1123 } 1124 1125 /** 1126 * Binding record for creating key bindings. 1127 * <p> 1128 * <strong>Warning:</strong> 1129 * Serialized objects of this class will not be compatible with 1130 * future Swing releases. The current serialization support is 1131 * appropriate for short term storage or RMI between applications running 1132 * the same version of Swing. As of 1.4, support for long term storage 1133 * of all JavaBeans 1134 * has been added to the <code>java.beans</code> package. 1135 * Please see {@link java.beans.XMLEncoder}. 1136 */ 1137 @SuppressWarnings("serial") // Same-version serialization only 1138 public static class KeyBinding { 1139 1140 /** 1141 * The key. 1142 */ 1143 public KeyStroke key; 1144 1145 /** 1146 * The name of the action for the key. 1147 */ 1148 public String actionName; 1149 1150 /** 1151 * Creates a new key binding. 1152 * 1153 * @param key the key 1154 * @param actionName the name of the action for the key 1155 */ KeyBinding(KeyStroke key, String actionName)1156 public KeyBinding(KeyStroke key, String actionName) { 1157 this.key = key; 1158 this.actionName = actionName; 1159 } 1160 } 1161 1162 /** 1163 * <p> 1164 * Loads a keymap with a bunch of 1165 * bindings. This can be used to take a static table of 1166 * definitions and load them into some keymap. The following 1167 * example illustrates an example of binding some keys to 1168 * the cut, copy, and paste actions associated with a 1169 * JTextComponent. A code fragment to accomplish 1170 * this might look as follows: 1171 * <pre><code> 1172 * 1173 * static final JTextComponent.KeyBinding[] defaultBindings = { 1174 * new JTextComponent.KeyBinding( 1175 * KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), 1176 * DefaultEditorKit.copyAction), 1177 * new JTextComponent.KeyBinding( 1178 * KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), 1179 * DefaultEditorKit.pasteAction), 1180 * new JTextComponent.KeyBinding( 1181 * KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK), 1182 * DefaultEditorKit.cutAction), 1183 * }; 1184 * 1185 * JTextComponent c = new JTextPane(); 1186 * Keymap k = c.getKeymap(); 1187 * JTextComponent.loadKeymap(k, defaultBindings, c.getActions()); 1188 * 1189 * </code></pre> 1190 * The sets of bindings and actions may be empty but must be 1191 * non-<code>null</code>. 1192 * 1193 * @param map the keymap 1194 * @param bindings the bindings 1195 * @param actions the set of actions 1196 */ loadKeymap(Keymap map, KeyBinding[] bindings, Action[] actions)1197 public static void loadKeymap(Keymap map, KeyBinding[] bindings, Action[] actions) { 1198 Hashtable<String, Action> h = new Hashtable<String, Action>(); 1199 for (Action a : actions) { 1200 String value = (String)a.getValue(Action.NAME); 1201 h.put((value!=null ? value:""), a); 1202 } 1203 for (KeyBinding binding : bindings) { 1204 Action a = h.get(binding.actionName); 1205 if (a != null) { 1206 map.addActionForKeyStroke(binding.key, a); 1207 } 1208 } 1209 } 1210 1211 /** 1212 * Fetches the current color used to render the 1213 * caret. 1214 * 1215 * @return the color 1216 */ getCaretColor()1217 public Color getCaretColor() { 1218 return caretColor; 1219 } 1220 1221 /** 1222 * Sets the current color used to render the caret. 1223 * Setting to <code>null</code> effectively restores the default color. 1224 * Setting the color results in a PropertyChange event ("caretColor") 1225 * being fired. 1226 * 1227 * @param c the color 1228 * @see #getCaretColor 1229 */ 1230 @BeanProperty(preferred = true, description 1231 = "the color used to render the caret") setCaretColor(Color c)1232 public void setCaretColor(Color c) { 1233 Color old = caretColor; 1234 caretColor = c; 1235 firePropertyChange("caretColor", old, caretColor); 1236 } 1237 1238 /** 1239 * Fetches the current color used to render the 1240 * selection. 1241 * 1242 * @return the color 1243 */ getSelectionColor()1244 public Color getSelectionColor() { 1245 return selectionColor; 1246 } 1247 1248 /** 1249 * Sets the current color used to render the selection. 1250 * Setting the color to <code>null</code> is the same as setting 1251 * <code>Color.white</code>. Setting the color results in a 1252 * PropertyChange event ("selectionColor"). 1253 * 1254 * @param c the color 1255 * @see #getSelectionColor 1256 */ 1257 @BeanProperty(preferred = true, description 1258 = "color used to render selection background") setSelectionColor(Color c)1259 public void setSelectionColor(Color c) { 1260 Color old = selectionColor; 1261 selectionColor = c; 1262 firePropertyChange("selectionColor", old, selectionColor); 1263 } 1264 1265 /** 1266 * Fetches the current color used to render the 1267 * selected text. 1268 * 1269 * @return the color 1270 */ getSelectedTextColor()1271 public Color getSelectedTextColor() { 1272 return selectedTextColor; 1273 } 1274 1275 /** 1276 * Sets the current color used to render the selected text. 1277 * Setting the color to <code>null</code> is the same as 1278 * <code>Color.black</code>. Setting the color results in a 1279 * PropertyChange event ("selectedTextColor") being fired. 1280 * 1281 * @param c the color 1282 * @see #getSelectedTextColor 1283 */ 1284 @BeanProperty(preferred = true, description 1285 = "color used to render selected text") setSelectedTextColor(Color c)1286 public void setSelectedTextColor(Color c) { 1287 Color old = selectedTextColor; 1288 selectedTextColor = c; 1289 firePropertyChange("selectedTextColor", old, selectedTextColor); 1290 } 1291 1292 /** 1293 * Fetches the current color used to render the 1294 * disabled text. 1295 * 1296 * @return the color 1297 */ getDisabledTextColor()1298 public Color getDisabledTextColor() { 1299 return disabledTextColor; 1300 } 1301 1302 /** 1303 * Sets the current color used to render the 1304 * disabled text. Setting the color fires off a 1305 * PropertyChange event ("disabledTextColor"). 1306 * 1307 * @param c the color 1308 * @see #getDisabledTextColor 1309 */ 1310 @BeanProperty(preferred = true, description 1311 = "color used to render disabled text") setDisabledTextColor(Color c)1312 public void setDisabledTextColor(Color c) { 1313 Color old = disabledTextColor; 1314 disabledTextColor = c; 1315 firePropertyChange("disabledTextColor", old, disabledTextColor); 1316 } 1317 1318 /** 1319 * Replaces the currently selected content with new content 1320 * represented by the given string. If there is no selection 1321 * this amounts to an insert of the given text. If there 1322 * is no replacement text this amounts to a removal of the 1323 * current selection. 1324 * <p> 1325 * This is the method that is used by the default implementation 1326 * of the action for inserting content that gets bound to the 1327 * keymap actions. 1328 * 1329 * @param content the content to replace the selection with 1330 */ replaceSelection(String content)1331 public void replaceSelection(String content) { 1332 Document doc = getDocument(); 1333 if (doc != null) { 1334 try { 1335 boolean composedTextSaved = saveComposedText(caret.getDot()); 1336 int p0 = Math.min(caret.getDot(), caret.getMark()); 1337 int p1 = Math.max(caret.getDot(), caret.getMark()); 1338 if (doc instanceof AbstractDocument) { 1339 ((AbstractDocument)doc).replace(p0, p1 - p0, content,null); 1340 } 1341 else { 1342 if (p0 != p1) { 1343 doc.remove(p0, p1 - p0); 1344 } 1345 if (content != null && content.length() > 0) { 1346 doc.insertString(p0, content, null); 1347 } 1348 } 1349 if (composedTextSaved) { 1350 restoreComposedText(); 1351 } 1352 } catch (BadLocationException e) { 1353 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this); 1354 } 1355 } 1356 } 1357 1358 /** 1359 * Fetches a portion of the text represented by the 1360 * component. Returns an empty string if length is 0. 1361 * 1362 * @param offs the offset ≥ 0 1363 * @param len the length ≥ 0 1364 * @return the text 1365 * @exception BadLocationException if the offset or length are invalid 1366 */ getText(int offs, int len)1367 public String getText(int offs, int len) throws BadLocationException { 1368 return getDocument().getText(offs, len); 1369 } 1370 1371 /** 1372 * Converts the given location in the model to a place in 1373 * the view coordinate system. 1374 * The component must have a positive size for 1375 * this translation to be computed (i.e. layout cannot 1376 * be computed until the component has been sized). The 1377 * component does not have to be visible or painted. 1378 * 1379 * @param pos the position ≥ 0 1380 * @return the coordinates as a rectangle, with (r.x, r.y) as the location 1381 * in the coordinate system, or null if the component does 1382 * not yet have a positive size. 1383 * @exception BadLocationException if the given position does not 1384 * represent a valid location in the associated document 1385 * @see TextUI#modelToView 1386 * 1387 * @deprecated replaced by 1388 * {@link #modelToView2D(int)} 1389 */ 1390 @Deprecated(since = "9") modelToView(int pos)1391 public Rectangle modelToView(int pos) throws BadLocationException { 1392 return getUI().modelToView(this, pos); 1393 } 1394 1395 /** 1396 * Converts the given location in the model to a place in 1397 * the view coordinate system. 1398 * The component must have a positive size for 1399 * this translation to be computed (i.e. layout cannot 1400 * be computed until the component has been sized). The 1401 * component does not have to be visible or painted. 1402 * 1403 * @param pos the position {@code >= 0} 1404 * @return the coordinates as a rectangle, with (r.x, r.y) as the location 1405 * in the coordinate system, or null if the component does 1406 * not yet have a positive size. 1407 * @exception BadLocationException if the given position does not 1408 * represent a valid location in the associated document 1409 * @see TextUI#modelToView2D 1410 * 1411 * @since 9 1412 */ modelToView2D(int pos)1413 public Rectangle2D modelToView2D(int pos) throws BadLocationException { 1414 return getUI().modelToView2D(this, pos, Position.Bias.Forward); 1415 } 1416 1417 /** 1418 * Converts the given place in the view coordinate system 1419 * to the nearest representative location in the model. 1420 * The component must have a positive size for 1421 * this translation to be computed (i.e. layout cannot 1422 * be computed until the component has been sized). The 1423 * component does not have to be visible or painted. 1424 * 1425 * @param pt the location in the view to translate 1426 * @return the offset ≥ 0 from the start of the document, 1427 * or -1 if the component does not yet have a positive 1428 * size. 1429 * @see TextUI#viewToModel 1430 * 1431 * @deprecated replaced by 1432 * {@link #viewToModel2D(Point2D)} 1433 */ 1434 @Deprecated(since = "9") viewToModel(Point pt)1435 public int viewToModel(Point pt) { 1436 return getUI().viewToModel(this, pt); 1437 } 1438 1439 /** 1440 * Converts the given place in the view coordinate system 1441 * to the nearest representative location in the model. 1442 * The component must have a positive size for 1443 * this translation to be computed (i.e. layout cannot 1444 * be computed until the component has been sized). The 1445 * component does not have to be visible or painted. 1446 * 1447 * @param pt the location in the view to translate 1448 * @return the offset {@code >= 0} from the start of the document, 1449 * or {@code -1} if the component does not yet have a positive 1450 * size. 1451 * @see TextUI#viewToModel2D 1452 * 1453 * @since 9 1454 */ viewToModel2D(Point2D pt)1455 public int viewToModel2D(Point2D pt) { 1456 return getUI().viewToModel2D(this, pt, new Position.Bias[1]); 1457 } 1458 1459 /** 1460 * Transfers the currently selected range in the associated 1461 * text model to the system clipboard, removing the contents 1462 * from the model. The current selection is reset. Does nothing 1463 * for <code>null</code> selections. 1464 * 1465 * @see java.awt.Toolkit#getSystemClipboard 1466 * @see java.awt.datatransfer.Clipboard 1467 */ cut()1468 public void cut() { 1469 if (isEditable() && isEnabled()) { 1470 invokeAction("cut", TransferHandler.getCutAction()); 1471 } 1472 } 1473 1474 /** 1475 * Transfers the currently selected range in the associated 1476 * text model to the system clipboard, leaving the contents 1477 * in the text model. The current selection remains intact. 1478 * Does nothing for <code>null</code> selections. 1479 * 1480 * @see java.awt.Toolkit#getSystemClipboard 1481 * @see java.awt.datatransfer.Clipboard 1482 */ copy()1483 public void copy() { 1484 invokeAction("copy", TransferHandler.getCopyAction()); 1485 } 1486 1487 /** 1488 * Transfers the contents of the system clipboard into the 1489 * associated text model. If there is a selection in the 1490 * associated view, it is replaced with the contents of the 1491 * clipboard. If there is no selection, the clipboard contents 1492 * are inserted in front of the current insert position in 1493 * the associated view. If the clipboard is empty, does nothing. 1494 * 1495 * @see #replaceSelection 1496 * @see java.awt.Toolkit#getSystemClipboard 1497 * @see java.awt.datatransfer.Clipboard 1498 */ paste()1499 public void paste() { 1500 if (isEditable() && isEnabled()) { 1501 invokeAction("paste", TransferHandler.getPasteAction()); 1502 } 1503 } 1504 1505 /** 1506 * This is a convenience method that is only useful for 1507 * <code>cut</code>, <code>copy</code> and <code>paste</code>. If 1508 * an <code>Action</code> with the name <code>name</code> does not 1509 * exist in the <code>ActionMap</code>, this will attempt to install a 1510 * <code>TransferHandler</code> and then use <code>altAction</code>. 1511 */ invokeAction(String name, Action altAction)1512 private void invokeAction(String name, Action altAction) { 1513 ActionMap map = getActionMap(); 1514 Action action = null; 1515 1516 if (map != null) { 1517 action = map.get(name); 1518 } 1519 if (action == null) { 1520 installDefaultTransferHandlerIfNecessary(); 1521 action = altAction; 1522 } 1523 action.actionPerformed(new ActionEvent(this, 1524 ActionEvent.ACTION_PERFORMED, (String)action. 1525 getValue(Action.NAME), 1526 EventQueue.getMostRecentEventTime(), 1527 getCurrentEventModifiers())); 1528 } 1529 1530 /** 1531 * If the current <code>TransferHandler</code> is null, this will 1532 * install a new one. 1533 */ installDefaultTransferHandlerIfNecessary()1534 private void installDefaultTransferHandlerIfNecessary() { 1535 if (getTransferHandler() == null) { 1536 if (defaultTransferHandler == null) { 1537 defaultTransferHandler = new DefaultTransferHandler(); 1538 } 1539 setTransferHandler(defaultTransferHandler); 1540 } 1541 } 1542 1543 /** 1544 * Moves the caret to a new position, leaving behind a mark 1545 * defined by the last time <code>setCaretPosition</code> was 1546 * called. This forms a selection. 1547 * If the document is <code>null</code>, does nothing. The position 1548 * must be between 0 and the length of the component's text or else 1549 * an exception is thrown. 1550 * 1551 * @param pos the position 1552 * @exception IllegalArgumentException if the value supplied 1553 * for <code>position</code> is less than zero or greater 1554 * than the component's text length 1555 * @see #setCaretPosition 1556 */ moveCaretPosition(int pos)1557 public void moveCaretPosition(int pos) { 1558 Document doc = getDocument(); 1559 if (doc != null) { 1560 if (pos > doc.getLength() || pos < 0) { 1561 throw new IllegalArgumentException("bad position: " + pos); 1562 } 1563 caret.moveDot(pos); 1564 } 1565 } 1566 1567 /** 1568 * The bound property name for the focus accelerator. 1569 */ 1570 public static final String FOCUS_ACCELERATOR_KEY = "focusAcceleratorKey"; 1571 1572 /** 1573 * Sets the key accelerator that will cause the receiving text 1574 * component to get the focus. The accelerator will be the 1575 * key combination of the platform-specific modifier key and 1576 * the character given (converted to upper case). For example, 1577 * the ALT key is used as a modifier on Windows and the CTRL+ALT 1578 * combination is used on Mac. By default, there is no focus 1579 * accelerator key. Any previous key accelerator setting will be 1580 * superseded. A '\0' key setting will be registered, and has the 1581 * effect of turning off the focus accelerator. When the new key 1582 * is set, a PropertyChange event (FOCUS_ACCELERATOR_KEY) will be fired. 1583 * 1584 * @param aKey the key 1585 * @see #getFocusAccelerator 1586 */ 1587 @BeanProperty(description 1588 = "accelerator character used to grab focus") setFocusAccelerator(char aKey)1589 public void setFocusAccelerator(char aKey) { 1590 aKey = Character.toUpperCase(aKey); 1591 char old = focusAccelerator; 1592 focusAccelerator = aKey; 1593 // Fix for 4341002: value of FOCUS_ACCELERATOR_KEY is wrong. 1594 // So we fire both FOCUS_ACCELERATOR_KEY, for compatibility, 1595 // and the correct event here. 1596 firePropertyChange(FOCUS_ACCELERATOR_KEY, old, focusAccelerator); 1597 firePropertyChange("focusAccelerator", old, focusAccelerator); 1598 } 1599 1600 /** 1601 * Returns the key accelerator that will cause the receiving 1602 * text component to get the focus. Return '\0' if no focus 1603 * accelerator has been set. 1604 * 1605 * @return the key 1606 */ getFocusAccelerator()1607 public char getFocusAccelerator() { 1608 return focusAccelerator; 1609 } 1610 1611 /** 1612 * Initializes from a stream. This creates a 1613 * model of the type appropriate for the component 1614 * and initializes the model from the stream. 1615 * By default this will load the model as plain 1616 * text. Previous contents of the model are discarded. 1617 * 1618 * @param in the stream to read from 1619 * @param desc an object describing the stream; this 1620 * might be a string, a File, a URL, etc. Some kinds 1621 * of documents (such as html for example) might be 1622 * able to make use of this information; if non-<code>null</code>, 1623 * it is added as a property of the document 1624 * @exception IOException as thrown by the stream being 1625 * used to initialize 1626 * @see EditorKit#createDefaultDocument 1627 * @see #setDocument 1628 * @see PlainDocument 1629 */ read(Reader in, Object desc)1630 public void read(Reader in, Object desc) throws IOException { 1631 EditorKit kit = getUI().getEditorKit(this); 1632 Document doc = kit.createDefaultDocument(); 1633 if (desc != null) { 1634 doc.putProperty(Document.StreamDescriptionProperty, desc); 1635 } 1636 try { 1637 kit.read(in, doc, 0); 1638 setDocument(doc); 1639 } catch (BadLocationException e) { 1640 throw new IOException(e.getMessage()); 1641 } 1642 } 1643 1644 /** 1645 * Stores the contents of the model into the given 1646 * stream. By default this will store the model as plain 1647 * text. 1648 * 1649 * @param out the output stream 1650 * @exception IOException on any I/O error 1651 */ write(Writer out)1652 public void write(Writer out) throws IOException { 1653 Document doc = getDocument(); 1654 try { 1655 getUI().getEditorKit(this).write(out, doc, 0, doc.getLength()); 1656 } catch (BadLocationException e) { 1657 throw new IOException(e.getMessage()); 1658 } 1659 } 1660 removeNotify()1661 public void removeNotify() { 1662 super.removeNotify(); 1663 if (getFocusedComponent() == this) { 1664 AppContext.getAppContext().remove(FOCUSED_COMPONENT); 1665 } 1666 } 1667 1668 // --- java.awt.TextComponent methods ------------------------ 1669 1670 /** 1671 * Sets the position of the text insertion caret for the 1672 * <code>TextComponent</code>. Note that the caret tracks change, 1673 * so this may move if the underlying text of the component is changed. 1674 * If the document is <code>null</code>, does nothing. The position 1675 * must be between 0 and the length of the component's text or else 1676 * an exception is thrown. 1677 * 1678 * @param position the position 1679 * @exception IllegalArgumentException if the value supplied 1680 * for <code>position</code> is less than zero or greater 1681 * than the component's text length 1682 */ 1683 @BeanProperty(bound = false, description 1684 = "the caret position") setCaretPosition(int position)1685 public void setCaretPosition(int position) { 1686 Document doc = getDocument(); 1687 if (doc != null) { 1688 if (position > doc.getLength() || position < 0) { 1689 throw new IllegalArgumentException("bad position: " + position); 1690 } 1691 caret.setDot(position); 1692 } 1693 } 1694 1695 /** 1696 * Returns the position of the text insertion caret for the 1697 * text component. 1698 * 1699 * @return the position of the text insertion caret for the 1700 * text component ≥ 0 1701 */ 1702 @Transient getCaretPosition()1703 public int getCaretPosition() { 1704 return caret.getDot(); 1705 } 1706 1707 /** 1708 * Sets the text of this <code>TextComponent</code> 1709 * to the specified text. If the text is <code>null</code> 1710 * or empty, has the effect of simply deleting the old text. 1711 * When text has been inserted, the resulting caret location 1712 * is determined by the implementation of the caret class. 1713 * 1714 * <p> 1715 * Note that text is not a bound property, so no <code>PropertyChangeEvent 1716 * </code> is fired when it changes. To listen for changes to the text, 1717 * use <code>DocumentListener</code>. 1718 * 1719 * @param t the new text to be set 1720 * @see #getText 1721 * @see DefaultCaret 1722 */ 1723 @BeanProperty(bound = false, description 1724 = "the text of this component") setText(String t)1725 public void setText(String t) { 1726 try { 1727 Document doc = getDocument(); 1728 if (doc instanceof AbstractDocument) { 1729 ((AbstractDocument)doc).replace(0, doc.getLength(), t,null); 1730 } 1731 else { 1732 doc.remove(0, doc.getLength()); 1733 doc.insertString(0, t, null); 1734 } 1735 } catch (BadLocationException e) { 1736 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this); 1737 } 1738 } 1739 1740 /** 1741 * Returns the text contained in this <code>TextComponent</code>. 1742 * If the underlying document is <code>null</code>, 1743 * will give a <code>NullPointerException</code>. 1744 * 1745 * Note that text is not a bound property, so no <code>PropertyChangeEvent 1746 * </code> is fired when it changes. To listen for changes to the text, 1747 * use <code>DocumentListener</code>. 1748 * 1749 * @return the text 1750 * @exception NullPointerException if the document is <code>null</code> 1751 * @see #setText 1752 */ getText()1753 public String getText() { 1754 Document doc = getDocument(); 1755 String txt; 1756 try { 1757 txt = doc.getText(0, doc.getLength()); 1758 } catch (BadLocationException e) { 1759 txt = null; 1760 } 1761 return txt; 1762 } 1763 1764 /** 1765 * Returns the selected text contained in this 1766 * <code>TextComponent</code>. If the selection is 1767 * <code>null</code> or the document empty, returns <code>null</code>. 1768 * 1769 * @return the text 1770 * @exception IllegalArgumentException if the selection doesn't 1771 * have a valid mapping into the document for some reason 1772 * @see #setText 1773 */ 1774 @BeanProperty(bound = false) getSelectedText()1775 public String getSelectedText() { 1776 String txt = null; 1777 int p0 = Math.min(caret.getDot(), caret.getMark()); 1778 int p1 = Math.max(caret.getDot(), caret.getMark()); 1779 if (p0 != p1) { 1780 try { 1781 Document doc = getDocument(); 1782 txt = doc.getText(p0, p1 - p0); 1783 } catch (BadLocationException e) { 1784 throw new IllegalArgumentException(e.getMessage()); 1785 } 1786 } 1787 return txt; 1788 } 1789 1790 /** 1791 * Returns the boolean indicating whether this 1792 * <code>TextComponent</code> is editable or not. 1793 * 1794 * @return the boolean value 1795 * @see #setEditable 1796 */ isEditable()1797 public boolean isEditable() { 1798 return editable; 1799 } 1800 1801 /** 1802 * Sets the specified boolean to indicate whether or not this 1803 * <code>TextComponent</code> should be editable. 1804 * A PropertyChange event ("editable") is fired when the 1805 * state is changed. 1806 * 1807 * @param b the boolean to be set 1808 * @see #isEditable 1809 */ 1810 @BeanProperty(description 1811 = "specifies if the text can be edited") setEditable(boolean b)1812 public void setEditable(boolean b) { 1813 if (b != editable) { 1814 boolean oldVal = editable; 1815 editable = b; 1816 enableInputMethods(editable); 1817 firePropertyChange("editable", Boolean.valueOf(oldVal), Boolean.valueOf(editable)); 1818 repaint(); 1819 } 1820 } 1821 1822 /** 1823 * Returns the selected text's start position. Return 0 for an 1824 * empty document, or the value of dot if no selection. 1825 * 1826 * @return the start position ≥ 0 1827 */ 1828 @Transient getSelectionStart()1829 public int getSelectionStart() { 1830 int start = Math.min(caret.getDot(), caret.getMark()); 1831 return start; 1832 } 1833 1834 /** 1835 * Sets the selection start to the specified position. The new 1836 * starting point is constrained to be before or at the current 1837 * selection end. 1838 * <p> 1839 * This is available for backward compatibility to code 1840 * that called this method on <code>java.awt.TextComponent</code>. 1841 * This is implemented to forward to the <code>Caret</code> 1842 * implementation which is where the actual selection is maintained. 1843 * 1844 * @param selectionStart the start position of the text ≥ 0 1845 */ 1846 @BeanProperty(bound = false, description 1847 = "starting location of the selection.") setSelectionStart(int selectionStart)1848 public void setSelectionStart(int selectionStart) { 1849 /* Route through select method to enforce consistent policy 1850 * between selectionStart and selectionEnd. 1851 */ 1852 select(selectionStart, getSelectionEnd()); 1853 } 1854 1855 /** 1856 * Returns the selected text's end position. Return 0 if the document 1857 * is empty, or the value of dot if there is no selection. 1858 * 1859 * @return the end position ≥ 0 1860 */ 1861 @Transient getSelectionEnd()1862 public int getSelectionEnd() { 1863 int end = Math.max(caret.getDot(), caret.getMark()); 1864 return end; 1865 } 1866 1867 /** 1868 * Sets the selection end to the specified position. The new 1869 * end point is constrained to be at or after the current 1870 * selection start. 1871 * <p> 1872 * This is available for backward compatibility to code 1873 * that called this method on <code>java.awt.TextComponent</code>. 1874 * This is implemented to forward to the <code>Caret</code> 1875 * implementation which is where the actual selection is maintained. 1876 * 1877 * @param selectionEnd the end position of the text ≥ 0 1878 */ 1879 @BeanProperty(bound = false, description 1880 = "ending location of the selection.") setSelectionEnd(int selectionEnd)1881 public void setSelectionEnd(int selectionEnd) { 1882 /* Route through select method to enforce consistent policy 1883 * between selectionStart and selectionEnd. 1884 */ 1885 select(getSelectionStart(), selectionEnd); 1886 } 1887 1888 /** 1889 * Selects the text between the specified start and end positions. 1890 * <p> 1891 * This method sets the start and end positions of the 1892 * selected text, enforcing the restriction that the start position 1893 * must be greater than or equal to zero. The end position must be 1894 * greater than or equal to the start position, and less than or 1895 * equal to the length of the text component's text. 1896 * <p> 1897 * If the caller supplies values that are inconsistent or out of 1898 * bounds, the method enforces these constraints silently, and 1899 * without failure. Specifically, if the start position or end 1900 * position is greater than the length of the text, it is reset to 1901 * equal the text length. If the start position is less than zero, 1902 * it is reset to zero, and if the end position is less than the 1903 * start position, it is reset to the start position. 1904 * <p> 1905 * This call is provided for backward compatibility. 1906 * It is routed to a call to <code>setCaretPosition</code> 1907 * followed by a call to <code>moveCaretPosition</code>. 1908 * The preferred way to manage selection is by calling 1909 * those methods directly. 1910 * 1911 * @param selectionStart the start position of the text 1912 * @param selectionEnd the end position of the text 1913 * @see #setCaretPosition 1914 * @see #moveCaretPosition 1915 */ select(int selectionStart, int selectionEnd)1916 public void select(int selectionStart, int selectionEnd) { 1917 // argument adjustment done by java.awt.TextComponent 1918 int docLength = getDocument().getLength(); 1919 1920 if (selectionStart < 0) { 1921 selectionStart = 0; 1922 } 1923 if (selectionStart > docLength) { 1924 selectionStart = docLength; 1925 } 1926 if (selectionEnd > docLength) { 1927 selectionEnd = docLength; 1928 } 1929 if (selectionEnd < selectionStart) { 1930 selectionEnd = selectionStart; 1931 } 1932 1933 setCaretPosition(selectionStart); 1934 moveCaretPosition(selectionEnd); 1935 } 1936 1937 /** 1938 * Selects all the text in the <code>TextComponent</code>. 1939 * Does nothing on a <code>null</code> or empty document. 1940 */ selectAll()1941 public void selectAll() { 1942 Document doc = getDocument(); 1943 if (doc != null) { 1944 setCaretPosition(0); 1945 moveCaretPosition(doc.getLength()); 1946 } 1947 } 1948 1949 // --- Tooltip Methods --------------------------------------------- 1950 1951 /** 1952 * Returns the string to be used as the tooltip for <code>event</code>. 1953 * This will return one of: 1954 * <ol> 1955 * <li>If <code>setToolTipText</code> has been invoked with a 1956 * non-<code>null</code> 1957 * value, it will be returned, otherwise 1958 * <li>The value from invoking <code>getToolTipText</code> on 1959 * the UI will be returned. 1960 * </ol> 1961 * By default <code>JTextComponent</code> does not register 1962 * itself with the <code>ToolTipManager</code>. 1963 * This means that tooltips will NOT be shown from the 1964 * <code>TextUI</code> unless <code>registerComponent</code> has 1965 * been invoked on the <code>ToolTipManager</code>. 1966 * 1967 * @param event the event in question 1968 * @return the string to be used as the tooltip for <code>event</code> 1969 * @see javax.swing.JComponent#setToolTipText 1970 * @see javax.swing.plaf.TextUI#getToolTipText 1971 * @see javax.swing.ToolTipManager#registerComponent 1972 */ 1973 @SuppressWarnings("deprecation") getToolTipText(MouseEvent event)1974 public String getToolTipText(MouseEvent event) { 1975 String retValue = super.getToolTipText(event); 1976 1977 if (retValue == null) { 1978 TextUI ui = getUI(); 1979 if (ui != null) { 1980 retValue = ui.getToolTipText(this, new Point(event.getX(), 1981 event.getY())); 1982 } 1983 } 1984 return retValue; 1985 } 1986 1987 // --- Scrollable methods --------------------------------------------- 1988 1989 /** 1990 * Returns the preferred size of the viewport for a view component. 1991 * This is implemented to do the default behavior of returning 1992 * the preferred size of the component. 1993 * 1994 * @return the <code>preferredSize</code> of a <code>JViewport</code> 1995 * whose view is this <code>Scrollable</code> 1996 */ 1997 @BeanProperty(bound = false) getPreferredScrollableViewportSize()1998 public Dimension getPreferredScrollableViewportSize() { 1999 return getPreferredSize(); 2000 } 2001 2002 2003 /** 2004 * Components that display logical rows or columns should compute 2005 * the scroll increment that will completely expose one new row 2006 * or column, depending on the value of orientation. Ideally, 2007 * components should handle a partially exposed row or column by 2008 * returning the distance required to completely expose the item. 2009 * <p> 2010 * The default implementation of this is to simply return 10% of 2011 * the visible area. Subclasses are likely to be able to provide 2012 * a much more reasonable value. 2013 * 2014 * @param visibleRect the view area visible within the viewport 2015 * @param orientation either <code>SwingConstants.VERTICAL</code> or 2016 * <code>SwingConstants.HORIZONTAL</code> 2017 * @param direction less than zero to scroll up/left, greater than 2018 * zero for down/right 2019 * @return the "unit" increment for scrolling in the specified direction 2020 * @exception IllegalArgumentException for an invalid orientation 2021 * @see JScrollBar#setUnitIncrement 2022 */ getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)2023 public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { 2024 switch(orientation) { 2025 case SwingConstants.VERTICAL: 2026 return visibleRect.height / 10; 2027 case SwingConstants.HORIZONTAL: 2028 return visibleRect.width / 10; 2029 default: 2030 throw new IllegalArgumentException("Invalid orientation: " + orientation); 2031 } 2032 } 2033 2034 2035 /** 2036 * Components that display logical rows or columns should compute 2037 * the scroll increment that will completely expose one block 2038 * of rows or columns, depending on the value of orientation. 2039 * <p> 2040 * The default implementation of this is to simply return the visible 2041 * area. Subclasses will likely be able to provide a much more 2042 * reasonable value. 2043 * 2044 * @param visibleRect the view area visible within the viewport 2045 * @param orientation either <code>SwingConstants.VERTICAL</code> or 2046 * <code>SwingConstants.HORIZONTAL</code> 2047 * @param direction less than zero to scroll up/left, greater than zero 2048 * for down/right 2049 * @return the "block" increment for scrolling in the specified direction 2050 * @exception IllegalArgumentException for an invalid orientation 2051 * @see JScrollBar#setBlockIncrement 2052 */ getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)2053 public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { 2054 switch(orientation) { 2055 case SwingConstants.VERTICAL: 2056 return visibleRect.height; 2057 case SwingConstants.HORIZONTAL: 2058 return visibleRect.width; 2059 default: 2060 throw new IllegalArgumentException("Invalid orientation: " + orientation); 2061 } 2062 } 2063 2064 2065 /** 2066 * Returns true if a viewport should always force the width of this 2067 * <code>Scrollable</code> to match the width of the viewport. 2068 * For example a normal text view that supported line wrapping 2069 * would return true here, since it would be undesirable for 2070 * wrapped lines to disappear beyond the right 2071 * edge of the viewport. Note that returning true for a 2072 * <code>Scrollable</code> whose ancestor is a <code>JScrollPane</code> 2073 * effectively disables horizontal scrolling. 2074 * <p> 2075 * Scrolling containers, like <code>JViewport</code>, 2076 * will use this method each time they are validated. 2077 * 2078 * @return true if a viewport should force the <code>Scrollable</code>s 2079 * width to match its own 2080 */ 2081 @BeanProperty(bound = false) getScrollableTracksViewportWidth()2082 public boolean getScrollableTracksViewportWidth() { 2083 Container parent = SwingUtilities.getUnwrappedParent(this); 2084 if (parent instanceof JViewport) { 2085 return parent.getWidth() > getPreferredSize().width; 2086 } 2087 return false; 2088 } 2089 2090 /** 2091 * Returns true if a viewport should always force the height of this 2092 * <code>Scrollable</code> to match the height of the viewport. 2093 * For example a columnar text view that flowed text in left to 2094 * right columns could effectively disable vertical scrolling by 2095 * returning true here. 2096 * <p> 2097 * Scrolling containers, like <code>JViewport</code>, 2098 * will use this method each time they are validated. 2099 * 2100 * @return true if a viewport should force the Scrollables height 2101 * to match its own 2102 */ 2103 @BeanProperty(bound = false) getScrollableTracksViewportHeight()2104 public boolean getScrollableTracksViewportHeight() { 2105 Container parent = SwingUtilities.getUnwrappedParent(this); 2106 if (parent instanceof JViewport) { 2107 return parent.getHeight() > getPreferredSize().height; 2108 } 2109 return false; 2110 } 2111 2112 2113 ////////////////// 2114 // Printing Support 2115 ////////////////// 2116 2117 /** 2118 * A convenience print method that displays a print dialog, and then 2119 * prints this {@code JTextComponent} in <i>interactive</i> mode with no 2120 * header or footer text. Note: this method 2121 * blocks until printing is done. 2122 * <p> 2123 * Note: In <i>headless</i> mode, no dialogs will be shown. 2124 * 2125 * <p> This method calls the full featured 2126 * {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean) 2127 * print} method to perform printing. 2128 * @return {@code true}, unless printing is canceled by the user 2129 * @throws PrinterException if an error in the print system causes the job 2130 * to be aborted 2131 * @throws SecurityException if this thread is not allowed to 2132 * initiate a print job request 2133 * 2134 * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean) 2135 * 2136 * @since 1.6 2137 */ 2138 print()2139 public boolean print() throws PrinterException { 2140 return print(null, null, true, null, null, true); 2141 } 2142 2143 /** 2144 * A convenience print method that displays a print dialog, and then 2145 * prints this {@code JTextComponent} in <i>interactive</i> mode with 2146 * the specified header and footer text. Note: this method 2147 * blocks until printing is done. 2148 * <p> 2149 * Note: In <i>headless</i> mode, no dialogs will be shown. 2150 * 2151 * <p> This method calls the full featured 2152 * {@link #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean) 2153 * print} method to perform printing. 2154 * @param headerFormat the text, in {@code MessageFormat}, to be 2155 * used as the header, or {@code null} for no header 2156 * @param footerFormat the text, in {@code MessageFormat}, to be 2157 * used as the footer, or {@code null} for no footer 2158 * @return {@code true}, unless printing is canceled by the user 2159 * @throws PrinterException if an error in the print system causes the job 2160 * to be aborted 2161 * @throws SecurityException if this thread is not allowed to 2162 * initiate a print job request 2163 * 2164 * @see #print(MessageFormat, MessageFormat, boolean, PrintService, PrintRequestAttributeSet, boolean) 2165 * @see java.text.MessageFormat 2166 * @since 1.6 2167 */ print(final MessageFormat headerFormat, final MessageFormat footerFormat)2168 public boolean print(final MessageFormat headerFormat, 2169 final MessageFormat footerFormat) throws PrinterException { 2170 return print(headerFormat, footerFormat, true, null, null, true); 2171 } 2172 2173 /** 2174 * Prints the content of this {@code JTextComponent}. Note: this method 2175 * blocks until printing is done. 2176 * 2177 * <p> 2178 * Page header and footer text can be added to the output by providing 2179 * {@code MessageFormat} arguments. The printing code requests 2180 * {@code Strings} from the formats, providing a single item which may be 2181 * included in the formatted string: an {@code Integer} representing the 2182 * current page number. 2183 * 2184 * <p> 2185 * {@code showPrintDialog boolean} parameter allows you to specify whether 2186 * a print dialog is displayed to the user. When it is, the user 2187 * may use the dialog to change printing attributes or even cancel the 2188 * print. 2189 * 2190 * <p> 2191 * {@code service} allows you to provide the initial 2192 * {@code PrintService} for the print dialog, or to specify 2193 * {@code PrintService} to print to when the dialog is not shown. 2194 * 2195 * <p> 2196 * {@code attributes} can be used to provide the 2197 * initial values for the print dialog, or to supply any needed 2198 * attributes when the dialog is not shown. {@code attributes} can 2199 * be used to control how the job will print, for example 2200 * <i>duplex</i> or <i>single-sided</i>. 2201 * 2202 * <p> 2203 * {@code interactive boolean} parameter allows you to specify 2204 * whether to perform printing in <i>interactive</i> 2205 * mode. If {@code true}, a progress dialog, with an abort option, 2206 * is displayed for the duration of printing. This dialog is 2207 * <i>modal</i> when {@code print} is invoked on the <i>Event Dispatch 2208 * Thread</i> and <i>non-modal</i> otherwise. <b>Warning</b>: 2209 * calling this method on the <i>Event Dispatch Thread</i> with {@code 2210 * interactive false} blocks <i>all</i> events, including repaints, from 2211 * being processed until printing is complete. It is only 2212 * recommended when printing from an application with no 2213 * visible GUI. 2214 * 2215 * <p> 2216 * Note: In <i>headless</i> mode, {@code showPrintDialog} and 2217 * {@code interactive} parameters are ignored and no dialogs are 2218 * shown. 2219 * 2220 * <p> 2221 * This method ensures the {@code document} is not mutated during printing. 2222 * To indicate it visually, {@code setEnabled(false)} is set for the 2223 * duration of printing. 2224 * 2225 * <p> 2226 * This method uses {@link #getPrintable} to render document content. 2227 * 2228 * <p> 2229 * This method is thread-safe, although most Swing methods are not. Please 2230 * see <A 2231 * HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html"> 2232 * Concurrency in Swing</A> for more information. 2233 * 2234 * <p> 2235 * <b>Sample Usage</b>. This code snippet shows a cross-platform print 2236 * dialog and then prints the {@code JTextComponent} in <i>interactive</i> mode 2237 * unless the user cancels the dialog: 2238 * 2239 * <pre> 2240 * textComponent.print(new MessageFormat("My text component header"), 2241 * new MessageFormat("Footer. Page - {0}"), true, null, null, true); 2242 * </pre> 2243 * <p> 2244 * Executing this code off the <i>Event Dispatch Thread</i> 2245 * performs printing on the <i>background</i>. 2246 * The following pattern might be used for <i>background</i> 2247 * printing: 2248 * <pre> 2249 * FutureTask<Boolean> future = 2250 * new FutureTask<Boolean>( 2251 * new Callable<Boolean>() { 2252 * public Boolean call() { 2253 * return textComponent.print(.....); 2254 * } 2255 * }); 2256 * executor.execute(future); 2257 * </pre> 2258 * 2259 * @param headerFormat the text, in {@code MessageFormat}, to be 2260 * used as the header, or {@code null} for no header 2261 * @param footerFormat the text, in {@code MessageFormat}, to be 2262 * used as the footer, or {@code null} for no footer 2263 * @param showPrintDialog {@code true} to display a print dialog, 2264 * {@code false} otherwise 2265 * @param service initial {@code PrintService}, or {@code null} for the 2266 * default 2267 * @param attributes the job attributes to be applied to the print job, or 2268 * {@code null} for none 2269 * @param interactive whether to print in an interactive mode 2270 * @return {@code true}, unless printing is canceled by the user 2271 * @throws PrinterException if an error in the print system causes the job 2272 * to be aborted 2273 * @throws SecurityException if this thread is not allowed to 2274 * initiate a print job request 2275 * 2276 * @see #getPrintable 2277 * @see java.text.MessageFormat 2278 * @see java.awt.GraphicsEnvironment#isHeadless 2279 * @see java.util.concurrent.FutureTask 2280 * 2281 * @since 1.6 2282 */ print(final MessageFormat headerFormat, final MessageFormat footerFormat, final boolean showPrintDialog, final PrintService service, final PrintRequestAttributeSet attributes, final boolean interactive)2283 public boolean print(final MessageFormat headerFormat, 2284 final MessageFormat footerFormat, 2285 final boolean showPrintDialog, 2286 final PrintService service, 2287 final PrintRequestAttributeSet attributes, 2288 final boolean interactive) 2289 throws PrinterException { 2290 2291 final PrinterJob job = PrinterJob.getPrinterJob(); 2292 final Printable printable; 2293 final PrintingStatus printingStatus; 2294 final boolean isHeadless = GraphicsEnvironment.isHeadless(); 2295 final boolean isEventDispatchThread = 2296 SwingUtilities.isEventDispatchThread(); 2297 final Printable textPrintable = getPrintable(headerFormat, footerFormat); 2298 if (interactive && ! isHeadless) { 2299 printingStatus = 2300 PrintingStatus.createPrintingStatus(this, job); 2301 printable = 2302 printingStatus.createNotificationPrintable(textPrintable); 2303 } else { 2304 printingStatus = null; 2305 printable = textPrintable; 2306 } 2307 2308 if (service != null) { 2309 job.setPrintService(service); 2310 } 2311 2312 job.setPrintable(printable); 2313 2314 final PrintRequestAttributeSet attr = (attributes == null) 2315 ? new HashPrintRequestAttributeSet() 2316 : attributes; 2317 2318 if (showPrintDialog && ! isHeadless && ! job.printDialog(attr)) { 2319 return false; 2320 } 2321 2322 /* 2323 * there are three cases for printing: 2324 * 1. print non interactively (! interactive || isHeadless) 2325 * 2. print interactively off EDT 2326 * 3. print interactively on EDT 2327 * 2328 * 1 and 2 prints on the current thread (3 prints on another thread) 2329 * 2 and 3 deal with PrintingStatusDialog 2330 */ 2331 final Callable<Object> doPrint = 2332 new Callable<Object>() { 2333 public Object call() throws Exception { 2334 try { 2335 job.print(attr); 2336 } finally { 2337 if (printingStatus != null) { 2338 printingStatus.dispose(); 2339 } 2340 } 2341 return null; 2342 } 2343 }; 2344 2345 final FutureTask<Object> futurePrinting = 2346 new FutureTask<Object>(doPrint); 2347 2348 final Runnable runnablePrinting = 2349 new Runnable() { 2350 public void run() { 2351 //disable component 2352 boolean wasEnabled = false; 2353 if (isEventDispatchThread) { 2354 if (isEnabled()) { 2355 wasEnabled = true; 2356 setEnabled(false); 2357 } 2358 } else { 2359 try { 2360 wasEnabled = SwingUtilities2.submit( 2361 new Callable<Boolean>() { 2362 public Boolean call() throws Exception { 2363 boolean rv = isEnabled(); 2364 if (rv) { 2365 setEnabled(false); 2366 } 2367 return rv; 2368 } 2369 }).get(); 2370 } catch (InterruptedException e) { 2371 throw new RuntimeException(e); 2372 } catch (ExecutionException e) { 2373 Throwable cause = e.getCause(); 2374 if (cause instanceof Error) { 2375 throw (Error) cause; 2376 } 2377 if (cause instanceof RuntimeException) { 2378 throw (RuntimeException) cause; 2379 } 2380 throw new AssertionError(cause); 2381 } 2382 } 2383 2384 getDocument().render(futurePrinting); 2385 2386 //enable component 2387 if (wasEnabled) { 2388 if (isEventDispatchThread) { 2389 setEnabled(true); 2390 } else { 2391 try { 2392 SwingUtilities2.submit( 2393 new Runnable() { 2394 public void run() { 2395 setEnabled(true); 2396 } 2397 }, null).get(); 2398 } catch (InterruptedException e) { 2399 throw new RuntimeException(e); 2400 } catch (ExecutionException e) { 2401 Throwable cause = e.getCause(); 2402 if (cause instanceof Error) { 2403 throw (Error) cause; 2404 } 2405 if (cause instanceof RuntimeException) { 2406 throw (RuntimeException) cause; 2407 } 2408 throw new AssertionError(cause); 2409 } 2410 } 2411 } 2412 } 2413 }; 2414 2415 if (! interactive || isHeadless) { 2416 runnablePrinting.run(); 2417 } else { 2418 if (isEventDispatchThread) { 2419 new Thread(null, runnablePrinting, 2420 "JTextComponentPrint", 0, false ).start(); 2421 printingStatus.showModal(true); 2422 } else { 2423 printingStatus.showModal(false); 2424 runnablePrinting.run(); 2425 } 2426 } 2427 2428 //the printing is done successfully or otherwise. 2429 //dialog is hidden if needed. 2430 try { 2431 futurePrinting.get(); 2432 } catch (InterruptedException e) { 2433 throw new RuntimeException(e); 2434 } catch (ExecutionException e) { 2435 Throwable cause = e.getCause(); 2436 if (cause instanceof PrinterAbortException) { 2437 if (printingStatus != null 2438 && printingStatus.isAborted()) { 2439 return false; 2440 } else { 2441 throw (PrinterAbortException) cause; 2442 } 2443 } else if (cause instanceof PrinterException) { 2444 throw (PrinterException) cause; 2445 } else if (cause instanceof RuntimeException) { 2446 throw (RuntimeException) cause; 2447 } else if (cause instanceof Error) { 2448 throw (Error) cause; 2449 } else { 2450 throw new AssertionError(cause); 2451 } 2452 } 2453 return true; 2454 } 2455 2456 2457 /** 2458 * Returns a {@code Printable} to use for printing the content of this 2459 * {@code JTextComponent}. The returned {@code Printable} prints 2460 * the document as it looks on the screen except being reformatted 2461 * to fit the paper. 2462 * The returned {@code Printable} can be wrapped inside another 2463 * {@code Printable} in order to create complex reports and 2464 * documents. 2465 * 2466 * 2467 * <p> 2468 * The returned {@code Printable} shares the {@code document} with this 2469 * {@code JTextComponent}. It is the responsibility of the developer to 2470 * ensure that the {@code document} is not mutated while this {@code Printable} 2471 * is used. Printing behavior is undefined when the {@code document} is 2472 * mutated during printing. 2473 * 2474 * <p> 2475 * Page header and footer text can be added to the output by providing 2476 * {@code MessageFormat} arguments. The printing code requests 2477 * {@code Strings} from the formats, providing a single item which may be 2478 * included in the formatted string: an {@code Integer} representing the 2479 * current page number. 2480 * 2481 * <p> 2482 * The returned {@code Printable} when printed, formats the 2483 * document content appropriately for the page size. For correct 2484 * line wrapping the {@code imageable width} of all pages must be the 2485 * same. See {@link java.awt.print.PageFormat#getImageableWidth}. 2486 * 2487 * <p> 2488 * This method is thread-safe, although most Swing methods are not. Please 2489 * see <A 2490 * HREF="https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html"> 2491 * Concurrency in Swing</A> for more information. 2492 * 2493 * <p> 2494 * The returned {@code Printable} can be printed on any thread. 2495 * 2496 * <p> 2497 * This implementation returned {@code Printable} performs all painting on 2498 * the <i>Event Dispatch Thread</i>, regardless of what thread it is 2499 * used on. 2500 * 2501 * @param headerFormat the text, in {@code MessageFormat}, to be 2502 * used as the header, or {@code null} for no header 2503 * @param footerFormat the text, in {@code MessageFormat}, to be 2504 * used as the footer, or {@code null} for no footer 2505 * @return a {@code Printable} for use in printing content of this 2506 * {@code JTextComponent} 2507 * 2508 * 2509 * @see java.awt.print.Printable 2510 * @see java.awt.print.PageFormat 2511 * @see javax.swing.text.Document#render(java.lang.Runnable) 2512 * 2513 * @since 1.6 2514 */ getPrintable(final MessageFormat headerFormat, final MessageFormat footerFormat)2515 public Printable getPrintable(final MessageFormat headerFormat, 2516 final MessageFormat footerFormat) { 2517 return TextComponentPrintable.getPrintable( 2518 this, headerFormat, footerFormat); 2519 } 2520 2521 2522 ///////////////// 2523 // Accessibility support 2524 //////////////// 2525 2526 2527 /** 2528 * Gets the <code>AccessibleContext</code> associated with this 2529 * <code>JTextComponent</code>. For text components, 2530 * the <code>AccessibleContext</code> takes the form of an 2531 * <code>AccessibleJTextComponent</code>. 2532 * A new <code>AccessibleJTextComponent</code> instance 2533 * is created if necessary. 2534 * 2535 * @return an <code>AccessibleJTextComponent</code> that serves as the 2536 * <code>AccessibleContext</code> of this 2537 * <code>JTextComponent</code> 2538 */ 2539 @BeanProperty(bound = false) getAccessibleContext()2540 public AccessibleContext getAccessibleContext() { 2541 if (accessibleContext == null) { 2542 accessibleContext = new AccessibleJTextComponent(); 2543 } 2544 return accessibleContext; 2545 } 2546 2547 /** 2548 * This class implements accessibility support for the 2549 * <code>JTextComponent</code> class. It provides an implementation of 2550 * the Java Accessibility API appropriate to menu user-interface elements. 2551 * <p> 2552 * <strong>Warning:</strong> 2553 * Serialized objects of this class will not be compatible with 2554 * future Swing releases. The current serialization support is 2555 * appropriate for short term storage or RMI between applications running 2556 * the same version of Swing. As of 1.4, support for long term storage 2557 * of all JavaBeans 2558 * has been added to the <code>java.beans</code> package. 2559 * Please see {@link java.beans.XMLEncoder}. 2560 */ 2561 @SuppressWarnings("serial") // Same-version serialization only 2562 public class AccessibleJTextComponent extends AccessibleJComponent 2563 implements AccessibleText, CaretListener, DocumentListener, 2564 AccessibleAction, AccessibleEditableText, 2565 AccessibleExtendedText { 2566 2567 int caretPos; 2568 Point oldLocationOnScreen; 2569 2570 /** 2571 * Constructs an AccessibleJTextComponent. Adds a listener to track 2572 * caret change. 2573 */ AccessibleJTextComponent()2574 public AccessibleJTextComponent() { 2575 Document doc = JTextComponent.this.getDocument(); 2576 if (doc != null) { 2577 doc.addDocumentListener(this); 2578 } 2579 JTextComponent.this.addCaretListener(this); 2580 caretPos = getCaretPosition(); 2581 2582 try { 2583 oldLocationOnScreen = getLocationOnScreen(); 2584 } catch (IllegalComponentStateException iae) { 2585 } 2586 2587 // Fire a ACCESSIBLE_VISIBLE_DATA_PROPERTY PropertyChangeEvent 2588 // when the text component moves (e.g., when scrolling). 2589 // Using an anonymous class since making AccessibleJTextComponent 2590 // implement ComponentListener would be an API change. 2591 JTextComponent.this.addComponentListener(new ComponentAdapter() { 2592 2593 public void componentMoved(ComponentEvent e) { 2594 try { 2595 Point newLocationOnScreen = getLocationOnScreen(); 2596 firePropertyChange(ACCESSIBLE_VISIBLE_DATA_PROPERTY, 2597 oldLocationOnScreen, 2598 newLocationOnScreen); 2599 2600 oldLocationOnScreen = newLocationOnScreen; 2601 } catch (IllegalComponentStateException iae) { 2602 } 2603 } 2604 }); 2605 } 2606 2607 /** 2608 * Handles caret updates (fire appropriate property change event, 2609 * which are AccessibleContext.ACCESSIBLE_CARET_PROPERTY and 2610 * AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY). 2611 * This keeps track of the dot position internally. When the caret 2612 * moves, the internal position is updated after firing the event. 2613 * 2614 * @param e the CaretEvent 2615 */ caretUpdate(CaretEvent e)2616 public void caretUpdate(CaretEvent e) { 2617 int dot = e.getDot(); 2618 int mark = e.getMark(); 2619 if (caretPos != dot) { 2620 // the caret moved 2621 firePropertyChange(ACCESSIBLE_CARET_PROPERTY, 2622 caretPos, dot); 2623 caretPos = dot; 2624 2625 try { 2626 oldLocationOnScreen = getLocationOnScreen(); 2627 } catch (IllegalComponentStateException iae) { 2628 } 2629 } 2630 if (mark != dot) { 2631 // there is a selection 2632 firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null, 2633 getSelectedText()); 2634 } 2635 } 2636 2637 // DocumentListener methods 2638 2639 /** 2640 * Handles document insert (fire appropriate property change event 2641 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY). 2642 * This tracks the changed offset via the event. 2643 * 2644 * @param e the DocumentEvent 2645 */ insertUpdate(DocumentEvent e)2646 public void insertUpdate(DocumentEvent e) { 2647 final Integer pos = e.getOffset(); 2648 if (SwingUtilities.isEventDispatchThread()) { 2649 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos); 2650 } else { 2651 Runnable doFire = new Runnable() { 2652 public void run() { 2653 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, 2654 null, pos); 2655 } 2656 }; 2657 SwingUtilities.invokeLater(doFire); 2658 } 2659 } 2660 2661 /** 2662 * Handles document remove (fire appropriate property change event, 2663 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY). 2664 * This tracks the changed offset via the event. 2665 * 2666 * @param e the DocumentEvent 2667 */ removeUpdate(DocumentEvent e)2668 public void removeUpdate(DocumentEvent e) { 2669 final Integer pos = e.getOffset(); 2670 if (SwingUtilities.isEventDispatchThread()) { 2671 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos); 2672 } else { 2673 Runnable doFire = new Runnable() { 2674 public void run() { 2675 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, 2676 null, pos); 2677 } 2678 }; 2679 SwingUtilities.invokeLater(doFire); 2680 } 2681 } 2682 2683 /** 2684 * Handles document remove (fire appropriate property change event, 2685 * which is AccessibleContext.ACCESSIBLE_TEXT_PROPERTY). 2686 * This tracks the changed offset via the event. 2687 * 2688 * @param e the DocumentEvent 2689 */ changedUpdate(DocumentEvent e)2690 public void changedUpdate(DocumentEvent e) { 2691 final Integer pos = e.getOffset(); 2692 if (SwingUtilities.isEventDispatchThread()) { 2693 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null, pos); 2694 } else { 2695 Runnable doFire = new Runnable() { 2696 public void run() { 2697 firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, 2698 null, pos); 2699 } 2700 }; 2701 SwingUtilities.invokeLater(doFire); 2702 } 2703 } 2704 2705 /** 2706 * Gets the state set of the JTextComponent. 2707 * The AccessibleStateSet of an object is composed of a set of 2708 * unique AccessibleState's. A change in the AccessibleStateSet 2709 * of an object will cause a PropertyChangeEvent to be fired 2710 * for the AccessibleContext.ACCESSIBLE_STATE_PROPERTY property. 2711 * 2712 * @return an instance of AccessibleStateSet containing the 2713 * current state set of the object 2714 * @see AccessibleStateSet 2715 * @see AccessibleState 2716 * @see #addPropertyChangeListener 2717 */ getAccessibleStateSet()2718 public AccessibleStateSet getAccessibleStateSet() { 2719 AccessibleStateSet states = super.getAccessibleStateSet(); 2720 if (JTextComponent.this.isEditable()) { 2721 states.add(AccessibleState.EDITABLE); 2722 } 2723 return states; 2724 } 2725 2726 2727 /** 2728 * Gets the role of this object. 2729 * 2730 * @return an instance of AccessibleRole describing the role of the 2731 * object (AccessibleRole.TEXT) 2732 * @see AccessibleRole 2733 */ getAccessibleRole()2734 public AccessibleRole getAccessibleRole() { 2735 return AccessibleRole.TEXT; 2736 } 2737 2738 /** 2739 * Get the AccessibleText associated with this object. In the 2740 * implementation of the Java Accessibility API for this class, 2741 * return this object, which is responsible for implementing the 2742 * AccessibleText interface on behalf of itself. 2743 * 2744 * @return this object 2745 */ getAccessibleText()2746 public AccessibleText getAccessibleText() { 2747 return this; 2748 } 2749 2750 2751 // --- interface AccessibleText methods ------------------------ 2752 2753 /** 2754 * Many of these methods are just convenience methods; they 2755 * just call the equivalent on the parent 2756 */ 2757 2758 /** 2759 * Given a point in local coordinates, return the zero-based index 2760 * of the character under that Point. If the point is invalid, 2761 * this method returns -1. 2762 * 2763 * @param p the Point in local coordinates 2764 * @return the zero-based index of the character under Point p. 2765 */ getIndexAtPoint(Point p)2766 public int getIndexAtPoint(Point p) { 2767 if (p == null) { 2768 return -1; 2769 } 2770 return JTextComponent.this.viewToModel(p); 2771 } 2772 2773 /** 2774 * Gets the editor's drawing rectangle. Stolen 2775 * from the unfortunately named 2776 * BasicTextUI.getVisibleEditorRect() 2777 * 2778 * @return the bounding box for the root view 2779 */ getRootEditorRect()2780 Rectangle getRootEditorRect() { 2781 Rectangle alloc = JTextComponent.this.getBounds(); 2782 if ((alloc.width > 0) && (alloc.height > 0)) { 2783 alloc.x = alloc.y = 0; 2784 Insets insets = JTextComponent.this.getInsets(); 2785 alloc.x += insets.left; 2786 alloc.y += insets.top; 2787 alloc.width -= insets.left + insets.right; 2788 alloc.height -= insets.top + insets.bottom; 2789 return alloc; 2790 } 2791 return null; 2792 } 2793 2794 /** 2795 * Determines the bounding box of the character at the given 2796 * index into the string. The bounds are returned in local 2797 * coordinates. If the index is invalid a null rectangle 2798 * is returned. 2799 * 2800 * The screen coordinates returned are "unscrolled coordinates" 2801 * if the JTextComponent is contained in a JScrollPane in which 2802 * case the resulting rectangle should be composed with the parent 2803 * coordinates. A good algorithm to use is: 2804 * <pre> 2805 * Accessible a: 2806 * AccessibleText at = a.getAccessibleText(); 2807 * AccessibleComponent ac = a.getAccessibleComponent(); 2808 * Rectangle r = at.getCharacterBounds(); 2809 * Point p = ac.getLocation(); 2810 * r.x += p.x; 2811 * r.y += p.y; 2812 * </pre> 2813 * 2814 * Note: the JTextComponent must have a valid size (e.g. have 2815 * been added to a parent container whose ancestor container 2816 * is a valid top-level window) for this method to be able 2817 * to return a meaningful (non-null) value. 2818 * 2819 * @param i the index into the String ≥ 0 2820 * @return the screen coordinates of the character's bounding box 2821 */ getCharacterBounds(int i)2822 public Rectangle getCharacterBounds(int i) { 2823 if (i < 0 || i > model.getLength()-1) { 2824 return null; 2825 } 2826 TextUI ui = getUI(); 2827 if (ui == null) { 2828 return null; 2829 } 2830 Rectangle rect = null; 2831 Rectangle alloc = getRootEditorRect(); 2832 if (alloc == null) { 2833 return null; 2834 } 2835 if (model instanceof AbstractDocument) { 2836 ((AbstractDocument)model).readLock(); 2837 } 2838 try { 2839 View rootView = ui.getRootView(JTextComponent.this); 2840 if (rootView != null) { 2841 rootView.setSize(alloc.width, alloc.height); 2842 2843 Shape bounds = rootView.modelToView(i, 2844 Position.Bias.Forward, i+1, 2845 Position.Bias.Backward, alloc); 2846 2847 rect = (bounds instanceof Rectangle) ? 2848 (Rectangle)bounds : bounds.getBounds(); 2849 2850 } 2851 } catch (BadLocationException e) { 2852 } finally { 2853 if (model instanceof AbstractDocument) { 2854 ((AbstractDocument)model).readUnlock(); 2855 } 2856 } 2857 return rect; 2858 } 2859 2860 /** 2861 * Returns the number of characters (valid indices) 2862 * 2863 * @return the number of characters ≥ 0 2864 */ getCharCount()2865 public int getCharCount() { 2866 return model.getLength(); 2867 } 2868 2869 /** 2870 * Returns the zero-based offset of the caret. 2871 * 2872 * Note: The character to the right of the caret will have the 2873 * same index value as the offset (the caret is between 2874 * two characters). 2875 * 2876 * @return the zero-based offset of the caret. 2877 */ getCaretPosition()2878 public int getCaretPosition() { 2879 return JTextComponent.this.getCaretPosition(); 2880 } 2881 2882 /** 2883 * Returns the AttributeSet for a given character (at a given index). 2884 * 2885 * @param i the zero-based index into the text 2886 * @return the AttributeSet of the character 2887 */ getCharacterAttribute(int i)2888 public AttributeSet getCharacterAttribute(int i) { 2889 Element e = null; 2890 if (model instanceof AbstractDocument) { 2891 ((AbstractDocument)model).readLock(); 2892 } 2893 try { 2894 for (e = model.getDefaultRootElement(); ! e.isLeaf(); ) { 2895 int index = e.getElementIndex(i); 2896 e = e.getElement(index); 2897 } 2898 } finally { 2899 if (model instanceof AbstractDocument) { 2900 ((AbstractDocument)model).readUnlock(); 2901 } 2902 } 2903 return e.getAttributes(); 2904 } 2905 2906 2907 /** 2908 * Returns the start offset within the selected text. 2909 * If there is no selection, but there is 2910 * a caret, the start and end offsets will be the same. 2911 * Return 0 if the text is empty, or the caret position 2912 * if no selection. 2913 * 2914 * @return the index into the text of the start of the selection ≥ 0 2915 */ getSelectionStart()2916 public int getSelectionStart() { 2917 return JTextComponent.this.getSelectionStart(); 2918 } 2919 2920 /** 2921 * Returns the end offset within the selected text. 2922 * If there is no selection, but there is 2923 * a caret, the start and end offsets will be the same. 2924 * Return 0 if the text is empty, or the caret position 2925 * if no selection. 2926 * 2927 * @return the index into the text of the end of the selection ≥ 0 2928 */ getSelectionEnd()2929 public int getSelectionEnd() { 2930 return JTextComponent.this.getSelectionEnd(); 2931 } 2932 2933 /** 2934 * Returns the portion of the text that is selected. 2935 * 2936 * @return the text, null if no selection 2937 */ getSelectedText()2938 public String getSelectedText() { 2939 return JTextComponent.this.getSelectedText(); 2940 } 2941 2942 /** 2943 * IndexedSegment extends Segment adding the offset into the 2944 * the model the <code>Segment</code> was asked for. 2945 */ 2946 private class IndexedSegment extends Segment { 2947 /** 2948 * Offset into the model that the position represents. 2949 */ 2950 public int modelOffset; 2951 } 2952 2953 2954 // TIGER - 4170173 2955 /** 2956 * Returns the String at a given index. Whitespace 2957 * between words is treated as a word. 2958 * 2959 * @param part the CHARACTER, WORD, or SENTENCE to retrieve 2960 * @param index an index within the text 2961 * @return the letter, word, or sentence. 2962 * 2963 */ getAtIndex(int part, int index)2964 public String getAtIndex(int part, int index) { 2965 return getAtIndex(part, index, 0); 2966 } 2967 2968 2969 /** 2970 * Returns the String after a given index. Whitespace 2971 * between words is treated as a word. 2972 * 2973 * @param part the CHARACTER, WORD, or SENTENCE to retrieve 2974 * @param index an index within the text 2975 * @return the letter, word, or sentence. 2976 */ getAfterIndex(int part, int index)2977 public String getAfterIndex(int part, int index) { 2978 return getAtIndex(part, index, 1); 2979 } 2980 2981 2982 /** 2983 * Returns the String before a given index. Whitespace 2984 * between words is treated a word. 2985 * 2986 * @param part the CHARACTER, WORD, or SENTENCE to retrieve 2987 * @param index an index within the text 2988 * @return the letter, word, or sentence. 2989 */ getBeforeIndex(int part, int index)2990 public String getBeforeIndex(int part, int index) { 2991 return getAtIndex(part, index, -1); 2992 } 2993 2994 2995 /** 2996 * Gets the word, sentence, or character at <code>index</code>. 2997 * If <code>direction</code> is non-null this will find the 2998 * next/previous word/sentence/character. 2999 */ getAtIndex(int part, int index, int direction)3000 private String getAtIndex(int part, int index, int direction) { 3001 if (model instanceof AbstractDocument) { 3002 ((AbstractDocument)model).readLock(); 3003 } 3004 try { 3005 if (index < 0 || index >= model.getLength()) { 3006 return null; 3007 } 3008 switch (part) { 3009 case AccessibleText.CHARACTER: 3010 if (index + direction < model.getLength() && 3011 index + direction >= 0) { 3012 return model.getText(index + direction, 1); 3013 } 3014 break; 3015 3016 3017 case AccessibleText.WORD: 3018 case AccessibleText.SENTENCE: 3019 IndexedSegment seg = getSegmentAt(part, index); 3020 if (seg != null) { 3021 if (direction != 0) { 3022 int next; 3023 3024 3025 if (direction < 0) { 3026 next = seg.modelOffset - 1; 3027 } 3028 else { 3029 next = seg.modelOffset + direction * seg.count; 3030 } 3031 if (next >= 0 && next <= model.getLength()) { 3032 seg = getSegmentAt(part, next); 3033 } 3034 else { 3035 seg = null; 3036 } 3037 } 3038 if (seg != null) { 3039 return new String(seg.array, seg.offset, 3040 seg.count); 3041 } 3042 } 3043 break; 3044 3045 3046 default: 3047 break; 3048 } 3049 } catch (BadLocationException e) { 3050 } finally { 3051 if (model instanceof AbstractDocument) { 3052 ((AbstractDocument)model).readUnlock(); 3053 } 3054 } 3055 return null; 3056 } 3057 3058 3059 /* 3060 * Returns the paragraph element for the specified index. 3061 */ getParagraphElement(int index)3062 private Element getParagraphElement(int index) { 3063 if (model instanceof PlainDocument ) { 3064 PlainDocument sdoc = (PlainDocument)model; 3065 return sdoc.getParagraphElement(index); 3066 } else if (model instanceof StyledDocument) { 3067 StyledDocument sdoc = (StyledDocument)model; 3068 return sdoc.getParagraphElement(index); 3069 } else { 3070 Element para; 3071 for (para = model.getDefaultRootElement(); ! para.isLeaf(); ) { 3072 int pos = para.getElementIndex(index); 3073 para = para.getElement(pos); 3074 } 3075 if (para == null) { 3076 return null; 3077 } 3078 return para.getParentElement(); 3079 } 3080 } 3081 3082 /* 3083 * Returns a <code>Segment</code> containing the paragraph text 3084 * at <code>index</code>, or null if <code>index</code> isn't 3085 * valid. 3086 */ getParagraphElementText(int index)3087 private IndexedSegment getParagraphElementText(int index) 3088 throws BadLocationException { 3089 Element para = getParagraphElement(index); 3090 3091 3092 if (para != null) { 3093 IndexedSegment segment = new IndexedSegment(); 3094 try { 3095 int length = para.getEndOffset() - para.getStartOffset(); 3096 model.getText(para.getStartOffset(), length, segment); 3097 } catch (BadLocationException e) { 3098 return null; 3099 } 3100 segment.modelOffset = para.getStartOffset(); 3101 return segment; 3102 } 3103 return null; 3104 } 3105 3106 3107 /** 3108 * Returns the Segment at <code>index</code> representing either 3109 * the paragraph or sentence as identified by <code>part</code>, or 3110 * null if a valid paragraph/sentence can't be found. The offset 3111 * will point to the start of the word/sentence in the array, and 3112 * the modelOffset will point to the location of the word/sentence 3113 * in the model. 3114 */ getSegmentAt(int part, int index)3115 private IndexedSegment getSegmentAt(int part, int index) throws 3116 BadLocationException { 3117 IndexedSegment seg = getParagraphElementText(index); 3118 if (seg == null) { 3119 return null; 3120 } 3121 BreakIterator iterator; 3122 switch (part) { 3123 case AccessibleText.WORD: 3124 iterator = BreakIterator.getWordInstance(getLocale()); 3125 break; 3126 case AccessibleText.SENTENCE: 3127 iterator = BreakIterator.getSentenceInstance(getLocale()); 3128 break; 3129 default: 3130 return null; 3131 } 3132 seg.first(); 3133 iterator.setText(seg); 3134 int end = iterator.following(index - seg.modelOffset + seg.offset); 3135 if (end == BreakIterator.DONE) { 3136 return null; 3137 } 3138 if (end > seg.offset + seg.count) { 3139 return null; 3140 } 3141 int begin = iterator.previous(); 3142 if (begin == BreakIterator.DONE || 3143 begin >= seg.offset + seg.count) { 3144 return null; 3145 } 3146 seg.modelOffset = seg.modelOffset + begin - seg.offset; 3147 seg.offset = begin; 3148 seg.count = end - begin; 3149 return seg; 3150 } 3151 3152 // begin AccessibleEditableText methods ----- 3153 3154 /** 3155 * Returns the AccessibleEditableText interface for 3156 * this text component. 3157 * 3158 * @return the AccessibleEditableText interface 3159 * @since 1.4 3160 */ getAccessibleEditableText()3161 public AccessibleEditableText getAccessibleEditableText() { 3162 return this; 3163 } 3164 3165 /** 3166 * Sets the text contents to the specified string. 3167 * 3168 * @param s the string to set the text contents 3169 * @since 1.4 3170 */ setTextContents(String s)3171 public void setTextContents(String s) { 3172 JTextComponent.this.setText(s); 3173 } 3174 3175 /** 3176 * Inserts the specified string at the given index 3177 * 3178 * @param index the index in the text where the string will 3179 * be inserted 3180 * @param s the string to insert in the text 3181 * @since 1.4 3182 */ insertTextAtIndex(int index, String s)3183 public void insertTextAtIndex(int index, String s) { 3184 Document doc = JTextComponent.this.getDocument(); 3185 if (doc != null) { 3186 try { 3187 if (s != null && s.length() > 0) { 3188 boolean composedTextSaved = saveComposedText(index); 3189 doc.insertString(index, s, null); 3190 if (composedTextSaved) { 3191 restoreComposedText(); 3192 } 3193 } 3194 } catch (BadLocationException e) { 3195 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this); 3196 } 3197 } 3198 } 3199 3200 /** 3201 * Returns the text string between two indices. 3202 * 3203 * @param startIndex the starting index in the text 3204 * @param endIndex the ending index in the text 3205 * @return the text string between the indices 3206 * @since 1.4 3207 */ getTextRange(int startIndex, int endIndex)3208 public String getTextRange(int startIndex, int endIndex) { 3209 String txt = null; 3210 int p0 = Math.min(startIndex, endIndex); 3211 int p1 = Math.max(startIndex, endIndex); 3212 if (p0 != p1) { 3213 try { 3214 Document doc = JTextComponent.this.getDocument(); 3215 txt = doc.getText(p0, p1 - p0); 3216 } catch (BadLocationException e) { 3217 throw new IllegalArgumentException(e.getMessage()); 3218 } 3219 } 3220 return txt; 3221 } 3222 3223 /** 3224 * Deletes the text between two indices 3225 * 3226 * @param startIndex the starting index in the text 3227 * @param endIndex the ending index in the text 3228 * @since 1.4 3229 */ delete(int startIndex, int endIndex)3230 public void delete(int startIndex, int endIndex) { 3231 if (isEditable() && isEnabled()) { 3232 try { 3233 int p0 = Math.min(startIndex, endIndex); 3234 int p1 = Math.max(startIndex, endIndex); 3235 if (p0 != p1) { 3236 Document doc = getDocument(); 3237 doc.remove(p0, p1 - p0); 3238 } 3239 } catch (BadLocationException e) { 3240 } 3241 } else { 3242 UIManager.getLookAndFeel().provideErrorFeedback(JTextComponent.this); 3243 } 3244 } 3245 3246 /** 3247 * Cuts the text between two indices into the system clipboard. 3248 * 3249 * @param startIndex the starting index in the text 3250 * @param endIndex the ending index in the text 3251 * @since 1.4 3252 */ cut(int startIndex, int endIndex)3253 public void cut(int startIndex, int endIndex) { 3254 selectText(startIndex, endIndex); 3255 JTextComponent.this.cut(); 3256 } 3257 3258 /** 3259 * Pastes the text from the system clipboard into the text 3260 * starting at the specified index. 3261 * 3262 * @param startIndex the starting index in the text 3263 * @since 1.4 3264 */ paste(int startIndex)3265 public void paste(int startIndex) { 3266 setCaretPosition(startIndex); 3267 JTextComponent.this.paste(); 3268 } 3269 3270 /** 3271 * Replaces the text between two indices with the specified 3272 * string. 3273 * 3274 * @param startIndex the starting index in the text 3275 * @param endIndex the ending index in the text 3276 * @param s the string to replace the text between two indices 3277 * @since 1.4 3278 */ replaceText(int startIndex, int endIndex, String s)3279 public void replaceText(int startIndex, int endIndex, String s) { 3280 selectText(startIndex, endIndex); 3281 JTextComponent.this.replaceSelection(s); 3282 } 3283 3284 /** 3285 * Selects the text between two indices. 3286 * 3287 * @param startIndex the starting index in the text 3288 * @param endIndex the ending index in the text 3289 * @since 1.4 3290 */ selectText(int startIndex, int endIndex)3291 public void selectText(int startIndex, int endIndex) { 3292 JTextComponent.this.select(startIndex, endIndex); 3293 } 3294 3295 /** 3296 * Sets attributes for the text between two indices. 3297 * 3298 * @param startIndex the starting index in the text 3299 * @param endIndex the ending index in the text 3300 * @param as the attribute set 3301 * @see AttributeSet 3302 * @since 1.4 3303 */ setAttributes(int startIndex, int endIndex, AttributeSet as)3304 public void setAttributes(int startIndex, int endIndex, 3305 AttributeSet as) { 3306 3307 // Fixes bug 4487492 3308 Document doc = JTextComponent.this.getDocument(); 3309 if (doc != null && doc instanceof StyledDocument) { 3310 StyledDocument sDoc = (StyledDocument)doc; 3311 int offset = startIndex; 3312 int length = endIndex - startIndex; 3313 sDoc.setCharacterAttributes(offset, length, as, true); 3314 } 3315 } 3316 3317 // ----- end AccessibleEditableText methods 3318 3319 3320 // ----- begin AccessibleExtendedText methods 3321 3322 // Probably should replace the helper method getAtIndex() to return 3323 // instead an AccessibleTextSequence also for LINE & ATTRIBUTE_RUN 3324 // and then make the AccessibleText methods get[At|After|Before]Point 3325 // call this new method instead and return only the string portion 3326 3327 /** 3328 * Returns the AccessibleTextSequence at a given <code>index</code>. 3329 * If <code>direction</code> is non-null this will find the 3330 * next/previous word/sentence/character. 3331 * 3332 * @param part the <code>CHARACTER</code>, <code>WORD</code>, 3333 * <code>SENTENCE</code>, <code>LINE</code> or 3334 * <code>ATTRIBUTE_RUN</code> to retrieve 3335 * @param index an index within the text 3336 * @param direction is either -1, 0, or 1 3337 * @return an <code>AccessibleTextSequence</code> specifying the text 3338 * if <code>part</code> and <code>index</code> are valid. Otherwise, 3339 * <code>null</code> is returned. 3340 * 3341 * @see javax.accessibility.AccessibleText#CHARACTER 3342 * @see javax.accessibility.AccessibleText#WORD 3343 * @see javax.accessibility.AccessibleText#SENTENCE 3344 * @see javax.accessibility.AccessibleExtendedText#LINE 3345 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN 3346 * 3347 * @since 1.6 3348 */ getSequenceAtIndex(int part, int index, int direction)3349 private AccessibleTextSequence getSequenceAtIndex(int part, 3350 int index, int direction) { 3351 if (index < 0 || index >= model.getLength()) { 3352 return null; 3353 } 3354 if (direction < -1 || direction > 1) { 3355 return null; // direction must be 1, 0, or -1 3356 } 3357 3358 switch (part) { 3359 case AccessibleText.CHARACTER: 3360 if (model instanceof AbstractDocument) { 3361 ((AbstractDocument)model).readLock(); 3362 } 3363 AccessibleTextSequence charSequence = null; 3364 try { 3365 if (index + direction < model.getLength() && 3366 index + direction >= 0) { 3367 charSequence = 3368 new AccessibleTextSequence(index + direction, 3369 index + direction + 1, 3370 model.getText(index + direction, 1)); 3371 } 3372 3373 } catch (BadLocationException e) { 3374 // we are intentionally silent; our contract says we return 3375 // null if there is any failure in this method 3376 } finally { 3377 if (model instanceof AbstractDocument) { 3378 ((AbstractDocument)model).readUnlock(); 3379 } 3380 } 3381 return charSequence; 3382 3383 case AccessibleText.WORD: 3384 case AccessibleText.SENTENCE: 3385 if (model instanceof AbstractDocument) { 3386 ((AbstractDocument)model).readLock(); 3387 } 3388 AccessibleTextSequence rangeSequence = null; 3389 try { 3390 IndexedSegment seg = getSegmentAt(part, index); 3391 if (seg != null) { 3392 if (direction != 0) { 3393 int next; 3394 3395 if (direction < 0) { 3396 next = seg.modelOffset - 1; 3397 } 3398 else { 3399 next = seg.modelOffset + seg.count; 3400 } 3401 if (next >= 0 && next <= model.getLength()) { 3402 seg = getSegmentAt(part, next); 3403 } 3404 else { 3405 seg = null; 3406 } 3407 } 3408 if (seg != null && 3409 (seg.offset + seg.count) <= model.getLength()) { 3410 rangeSequence = 3411 new AccessibleTextSequence (seg.offset, 3412 seg.offset + seg.count, 3413 new String(seg.array, seg.offset, seg.count)); 3414 } // else we leave rangeSequence set to null 3415 } 3416 } catch(BadLocationException e) { 3417 // we are intentionally silent; our contract says we return 3418 // null if there is any failure in this method 3419 } finally { 3420 if (model instanceof AbstractDocument) { 3421 ((AbstractDocument)model).readUnlock(); 3422 } 3423 } 3424 return rangeSequence; 3425 3426 case AccessibleExtendedText.LINE: 3427 AccessibleTextSequence lineSequence = null; 3428 if (model instanceof AbstractDocument) { 3429 ((AbstractDocument)model).readLock(); 3430 } 3431 try { 3432 int startIndex = 3433 Utilities.getRowStart(JTextComponent.this, index); 3434 int endIndex = 3435 Utilities.getRowEnd(JTextComponent.this, index); 3436 if (startIndex >= 0 && endIndex >= startIndex) { 3437 if (direction == 0) { 3438 lineSequence = 3439 new AccessibleTextSequence(startIndex, endIndex, 3440 model.getText(startIndex, 3441 endIndex - startIndex + 1)); 3442 } else if (direction == -1 && startIndex > 0) { 3443 endIndex = 3444 Utilities.getRowEnd(JTextComponent.this, 3445 startIndex - 1); 3446 startIndex = 3447 Utilities.getRowStart(JTextComponent.this, 3448 startIndex - 1); 3449 if (startIndex >= 0 && endIndex >= startIndex) { 3450 lineSequence = 3451 new AccessibleTextSequence(startIndex, 3452 endIndex, 3453 model.getText(startIndex, 3454 endIndex - startIndex + 1)); 3455 } 3456 } else if (direction == 1 && 3457 endIndex < model.getLength()) { 3458 startIndex = 3459 Utilities.getRowStart(JTextComponent.this, 3460 endIndex + 1); 3461 endIndex = 3462 Utilities.getRowEnd(JTextComponent.this, 3463 endIndex + 1); 3464 if (startIndex >= 0 && endIndex >= startIndex) { 3465 lineSequence = 3466 new AccessibleTextSequence(startIndex, 3467 endIndex, model.getText(startIndex, 3468 endIndex - startIndex + 1)); 3469 } 3470 } 3471 // already validated 'direction' above... 3472 } 3473 } catch(BadLocationException e) { 3474 // we are intentionally silent; our contract says we return 3475 // null if there is any failure in this method 3476 } finally { 3477 if (model instanceof AbstractDocument) { 3478 ((AbstractDocument)model).readUnlock(); 3479 } 3480 } 3481 return lineSequence; 3482 3483 case AccessibleExtendedText.ATTRIBUTE_RUN: 3484 // assumptions: (1) that all characters in a single element 3485 // share the same attribute set; (2) that adjacent elements 3486 // *may* share the same attribute set 3487 3488 int attributeRunStartIndex, attributeRunEndIndex; 3489 String runText = null; 3490 if (model instanceof AbstractDocument) { 3491 ((AbstractDocument)model).readLock(); 3492 } 3493 3494 try { 3495 attributeRunStartIndex = attributeRunEndIndex = 3496 Integer.MIN_VALUE; 3497 int tempIndex = index; 3498 switch (direction) { 3499 case -1: 3500 // going backwards, so find left edge of this run - 3501 // that'll be the end of the previous run 3502 // (off-by-one counting) 3503 attributeRunEndIndex = getRunEdge(index, direction); 3504 // now set ourselves up to find the left edge of the 3505 // prev. run 3506 tempIndex = attributeRunEndIndex - 1; 3507 break; 3508 case 1: 3509 // going forward, so find right edge of this run - 3510 // that'll be the start of the next run 3511 // (off-by-one counting) 3512 attributeRunStartIndex = getRunEdge(index, direction); 3513 // now set ourselves up to find the right edge of the 3514 // next run 3515 tempIndex = attributeRunStartIndex; 3516 break; 3517 case 0: 3518 // interested in the current run, so nothing special to 3519 // set up in advance... 3520 break; 3521 default: 3522 // only those three values of direction allowed... 3523 throw new AssertionError(direction); 3524 } 3525 3526 // set the unset edge; if neither set then we're getting 3527 // both edges of the current run around our 'index' 3528 attributeRunStartIndex = 3529 (attributeRunStartIndex != Integer.MIN_VALUE) ? 3530 attributeRunStartIndex : getRunEdge(tempIndex, -1); 3531 attributeRunEndIndex = 3532 (attributeRunEndIndex != Integer.MIN_VALUE) ? 3533 attributeRunEndIndex : getRunEdge(tempIndex, 1); 3534 3535 runText = model.getText(attributeRunStartIndex, 3536 attributeRunEndIndex - 3537 attributeRunStartIndex); 3538 } catch (BadLocationException e) { 3539 // we are intentionally silent; our contract says we return 3540 // null if there is any failure in this method 3541 return null; 3542 } finally { 3543 if (model instanceof AbstractDocument) { 3544 ((AbstractDocument)model).readUnlock(); 3545 } 3546 } 3547 return new AccessibleTextSequence(attributeRunStartIndex, 3548 attributeRunEndIndex, 3549 runText); 3550 3551 default: 3552 break; 3553 } 3554 return null; 3555 } 3556 3557 3558 /** 3559 * Starting at text position <code>index</code>, and going in 3560 * <code>direction</code>, return the edge of run that shares the 3561 * same <code>AttributeSet</code> and parent element as those at 3562 * <code>index</code>. 3563 * 3564 * Note: we assume the document is already locked... 3565 */ getRunEdge(int index, int direction)3566 private int getRunEdge(int index, int direction) throws 3567 BadLocationException { 3568 if (index < 0 || index >= model.getLength()) { 3569 throw new BadLocationException("Location out of bounds", index); 3570 } 3571 // locate the Element at index 3572 Element indexElement; 3573 // locate the Element at our index/offset 3574 int elementIndex = -1; // test for initialization 3575 for (indexElement = model.getDefaultRootElement(); 3576 ! indexElement.isLeaf(); ) { 3577 elementIndex = indexElement.getElementIndex(index); 3578 indexElement = indexElement.getElement(elementIndex); 3579 } 3580 if (elementIndex == -1) { 3581 throw new AssertionError(index); 3582 } 3583 // cache the AttributeSet and parentElement atindex 3584 AttributeSet indexAS = indexElement.getAttributes(); 3585 Element parent = indexElement.getParentElement(); 3586 3587 // find the first Element before/after ours w/the same AttributeSet 3588 // if we are already at edge of the first element in our parent 3589 // then return that edge 3590 Element edgeElement; 3591 switch (direction) { 3592 case -1: 3593 case 1: 3594 int edgeElementIndex = elementIndex; 3595 int elementCount = parent.getElementCount(); 3596 while ((edgeElementIndex + direction) > 0 && 3597 ((edgeElementIndex + direction) < elementCount) && 3598 parent.getElement(edgeElementIndex 3599 + direction).getAttributes().isEqual(indexAS)) { 3600 edgeElementIndex += direction; 3601 } 3602 edgeElement = parent.getElement(edgeElementIndex); 3603 break; 3604 default: 3605 throw new AssertionError(direction); 3606 } 3607 switch (direction) { 3608 case -1: 3609 return edgeElement.getStartOffset(); 3610 case 1: 3611 return edgeElement.getEndOffset(); 3612 default: 3613 // we already caught this case earlier; this is to satisfy 3614 // the compiler... 3615 return Integer.MIN_VALUE; 3616 } 3617 } 3618 3619 // getTextRange() not needed; defined in AccessibleEditableText 3620 3621 /** 3622 * Returns the <code>AccessibleTextSequence</code> at a given 3623 * <code>index</code>. 3624 * 3625 * @param part the <code>CHARACTER</code>, <code>WORD</code>, 3626 * <code>SENTENCE</code>, <code>LINE</code> or 3627 * <code>ATTRIBUTE_RUN</code> to retrieve 3628 * @param index an index within the text 3629 * @return an <code>AccessibleTextSequence</code> specifying the text if 3630 * <code>part</code> and <code>index</code> are valid. Otherwise, 3631 * <code>null</code> is returned 3632 * 3633 * @see javax.accessibility.AccessibleText#CHARACTER 3634 * @see javax.accessibility.AccessibleText#WORD 3635 * @see javax.accessibility.AccessibleText#SENTENCE 3636 * @see javax.accessibility.AccessibleExtendedText#LINE 3637 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN 3638 * 3639 * @since 1.6 3640 */ getTextSequenceAt(int part, int index)3641 public AccessibleTextSequence getTextSequenceAt(int part, int index) { 3642 return getSequenceAtIndex(part, index, 0); 3643 } 3644 3645 /** 3646 * Returns the <code>AccessibleTextSequence</code> after a given 3647 * <code>index</code>. 3648 * 3649 * @param part the <code>CHARACTER</code>, <code>WORD</code>, 3650 * <code>SENTENCE</code>, <code>LINE</code> or 3651 * <code>ATTRIBUTE_RUN</code> to retrieve 3652 * @param index an index within the text 3653 * @return an <code>AccessibleTextSequence</code> specifying the text 3654 * if <code>part</code> and <code>index</code> are valid. Otherwise, 3655 * <code>null</code> is returned 3656 * 3657 * @see javax.accessibility.AccessibleText#CHARACTER 3658 * @see javax.accessibility.AccessibleText#WORD 3659 * @see javax.accessibility.AccessibleText#SENTENCE 3660 * @see javax.accessibility.AccessibleExtendedText#LINE 3661 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN 3662 * 3663 * @since 1.6 3664 */ getTextSequenceAfter(int part, int index)3665 public AccessibleTextSequence getTextSequenceAfter(int part, int index) { 3666 return getSequenceAtIndex(part, index, 1); 3667 } 3668 3669 /** 3670 * Returns the <code>AccessibleTextSequence</code> before a given 3671 * <code>index</code>. 3672 * 3673 * @param part the <code>CHARACTER</code>, <code>WORD</code>, 3674 * <code>SENTENCE</code>, <code>LINE</code> or 3675 * <code>ATTRIBUTE_RUN</code> to retrieve 3676 * @param index an index within the text 3677 * @return an <code>AccessibleTextSequence</code> specifying the text 3678 * if <code>part</code> and <code>index</code> are valid. Otherwise, 3679 * <code>null</code> is returned 3680 * 3681 * @see javax.accessibility.AccessibleText#CHARACTER 3682 * @see javax.accessibility.AccessibleText#WORD 3683 * @see javax.accessibility.AccessibleText#SENTENCE 3684 * @see javax.accessibility.AccessibleExtendedText#LINE 3685 * @see javax.accessibility.AccessibleExtendedText#ATTRIBUTE_RUN 3686 * 3687 * @since 1.6 3688 */ getTextSequenceBefore(int part, int index)3689 public AccessibleTextSequence getTextSequenceBefore(int part, int index) { 3690 return getSequenceAtIndex(part, index, -1); 3691 } 3692 3693 /** 3694 * Returns the <code>Rectangle</code> enclosing the text between 3695 * two indicies. 3696 * 3697 * @param startIndex the start index in the text 3698 * @param endIndex the end index in the text 3699 * @return the bounding rectangle of the text if the indices are valid. 3700 * Otherwise, <code>null</code> is returned 3701 * 3702 * @since 1.6 3703 */ getTextBounds(int startIndex, int endIndex)3704 public Rectangle getTextBounds(int startIndex, int endIndex) { 3705 if (startIndex < 0 || startIndex > model.getLength()-1 || 3706 endIndex < 0 || endIndex > model.getLength()-1 || 3707 startIndex > endIndex) { 3708 return null; 3709 } 3710 TextUI ui = getUI(); 3711 if (ui == null) { 3712 return null; 3713 } 3714 Rectangle rect = null; 3715 Rectangle alloc = getRootEditorRect(); 3716 if (alloc == null) { 3717 return null; 3718 } 3719 if (model instanceof AbstractDocument) { 3720 ((AbstractDocument)model).readLock(); 3721 } 3722 try { 3723 View rootView = ui.getRootView(JTextComponent.this); 3724 if (rootView != null) { 3725 Shape bounds = rootView.modelToView(startIndex, 3726 Position.Bias.Forward, endIndex, 3727 Position.Bias.Backward, alloc); 3728 3729 rect = (bounds instanceof Rectangle) ? 3730 (Rectangle)bounds : bounds.getBounds(); 3731 3732 } 3733 } catch (BadLocationException e) { 3734 } finally { 3735 if (model instanceof AbstractDocument) { 3736 ((AbstractDocument)model).readUnlock(); 3737 } 3738 } 3739 return rect; 3740 } 3741 3742 // ----- end AccessibleExtendedText methods 3743 3744 3745 // --- interface AccessibleAction methods ------------------------ 3746 getAccessibleAction()3747 public AccessibleAction getAccessibleAction() { 3748 return this; 3749 } 3750 3751 /** 3752 * Returns the number of accessible actions available in this object 3753 * If there are more than one, the first one is considered the 3754 * "default" action of the object. 3755 * 3756 * @return the zero-based number of Actions in this object 3757 * @since 1.4 3758 */ getAccessibleActionCount()3759 public int getAccessibleActionCount() { 3760 Action [] actions = JTextComponent.this.getActions(); 3761 return actions.length; 3762 } 3763 3764 /** 3765 * Returns a description of the specified action of the object. 3766 * 3767 * @param i zero-based index of the actions 3768 * @return a String description of the action 3769 * @see #getAccessibleActionCount 3770 * @since 1.4 3771 */ getAccessibleActionDescription(int i)3772 public String getAccessibleActionDescription(int i) { 3773 Action [] actions = JTextComponent.this.getActions(); 3774 if (i < 0 || i >= actions.length) { 3775 return null; 3776 } 3777 return (String)actions[i].getValue(Action.NAME); 3778 } 3779 3780 /** 3781 * Performs the specified Action on the object 3782 * 3783 * @param i zero-based index of actions 3784 * @return true if the action was performed; otherwise false. 3785 * @see #getAccessibleActionCount 3786 * @since 1.4 3787 */ doAccessibleAction(int i)3788 public boolean doAccessibleAction(int i) { 3789 Action [] actions = JTextComponent.this.getActions(); 3790 if (i < 0 || i >= actions.length) { 3791 return false; 3792 } 3793 ActionEvent ae = 3794 new ActionEvent(JTextComponent.this, 3795 ActionEvent.ACTION_PERFORMED, null, 3796 EventQueue.getMostRecentEventTime(), 3797 getCurrentEventModifiers()); 3798 actions[i].actionPerformed(ae); 3799 return true; 3800 } 3801 3802 // ----- end AccessibleAction methods 3803 3804 3805 } 3806 3807 3808 // --- serialization --------------------------------------------- 3809 readObject(ObjectInputStream s)3810 private void readObject(ObjectInputStream s) 3811 throws IOException, ClassNotFoundException 3812 { 3813 ObjectInputStream.GetField f = s.readFields(); 3814 3815 model = (Document) f.get("model", null); 3816 navigationFilter = (NavigationFilter) f.get("navigationFilter", null); 3817 caretColor = (Color) f.get("caretColor", null); 3818 selectionColor = (Color) f.get("selectionColor", null); 3819 selectedTextColor = (Color) f.get("selectedTextColor", null); 3820 disabledTextColor = (Color) f.get("disabledTextColor", null); 3821 editable = f.get("editable", false); 3822 margin = (Insets) f.get("margin", null); 3823 focusAccelerator = f.get("focusAccelerator", '\0'); 3824 boolean newDragEnabled = f.get("dragEnabled", false); 3825 checkDragEnabled(newDragEnabled); 3826 dragEnabled = newDragEnabled; 3827 DropMode newDropMode = (DropMode) f.get("dropMode", 3828 DropMode.USE_SELECTION); 3829 checkDropMode(newDropMode); 3830 dropMode = newDropMode; 3831 composedTextAttribute = (SimpleAttributeSet) f.get("composedTextAttribute", null); 3832 composedTextContent = (String) f.get("composedTextContent", null); 3833 composedTextStart = (Position) f.get("composedTextStart", null); 3834 composedTextEnd = (Position) f.get("composedTextEnd", null); 3835 latestCommittedTextStart = (Position) f.get("latestCommittedTextStart", null); 3836 latestCommittedTextEnd = (Position) f.get("latestCommittedTextEnd", null); 3837 composedTextCaret = (ComposedTextCaret) f.get("composedTextCaret", null); 3838 checkedInputOverride = f.get("checkedInputOverride", false); 3839 needToSendKeyTypedEvent = f.get("needToSendKeyTypedEvent", false); 3840 3841 caretEvent = new MutableCaretEvent(this); 3842 addMouseListener(caretEvent); 3843 addFocusListener(caretEvent); 3844 } 3845 3846 // --- member variables ---------------------------------- 3847 3848 /** 3849 * The document model. 3850 */ 3851 private Document model; 3852 3853 /** 3854 * The caret used to display the insert position 3855 * and navigate throughout the document. 3856 * 3857 * PENDING(prinz) 3858 * This should be serializable, default installed 3859 * by UI. 3860 */ 3861 private transient Caret caret; 3862 3863 /** 3864 * Object responsible for restricting the cursor navigation. 3865 */ 3866 private NavigationFilter navigationFilter; 3867 3868 /** 3869 * The object responsible for managing highlights. 3870 * 3871 * PENDING(prinz) 3872 * This should be serializable, default installed 3873 * by UI. 3874 */ 3875 private transient Highlighter highlighter; 3876 3877 /** 3878 * The current key bindings in effect. 3879 * 3880 * PENDING(prinz) 3881 * This should be serializable, default installed 3882 * by UI. 3883 */ 3884 private transient Keymap keymap; 3885 3886 private transient MutableCaretEvent caretEvent; 3887 private Color caretColor; 3888 private Color selectionColor; 3889 private Color selectedTextColor; 3890 private Color disabledTextColor; 3891 private boolean editable; 3892 private Insets margin; 3893 private char focusAccelerator; 3894 private boolean dragEnabled; 3895 3896 /** 3897 * The drop mode for this component. 3898 */ 3899 private DropMode dropMode = DropMode.USE_SELECTION; 3900 3901 /** 3902 * The drop location. 3903 */ 3904 private transient DropLocation dropLocation; 3905 3906 /** 3907 * Represents a drop location for <code>JTextComponent</code>s. 3908 * 3909 * @see #getDropLocation 3910 * @since 1.6 3911 */ 3912 public static final class DropLocation extends TransferHandler.DropLocation { 3913 private final int index; 3914 private final Position.Bias bias; 3915 DropLocation(Point p, int index, Position.Bias bias)3916 private DropLocation(Point p, int index, Position.Bias bias) { 3917 super(p); 3918 this.index = index; 3919 this.bias = bias; 3920 } 3921 3922 /** 3923 * Returns the index where dropped data should be inserted into the 3924 * associated component. This index represents a position between 3925 * characters, as would be interpreted by a caret. 3926 * 3927 * @return the drop index 3928 */ getIndex()3929 public int getIndex() { 3930 return index; 3931 } 3932 3933 /** 3934 * Returns the bias for the drop index. 3935 * 3936 * @return the drop bias 3937 */ getBias()3938 public Position.Bias getBias() { 3939 return bias; 3940 } 3941 3942 /** 3943 * Returns a string representation of this drop location. 3944 * This method is intended to be used for debugging purposes, 3945 * and the content and format of the returned string may vary 3946 * between implementations. 3947 * 3948 * @return a string representation of this drop location 3949 */ toString()3950 public String toString() { 3951 return getClass().getName() 3952 + "[dropPoint=" + getDropPoint() + "," 3953 + "index=" + index + "," 3954 + "bias=" + bias + "]"; 3955 } 3956 } 3957 3958 /** 3959 * TransferHandler used if one hasn't been supplied by the UI. 3960 */ 3961 private static DefaultTransferHandler defaultTransferHandler; 3962 3963 /** 3964 * Maps from class name to Boolean indicating if 3965 * <code>processInputMethodEvent</code> has been overriden. 3966 */ 3967 private static Cache<Class<?>,Boolean> METHOD_OVERRIDDEN 3968 = new Cache<Class<?>,Boolean>(Cache.Kind.WEAK, Cache.Kind.STRONG) { 3969 /** 3970 * Returns {@code true} if the specified {@code type} extends {@link JTextComponent} 3971 * and the {@link JTextComponent#processInputMethodEvent} method is overridden. 3972 */ 3973 @Override 3974 public Boolean create(final Class<?> type) { 3975 if (JTextComponent.class == type) { 3976 return Boolean.FALSE; 3977 } 3978 if (get(type.getSuperclass())) { 3979 return Boolean.TRUE; 3980 } 3981 return AccessController.doPrivileged( 3982 new PrivilegedAction<Boolean>() { 3983 public Boolean run() { 3984 try { 3985 type.getDeclaredMethod("processInputMethodEvent", InputMethodEvent.class); 3986 return Boolean.TRUE; 3987 } catch (NoSuchMethodException exception) { 3988 return Boolean.FALSE; 3989 } 3990 } 3991 }); 3992 } 3993 }; 3994 3995 /** 3996 * Returns a string representation of this <code>JTextComponent</code>. 3997 * This method is intended to be used only for debugging purposes, and the 3998 * content and format of the returned string may vary between 3999 * implementations. The returned string may be empty but may not 4000 * be <code>null</code>. 4001 * <P> 4002 * Overriding <code>paramString</code> to provide information about the 4003 * specific new aspects of the JFC components. 4004 * 4005 * @return a string representation of this <code>JTextComponent</code> 4006 */ paramString()4007 protected String paramString() { 4008 String editableString = (editable ? 4009 "true" : "false"); 4010 String caretColorString = (caretColor != null ? 4011 caretColor.toString() : ""); 4012 String selectionColorString = (selectionColor != null ? 4013 selectionColor.toString() : ""); 4014 String selectedTextColorString = (selectedTextColor != null ? 4015 selectedTextColor.toString() : ""); 4016 String disabledTextColorString = (disabledTextColor != null ? 4017 disabledTextColor.toString() : ""); 4018 String marginString = (margin != null ? 4019 margin.toString() : ""); 4020 4021 return super.paramString() + 4022 ",caretColor=" + caretColorString + 4023 ",disabledTextColor=" + disabledTextColorString + 4024 ",editable=" + editableString + 4025 ",margin=" + marginString + 4026 ",selectedTextColor=" + selectedTextColorString + 4027 ",selectionColor=" + selectionColorString; 4028 } 4029 4030 4031 /** 4032 * A Simple TransferHandler that exports the data as a String, and 4033 * imports the data from the String clipboard. This is only used 4034 * if the UI hasn't supplied one, which would only happen if someone 4035 * hasn't subclassed Basic. 4036 */ 4037 static class DefaultTransferHandler extends TransferHandler implements 4038 UIResource { exportToClipboard(JComponent comp, Clipboard clipboard, int action)4039 public void exportToClipboard(JComponent comp, Clipboard clipboard, 4040 int action) throws IllegalStateException { 4041 if (comp instanceof JTextComponent) { 4042 JTextComponent text = (JTextComponent)comp; 4043 int p0 = text.getSelectionStart(); 4044 int p1 = text.getSelectionEnd(); 4045 if (p0 != p1) { 4046 try { 4047 Document doc = text.getDocument(); 4048 String srcData = doc.getText(p0, p1 - p0); 4049 StringSelection contents =new StringSelection(srcData); 4050 4051 // this may throw an IllegalStateException, 4052 // but it will be caught and handled in the 4053 // action that invoked this method 4054 clipboard.setContents(contents, null); 4055 4056 if (action == TransferHandler.MOVE) { 4057 doc.remove(p0, p1 - p0); 4058 } 4059 } catch (BadLocationException ble) {} 4060 } 4061 } 4062 } importData(JComponent comp, Transferable t)4063 public boolean importData(JComponent comp, Transferable t) { 4064 if (comp instanceof JTextComponent) { 4065 DataFlavor flavor = getFlavor(t.getTransferDataFlavors()); 4066 4067 if (flavor != null) { 4068 InputContext ic = comp.getInputContext(); 4069 if (ic != null) { 4070 ic.endComposition(); 4071 } 4072 try { 4073 String data = (String)t.getTransferData(flavor); 4074 4075 ((JTextComponent)comp).replaceSelection(data); 4076 return true; 4077 } catch (UnsupportedFlavorException ufe) { 4078 } catch (IOException ioe) { 4079 } 4080 } 4081 } 4082 return false; 4083 } canImport(JComponent comp, DataFlavor[] transferFlavors)4084 public boolean canImport(JComponent comp, 4085 DataFlavor[] transferFlavors) { 4086 JTextComponent c = (JTextComponent)comp; 4087 if (!(c.isEditable() && c.isEnabled())) { 4088 return false; 4089 } 4090 return (getFlavor(transferFlavors) != null); 4091 } getSourceActions(JComponent c)4092 public int getSourceActions(JComponent c) { 4093 return NONE; 4094 } getFlavor(DataFlavor[] flavors)4095 private DataFlavor getFlavor(DataFlavor[] flavors) { 4096 if (flavors != null) { 4097 for (DataFlavor flavor : flavors) { 4098 if (flavor.equals(DataFlavor.stringFlavor)) { 4099 return flavor; 4100 } 4101 } 4102 } 4103 return null; 4104 } 4105 } 4106 4107 /** 4108 * Returns the JTextComponent that most recently had focus. The returned 4109 * value may currently have focus. 4110 */ getFocusedComponent()4111 static final JTextComponent getFocusedComponent() { 4112 return (JTextComponent)AppContext.getAppContext(). 4113 get(FOCUSED_COMPONENT); 4114 } 4115 4116 @SuppressWarnings("deprecation") getCurrentEventModifiers()4117 private int getCurrentEventModifiers() { 4118 int modifiers = 0; 4119 AWTEvent currentEvent = EventQueue.getCurrentEvent(); 4120 if (currentEvent instanceof InputEvent) { 4121 modifiers = ((InputEvent)currentEvent).getModifiers(); 4122 } else if (currentEvent instanceof ActionEvent) { 4123 modifiers = ((ActionEvent)currentEvent).getModifiers(); 4124 } 4125 return modifiers; 4126 } 4127 4128 private static final Object KEYMAP_TABLE = 4129 new StringBuilder("JTextComponent_KeymapTable"); 4130 4131 // 4132 // member variables used for on-the-spot input method 4133 // editing style support 4134 // 4135 private transient InputMethodRequests inputMethodRequestsHandler; 4136 private SimpleAttributeSet composedTextAttribute; 4137 private String composedTextContent; 4138 private Position composedTextStart; 4139 private Position composedTextEnd; 4140 private Position latestCommittedTextStart; 4141 private Position latestCommittedTextEnd; 4142 private ComposedTextCaret composedTextCaret; 4143 private transient Caret originalCaret; 4144 /** 4145 * Set to true after the check for the override of processInputMethodEvent 4146 * has been checked. 4147 */ 4148 private boolean checkedInputOverride; 4149 private boolean needToSendKeyTypedEvent; 4150 4151 static class DefaultKeymap implements Keymap { 4152 DefaultKeymap(String nm, Keymap parent)4153 DefaultKeymap(String nm, Keymap parent) { 4154 this.nm = nm; 4155 this.parent = parent; 4156 bindings = new Hashtable<KeyStroke, Action>(); 4157 } 4158 4159 /** 4160 * Fetch the default action to fire if a 4161 * key is typed (ie a KEY_TYPED KeyEvent is received) 4162 * and there is no binding for it. Typically this 4163 * would be some action that inserts text so that 4164 * the keymap doesn't require an action for each 4165 * possible key. 4166 */ getDefaultAction()4167 public Action getDefaultAction() { 4168 if (defaultAction != null) { 4169 return defaultAction; 4170 } 4171 return (parent != null) ? parent.getDefaultAction() : null; 4172 } 4173 4174 /** 4175 * Set the default action to fire if a key is typed. 4176 */ setDefaultAction(Action a)4177 public void setDefaultAction(Action a) { 4178 defaultAction = a; 4179 } 4180 getName()4181 public String getName() { 4182 return nm; 4183 } 4184 getAction(KeyStroke key)4185 public Action getAction(KeyStroke key) { 4186 Action a = bindings.get(key); 4187 if ((a == null) && (parent != null)) { 4188 a = parent.getAction(key); 4189 } 4190 return a; 4191 } 4192 getBoundKeyStrokes()4193 public KeyStroke[] getBoundKeyStrokes() { 4194 KeyStroke[] keys = new KeyStroke[bindings.size()]; 4195 int i = 0; 4196 for (Enumeration<KeyStroke> e = bindings.keys() ; e.hasMoreElements() ;) { 4197 keys[i++] = e.nextElement(); 4198 } 4199 return keys; 4200 } 4201 getBoundActions()4202 public Action[] getBoundActions() { 4203 Action[] actions = new Action[bindings.size()]; 4204 int i = 0; 4205 for (Enumeration<Action> e = bindings.elements() ; e.hasMoreElements() ;) { 4206 actions[i++] = e.nextElement(); 4207 } 4208 return actions; 4209 } 4210 getKeyStrokesForAction(Action a)4211 public KeyStroke[] getKeyStrokesForAction(Action a) { 4212 if (a == null) { 4213 return null; 4214 } 4215 KeyStroke[] retValue = null; 4216 // Determine local bindings first. 4217 Vector<KeyStroke> keyStrokes = null; 4218 for (Enumeration<KeyStroke> keys = bindings.keys(); keys.hasMoreElements();) { 4219 KeyStroke key = keys.nextElement(); 4220 if (bindings.get(key) == a) { 4221 if (keyStrokes == null) { 4222 keyStrokes = new Vector<KeyStroke>(); 4223 } 4224 keyStrokes.addElement(key); 4225 } 4226 } 4227 // See if the parent has any. 4228 if (parent != null) { 4229 KeyStroke[] pStrokes = parent.getKeyStrokesForAction(a); 4230 if (pStrokes != null) { 4231 // Remove any bindings defined in the parent that 4232 // are locally defined. 4233 int rCount = 0; 4234 for (int counter = pStrokes.length - 1; counter >= 0; 4235 counter--) { 4236 if (isLocallyDefined(pStrokes[counter])) { 4237 pStrokes[counter] = null; 4238 rCount++; 4239 } 4240 } 4241 if (rCount > 0 && rCount < pStrokes.length) { 4242 if (keyStrokes == null) { 4243 keyStrokes = new Vector<KeyStroke>(); 4244 } 4245 for (int counter = pStrokes.length - 1; counter >= 0; 4246 counter--) { 4247 if (pStrokes[counter] != null) { 4248 keyStrokes.addElement(pStrokes[counter]); 4249 } 4250 } 4251 } 4252 else if (rCount == 0) { 4253 if (keyStrokes == null) { 4254 retValue = pStrokes; 4255 } 4256 else { 4257 retValue = new KeyStroke[keyStrokes.size() + 4258 pStrokes.length]; 4259 keyStrokes.copyInto(retValue); 4260 System.arraycopy(pStrokes, 0, retValue, 4261 keyStrokes.size(), pStrokes.length); 4262 keyStrokes = null; 4263 } 4264 } 4265 } 4266 } 4267 if (keyStrokes != null) { 4268 retValue = new KeyStroke[keyStrokes.size()]; 4269 keyStrokes.copyInto(retValue); 4270 } 4271 return retValue; 4272 } 4273 isLocallyDefined(KeyStroke key)4274 public boolean isLocallyDefined(KeyStroke key) { 4275 return bindings.containsKey(key); 4276 } 4277 addActionForKeyStroke(KeyStroke key, Action a)4278 public void addActionForKeyStroke(KeyStroke key, Action a) { 4279 bindings.put(key, a); 4280 } 4281 removeKeyStrokeBinding(KeyStroke key)4282 public void removeKeyStrokeBinding(KeyStroke key) { 4283 bindings.remove(key); 4284 } 4285 removeBindings()4286 public void removeBindings() { 4287 bindings.clear(); 4288 } 4289 getResolveParent()4290 public Keymap getResolveParent() { 4291 return parent; 4292 } 4293 setResolveParent(Keymap parent)4294 public void setResolveParent(Keymap parent) { 4295 this.parent = parent; 4296 } 4297 4298 /** 4299 * String representation of the keymap... potentially 4300 * a very long string. 4301 */ toString()4302 public String toString() { 4303 return "Keymap[" + nm + "]" + bindings; 4304 } 4305 4306 String nm; 4307 Keymap parent; 4308 Hashtable<KeyStroke, Action> bindings; 4309 Action defaultAction; 4310 } 4311 4312 4313 /** 4314 * KeymapWrapper wraps a Keymap inside an InputMap. For KeymapWrapper 4315 * to be useful it must be used with a KeymapActionMap. 4316 * KeymapWrapper for the most part, is an InputMap with two parents. 4317 * The first parent visited is ALWAYS the Keymap, with the second 4318 * parent being the parent inherited from InputMap. If 4319 * <code>keymap.getAction</code> returns null, implying the Keymap 4320 * does not have a binding for the KeyStroke, 4321 * the parent is then visited. If the Keymap has a binding, the 4322 * Action is returned, if not and the KeyStroke represents a 4323 * KeyTyped event and the Keymap has a defaultAction, 4324 * <code>DefaultActionKey</code> is returned. 4325 * <p>KeymapActionMap is then able to transate the object passed in 4326 * to either message the Keymap, or message its default implementation. 4327 */ 4328 static class KeymapWrapper extends InputMap { 4329 static final Object DefaultActionKey = new Object(); 4330 4331 private Keymap keymap; 4332 KeymapWrapper(Keymap keymap)4333 KeymapWrapper(Keymap keymap) { 4334 this.keymap = keymap; 4335 } 4336 keys()4337 public KeyStroke[] keys() { 4338 KeyStroke[] sKeys = super.keys(); 4339 KeyStroke[] keymapKeys = keymap.getBoundKeyStrokes(); 4340 int sCount = (sKeys == null) ? 0 : sKeys.length; 4341 int keymapCount = (keymapKeys == null) ? 0 : keymapKeys.length; 4342 if (sCount == 0) { 4343 return keymapKeys; 4344 } 4345 if (keymapCount == 0) { 4346 return sKeys; 4347 } 4348 KeyStroke[] retValue = new KeyStroke[sCount + keymapCount]; 4349 // There may be some duplication here... 4350 System.arraycopy(sKeys, 0, retValue, 0, sCount); 4351 System.arraycopy(keymapKeys, 0, retValue, sCount, keymapCount); 4352 return retValue; 4353 } 4354 size()4355 public int size() { 4356 // There may be some duplication here... 4357 KeyStroke[] keymapStrokes = keymap.getBoundKeyStrokes(); 4358 int keymapCount = (keymapStrokes == null) ? 0: 4359 keymapStrokes.length; 4360 return super.size() + keymapCount; 4361 } 4362 get(KeyStroke keyStroke)4363 public Object get(KeyStroke keyStroke) { 4364 Object retValue = keymap.getAction(keyStroke); 4365 if (retValue == null) { 4366 retValue = super.get(keyStroke); 4367 if (retValue == null && 4368 keyStroke.getKeyChar() != KeyEvent.CHAR_UNDEFINED && 4369 keymap.getDefaultAction() != null) { 4370 // Implies this is a KeyTyped event, use the default 4371 // action. 4372 retValue = DefaultActionKey; 4373 } 4374 } 4375 return retValue; 4376 } 4377 } 4378 4379 4380 /** 4381 * Wraps a Keymap inside an ActionMap. This is used with 4382 * a KeymapWrapper. If <code>get</code> is passed in 4383 * <code>KeymapWrapper.DefaultActionKey</code>, the default action is 4384 * returned, otherwise if the key is an Action, it is returned. 4385 */ 4386 static class KeymapActionMap extends ActionMap { 4387 private Keymap keymap; 4388 KeymapActionMap(Keymap keymap)4389 KeymapActionMap(Keymap keymap) { 4390 this.keymap = keymap; 4391 } 4392 keys()4393 public Object[] keys() { 4394 Object[] sKeys = super.keys(); 4395 Object[] keymapKeys = keymap.getBoundActions(); 4396 int sCount = (sKeys == null) ? 0 : sKeys.length; 4397 int keymapCount = (keymapKeys == null) ? 0 : keymapKeys.length; 4398 boolean hasDefault = (keymap.getDefaultAction() != null); 4399 if (hasDefault) { 4400 keymapCount++; 4401 } 4402 if (sCount == 0) { 4403 if (hasDefault) { 4404 Object[] retValue = new Object[keymapCount]; 4405 if (keymapCount > 1) { 4406 System.arraycopy(keymapKeys, 0, retValue, 0, 4407 keymapCount - 1); 4408 } 4409 retValue[keymapCount - 1] = KeymapWrapper.DefaultActionKey; 4410 return retValue; 4411 } 4412 return keymapKeys; 4413 } 4414 if (keymapCount == 0) { 4415 return sKeys; 4416 } 4417 Object[] retValue = new Object[sCount + keymapCount]; 4418 // There may be some duplication here... 4419 System.arraycopy(sKeys, 0, retValue, 0, sCount); 4420 if (hasDefault) { 4421 if (keymapCount > 1) { 4422 System.arraycopy(keymapKeys, 0, retValue, sCount, 4423 keymapCount - 1); 4424 } 4425 retValue[sCount + keymapCount - 1] = KeymapWrapper. 4426 DefaultActionKey; 4427 } 4428 else { 4429 System.arraycopy(keymapKeys, 0, retValue, sCount, keymapCount); 4430 } 4431 return retValue; 4432 } 4433 size()4434 public int size() { 4435 // There may be some duplication here... 4436 Object[] actions = keymap.getBoundActions(); 4437 int keymapCount = (actions == null) ? 0 : actions.length; 4438 if (keymap.getDefaultAction() != null) { 4439 keymapCount++; 4440 } 4441 return super.size() + keymapCount; 4442 } 4443 get(Object key)4444 public Action get(Object key) { 4445 Action retValue = super.get(key); 4446 if (retValue == null) { 4447 // Try the Keymap. 4448 if (key == KeymapWrapper.DefaultActionKey) { 4449 retValue = keymap.getDefaultAction(); 4450 } 4451 else if (key instanceof Action) { 4452 // This is a little iffy, technically an Action is 4453 // a valid Key. We're assuming the Action came from 4454 // the InputMap though. 4455 retValue = (Action)key; 4456 } 4457 } 4458 return retValue; 4459 } 4460 } 4461 4462 private static final Object FOCUSED_COMPONENT = 4463 new StringBuilder("JTextComponent_FocusedComponent"); 4464 4465 /** 4466 * The default keymap that will be shared by all 4467 * <code>JTextComponent</code> instances unless they 4468 * have had a different keymap set. 4469 */ 4470 public static final String DEFAULT_KEYMAP = "default"; 4471 4472 /** 4473 * Event to use when firing a notification of change to caret 4474 * position. This is mutable so that the event can be reused 4475 * since caret events can be fairly high in bandwidth. 4476 */ 4477 static class MutableCaretEvent extends CaretEvent implements ChangeListener, FocusListener, MouseListener { 4478 MutableCaretEvent(JTextComponent c)4479 MutableCaretEvent(JTextComponent c) { 4480 super(c); 4481 } 4482 fire()4483 final void fire() { 4484 JTextComponent c = (JTextComponent) getSource(); 4485 if (c != null) { 4486 Caret caret = c.getCaret(); 4487 dot = caret.getDot(); 4488 mark = caret.getMark(); 4489 c.fireCaretUpdate(this); 4490 } 4491 } 4492 toString()4493 public final String toString() { 4494 return "dot=" + dot + "," + "mark=" + mark; 4495 } 4496 4497 // --- CaretEvent methods ----------------------- 4498 getDot()4499 public final int getDot() { 4500 return dot; 4501 } 4502 getMark()4503 public final int getMark() { 4504 return mark; 4505 } 4506 4507 // --- ChangeListener methods ------------------- 4508 stateChanged(ChangeEvent e)4509 public final void stateChanged(ChangeEvent e) { 4510 if (! dragActive) { 4511 fire(); 4512 } 4513 } 4514 4515 // --- FocusListener methods ----------------------------------- focusGained(FocusEvent fe)4516 public void focusGained(FocusEvent fe) { 4517 AppContext.getAppContext().put(FOCUSED_COMPONENT, 4518 fe.getSource()); 4519 } 4520 focusLost(FocusEvent fe)4521 public void focusLost(FocusEvent fe) { 4522 } 4523 4524 // --- MouseListener methods ----------------------------------- 4525 4526 /** 4527 * Requests focus on the associated 4528 * text component, and try to set the cursor position. 4529 * 4530 * @param e the mouse event 4531 * @see MouseListener#mousePressed 4532 */ mousePressed(MouseEvent e)4533 public final void mousePressed(MouseEvent e) { 4534 dragActive = true; 4535 } 4536 4537 /** 4538 * Called when the mouse is released. 4539 * 4540 * @param e the mouse event 4541 * @see MouseListener#mouseReleased 4542 */ mouseReleased(MouseEvent e)4543 public final void mouseReleased(MouseEvent e) { 4544 dragActive = false; 4545 fire(); 4546 } 4547 mouseClicked(MouseEvent e)4548 public final void mouseClicked(MouseEvent e) { 4549 } 4550 mouseEntered(MouseEvent e)4551 public final void mouseEntered(MouseEvent e) { 4552 } 4553 mouseExited(MouseEvent e)4554 public final void mouseExited(MouseEvent e) { 4555 } 4556 4557 private boolean dragActive; 4558 private int dot; 4559 private int mark; 4560 } 4561 4562 // 4563 // Process any input method events that the component itself 4564 // recognizes. The default on-the-spot handling for input method 4565 // composed(uncommitted) text is done here after all input 4566 // method listeners get called for stealing the events. 4567 // 4568 @SuppressWarnings("fallthrough") processInputMethodEvent(InputMethodEvent e)4569 protected void processInputMethodEvent(InputMethodEvent e) { 4570 // let listeners handle the events 4571 super.processInputMethodEvent(e); 4572 4573 if (!e.isConsumed()) { 4574 if (! isEditable()) { 4575 return; 4576 } else { 4577 switch (e.getID()) { 4578 case InputMethodEvent.INPUT_METHOD_TEXT_CHANGED: 4579 replaceInputMethodText(e); 4580 4581 // fall through 4582 4583 case InputMethodEvent.CARET_POSITION_CHANGED: 4584 setInputMethodCaretPosition(e); 4585 break; 4586 } 4587 } 4588 4589 e.consume(); 4590 } 4591 } 4592 4593 // 4594 // Overrides this method to become an active input method client. 4595 // 4596 @BeanProperty(bound = false) getInputMethodRequests()4597 public InputMethodRequests getInputMethodRequests() { 4598 if (inputMethodRequestsHandler == null) { 4599 inputMethodRequestsHandler = new InputMethodRequestsHandler(); 4600 Document doc = getDocument(); 4601 if (doc != null) { 4602 doc.addDocumentListener((DocumentListener)inputMethodRequestsHandler); 4603 } 4604 } 4605 4606 return inputMethodRequestsHandler; 4607 } 4608 4609 // 4610 // Overrides this method to watch the listener installed. 4611 // addInputMethodListener(InputMethodListener l)4612 public void addInputMethodListener(InputMethodListener l) { 4613 super.addInputMethodListener(l); 4614 if (l != null) { 4615 needToSendKeyTypedEvent = false; 4616 checkedInputOverride = true; 4617 } 4618 } 4619 4620 4621 // 4622 // Default implementation of the InputMethodRequests interface. 4623 // 4624 class InputMethodRequestsHandler implements InputMethodRequests, DocumentListener { 4625 4626 // --- InputMethodRequests methods --- 4627 cancelLatestCommittedText( Attribute[] attributes)4628 public AttributedCharacterIterator cancelLatestCommittedText( 4629 Attribute[] attributes) { 4630 Document doc = getDocument(); 4631 if ((doc != null) && (latestCommittedTextStart != null) 4632 && (!latestCommittedTextStart.equals(latestCommittedTextEnd))) { 4633 try { 4634 int startIndex = latestCommittedTextStart.getOffset(); 4635 int endIndex = latestCommittedTextEnd.getOffset(); 4636 String latestCommittedText = 4637 doc.getText(startIndex, endIndex - startIndex); 4638 doc.remove(startIndex, endIndex - startIndex); 4639 return new AttributedString(latestCommittedText).getIterator(); 4640 } catch (BadLocationException ble) {} 4641 } 4642 return null; 4643 } 4644 getCommittedText(int beginIndex, int endIndex, Attribute[] attributes)4645 public AttributedCharacterIterator getCommittedText(int beginIndex, 4646 int endIndex, Attribute[] attributes) { 4647 int composedStartIndex = 0; 4648 int composedEndIndex = 0; 4649 if (composedTextExists()) { 4650 composedStartIndex = composedTextStart.getOffset(); 4651 composedEndIndex = composedTextEnd.getOffset(); 4652 } 4653 4654 String committed; 4655 try { 4656 if (beginIndex < composedStartIndex) { 4657 if (endIndex <= composedStartIndex) { 4658 committed = getText(beginIndex, endIndex - beginIndex); 4659 } else { 4660 int firstPartLength = composedStartIndex - beginIndex; 4661 committed = getText(beginIndex, firstPartLength) + 4662 getText(composedEndIndex, endIndex - beginIndex - firstPartLength); 4663 } 4664 } else { 4665 committed = getText(beginIndex + (composedEndIndex - composedStartIndex), 4666 endIndex - beginIndex); 4667 } 4668 } catch (BadLocationException ble) { 4669 throw new IllegalArgumentException("Invalid range"); 4670 } 4671 return new AttributedString(committed).getIterator(); 4672 } 4673 getCommittedTextLength()4674 public int getCommittedTextLength() { 4675 Document doc = getDocument(); 4676 int length = 0; 4677 if (doc != null) { 4678 length = doc.getLength(); 4679 if (composedTextContent != null) { 4680 if (composedTextEnd == null 4681 || composedTextStart == null) { 4682 /* 4683 * fix for : 6355666 4684 * this is the case when this method is invoked 4685 * from DocumentListener. At this point 4686 * composedTextEnd and composedTextStart are 4687 * not defined yet. 4688 */ 4689 length -= composedTextContent.length(); 4690 } else { 4691 length -= composedTextEnd.getOffset() - 4692 composedTextStart.getOffset(); 4693 } 4694 } 4695 } 4696 return length; 4697 } 4698 getInsertPositionOffset()4699 public int getInsertPositionOffset() { 4700 int composedStartIndex = 0; 4701 int composedEndIndex = 0; 4702 if (composedTextExists()) { 4703 composedStartIndex = composedTextStart.getOffset(); 4704 composedEndIndex = composedTextEnd.getOffset(); 4705 } 4706 int caretIndex = getCaretPosition(); 4707 4708 if (caretIndex < composedStartIndex) { 4709 return caretIndex; 4710 } else if (caretIndex < composedEndIndex) { 4711 return composedStartIndex; 4712 } else { 4713 return caretIndex - (composedEndIndex - composedStartIndex); 4714 } 4715 } 4716 getLocationOffset(int x, int y)4717 public TextHitInfo getLocationOffset(int x, int y) { 4718 if (composedTextAttribute == null) { 4719 return null; 4720 } else { 4721 Point p = getLocationOnScreen(); 4722 p.x = x - p.x; 4723 p.y = y - p.y; 4724 int pos = viewToModel(p); 4725 if ((pos >= composedTextStart.getOffset()) && 4726 (pos <= composedTextEnd.getOffset())) { 4727 return TextHitInfo.leading(pos - composedTextStart.getOffset()); 4728 } else { 4729 return null; 4730 } 4731 } 4732 } 4733 getTextLocation(TextHitInfo offset)4734 public Rectangle getTextLocation(TextHitInfo offset) { 4735 Rectangle r; 4736 4737 try { 4738 r = modelToView(getCaretPosition()); 4739 if (r != null) { 4740 Point p = getLocationOnScreen(); 4741 r.translate(p.x, p.y); 4742 } 4743 } catch (BadLocationException ble) { 4744 r = null; 4745 } 4746 4747 if (r == null) 4748 r = new Rectangle(); 4749 4750 return r; 4751 } 4752 getSelectedText( Attribute[] attributes)4753 public AttributedCharacterIterator getSelectedText( 4754 Attribute[] attributes) { 4755 String selection = JTextComponent.this.getSelectedText(); 4756 if (selection != null) { 4757 return new AttributedString(selection).getIterator(); 4758 } else { 4759 return null; 4760 } 4761 } 4762 4763 // --- DocumentListener methods --- 4764 changedUpdate(DocumentEvent e)4765 public void changedUpdate(DocumentEvent e) { 4766 latestCommittedTextStart = latestCommittedTextEnd = null; 4767 } 4768 insertUpdate(DocumentEvent e)4769 public void insertUpdate(DocumentEvent e) { 4770 latestCommittedTextStart = latestCommittedTextEnd = null; 4771 } 4772 removeUpdate(DocumentEvent e)4773 public void removeUpdate(DocumentEvent e) { 4774 latestCommittedTextStart = latestCommittedTextEnd = null; 4775 } 4776 } 4777 4778 // 4779 // Replaces the current input method (composed) text according to 4780 // the passed input method event. This method also inserts the 4781 // committed text into the document. 4782 // replaceInputMethodText(InputMethodEvent e)4783 private void replaceInputMethodText(InputMethodEvent e) { 4784 int commitCount = e.getCommittedCharacterCount(); 4785 AttributedCharacterIterator text = e.getText(); 4786 int composedTextIndex; 4787 4788 // old composed text deletion 4789 Document doc = getDocument(); 4790 if (composedTextExists()) { 4791 try { 4792 doc.remove(composedTextStart.getOffset(), 4793 composedTextEnd.getOffset() - 4794 composedTextStart.getOffset()); 4795 } catch (BadLocationException ble) {} 4796 composedTextStart = composedTextEnd = null; 4797 composedTextAttribute = null; 4798 composedTextContent = null; 4799 } 4800 4801 if (text != null) { 4802 text.first(); 4803 int committedTextStartIndex = 0; 4804 int committedTextEndIndex = 0; 4805 4806 // committed text insertion 4807 if (commitCount > 0) { 4808 // Remember latest committed text start index 4809 committedTextStartIndex = caret.getDot(); 4810 4811 // Need to generate KeyTyped events for the committed text for components 4812 // that are not aware they are active input method clients. 4813 if (shouldSynthensizeKeyEvents()) { 4814 for (char c = text.current(); commitCount > 0; 4815 c = text.next(), commitCount--) { 4816 KeyEvent ke = new KeyEvent(this, KeyEvent.KEY_TYPED, 4817 EventQueue.getMostRecentEventTime(), 4818 0, KeyEvent.VK_UNDEFINED, c); 4819 processKeyEvent(ke); 4820 } 4821 } else { 4822 StringBuilder strBuf = new StringBuilder(); 4823 for (char c = text.current(); commitCount > 0; 4824 c = text.next(), commitCount--) { 4825 strBuf.append(c); 4826 } 4827 4828 // map it to an ActionEvent 4829 mapCommittedTextToAction(strBuf.toString()); 4830 } 4831 4832 // Remember latest committed text end index 4833 committedTextEndIndex = caret.getDot(); 4834 } 4835 4836 // new composed text insertion 4837 composedTextIndex = text.getIndex(); 4838 if (composedTextIndex < text.getEndIndex()) { 4839 createComposedTextAttribute(composedTextIndex, text); 4840 try { 4841 replaceSelection(null); 4842 doc.insertString(caret.getDot(), composedTextContent, 4843 composedTextAttribute); 4844 composedTextStart = doc.createPosition(caret.getDot() - 4845 composedTextContent.length()); 4846 composedTextEnd = doc.createPosition(caret.getDot()); 4847 } catch (BadLocationException ble) { 4848 composedTextStart = composedTextEnd = null; 4849 composedTextAttribute = null; 4850 composedTextContent = null; 4851 } 4852 } 4853 4854 // Save the latest committed text information 4855 if (committedTextStartIndex != committedTextEndIndex) { 4856 try { 4857 latestCommittedTextStart = doc. 4858 createPosition(committedTextStartIndex); 4859 latestCommittedTextEnd = doc. 4860 createPosition(committedTextEndIndex); 4861 } catch (BadLocationException ble) { 4862 latestCommittedTextStart = 4863 latestCommittedTextEnd = null; 4864 } 4865 } else { 4866 latestCommittedTextStart = 4867 latestCommittedTextEnd = null; 4868 } 4869 } 4870 } 4871 createComposedTextAttribute(int composedIndex, AttributedCharacterIterator text)4872 private void createComposedTextAttribute(int composedIndex, 4873 AttributedCharacterIterator text) { 4874 Document doc = getDocument(); 4875 StringBuilder strBuf = new StringBuilder(); 4876 4877 // create attributed string with no attributes 4878 for (char c = text.setIndex(composedIndex); 4879 c != CharacterIterator.DONE; c = text.next()) { 4880 strBuf.append(c); 4881 } 4882 4883 composedTextContent = strBuf.toString(); 4884 composedTextAttribute = new SimpleAttributeSet(); 4885 composedTextAttribute.addAttribute(StyleConstants.ComposedTextAttribute, 4886 new AttributedString(text, composedIndex, text.getEndIndex())); 4887 } 4888 4889 /** 4890 * Saves composed text around the specified position. 4891 * 4892 * The composed text (if any) around the specified position is saved 4893 * in a backing store and removed from the document. 4894 * 4895 * @param pos document position to identify the composed text location 4896 * @return {@code true} if the composed text exists and is saved, 4897 * {@code false} otherwise 4898 * @see #restoreComposedText 4899 * @since 1.7 4900 */ saveComposedText(int pos)4901 protected boolean saveComposedText(int pos) { 4902 if (composedTextExists()) { 4903 int start = composedTextStart.getOffset(); 4904 int len = composedTextEnd.getOffset() - 4905 composedTextStart.getOffset(); 4906 if (pos >= start && pos <= start + len) { 4907 try { 4908 getDocument().remove(start, len); 4909 return true; 4910 } catch (BadLocationException ble) {} 4911 } 4912 } 4913 return false; 4914 } 4915 4916 /** 4917 * Restores composed text previously saved by {@code saveComposedText}. 4918 * 4919 * The saved composed text is inserted back into the document. This method 4920 * should be invoked only if {@code saveComposedText} returns {@code true}. 4921 * 4922 * @see #saveComposedText 4923 * @since 1.7 4924 */ restoreComposedText()4925 protected void restoreComposedText() { 4926 Document doc = getDocument(); 4927 try { 4928 doc.insertString(caret.getDot(), 4929 composedTextContent, 4930 composedTextAttribute); 4931 composedTextStart = doc.createPosition(caret.getDot() - 4932 composedTextContent.length()); 4933 composedTextEnd = doc.createPosition(caret.getDot()); 4934 } catch (BadLocationException ble) {} 4935 } 4936 4937 // 4938 // Map committed text to an ActionEvent. If the committed text length is 1, 4939 // treat it as a KeyStroke, otherwise or there is no KeyStroke defined, 4940 // treat it just as a default action. 4941 // mapCommittedTextToAction(String committedText)4942 private void mapCommittedTextToAction(String committedText) { 4943 Keymap binding = getKeymap(); 4944 if (binding != null) { 4945 Action a = null; 4946 if (committedText.length() == 1) { 4947 KeyStroke k = KeyStroke.getKeyStroke(committedText.charAt(0)); 4948 a = binding.getAction(k); 4949 } 4950 4951 if (a == null) { 4952 a = binding.getDefaultAction(); 4953 } 4954 4955 if (a != null) { 4956 ActionEvent ae = 4957 new ActionEvent(this, ActionEvent.ACTION_PERFORMED, 4958 committedText, 4959 EventQueue.getMostRecentEventTime(), 4960 getCurrentEventModifiers()); 4961 a.actionPerformed(ae); 4962 } 4963 } 4964 } 4965 4966 // 4967 // Sets the caret position according to the passed input method 4968 // event. Also, sets/resets composed text caret appropriately. 4969 // setInputMethodCaretPosition(InputMethodEvent e)4970 private void setInputMethodCaretPosition(InputMethodEvent e) { 4971 int dot; 4972 4973 if (composedTextExists()) { 4974 dot = composedTextStart.getOffset(); 4975 if (!(caret instanceof ComposedTextCaret)) { 4976 if (composedTextCaret == null) { 4977 composedTextCaret = new ComposedTextCaret(); 4978 } 4979 originalCaret = caret; 4980 // Sets composed text caret 4981 exchangeCaret(originalCaret, composedTextCaret); 4982 } 4983 4984 TextHitInfo caretPos = e.getCaret(); 4985 if (caretPos != null) { 4986 int index = caretPos.getInsertionIndex(); 4987 dot += index; 4988 if (index == 0) { 4989 // Scroll the component if needed so that the composed text 4990 // becomes visible. 4991 try { 4992 Rectangle d = modelToView(dot); 4993 Rectangle end = modelToView(composedTextEnd.getOffset()); 4994 Rectangle b = getBounds(); 4995 d.x += Math.min(end.x - d.x, b.width); 4996 scrollRectToVisible(d); 4997 } catch (BadLocationException ble) {} 4998 } 4999 } 5000 caret.setDot(dot); 5001 } else if (caret instanceof ComposedTextCaret) { 5002 dot = caret.getDot(); 5003 // Restores original caret 5004 exchangeCaret(caret, originalCaret); 5005 caret.setDot(dot); 5006 } 5007 } 5008 exchangeCaret(Caret oldCaret, Caret newCaret)5009 private void exchangeCaret(Caret oldCaret, Caret newCaret) { 5010 int blinkRate = oldCaret.getBlinkRate(); 5011 setCaret(newCaret); 5012 caret.setBlinkRate(blinkRate); 5013 caret.setVisible(hasFocus()); 5014 } 5015 5016 /** 5017 * Returns true if KeyEvents should be synthesized from an InputEvent. 5018 */ shouldSynthensizeKeyEvents()5019 private boolean shouldSynthensizeKeyEvents() { 5020 if (!checkedInputOverride) { 5021 // Checks whether the client code overrides processInputMethodEvent. 5022 // If it is overridden, need not to generate KeyTyped events for committed text. 5023 // If it's not, behave as an passive input method client. 5024 needToSendKeyTypedEvent = !METHOD_OVERRIDDEN.get(getClass()); 5025 checkedInputOverride = true; 5026 } 5027 return needToSendKeyTypedEvent; 5028 } 5029 5030 // 5031 // Checks whether a composed text in this text component 5032 // composedTextExists()5033 boolean composedTextExists() { 5034 return (composedTextStart != null); 5035 } 5036 5037 // 5038 // Caret implementation for editing the composed text. 5039 // 5040 class ComposedTextCaret extends DefaultCaret implements Serializable { 5041 Color bg; 5042 5043 // 5044 // Get the background color of the component 5045 // install(JTextComponent c)5046 public void install(JTextComponent c) { 5047 super.install(c); 5048 5049 Document doc = c.getDocument(); 5050 if (doc instanceof StyledDocument) { 5051 StyledDocument sDoc = (StyledDocument)doc; 5052 Element elem = sDoc.getCharacterElement(c.composedTextStart.getOffset()); 5053 AttributeSet attr = elem.getAttributes(); 5054 bg = sDoc.getBackground(attr); 5055 } 5056 5057 if (bg == null) { 5058 bg = c.getBackground(); 5059 } 5060 } 5061 5062 // 5063 // Draw caret in XOR mode. 5064 // paint(Graphics g)5065 public void paint(Graphics g) { 5066 if(isVisible()) { 5067 try { 5068 Rectangle r = component.modelToView(getDot()); 5069 g.setXORMode(bg); 5070 g.drawLine(r.x, r.y, r.x, r.y + r.height - 1); 5071 g.setPaintMode(); 5072 } catch (BadLocationException e) { 5073 // can't render I guess 5074 //System.err.println("Can't render cursor"); 5075 } 5076 } 5077 } 5078 5079 // 5080 // If some area other than the composed text is clicked by mouse, 5081 // issue endComposition() to force commit the composed text. 5082 // positionCaret(MouseEvent me)5083 protected void positionCaret(MouseEvent me) { 5084 JTextComponent host = component; 5085 Point pt = new Point(me.getX(), me.getY()); 5086 int offset = host.viewToModel(pt); 5087 int composedStartIndex = host.composedTextStart.getOffset(); 5088 if ((offset < composedStartIndex) || 5089 (offset > composedTextEnd.getOffset())) { 5090 try { 5091 // Issue endComposition 5092 Position newPos = host.getDocument().createPosition(offset); 5093 host.getInputContext().endComposition(); 5094 5095 // Post a caret positioning runnable to assure that the positioning 5096 // occurs *after* committing the composed text. 5097 EventQueue.invokeLater(new DoSetCaretPosition(host, newPos)); 5098 } catch (BadLocationException ble) { 5099 System.err.println(ble); 5100 } 5101 } else { 5102 // Normal processing 5103 super.positionCaret(me); 5104 } 5105 } 5106 } 5107 5108 // 5109 // Runnable class for invokeLater() to set caret position later. 5110 // 5111 private class DoSetCaretPosition implements Runnable { 5112 JTextComponent host; 5113 Position newPos; 5114 DoSetCaretPosition(JTextComponent host, Position newPos)5115 DoSetCaretPosition(JTextComponent host, Position newPos) { 5116 this.host = host; 5117 this.newPos = newPos; 5118 } 5119 run()5120 public void run() { 5121 host.setCaretPosition(newPos.getOffset()); 5122 } 5123 } 5124 } 5125