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