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