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