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