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