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