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