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