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