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