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