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