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