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