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