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