History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/JTextComponent.java (1 of 18352 files). Show all

Unified · Split

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

74 more lines aren’t shown here.